diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 78245fb..e9ad12f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -39,7 +39,7 @@ jobs: env: # The game commit whose runner, scripts and judges this checkout is # held to (parity/README.md, "Changing a rule"). - SERVER_COMMIT: 56781091e05534b5a88c9c484de9790aa6e05cad + SERVER_COMMIT: 6b85c8f71047710367f3c0dbcef17272a609c442 steps: # Side by side, not one inside the other: the game's extension is a # crate of its own, and inside this checkout cargo would read it as a diff --git a/README.md b/README.md index 7abfc2c..dd7f90e 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ rule rewrites them here, and the game vendors them at the commit it pins. | `parameters` | `Location::Parameters` | a building's picks resolved against its tables, kept in key order | | `boxes` | `Location::Box` | walls, shared ground, bearings and distances between rooms | | `interior` | `Location::Interior` | a building's rooms and doorways, returned rather than written | -| `floor` | none: it was written here | a floor's rooms and doorways (a place its world gives a `floors` row): exactly its room count on one storey, cut into a near-square grid walked as a serpentine, each room's danger and hazard from the floor's own words, its sorts dealt from its own kind, and the way-down room rolled from every room but the entry, out of the place's own generator; returned rather than written, and written once, on the first entry (`turn::Turn`'s layout, which opens the way down onto that room) | +| `floor` | none: it was written here | a floor's rooms and doorways (a place its world gives a `floors` row): exactly its room count on one storey, cut into a near-square grid walked as a serpentine, each room's danger and hazard from the floor's own words, its sorts dealt from its own kind, and the way-down room rolled from every room but the entry, and on a floor somebody is posted to (`characters.posted_to_id`) the room its guards stand in, the way-down room or with no way down a room rolled last, out of the place's own generator; returned rather than written, and written once, on the first entry (`turn::Turn`'s layout, which opens the way down onto that room and stands the guards in theirs) | | `kind` | `Location::Kind` | what sort of place a room may be and how cluttered, and the sort each room of a building is dealt | | `kit` | `Item::Kit`, `Item::Contents` | what stands in a room of a kind and what lies about in it, rolled on the room's name from `data/item/kits.yml`, and the parts each piece is born with (`kit::furnish`); and what lies inside a closed piece, drawn on the piece's own row (`Roll::CONTENTS`) | | `material` | none: it was written here | what a thing is made of (`items.material`), one of ten or unknown: the material a kit writes each name with, what a material settles about a thing's other columns (paper always burns, glass is never sturdy), and the doctor's findings for a thing they contradict. Breaking apart (`divide`) is the one act that reads it | diff --git a/parity/PASSING b/parity/PASSING index 0b625da..f96aff9 100644 --- a/parity/PASSING +++ b/parity/PASSING @@ -11,6 +11,7 @@ a-cellar-below-the-way-in a-chamber-of-one-room a-check-against-an-ability a-deadline-places-what-the-arc-waits-for +a-deeper-floor-births-stronger-people a-departure-reaches-a-later-paragraph a-desk-and-what-lies-on-it a-drawer-searched-once @@ -19,6 +20,7 @@ a-fight-the-player-wins a-floor-laid-out-to-its-count a-foe-demands-and-still-strikes a-free-typed-throw +a-guard-waits-at-the-stairs a-jump-from-the-loft-window a-key-opens-only-its-own-door a-keyed-stair-onto-a-place-stays-keyed diff --git a/parity/goldens/a-deeper-floor-births-stronger-people.json b/parity/goldens/a-deeper-floor-births-stronger-people.json new file mode 100644 index 0000000..21638c6 --- /dev/null +++ b/parity/goldens/a-deeper-floor-births-stronger-people.json @@ -0,0 +1,239 @@ +{ + "script": "a-deeper-floor-births-stronger-people", + "story": "A Deeper Floor", + "steps": [ + { + "step": "step 1 down-to-the-bottom-floor", + "player": "first", + "typed": "/move The Bottom Floor", + "dump": { + "location": { + "id": 1000000004, + "name": "The Bottom Floor room 1", + "detail": "realized" + }, + "storey": 0, + "exits": [ + { + "id": 1000000001, + "name": "The Landing", + "detail": "realized" + } + ], + "here": [ + { + "id": 1000000011, + "name": "bone" + }, + { + "id": 1000000010, + "name": "pool" + }, + { + "id": 1000000007, + "name": "rock shelf" + }, + { + "id": 1000000008, + "name": "rubble" + }, + { + "id": 1000000012, + "name": "rusted hinge" + }, + { + "id": 1000000009, + "name": "stalagmite" + } + ], + "carrying": [], + "present": [ + { + "id": 1000000002, + "name": "Hollin Graves" + } + ], + "foes": [], + "inscription": [], + "hp": 18, + "hp_of": { + "Hollin Graves": 18 + }, + "abilities": { + "strength": 12, + "dexterity": 12, + "will": 12 + }, + "dead": false, + "changed": true, + "change": "The browser turn completed.", + "refused": false, + "offers": null, + "understood": "move -> The Bottom Floor", + "resolved_by": "grammar", + "note": "into-the-bottom-floor: completed", + "drifts": 0, + "blows": 0, + "hazards": 0, + "quest": {}, + "volitions": 0, + "acts": 0, + "ending": "none", + "ending_words": null, + "scheduled": 0, + "fired": 0, + "elapsed_minutes": 1.5, + "shown": null + } + }, + { + "step": "step 2 back-up-to-the-landing", + "player": "first", + "typed": "/move The Landing", + "dump": { + "location": { + "id": 1000000001, + "name": "The Landing", + "detail": "realized" + }, + "storey": null, + "exits": [ + { + "id": 1000000004, + "name": "The Bottom Floor room 1", + "detail": "realized" + }, + { + "id": 1000000002, + "name": "The Upper Floor", + "detail": "stub" + } + ], + "here": [], + "carrying": [], + "present": [], + "foes": [], + "inscription": [], + "hp": 18, + "hp_of": {}, + "abilities": { + "strength": 12, + "dexterity": 12, + "will": 12 + }, + "dead": false, + "changed": true, + "change": "moved: The Bottom Floor room 1 -> The Landing", + "refused": false, + "offers": null, + "understood": "move -> The Landing", + "resolved_by": "grammar", + "note": null, + "drifts": 0, + "blows": 0, + "hazards": 0, + "quest": {}, + "volitions": 0, + "acts": 0, + "ending": "none", + "ending_words": null, + "scheduled": 0, + "fired": 0, + "elapsed_minutes": 0.0, + "shown": null + } + }, + { + "step": "step 3 into-the-upper-floor", + "player": "first", + "typed": "/move The Upper Floor", + "dump": { + "location": { + "id": 1000000005, + "name": "The Upper Floor room 1", + "detail": "realized" + }, + "storey": 0, + "exits": [ + { + "id": 1000000001, + "name": "The Landing", + "detail": "realized" + } + ], + "here": [ + { + "id": 1000000025, + "name": "bone" + }, + { + "id": 1000000026, + "name": "candle stub" + }, + { + "id": 1000000028, + "name": "coin" + }, + { + "id": 1000000024, + "name": "ledge" + }, + { + "id": 1000000023, + "name": "pool" + }, + { + "id": 1000000021, + "name": "rock shelf" + }, + { + "id": 1000000022, + "name": "rubble" + }, + { + "id": 1000000027, + "name": "shard of pottery" + } + ], + "carrying": [], + "present": [ + { + "id": 1000000003, + "name": "Wenna Cole" + } + ], + "foes": [], + "inscription": [], + "hp": 18, + "hp_of": { + "Wenna Cole": 8 + }, + "abilities": { + "strength": 12, + "dexterity": 12, + "will": 12 + }, + "dead": false, + "changed": true, + "change": "The browser turn completed.", + "refused": false, + "offers": null, + "understood": "move -> The Upper Floor", + "resolved_by": "grammar", + "note": "into-the-upper-floor: completed", + "drifts": 0, + "blows": 0, + "hazards": 0, + "quest": {}, + "volitions": 0, + "acts": 0, + "ending": "none", + "ending_words": null, + "scheduled": 0, + "fired": 0, + "elapsed_minutes": 1.5, + "shown": null + } + } + ] +} diff --git a/parity/goldens/a-guard-waits-at-the-stairs.json b/parity/goldens/a-guard-waits-at-the-stairs.json new file mode 100644 index 0000000..b987189 --- /dev/null +++ b/parity/goldens/a-guard-waits-at-the-stairs.json @@ -0,0 +1,910 @@ +{ + "script": "a-guard-waits-at-the-stairs", + "story": "A Guarded Stair", + "steps": [ + { + "step": "step 1 into-the-upper-floor", + "player": "first", + "typed": "/move The Upper Floor", + "dump": { + "location": { + "id": 1000000004, + "name": "The Upper Floor room 1", + "detail": "realized" + }, + "storey": 0, + "exits": [ + { + "id": 1000000001, + "name": "The Landing", + "detail": "realized" + }, + { + "id": 1000000005, + "name": "The Upper Floor room 2", + "detail": "stub" + } + ], + "here": [ + { + "id": 1000000014, + "name": "bone" + }, + { + "id": 1000000015, + "name": "candle stub" + }, + { + "id": 1000000017, + "name": "coin" + }, + { + "id": 1000000013, + "name": "ledge" + }, + { + "id": 1000000012, + "name": "pool" + }, + { + "id": 1000000010, + "name": "rock shelf" + }, + { + "id": 1000000011, + "name": "rubble" + }, + { + "id": 1000000016, + "name": "shard of pottery" + } + ], + "carrying": [], + "present": [], + "foes": [], + "inscription": [], + "hp": 18, + "hp_of": {}, + "abilities": { + "strength": 14, + "dexterity": 12, + "will": 12 + }, + "dead": false, + "changed": true, + "change": "The browser turn completed.", + "refused": false, + "offers": null, + "understood": "move -> The Upper Floor", + "resolved_by": "grammar", + "note": "into-the-upper-floor: completed", + "drifts": 0, + "blows": 0, + "hazards": 0, + "quest": {}, + "volitions": 0, + "acts": 0, + "ending": "none", + "ending_words": null, + "scheduled": 0, + "fired": 0, + "elapsed_minutes": 1.5, + "shown": null + } + }, + { + "step": "step 2 the-guard-stands-on-the-way-down", + "player": "first", + "typed": "/move The Upper Floor room 2", + "dump": { + "location": { + "id": 1000000005, + "name": "The Upper Floor room 2", + "detail": "realized" + }, + "storey": 0, + "exits": [ + { + "id": 1000000003, + "name": "The Lower Floor", + "detail": "stub" + }, + { + "id": 1000000004, + "name": "The Upper Floor room 1", + "detail": "realized" + }, + { + "id": 1000000006, + "name": "The Upper Floor room 3", + "detail": "stub" + } + ], + "here": [ + { + "id": 1000000033, + "name": "bone" + }, + { + "id": 1000000035, + "name": "coin" + }, + { + "id": 1000000031, + "name": "crack" + }, + { + "id": 1000000032, + "name": "pebble" + }, + { + "id": 1000000030, + "name": "pool" + }, + { + "id": 1000000027, + "name": "rock shelf" + }, + { + "id": 1000000028, + "name": "rubble" + }, + { + "id": 1000000034, + "name": "rusted hinge" + }, + { + "id": 1000000029, + "name": "stalagmite" + } + ], + "carrying": [], + "present": [ + { + "id": 1000000002, + "name": "Brask the Stairwarden" + } + ], + "foes": [ + { + "id": 1000000002, + "name": "Brask the Stairwarden" + } + ], + "inscription": [], + "hp": 18, + "hp_of": { + "Brask the Stairwarden": 6 + }, + "abilities": { + "strength": 14, + "dexterity": 12, + "will": 12 + }, + "dead": false, + "changed": true, + "change": "The browser turn completed.", + "refused": false, + "offers": null, + "understood": "move -> The Upper Floor room 2", + "resolved_by": "grammar", + "note": "room-2: completed", + "drifts": 0, + "blows": 0, + "hazards": 0, + "quest": {}, + "volitions": 0, + "acts": 0, + "ending": "none", + "ending_words": null, + "scheduled": 0, + "fired": 0, + "elapsed_minutes": 1.0, + "shown": null + } + }, + { + "step": "step 3 kill-the-guard", + "player": "first", + "typed": "/attack Brask the Stairwarden", + "dump": { + "location": { + "id": 1000000005, + "name": "The Upper Floor room 2", + "detail": "realized" + }, + "storey": 0, + "exits": [ + { + "id": 1000000003, + "name": "The Lower Floor", + "detail": "stub" + }, + { + "id": 1000000004, + "name": "The Upper Floor room 1", + "detail": "realized" + }, + { + "id": 1000000006, + "name": "The Upper Floor room 3", + "detail": "stub" + } + ], + "here": [ + { + "id": 1000000033, + "name": "bone" + }, + { + "id": 1000000035, + "name": "coin" + }, + { + "id": 1000000031, + "name": "crack" + }, + { + "id": 1000000032, + "name": "pebble" + }, + { + "id": 1000000030, + "name": "pool" + }, + { + "id": 1000000027, + "name": "rock shelf" + }, + { + "id": 1000000028, + "name": "rubble" + }, + { + "id": 1000000034, + "name": "rusted hinge" + }, + { + "id": 1000000036, + "name": "stair key" + }, + { + "id": 1000000029, + "name": "stalagmite" + } + ], + "carrying": [], + "present": [], + "foes": [], + "inscription": [], + "hp": 18, + "hp_of": {}, + "abilities": { + "strength": 14, + "dexterity": 12, + "will": 12 + }, + "dead": false, + "changed": true, + "change": "struck: Odile hit Brask the Stairwarden for 7 (round 1); Brask the Stairwarden is dead", + "refused": false, + "offers": null, + "understood": "attack -> Brask the Stairwarden", + "resolved_by": "grammar", + "note": "the fight is over: The fight in The Upper Floor room 2 is over after 1 round. Brask the Stairwarden is dead. 1 blow landed: Odile hit Brask the Stairwarden for 7 (round 1); Brask the Stairwarden is dead.", + "drifts": 0, + "blows": 1, + "hazards": 0, + "quest": {}, + "volitions": 0, + "acts": 0, + "ending": "none", + "ending_words": null, + "scheduled": 0, + "fired": 0, + "elapsed_minutes": 5.0, + "shown": null + } + }, + { + "step": "step 4 the-stair-key-off-the-floor", + "player": "first", + "typed": "/take stair key", + "dump": { + "location": { + "id": 1000000005, + "name": "The Upper Floor room 2", + "detail": "realized" + }, + "storey": 0, + "exits": [ + { + "id": 1000000003, + "name": "The Lower Floor", + "detail": "stub" + }, + { + "id": 1000000004, + "name": "The Upper Floor room 1", + "detail": "realized" + }, + { + "id": 1000000006, + "name": "The Upper Floor room 3", + "detail": "stub" + } + ], + "here": [ + { + "id": 1000000033, + "name": "bone" + }, + { + "id": 1000000035, + "name": "coin" + }, + { + "id": 1000000031, + "name": "crack" + }, + { + "id": 1000000032, + "name": "pebble" + }, + { + "id": 1000000030, + "name": "pool" + }, + { + "id": 1000000027, + "name": "rock shelf" + }, + { + "id": 1000000028, + "name": "rubble" + }, + { + "id": 1000000034, + "name": "rusted hinge" + }, + { + "id": 1000000029, + "name": "stalagmite" + } + ], + "carrying": [ + { + "id": 1000000036, + "name": "stair key" + } + ], + "present": [], + "foes": [], + "inscription": [], + "hp": 18, + "hp_of": {}, + "abilities": { + "strength": 14, + "dexterity": 12, + "will": 12 + }, + "dead": false, + "changed": true, + "change": "took: stair key (was lying in The Upper Floor room 2, now carried by Odile)", + "refused": false, + "offers": null, + "understood": "take -> stair key", + "resolved_by": "grammar", + "note": null, + "drifts": 0, + "blows": 0, + "hazards": 0, + "quest": {}, + "volitions": 0, + "acts": 0, + "ending": "none", + "ending_words": null, + "scheduled": 0, + "fired": 0, + "elapsed_minutes": 0.0, + "shown": null + } + }, + { + "step": "step 5 the-stair-down-is-locked", + "player": "first", + "typed": "/move The Lower Floor", + "dump": { + "location": { + "id": 1000000005, + "name": "The Upper Floor room 2", + "detail": "realized" + }, + "storey": 0, + "exits": [ + { + "id": 1000000003, + "name": "The Lower Floor", + "detail": "stub" + }, + { + "id": 1000000004, + "name": "The Upper Floor room 1", + "detail": "realized" + }, + { + "id": 1000000006, + "name": "The Upper Floor room 3", + "detail": "stub" + } + ], + "here": [ + { + "id": 1000000033, + "name": "bone" + }, + { + "id": 1000000035, + "name": "coin" + }, + { + "id": 1000000031, + "name": "crack" + }, + { + "id": 1000000032, + "name": "pebble" + }, + { + "id": 1000000030, + "name": "pool" + }, + { + "id": 1000000027, + "name": "rock shelf" + }, + { + "id": 1000000028, + "name": "rubble" + }, + { + "id": 1000000034, + "name": "rusted hinge" + }, + { + "id": 1000000029, + "name": "stalagmite" + } + ], + "carrying": [ + { + "id": 1000000036, + "name": "stair key" + } + ], + "present": [], + "foes": [], + "inscription": [], + "hp": 18, + "hp_of": {}, + "abilities": { + "strength": 14, + "dexterity": 12, + "will": 12 + }, + "dead": false, + "changed": false, + "change": null, + "refused": true, + "offers": "The way to The Lower Floor is locked. Open it before crossing or throwing anything through it. Nothing has changed.", + "understood": "move -> The Lower Floor", + "resolved_by": "grammar", + "note": null, + "drifts": 0, + "blows": 0, + "hazards": 0, + "quest": {}, + "volitions": 0, + "acts": 0, + "ending": "none", + "ending_words": null, + "scheduled": 0, + "fired": 0, + "elapsed_minutes": 0.0, + "shown": null + } + }, + { + "step": "step 6 his-key-opens-it", + "player": "first", + "typed": "/unlock The Lower Floor with stair key", + "dump": { + "location": { + "id": 1000000005, + "name": "The Upper Floor room 2", + "detail": "realized" + }, + "storey": 0, + "exits": [ + { + "id": 1000000003, + "name": "The Lower Floor", + "detail": "stub" + }, + { + "id": 1000000004, + "name": "The Upper Floor room 1", + "detail": "realized" + }, + { + "id": 1000000006, + "name": "The Upper Floor room 3", + "detail": "stub" + } + ], + "here": [ + { + "id": 1000000033, + "name": "bone" + }, + { + "id": 1000000035, + "name": "coin" + }, + { + "id": 1000000031, + "name": "crack" + }, + { + "id": 1000000032, + "name": "pebble" + }, + { + "id": 1000000030, + "name": "pool" + }, + { + "id": 1000000027, + "name": "rock shelf" + }, + { + "id": 1000000028, + "name": "rubble" + }, + { + "id": 1000000034, + "name": "rusted hinge" + }, + { + "id": 1000000029, + "name": "stalagmite" + } + ], + "carrying": [ + { + "id": 1000000036, + "name": "stair key" + } + ], + "present": [], + "foes": [], + "inscription": [], + "hp": 18, + "hp_of": {}, + "abilities": { + "strength": 14, + "dexterity": 12, + "will": 12 + }, + "dead": false, + "changed": true, + "change": "You opened the way to The Lower Floor. You have not crossed it; you remain in The Upper Floor room 2.", + "refused": false, + "offers": null, + "understood": "use -> The Lower Floor", + "resolved_by": "grammar", + "note": null, + "drifts": 0, + "blows": 0, + "hazards": 0, + "quest": {}, + "volitions": 0, + "acts": 0, + "ending": "none", + "ending_words": null, + "scheduled": 0, + "fired": 0, + "elapsed_minutes": 0.0, + "shown": null + } + }, + { + "step": "step 7 down-to-the-lower-floor", + "player": "first", + "typed": "/move The Lower Floor", + "dump": { + "location": { + "id": 1000000007, + "name": "The Lower Floor room 1", + "detail": "realized" + }, + "storey": 0, + "exits": [ + { + "id": 1000000008, + "name": "The Lower Floor room 2", + "detail": "stub" + }, + { + "id": 1000000005, + "name": "The Upper Floor room 2", + "detail": "realized" + } + ], + "here": [ + { + "id": 1000000047, + "name": "bone" + }, + { + "id": 1000000046, + "name": "candle stub" + }, + { + "id": 1000000045, + "name": "pool" + }, + { + "id": 1000000043, + "name": "rock shelf" + }, + { + "id": 1000000044, + "name": "rubble" + }, + { + "id": 1000000048, + "name": "shard of pottery" + } + ], + "carrying": [ + { + "id": 1000000036, + "name": "stair key" + } + ], + "present": [], + "foes": [], + "inscription": [], + "hp": 18, + "hp_of": {}, + "abilities": { + "strength": 14, + "dexterity": 12, + "will": 12 + }, + "dead": false, + "changed": true, + "change": "The browser turn completed.", + "refused": false, + "offers": null, + "understood": "move -> The Lower Floor", + "resolved_by": "grammar", + "note": "into-the-lower-floor: completed", + "drifts": 0, + "blows": 0, + "hazards": 0, + "quest": {}, + "volitions": 0, + "acts": 0, + "ending": "none", + "ending_words": null, + "scheduled": 0, + "fired": 0, + "elapsed_minutes": 1.5, + "shown": null + } + }, + { + "step": "step 8 a-second-game-walks-onto-the-laid-out-floor (second)", + "player": "second", + "typed": "/move The Upper Floor room 1", + "dump": { + "location": { + "id": 1000000004, + "name": "The Upper Floor room 1", + "detail": "realized" + }, + "storey": 0, + "exits": [ + { + "id": 1000000001, + "name": "The Landing", + "detail": "realized" + }, + { + "id": 1000000005, + "name": "The Upper Floor room 2", + "detail": "realized" + } + ], + "here": [ + { + "id": 1000000053, + "name": "bone" + }, + { + "id": 1000000054, + "name": "candle stub" + }, + { + "id": 1000000056, + "name": "coin" + }, + { + "id": 1000000052, + "name": "ledge" + }, + { + "id": 1000000051, + "name": "pool" + }, + { + "id": 1000000049, + "name": "rock shelf" + }, + { + "id": 1000000050, + "name": "rubble" + }, + { + "id": 1000000055, + "name": "shard of pottery" + } + ], + "carrying": [], + "present": [], + "foes": [], + "inscription": [], + "hp": 18, + "hp_of": {}, + "abilities": { + "strength": 14, + "dexterity": 12, + "will": 12 + }, + "dead": false, + "changed": true, + "change": "moved: The Landing -> The Upper Floor room 1", + "refused": false, + "offers": null, + "understood": "move -> The Upper Floor room 1", + "resolved_by": "grammar", + "note": null, + "drifts": 0, + "blows": 0, + "hazards": 0, + "quest": {}, + "volitions": 0, + "acts": 0, + "ending": "none", + "ending_words": null, + "scheduled": 0, + "fired": 0, + "elapsed_minutes": 0.0, + "shown": null + } + }, + { + "step": "step 9 and-finds-the-guard-where-the-layout-stood-him (second)", + "player": "second", + "typed": "/move The Upper Floor room 2", + "dump": { + "location": { + "id": 1000000005, + "name": "The Upper Floor room 2", + "detail": "realized" + }, + "storey": 0, + "exits": [ + { + "id": 1000000007, + "name": "The Lower Floor room 1", + "detail": "realized" + }, + { + "id": 1000000004, + "name": "The Upper Floor room 1", + "detail": "realized" + }, + { + "id": 1000000006, + "name": "The Upper Floor room 3", + "detail": "stub" + } + ], + "here": [ + { + "id": 1000000063, + "name": "bone" + }, + { + "id": 1000000065, + "name": "coin" + }, + { + "id": 1000000061, + "name": "crack" + }, + { + "id": 1000000062, + "name": "pebble" + }, + { + "id": 1000000060, + "name": "pool" + }, + { + "id": 1000000057, + "name": "rock shelf" + }, + { + "id": 1000000058, + "name": "rubble" + }, + { + "id": 1000000064, + "name": "rusted hinge" + }, + { + "id": 1000000059, + "name": "stalagmite" + } + ], + "carrying": [], + "present": [ + { + "id": 1000000002, + "name": "Brask the Stairwarden" + } + ], + "foes": [ + { + "id": 1000000002, + "name": "Brask the Stairwarden" + } + ], + "inscription": [], + "hp": 18, + "hp_of": { + "Brask the Stairwarden": 6 + }, + "abilities": { + "strength": 14, + "dexterity": 12, + "will": 12 + }, + "dead": false, + "changed": true, + "change": "moved: The Upper Floor room 1 -> The Upper Floor room 2", + "refused": false, + "offers": null, + "understood": "move -> The Upper Floor room 2", + "resolved_by": "grammar", + "note": null, + "drifts": 0, + "blows": 0, + "hazards": 0, + "quest": {}, + "volitions": 0, + "acts": 0, + "ending": "none", + "ending_words": null, + "scheduled": 0, + "fired": 0, + "elapsed_minutes": 0.0, + "shown": null + } + } + ] +} diff --git a/parity/records/a-deadline-places-what-the-arc-waits-for.json b/parity/records/a-deadline-places-what-the-arc-waits-for.json index beb8b57..60c31dd 100644 --- a/parity/records/a-deadline-places-what-the-arc-waits-for.json +++ b/parity/records/a-deadline-places-what-the-arc-waits-for.json @@ -894,7 +894,8 @@ "unrecognized_need": "", "will": 11, "x": 7, - "y": 3 + "y": 3, + "posted_to_id": null } ], "removed": [] @@ -1488,7 +1489,8 @@ "unrecognized_need": "", "will": 11, "x": 7, - "y": 3 + "y": 3, + "posted_to_id": null } ], "removed": [] @@ -3319,7 +3321,8 @@ "unrecognized_need": "", "will": 11, "x": 7, - "y": 3 + "y": 3, + "posted_to_id": null } ], "removed": [] @@ -4805,7 +4808,8 @@ "unrecognized_need": "", "will": 11, "x": 7, - "y": 3 + "y": 3, + "posted_to_id": null } ], "removed": [] @@ -6696,7 +6700,8 @@ "unrecognized_need": "", "will": 11, "x": 7, - "y": 3 + "y": 3, + "posted_to_id": null } ], "removed": [] @@ -8273,7 +8278,8 @@ "unrecognized_need": "", "will": 11, "x": 7, - "y": 3 + "y": 3, + "posted_to_id": null } ], "removed": [] @@ -9850,7 +9856,8 @@ "unrecognized_need": "", "will": 11, "x": 7, - "y": 3 + "y": 3, + "posted_to_id": null } ], "removed": [] @@ -11427,7 +11434,8 @@ "unrecognized_need": "", "will": 11, "x": 7, - "y": 3 + "y": 3, + "posted_to_id": null } ], "removed": [] @@ -13004,7 +13012,8 @@ "unrecognized_need": "", "will": 11, "x": 7, - "y": 3 + "y": 3, + "posted_to_id": null } ], "removed": [] diff --git a/parity/records/a-deeper-floor-births-stronger-people.json b/parity/records/a-deeper-floor-births-stronger-people.json new file mode 100644 index 0000000..3cb56f7 --- /dev/null +++ b/parity/records/a-deeper-floor-births-stronger-people.json @@ -0,0 +1,3002 @@ +{ + "script": "a-deeper-floor-births-stronger-people", + "steps": [ + { + "step": "step 1 down-to-the-bottom-floor", + "requests": [ + { + "purpose": "location", + "system": "You build the rooms of a text adventure one at a time. You write places a\nplayer can stand in and walk out of: concrete, specific, and consistent\nwith the world they belong to. Every exit you name is somewhere the player\ncould actually go.\n\nDO NOT INCLUDE EMOJIS IN YOUR RESPONSE.\n", + "user": "## Universe Details\nphysics: Ordinary physics.\ntechnology: Lanterns, iron keys and hand tools.\ngeographies: A dungeon cut down into black stone under a lantern-lit landing.\nraces: Delvers\ncivilizations: Delvers who sell what they bring up.\n\n\n## Story Details\ntitle: A Deeper Floor (engine sweep)\ngenre: dungeon crawl\npreface: The deeper you go, the harder what lives there.\nsummary: Odile looks into an upper floor and the bottom one.\n\n\n## The Place\nname: The Bottom Floor\nteaser: The bottom level of the dungeon, one chamber deep.\n\n## Instructions\nWrite this place out in full. It is a BUILDING -- somewhere with rooms\ninside it that a player walks into and moves around in.\n- The description is what the player reads as they come in. Address them as \"you\"\n- Describe what is here now, not the history -- the history is the lore\n- Stay consistent with the universe and with the teaser above\n- Do NOT describe the floor plan: how many rooms there are, where the stairs\n are and which door leads where are the game's to decide, out of the answers\n below, and it will tell you room by room as the player reaches them\n- Do not name anybody standing here and do not list anything lying here.\n People and things belong to the rooms, and each room is written as it is\n reached\n- Respect the stated length of each field\n\n## What Kind Of Building This Is\nThe game lays the inside out itself -- every room, every door, every stair\n-- from the answers to these, and then writes each room as the player\nreaches it. You are choosing what KIND of place this is, not drawing it.\n- Answer for the place you have just described and for the story it stands in\n- Every one of them can be left out, and the quietest answer is usually the\n right one: one floor, nothing underneath, nothing dangerous, nothing that\n hurts you\n- A HAZARD is not atmosphere. It takes hit points off everybody who walks\n through those rooms, every turn in some cases, so pick one only for a\n place that really is flooded, unlit, silent or airless\n- A GRADIENT is only worth saying when the place itself makes it true: a\n cellar that gets worse the further down you go, a tower that gets worse\n the higher you climb\n", + "schema": { + "name": "Location::PlaceSchema", + "description": null, + "schema": { + "type": "object", + "properties": { + "description": { + "type": "string", + "description": "What the player sees, hears and smells standing in this place right now. Describe THIS place only -- not what neighbours it, not what is visible out of a window or across the way, because the world around it can move. Second person. One paragraph, 4 to 6 sentences.", + "maxLength": 1200 + }, + "lore": { + "type": "string", + "description": "What this place is, who made it and what happened here. Written for the game engine rather than the player. One paragraph, 3 to 5 sentences.", + "maxLength": 900 + }, + "place_kind": { + "type": "string", + "enum": [ + "house", + "farmhouse", + "mansion", + "apartment block", + "inn", + "shop", + "office building", + "guildhall", + "workshop", + "smithy", + "mill", + "factory", + "warehouse", + "barn", + "temple", + "library", + "school", + "hospital", + "theatre", + "bathhouse", + "barracks", + "fortress", + "jail", + "tower", + "lighthouse", + "warren", + "ruin", + "spaceship", + "space station" + ], + "description": "What sort of place with rooms this is -- a building, a ship, a station -- as the game will lay out and furnish its rooms: pick the closest. The game decides which rooms it has and what stands in each; you describe it from outside and at its way in." + }, + "parameters": { + "type": "object", + "properties": { + "storeys_above": { + "type": "string", + "enum": [ + "ground floor only", + "one storey up", + "two storeys up" + ], + "description": "How far up it goes, counting the ground floor. Most buildings are one floor." + }, + "storeys_below": { + "type": "string", + "enum": [ + "none", + "a cellar", + "two levels down", + "deep" + ], + "description": "How far down it goes below the ground floor. Most buildings have nothing under them." + }, + "danger": { + "type": "string", + "enum": [ + "safe", + "uneasy", + "dangerous" + ], + "description": "How likely a room of it is to hold something that means the player harm. Most buildings are safe." + }, + "gradient": { + "type": "string", + "enum": [ + "the same throughout", + "worse the deeper you go", + "worse the higher you climb" + ], + "description": "Whether that gets worse in one direction. Only say so when the place itself makes it true -- a cellar that gets worse the further down you go, a tower that gets worse the higher you climb." + }, + "hazard": { + "type": "string", + "enum": [ + "none", + "flooded", + "unlit", + "silent", + "airless" + ], + "description": "What standing in its rooms does to a person, if anything. NONE is the right answer for almost every building: this is not atmosphere, it is a place that takes hit points off whoever walks through it." + } + }, + "required": [], + "additionalProperties": false, + "description": "What kind of building this is. Every field is optional; leave one out and the game takes the quietest option. Answer for the place you have just described." + } + }, + "required": [ + "description", + "lore" + ], + "additionalProperties": false, + "strict": true + } + } + }, + { + "purpose": "location", + "system": "You build the rooms of a text adventure one at a time. You write places a\nplayer can stand in and walk out of: concrete, specific, and consistent\nwith the world they belong to. Every exit you name is somewhere the player\ncould actually go.\n\nDO NOT INCLUDE EMOJIS IN YOUR RESPONSE.\n", + "user": "## Universe Details\nphysics: Ordinary physics.\ntechnology: Lanterns, iron keys and hand tools.\ngeographies: A dungeon cut down into black stone under a lantern-lit landing.\nraces: Delvers\ncivilizations: Delvers who sell what they bring up.\n\n\n## Story Details\ntitle: A Deeper Floor (engine sweep)\ngenre: dungeon crawl\npreface: The deeper you go, the harder what lives there.\nsummary: Odile looks into an upper floor and the bottom one.\n\n\n## The Place\nname: The Bottom Floor room 1\nteaser: A room inside The Bottom Floor, 6 by 6 paces on storey 0.\n## Where This Room Is\nThe game's own records of this room, already decided and not yours to\nchange. Write the room around them: do not contradict a measurement, and\ndo not give it a way out this list does not have.\nThis room is 6 by 6 paces -- about 9 by 9 metres. It is on storey 0 of The Bottom Floor, which is 6 by 6 paces across; storey 0 is the ground floor. Its ways out: a way out to The Landing, which is outside The Bottom Floor. Those are every way out of this room.\n## Already Here, Decided By The Game\nThe game's own records of what is in this room, already decided and not yours\nto change, and it will tell the player so. Write the room around them, and do\nnot add another piece of furniture or fixed thing a player could reach for.\nFixed in place: rock shelf, rubble, stalagmite, pool.\nIn the rubble: bone, rusted hinge.\nDo not list any of them again as a thing lying here.\n## Instructions\nWrite this place out in full.\n- The description is what the player reads on arrival. Address them as \"you\"\n- Describe what is here now, not the history -- the history is the lore\n- Stay consistent with the universe and with the teaser above\n- Respect the stated length of each field\n- NAME THIS ROOM. It is one room inside The Bottom Floor. The player reads the room and\n the place together -- \"the of The Bottom Floor\" -- so name the ROOM only,\n and never put the place's own name into it. A short noun phrase carrying the\n article English wants on it, 2 to 4 words: \"the counting room\", \"the cold\n store\", \"the harbourmaster's office\". Name it for what the floor plan above\n says this room is and for what you have just described standing in it. Never\n a comma in it\n\n## What Is Lying Here\nList AT MOST 3 portable things a player could pick up and carry away, besides the ones Already Here above: those are written already, and a thing named again is not a new one.\n- Nothing is the right answer for most rooms. An empty list is a complete answer\n- Only loose, portable things. Not the door, not the floor, not the machinery\n bolted to it -- something a person could put in a pocket or under an arm\n- Each one must be consistent with the description you just wrote, and worth\n the player noticing\n- Pick its use_kind from the supplied physical profiles. Ordinary is the\n default. Food and drink can be consumed without healing; healing is only\n a restorative dose this world permits. Firestarters burn combustible\n objects, levers pry jammed passages, lockpicks try locks, and a key only\n opens a passage explicitly matched to it. Mark combustible only when an\n ordinary firestarter can destroy the object. The engine owns the effects\n and their amounts; its description cannot grant extra powers\n- If a thing has WRITING on it -- a note, a letter, a handbill, a label, a\n docket, a page, a sign -- mark it readable and WRITE OUT WHAT IS WRITTEN\n ON IT, exactly as it appears on the thing. The words themselves, not a\n description of them, and short enough to finish -- a few words, a line,\n or a few short lines. The game keeps those words and a player reading it\n twice reads the same ones\n- Never name it after a person or after a place. Already spoken for in this story, so do not reuse: Odile\n\n## Who Is Here\nWrite EXACTLY 1 person who is in this place right now.\n- Anyone you write is somebody the player can walk up to and talk to, so they\n have to have a reason to be standing here and an enduring aim that today's\n reason serves, threatens or exposes\n- Do not write the player, and do not write somebody passing through\n- Never give them the name of a place, of a thing, or any name already\n spoken for above\n\nWho they are is already decided. Write these people and do not change them:\n the 1st is Delvers, about 25, trans man\n\n## Their Four Objects of Desire\n\nWrite four STORY-SCALE things this person is after, then pick two TURN-SCALE labels.\nWork from everything above -- especially the years in their backstory, their repeated\nchoices, what they fear losing, the future they imagine, and where today's situation\npresses on that life.\n\nAn object of desire must satisfy both tests:\n1. It was shaping this person before the opening scene.\n2. Finishing today's errand would not finish it; it can drive choices across the\n whole story.\n\nThe two labels are picked from this list and nothing else:\n\n keep They are holding on to something they already have.\n obtain They are trying to get hold of something they do not have.\n reach They are trying to get to a particular place.\n attend They are trying to stay near a particular person.\n avoid They are trying to get away from a person or a place.\n withhold They are trying to stop somebody else getting something.\n offer They are trying to put something into somebody else's hands.\n\nconscious_desire\n The future, standing, relationship, legacy, place or way of life they knowingly\n organize their choices around and would name if asked what they want from their\n life. Make it specific to this person and world. Do not substitute the clue,\n deadline, delivery, payment or room they are dealing with today. One sentence.\n\nunconscious_desire\n The deeper reward their choices have pursued for years and they would deny: the\n recognition, absolution, dependence, belonging, power or intimacy beneath the\n conscious account. It must explain a repeated pattern in the backstory and pull at\n an angle to the conscious desire, not merely restate today's hidden motive. One\n sentence.\n\nrecognized_need\n The duty, oath, debt, craft, family burden or bodily discipline they believe they\n must keep over years whether they want to or not. It predates today's assignment,\n survives it, and can repeatedly obstruct the conscious desire. One sentence.\n\nunrecognized_need\n The enduring change, truth or relationship their life requires and their repeated\n pattern prevents them from seeing. A reader can infer it from the backstory. It\n cannot be completed by one confession, realization or errand: it demands a new way\n of choosing across the story. If they never move toward it, pursuit of the\n conscious desire ruins them. One sentence.\n\ndesire_pursuit\n Pick the listed ENGINE ACT that could repeatedly advance or protect the conscious\n desire in ordinary rooms. The label is the next-step expression of the larger\n desire, not its timescale.\n\nneed_pursuit\n Pick the listed ENGINE ACT that could repeatedly move them toward the unrecognized\n need in ordinary rooms. It may match desire_pursuit, but a different label should\n reflect a real conflict rather than manufactured variety.\n\nRules:\n- Every field names something to move TOWARD. State what lies beyond a fear, refusal\n or escape.\n- The object is larger than any single room act. Picking something up, handing it\n over, walking, waiting or staying near somebody may advance, protect, rehearse or\n betray it; that act must not become the whole desire.\n- Use today's room as pressure or evidence, not as the horizon of the person's life.\n- Keep the four consistent with the backstory, fears and dislikes. A fear is often\n the shadow of an object, but is not the object itself.\n- The conscious desire and unrecognized need must not be the same thing said twice.\n- Write in the third person, by name.\n- Do not resolve any object or say how the story ends.\n- Respect the stated length of each field.\n", + "schema": { + "name": "Location::DetailSchema", + "description": null, + "schema": { + "type": "object", + "properties": { + "description": { + "type": "string", + "description": "What the player sees, hears and smells standing in this place right now. Describe THIS place only -- not what neighbours it, not what is visible out of a window or across the way, because the world around it can move. Second person. One paragraph, 4 to 6 sentences.", + "maxLength": 1200 + }, + "lore": { + "type": "string", + "description": "What this place is, who made it and what happened here. Written for the game engine rather than the player. One paragraph, 3 to 5 sentences.", + "maxLength": 900 + }, + "name": { + "type": "string", + "description": "What this room is called -- ONLY when the instructions above ask you to name it. Leave this out entirely otherwise. A short noun phrase a player would type to walk into it, carrying the article English wants on it: \"the counting room\". Never the name of the building it is in, never a name this story has already given to a room, a person or a thing, and never a comma.", + "maxLength": 60 + }, + "items": { + "type": "array", + "description": "Portable things lying loose in this place that a player could pick up and carry away. Empty is the right answer for most rooms.", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "What the thing is called, as a player would type it to pick it up. A short noun phrase, 1 to 4 words, lower case unless it is a proper name. Never the name of a person or of a place.", + "maxLength": 60 + }, + "description": { + "type": "string", + "description": "What it is and what state it is in, consistent with the description of the room you just wrote. One or two sentences.", + "maxLength": 400 + }, + "use_kind": { + "type": "string", + "enum": [ + "ordinary", + "food", + "drink", + "healing", + "firestarter", + "lever", + "lockpick", + "key" + ], + "description": "The engine's physical profile: ordinary unless it is food, a drink, a healing dose, a firestarter, a prying lever, lockpicks, or a key. Choose healing only for an actual restorative dose allowed by this world. Profiles supply behavior from code, never from the description." + }, + "combustible": { + "type": "boolean", + "description": "True only if this portable thing can be destroyed by an ordinary carried firestarter: dry paper, cloth or wood. False for stone, metal, liquids, or anything uncertain." + }, + "readable": { + "type": "boolean", + "description": "True only if this thing has WRITING on it that a player could read: a note, a letter, a label, a docket, a page, a sign, an inscription. False for everything else, which is most things." + }, + "inscription": { + "type": "string", + "description": "The words written on it, exactly as they appear, and only when `readable` is true. Write what is actually on the thing -- what a player would read off it -- not a description of it. A few words, a line, or a few short lines; well under the limit, and finished rather than trailing off. Leave this out entirely when nothing is written on it.", + "maxLength": 400 + } + }, + "required": [ + "name", + "description", + "use_kind", + "combustible", + "readable" + ], + "additionalProperties": false + }, + "maxItems": 3 + }, + "people": { + "type": "array", + "description": "The person who is in this place right now. Write the one the instructions describe.", + "items": { + "type": "object", + "properties": { + "fullname": { + "type": "string", + "description": "Their full name, as a player would type it to speak to them. 2 or 3 words.", + "maxLength": 60 + }, + "nickname": { + "type": "string", + "description": "What they are called to their face. 1 or 2 words.", + "maxLength": 30 + }, + "appearance": { + "type": "string", + "description": "What somebody walking in sees of them, consistent with the room you just wrote and with the race and age you were given for them. One or two sentences.", + "maxLength": 300 + }, + "personality": { + "type": "string", + "description": "How they behave and how they treat a stranger. One or two sentences.", + "maxLength": 300 + }, + "backstory": { + "type": "string", + "description": "The life history that brought them here, the repeated choices it taught them, and why today's presence presses on an enduring aim. Third person, by name. Two or three sentences.", + "maxLength": 450 + }, + "likes": { + "type": "string", + "description": "A comma separated list of 2 or 3 things they enjoy.", + "maxLength": 160 + }, + "dislikes": { + "type": "string", + "description": "A comma separated list of 2 or 3 things they cannot stand.", + "maxLength": 160 + }, + "fears": { + "type": "string", + "description": "A comma separated list of 1 or 2 things they are afraid of.", + "maxLength": 160 + }, + "conscious_desire": { + "type": "string", + "description": "The future, standing, relationship, legacy, place or way of life they knowingly organize their choices around and would name if asked what they want from their life. Make it specific to this person and world. Do not substitute the clue, deadline, delivery, payment or room they are dealing with today. One sentence, third person, by name.", + "maxLength": 180 + }, + "unconscious_desire": { + "type": "string", + "description": "The deeper reward their choices have pursued for years and they would deny: the recognition, absolution, dependence, belonging, power or intimacy beneath the conscious account. It explains a repeated pattern in the backstory and pulls at an angle to the conscious desire, not merely today's hidden motive. One sentence, third person, by name.", + "maxLength": 180 + }, + "recognized_need": { + "type": "string", + "description": "The duty, oath, debt, craft, family burden or bodily discipline they believe they must keep over years whether they want to or not. It predates today's assignment, survives it, and can repeatedly obstruct the conscious desire. One sentence, third person, by name.", + "maxLength": 180 + }, + "unrecognized_need": { + "type": "string", + "description": "The enduring change, truth or relationship their life requires and their repeated pattern prevents them from seeing. A reader can infer it from the backstory. It cannot be completed by one confession, realization or errand: it demands a new way of choosing across the story. If they never move toward it, pursuit of the conscious desire ruins them. It must not be the conscious desire said twice. One sentence, third person, by name.", + "maxLength": 180 + }, + "desire_pursuit": { + "type": "string", + "enum": [ + "keep", + "obtain", + "reach", + "attend", + "avoid", + "withhold", + "offer" + ], + "description": "Which listed ENGINE ACT could repeatedly advance or protect the CONSCIOUS DESIRE in ordinary rooms. The label is the next-step expression of the larger desire, not its timescale." + }, + "need_pursuit": { + "type": "string", + "enum": [ + "keep", + "obtain", + "reach", + "attend", + "avoid", + "withhold", + "offer" + ], + "description": "Which listed ENGINE ACT could repeatedly move them toward the UNRECOGNIZED NEED in ordinary rooms. It may match desire_pursuit, but a different label should reflect a real conflict rather than manufactured variety." + } + }, + "required": [ + "fullname", + "nickname", + "appearance", + "personality", + "backstory", + "likes", + "dislikes", + "fears", + "conscious_desire", + "unconscious_desire", + "recognized_need", + "unrecognized_need", + "desire_pursuit", + "need_pursuit" + ], + "additionalProperties": false + }, + "minItems": 1, + "maxItems": 1 + } + }, + "required": [ + "description", + "lore", + "people" + ], + "additionalProperties": false, + "strict": true + } + } + }, + { + "purpose": "arrival", + "system": "You narrate a text adventure. You write the moment a player walks into a\nplace: what reaches them first, in the second person and the present\ntense. You never break character, never offer a numbered menu, and never\nmention that you are a narrator.\n\nDO NOT INCLUDE EMOJIS IN YOUR RESPONSE.\n", + "user": "## Universe Details\nphysics: Ordinary physics.\ntechnology: Lanterns, iron keys and hand tools.\n\n## Story Details\ntitle: A Deeper Floor (engine sweep)\ngenre: dungeon crawl\nsummary: Odile looks into an upper floor and the bottom one.\n\n## The Place\nname: The Bottom Floor room 1\ndescription: A wide chamber where somebody has made a camp among the stones.\nlore: Delvers who stay down here too long stop going back up.\nways out: The Landing\n\n## Who Is Here\nOdile (Odile), Delvers -- the player, the one arriving\nHollin Graves (Hollin), Delvers\n\n## Just Before This\nThe player has come from The Landing. Odile reaches the landing.\n\n## Instructions\n- The player has never been here. This is the first time they have set\n foot in it\n- Narrate discovery: what catches them on the way in, before they\n have made sense of the rest\n- Address the player as \"you\", in the present tense\n- One paragraph. Do not re-describe the place item by item -- the\n description above is already what is here, and your job is the moment\n of coming into it\n- Anyone listed above is here; write them as already present, not as\n arriving. Do not add a person who is not on that list\n- Do not name a way out that is not on the list above\n- Respect the stated length of each field\n\n## Current State On Arrival\nThe place description is the world's original account. These current records\ntake precedence over its claims about people, items and wounds. Narrate the\nrecorded crossing result as part of this arrival.\nYou are unhurt.\nAlso here: Hollin Graves. Nobody else is alive here.\nFixed here, and not takeable: rock shelf, rubble, stalagmite, pool.\nLying here: bone, rusted hinge.\nYou are carrying: nothing.\n", + "schema": { + "name": "Scene::Schema", + "description": null, + "schema": { + "type": "object", + "properties": { + "description": { + "type": "string", + "description": "What the player experiences as they arrive here, right now. Second person, present tense. One paragraph, 3 to 5 sentences.", + "maxLength": 900 + }, + "summary": { + "type": "string", + "description": "What happened in this moment, for the game engine rather than the player. Third person. One sentence.", + "maxLength": 200 + } + }, + "required": [ + "description", + "summary" + ], + "additionalProperties": false, + "strict": true + } + } + } + ], + "written": { + "locations": { + "rows": [ + { + "id": 1000000003, + "story_id": 1000000001, + "name": "The Bottom Floor", + "description": "The bottom level of the dungeon, one chamber cut far down.", + "lore": "Whatever lives this deep has lived here a long time.", + "last_protagonist_visit": null, + "parent_location_id": null, + "detail_level": "realized", + "teaser": "The bottom level of the dungeon, one chamber deep.", + "mobile": 0, + "danger": "safe", + "hazard": null, + "hazard_die": null, + "x": null, + "y": null, + "z": null, + "width": 6, + "depth": 6, + "population": null, + "generation_checkpoint": null, + "surface": null, + "kind": "warren", + "density": null + }, + { + "id": 1000000004, + "story_id": 1000000001, + "name": "The Bottom Floor room 1", + "description": "A wide chamber where somebody has made a camp among the stones.", + "lore": "Delvers who stay down here too long stop going back up.", + "last_protagonist_visit": "2026-10-30 08:01:30", + "parent_location_id": 1000000003, + "detail_level": "realized", + "teaser": "A room inside The Bottom Floor, 6 by 6 paces on storey 0.", + "mobile": 0, + "danger": "safe", + "hazard": null, + "hazard_die": null, + "x": 0, + "y": 0, + "z": 0, + "width": 6, + "depth": 6, + "population": null, + "generation_checkpoint": null, + "surface": null, + "kind": "cave", + "density": null + } + ], + "removed": [] + }, + "location_connections": { + "rows": [ + { + "id": 1000000005, + "location_id": 1000000004, + "connected_location_id": 1000000001, + "distance": "adjacent", + "time_to_travel": "about a minute", + "travel_method": "taking stairs", + "hazard": null, + "hazard_die": null, + "barrier": "open", + "key_template_id": null + }, + { + "id": 1000000006, + "location_id": 1000000001, + "connected_location_id": 1000000004, + "distance": "adjacent", + "time_to_travel": "about a minute", + "travel_method": "taking stairs", + "hazard": null, + "hazard_die": null, + "barrier": "open", + "key_template_id": null + } + ], + "removed": [ + 1000000003, + 1000000004 + ] + }, + "characters": { + "rows": [ + { + "id": 1000000002, + "story_id": 1000000001, + "fullname": "Hollin Graves", + "nickname": "Hollin", + "age": 25, + "sex": "trans man", + "personality": "Watchful and slow to speak.", + "appearance": "A delver's coat gone grey with stone dust.", + "likes": "The quiet down here.", + "dislikes": "Lanterns from above.", + "fears": "The surface.", + "backstory": "A delver who stopped going back up years ago.", + "is_companion": 0, + "race_id": 1000000001, + "is_protagonist": 0, + "location_id": 1000000004, + "deliberately_absent": 0, + "level": 4, + "hit_die": 6, + "strength": 13, + "dexterity": 11, + "will": 10, + "hostile": 0, + "x": 0, + "y": 3, + "conscious_desire": "", + "unconscious_desire": "", + "recognized_need": "", + "unrecognized_need": "", + "desire_pursuit": null, + "need_pursuit": null, + "posted_to_id": null + } + ], + "removed": [] + }, + "items": { + "rows": [ + { + "id": 1000000001, + "character_id": null, + "name": "rock shelf", + "description": "The rock shelf, fixed in place.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "immovable", + "x": 5, + "y": 2, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "top", + "within_id": null, + "how": null, + "kit_key": "cave/rock shelf", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "stone", + "wear": null, + "worn": 0 + }, + { + "id": 1000000002, + "character_id": null, + "name": "rubble", + "description": "The rubble, fixed in place.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "immovable", + "x": 4, + "y": 0, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "hollow", + "within_id": null, + "how": null, + "kit_key": "cave/rubble", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000003, + "character_id": null, + "name": "stalagmite", + "description": "The stalagmite, fixed in place.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "immovable", + "x": 1, + "y": 5, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "nothing", + "within_id": null, + "how": null, + "kit_key": "cave/stalagmite", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "stone", + "wear": null, + "worn": 0 + }, + { + "id": 1000000004, + "character_id": null, + "name": "pool", + "description": "The pool, fixed in place.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "immovable", + "x": 0, + "y": 4, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "nothing", + "within_id": null, + "how": null, + "kit_key": "cave/pool", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000005, + "character_id": null, + "name": "bone", + "description": "The bone, in the rubble.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000002, + "how": "in", + "kit_key": "cave/rubble/bone", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000006, + "character_id": null, + "name": "rusted hinge", + "description": "The rusted hinge, in the rubble.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000002, + "how": "in", + "kit_key": "cave/rubble/rusted hinge", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "metal", + "wear": null, + "worn": 0 + }, + { + "id": 1000000007, + "character_id": null, + "name": "rock shelf", + "description": "The rock shelf, fixed in place.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000001, + "bulk": "immovable", + "x": 5, + "y": 2, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "top", + "within_id": null, + "how": null, + "kit_key": "cave/rock shelf", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "stone", + "wear": null, + "worn": 0 + }, + { + "id": 1000000008, + "character_id": null, + "name": "rubble", + "description": "The rubble, fixed in place.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000002, + "bulk": "immovable", + "x": 4, + "y": 0, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "hollow", + "within_id": null, + "how": null, + "kit_key": "cave/rubble", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000009, + "character_id": null, + "name": "stalagmite", + "description": "The stalagmite, fixed in place.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000003, + "bulk": "immovable", + "x": 1, + "y": 5, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "nothing", + "within_id": null, + "how": null, + "kit_key": "cave/stalagmite", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "stone", + "wear": null, + "worn": 0 + }, + { + "id": 1000000010, + "character_id": null, + "name": "pool", + "description": "The pool, fixed in place.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000004, + "bulk": "immovable", + "x": 0, + "y": 4, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "nothing", + "within_id": null, + "how": null, + "kit_key": "cave/pool", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000011, + "character_id": null, + "name": "bone", + "description": "The bone, in the rubble.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000005, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000008, + "how": "in", + "kit_key": "cave/rubble/bone", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000012, + "character_id": null, + "name": "rusted hinge", + "description": "The rusted hinge, in the rubble.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000006, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000008, + "how": "in", + "kit_key": "cave/rubble/rusted hinge", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "metal", + "wear": null, + "worn": 0 + } + ], + "removed": [] + }, + "quest_steps": { + "rows": [], + "removed": [] + }, + "scenes": { + "rows": [ + { + "id": 1000000002, + "story_id": 1000000001, + "location_id": 1000000004, + "previous_scene_id": 1000000001, + "description": "You arrive at The Bottom Floor room 1. You are unhurt. Also here: Hollin Graves. Nobody else is alive here. Fixed here, and not takeable: rock shelf, rubble, stalagmite, pool. Lying here: bone, rusted hinge. You are carrying: nothing.", + "story_timestamp": "2026-10-30 08:01:30", + "summary": "You arrive at The Bottom Floor room 1. You are unhurt. Also here: Hollin Graves. Nobody else is alive here. Fixed here, and not takeable: rock shelf, rubble, stalagmite, pool. Lying here: bone, rusted hinge. You are carrying: nothing.", + "is_opening": 0, + "typed": "move The Bottom Floor", + "resolved_action": "move", + "acted_on_type": "Location", + "acted_on_id": 1000000003, + "resolved_by": "grammar", + "engine_fallback": 1, + "engine_fact": "You are unhurt.\nAlso here: Hollin Graves. Nobody else is alive here.\nFixed here, and not takeable: rock shelf, rubble, stalagmite, pool.\nLying here: bone, rusted hinge.\nYou are carrying: nothing." + } + ], + "removed": [] + }, + "characters_scenes": { + "rows": [ + { + "character_id": 1000000001, + "scene_id": 1000000002 + }, + { + "character_id": 1000000002, + "scene_id": 1000000002 + } + ], + "removed": [] + }, + "interactions": { + "rows": [], + "removed": [] + }, + "playthrough_endings": { + "rows": [], + "removed": [] + }, + "playthrough_beats": { + "rows": [], + "removed": [] + }, + "playthrough_npc_states": { + "rows": [], + "removed": [] + }, + "world_events": { + "rows": [], + "removed": [] + } + } + }, + { + "step": "step 2 back-up-to-the-landing", + "requests": [], + "written": { + "locations": { + "rows": [ + { + "id": 1000000003, + "story_id": 1000000001, + "name": "The Bottom Floor", + "description": "The bottom level of the dungeon, one chamber cut far down.", + "lore": "Whatever lives this deep has lived here a long time.", + "last_protagonist_visit": null, + "parent_location_id": null, + "detail_level": "realized", + "teaser": "The bottom level of the dungeon, one chamber deep.", + "mobile": 0, + "danger": "safe", + "hazard": null, + "hazard_die": null, + "x": null, + "y": null, + "z": null, + "width": 6, + "depth": 6, + "population": null, + "generation_checkpoint": null, + "surface": null, + "kind": "warren", + "density": null + }, + { + "id": 1000000004, + "story_id": 1000000001, + "name": "The Bottom Floor room 1", + "description": "A wide chamber where somebody has made a camp among the stones.", + "lore": "Delvers who stay down here too long stop going back up.", + "last_protagonist_visit": "2026-10-30 08:01:30", + "parent_location_id": 1000000003, + "detail_level": "realized", + "teaser": "A room inside The Bottom Floor, 6 by 6 paces on storey 0.", + "mobile": 0, + "danger": "safe", + "hazard": null, + "hazard_die": null, + "x": 0, + "y": 0, + "z": 0, + "width": 6, + "depth": 6, + "population": null, + "generation_checkpoint": null, + "surface": null, + "kind": "cave", + "density": null + } + ], + "removed": [] + }, + "location_connections": { + "rows": [ + { + "id": 1000000005, + "location_id": 1000000004, + "connected_location_id": 1000000001, + "distance": "adjacent", + "time_to_travel": "about a minute", + "travel_method": "taking stairs", + "hazard": null, + "hazard_die": null, + "barrier": "open", + "key_template_id": null + }, + { + "id": 1000000006, + "location_id": 1000000001, + "connected_location_id": 1000000004, + "distance": "adjacent", + "time_to_travel": "about a minute", + "travel_method": "taking stairs", + "hazard": null, + "hazard_die": null, + "barrier": "open", + "key_template_id": null + } + ], + "removed": [ + 1000000003, + 1000000004 + ] + }, + "characters": { + "rows": [ + { + "id": 1000000002, + "story_id": 1000000001, + "fullname": "Hollin Graves", + "nickname": "Hollin", + "age": 25, + "sex": "trans man", + "personality": "Watchful and slow to speak.", + "appearance": "A delver's coat gone grey with stone dust.", + "likes": "The quiet down here.", + "dislikes": "Lanterns from above.", + "fears": "The surface.", + "backstory": "A delver who stopped going back up years ago.", + "is_companion": 0, + "race_id": 1000000001, + "is_protagonist": 0, + "location_id": 1000000004, + "deliberately_absent": 0, + "level": 4, + "hit_die": 6, + "strength": 13, + "dexterity": 11, + "will": 10, + "hostile": 0, + "x": 0, + "y": 3, + "conscious_desire": "", + "unconscious_desire": "", + "recognized_need": "", + "unrecognized_need": "", + "desire_pursuit": null, + "need_pursuit": null, + "posted_to_id": null + } + ], + "removed": [] + }, + "items": { + "rows": [ + { + "id": 1000000001, + "character_id": null, + "name": "rock shelf", + "description": "The rock shelf, fixed in place.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "immovable", + "x": 5, + "y": 2, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "top", + "within_id": null, + "how": null, + "kit_key": "cave/rock shelf", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "stone", + "wear": null, + "worn": 0 + }, + { + "id": 1000000002, + "character_id": null, + "name": "rubble", + "description": "The rubble, fixed in place.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "immovable", + "x": 4, + "y": 0, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "hollow", + "within_id": null, + "how": null, + "kit_key": "cave/rubble", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000003, + "character_id": null, + "name": "stalagmite", + "description": "The stalagmite, fixed in place.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "immovable", + "x": 1, + "y": 5, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "nothing", + "within_id": null, + "how": null, + "kit_key": "cave/stalagmite", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "stone", + "wear": null, + "worn": 0 + }, + { + "id": 1000000004, + "character_id": null, + "name": "pool", + "description": "The pool, fixed in place.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "immovable", + "x": 0, + "y": 4, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "nothing", + "within_id": null, + "how": null, + "kit_key": "cave/pool", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000005, + "character_id": null, + "name": "bone", + "description": "The bone, in the rubble.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000002, + "how": "in", + "kit_key": "cave/rubble/bone", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000006, + "character_id": null, + "name": "rusted hinge", + "description": "The rusted hinge, in the rubble.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000002, + "how": "in", + "kit_key": "cave/rubble/rusted hinge", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "metal", + "wear": null, + "worn": 0 + }, + { + "id": 1000000007, + "character_id": null, + "name": "rock shelf", + "description": "The rock shelf, fixed in place.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000001, + "bulk": "immovable", + "x": 5, + "y": 2, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "top", + "within_id": null, + "how": null, + "kit_key": "cave/rock shelf", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "stone", + "wear": null, + "worn": 0 + }, + { + "id": 1000000008, + "character_id": null, + "name": "rubble", + "description": "The rubble, fixed in place.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000002, + "bulk": "immovable", + "x": 4, + "y": 0, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "hollow", + "within_id": null, + "how": null, + "kit_key": "cave/rubble", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000009, + "character_id": null, + "name": "stalagmite", + "description": "The stalagmite, fixed in place.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000003, + "bulk": "immovable", + "x": 1, + "y": 5, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "nothing", + "within_id": null, + "how": null, + "kit_key": "cave/stalagmite", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "stone", + "wear": null, + "worn": 0 + }, + { + "id": 1000000010, + "character_id": null, + "name": "pool", + "description": "The pool, fixed in place.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000004, + "bulk": "immovable", + "x": 0, + "y": 4, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "nothing", + "within_id": null, + "how": null, + "kit_key": "cave/pool", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000011, + "character_id": null, + "name": "bone", + "description": "The bone, in the rubble.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000005, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000008, + "how": "in", + "kit_key": "cave/rubble/bone", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000012, + "character_id": null, + "name": "rusted hinge", + "description": "The rusted hinge, in the rubble.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000006, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000008, + "how": "in", + "kit_key": "cave/rubble/rusted hinge", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "metal", + "wear": null, + "worn": 0 + } + ], + "removed": [] + }, + "quest_steps": { + "rows": [], + "removed": [] + }, + "scenes": { + "rows": [ + { + "id": 1000000002, + "story_id": 1000000001, + "location_id": 1000000004, + "previous_scene_id": 1000000001, + "description": "You arrive at The Bottom Floor room 1. You are unhurt. Also here: Hollin Graves. Nobody else is alive here. Fixed here, and not takeable: rock shelf, rubble, stalagmite, pool. Lying here: bone, rusted hinge. You are carrying: nothing.", + "story_timestamp": "2026-10-30 08:01:30", + "summary": "You arrive at The Bottom Floor room 1. You are unhurt. Also here: Hollin Graves. Nobody else is alive here. Fixed here, and not takeable: rock shelf, rubble, stalagmite, pool. Lying here: bone, rusted hinge. You are carrying: nothing.", + "is_opening": 0, + "typed": "move The Bottom Floor", + "resolved_action": "move", + "acted_on_type": "Location", + "acted_on_id": 1000000003, + "resolved_by": "grammar", + "engine_fallback": 1, + "engine_fact": "You are unhurt.\nAlso here: Hollin Graves. Nobody else is alive here.\nFixed here, and not takeable: rock shelf, rubble, stalagmite, pool.\nLying here: bone, rusted hinge.\nYou are carrying: nothing." + } + ], + "removed": [] + }, + "characters_scenes": { + "rows": [ + { + "character_id": 1000000001, + "scene_id": 1000000002 + }, + { + "character_id": 1000000002, + "scene_id": 1000000002 + } + ], + "removed": [] + }, + "interactions": { + "rows": [], + "removed": [] + }, + "playthrough_endings": { + "rows": [], + "removed": [] + }, + "playthrough_beats": { + "rows": [], + "removed": [] + }, + "playthrough_npc_states": { + "rows": [], + "removed": [] + }, + "world_events": { + "rows": [], + "removed": [] + } + } + }, + { + "step": "step 3 into-the-upper-floor", + "requests": [ + { + "purpose": "location", + "system": "You build the rooms of a text adventure one at a time. You write places a\nplayer can stand in and walk out of: concrete, specific, and consistent\nwith the world they belong to. Every exit you name is somewhere the player\ncould actually go.\n\nDO NOT INCLUDE EMOJIS IN YOUR RESPONSE.\n", + "user": "## Universe Details\nphysics: Ordinary physics.\ntechnology: Lanterns, iron keys and hand tools.\ngeographies: A dungeon cut down into black stone under a lantern-lit landing.\nraces: Delvers\ncivilizations: Delvers who sell what they bring up.\n\n\n## Story Details\ntitle: A Deeper Floor (engine sweep)\ngenre: dungeon crawl\npreface: The deeper you go, the harder what lives there.\nsummary: Odile looks into an upper floor and the bottom one.\n\n\n## The Place\nname: The Upper Floor\nteaser: The upper level of the dungeon, one chamber deep.\n\n## Instructions\nWrite this place out in full. It is a BUILDING -- somewhere with rooms\ninside it that a player walks into and moves around in.\n- The description is what the player reads as they come in. Address them as \"you\"\n- Describe what is here now, not the history -- the history is the lore\n- Stay consistent with the universe and with the teaser above\n- Do NOT describe the floor plan: how many rooms there are, where the stairs\n are and which door leads where are the game's to decide, out of the answers\n below, and it will tell you room by room as the player reaches them\n- Do not name anybody standing here and do not list anything lying here.\n People and things belong to the rooms, and each room is written as it is\n reached\n- Respect the stated length of each field\n\n## What Kind Of Building This Is\nThe game lays the inside out itself -- every room, every door, every stair\n-- from the answers to these, and then writes each room as the player\nreaches it. You are choosing what KIND of place this is, not drawing it.\n- Answer for the place you have just described and for the story it stands in\n- Every one of them can be left out, and the quietest answer is usually the\n right one: one floor, nothing underneath, nothing dangerous, nothing that\n hurts you\n- A HAZARD is not atmosphere. It takes hit points off everybody who walks\n through those rooms, every turn in some cases, so pick one only for a\n place that really is flooded, unlit, silent or airless\n- A GRADIENT is only worth saying when the place itself makes it true: a\n cellar that gets worse the further down you go, a tower that gets worse\n the higher you climb\n", + "schema": { + "name": "Location::PlaceSchema", + "description": null, + "schema": { + "type": "object", + "properties": { + "description": { + "type": "string", + "description": "What the player sees, hears and smells standing in this place right now. Describe THIS place only -- not what neighbours it, not what is visible out of a window or across the way, because the world around it can move. Second person. One paragraph, 4 to 6 sentences.", + "maxLength": 1200 + }, + "lore": { + "type": "string", + "description": "What this place is, who made it and what happened here. Written for the game engine rather than the player. One paragraph, 3 to 5 sentences.", + "maxLength": 900 + }, + "place_kind": { + "type": "string", + "enum": [ + "house", + "farmhouse", + "mansion", + "apartment block", + "inn", + "shop", + "office building", + "guildhall", + "workshop", + "smithy", + "mill", + "factory", + "warehouse", + "barn", + "temple", + "library", + "school", + "hospital", + "theatre", + "bathhouse", + "barracks", + "fortress", + "jail", + "tower", + "lighthouse", + "warren", + "ruin", + "spaceship", + "space station" + ], + "description": "What sort of place with rooms this is -- a building, a ship, a station -- as the game will lay out and furnish its rooms: pick the closest. The game decides which rooms it has and what stands in each; you describe it from outside and at its way in." + }, + "parameters": { + "type": "object", + "properties": { + "storeys_above": { + "type": "string", + "enum": [ + "ground floor only", + "one storey up", + "two storeys up" + ], + "description": "How far up it goes, counting the ground floor. Most buildings are one floor." + }, + "storeys_below": { + "type": "string", + "enum": [ + "none", + "a cellar", + "two levels down", + "deep" + ], + "description": "How far down it goes below the ground floor. Most buildings have nothing under them." + }, + "danger": { + "type": "string", + "enum": [ + "safe", + "uneasy", + "dangerous" + ], + "description": "How likely a room of it is to hold something that means the player harm. Most buildings are safe." + }, + "gradient": { + "type": "string", + "enum": [ + "the same throughout", + "worse the deeper you go", + "worse the higher you climb" + ], + "description": "Whether that gets worse in one direction. Only say so when the place itself makes it true -- a cellar that gets worse the further down you go, a tower that gets worse the higher you climb." + }, + "hazard": { + "type": "string", + "enum": [ + "none", + "flooded", + "unlit", + "silent", + "airless" + ], + "description": "What standing in its rooms does to a person, if anything. NONE is the right answer for almost every building: this is not atmosphere, it is a place that takes hit points off whoever walks through it." + } + }, + "required": [], + "additionalProperties": false, + "description": "What kind of building this is. Every field is optional; leave one out and the game takes the quietest option. Answer for the place you have just described." + } + }, + "required": [ + "description", + "lore" + ], + "additionalProperties": false, + "strict": true + } + } + }, + { + "purpose": "location", + "system": "You build the rooms of a text adventure one at a time. You write places a\nplayer can stand in and walk out of: concrete, specific, and consistent\nwith the world they belong to. Every exit you name is somewhere the player\ncould actually go.\n\nDO NOT INCLUDE EMOJIS IN YOUR RESPONSE.\n", + "user": "## Universe Details\nphysics: Ordinary physics.\ntechnology: Lanterns, iron keys and hand tools.\ngeographies: A dungeon cut down into black stone under a lantern-lit landing.\nraces: Delvers\ncivilizations: Delvers who sell what they bring up.\n\n\n## Story Details\ntitle: A Deeper Floor (engine sweep)\ngenre: dungeon crawl\npreface: The deeper you go, the harder what lives there.\nsummary: Odile looks into an upper floor and the bottom one.\n\n\n## The Place\nname: The Upper Floor room 1\nteaser: A room inside The Upper Floor, 6 by 6 paces on storey 0.\n## Where This Room Is\nThe game's own records of this room, already decided and not yours to\nchange. Write the room around them: do not contradict a measurement, and\ndo not give it a way out this list does not have.\nThis room is 6 by 6 paces -- about 9 by 9 metres. It is on storey 0 of The Upper Floor, which is 6 by 6 paces across; storey 0 is the ground floor. Its ways out: a way out to The Landing, which is outside The Upper Floor. Those are every way out of this room.\n## Already Here, Decided By The Game\nThe game's own records of what is in this room, already decided and not yours\nto change, and it will tell the player so. Write the room around them, and do\nnot add another piece of furniture or fixed thing a player could reach for.\nFixed in place: rock shelf, rubble, pool, ledge.\nOn the rock shelf: bone, candle stub.\nIn the rubble: shard of pottery, coin.\nDo not list any of them again as a thing lying here.\n## Instructions\nWrite this place out in full.\n- The description is what the player reads on arrival. Address them as \"you\"\n- Describe what is here now, not the history -- the history is the lore\n- Stay consistent with the universe and with the teaser above\n- Respect the stated length of each field\n- NAME THIS ROOM. It is one room inside The Upper Floor. The player reads the room and\n the place together -- \"the of The Upper Floor\" -- so name the ROOM only,\n and never put the place's own name into it. A short noun phrase carrying the\n article English wants on it, 2 to 4 words: \"the counting room\", \"the cold\n store\", \"the harbourmaster's office\". Name it for what the floor plan above\n says this room is and for what you have just described standing in it. Never\n a comma in it\n\n## What Is Lying Here\nList AT MOST 3 portable things a player could pick up and carry away, besides the ones Already Here above: those are written already, and a thing named again is not a new one.\n- Nothing is the right answer for most rooms. An empty list is a complete answer\n- Only loose, portable things. Not the door, not the floor, not the machinery\n bolted to it -- something a person could put in a pocket or under an arm\n- Each one must be consistent with the description you just wrote, and worth\n the player noticing\n- Pick its use_kind from the supplied physical profiles. Ordinary is the\n default. Food and drink can be consumed without healing; healing is only\n a restorative dose this world permits. Firestarters burn combustible\n objects, levers pry jammed passages, lockpicks try locks, and a key only\n opens a passage explicitly matched to it. Mark combustible only when an\n ordinary firestarter can destroy the object. The engine owns the effects\n and their amounts; its description cannot grant extra powers\n- If a thing has WRITING on it -- a note, a letter, a handbill, a label, a\n docket, a page, a sign -- mark it readable and WRITE OUT WHAT IS WRITTEN\n ON IT, exactly as it appears on the thing. The words themselves, not a\n description of them, and short enough to finish -- a few words, a line,\n or a few short lines. The game keeps those words and a player reading it\n twice reads the same ones\n- Never name it after a person or after a place. Already spoken for in this story, so do not reuse: Odile, Hollin Graves\n\n## Who Is Here\nWrite EXACTLY 1 person who is in this place right now.\n- Anyone you write is somebody the player can walk up to and talk to, so they\n have to have a reason to be standing here and an enduring aim that today's\n reason serves, threatens or exposes\n- Do not write the player, and do not write somebody passing through\n- Never give them the name of a place, of a thing, or any name already\n spoken for above\n\nWho they are is already decided. Write these people and do not change them:\n the 1st is Delvers, about 75, female\n\n## Their Four Objects of Desire\n\nWrite four STORY-SCALE things this person is after, then pick two TURN-SCALE labels.\nWork from everything above -- especially the years in their backstory, their repeated\nchoices, what they fear losing, the future they imagine, and where today's situation\npresses on that life.\n\nAn object of desire must satisfy both tests:\n1. It was shaping this person before the opening scene.\n2. Finishing today's errand would not finish it; it can drive choices across the\n whole story.\n\nThe two labels are picked from this list and nothing else:\n\n keep They are holding on to something they already have.\n obtain They are trying to get hold of something they do not have.\n reach They are trying to get to a particular place.\n attend They are trying to stay near a particular person.\n avoid They are trying to get away from a person or a place.\n withhold They are trying to stop somebody else getting something.\n offer They are trying to put something into somebody else's hands.\n\nconscious_desire\n The future, standing, relationship, legacy, place or way of life they knowingly\n organize their choices around and would name if asked what they want from their\n life. Make it specific to this person and world. Do not substitute the clue,\n deadline, delivery, payment or room they are dealing with today. One sentence.\n\nunconscious_desire\n The deeper reward their choices have pursued for years and they would deny: the\n recognition, absolution, dependence, belonging, power or intimacy beneath the\n conscious account. It must explain a repeated pattern in the backstory and pull at\n an angle to the conscious desire, not merely restate today's hidden motive. One\n sentence.\n\nrecognized_need\n The duty, oath, debt, craft, family burden or bodily discipline they believe they\n must keep over years whether they want to or not. It predates today's assignment,\n survives it, and can repeatedly obstruct the conscious desire. One sentence.\n\nunrecognized_need\n The enduring change, truth or relationship their life requires and their repeated\n pattern prevents them from seeing. A reader can infer it from the backstory. It\n cannot be completed by one confession, realization or errand: it demands a new way\n of choosing across the story. If they never move toward it, pursuit of the\n conscious desire ruins them. One sentence.\n\ndesire_pursuit\n Pick the listed ENGINE ACT that could repeatedly advance or protect the conscious\n desire in ordinary rooms. The label is the next-step expression of the larger\n desire, not its timescale.\n\nneed_pursuit\n Pick the listed ENGINE ACT that could repeatedly move them toward the unrecognized\n need in ordinary rooms. It may match desire_pursuit, but a different label should\n reflect a real conflict rather than manufactured variety.\n\nRules:\n- Every field names something to move TOWARD. State what lies beyond a fear, refusal\n or escape.\n- The object is larger than any single room act. Picking something up, handing it\n over, walking, waiting or staying near somebody may advance, protect, rehearse or\n betray it; that act must not become the whole desire.\n- Use today's room as pressure or evidence, not as the horizon of the person's life.\n- Keep the four consistent with the backstory, fears and dislikes. A fear is often\n the shadow of an object, but is not the object itself.\n- The conscious desire and unrecognized need must not be the same thing said twice.\n- Write in the third person, by name.\n- Do not resolve any object or say how the story ends.\n- Respect the stated length of each field.\n", + "schema": { + "name": "Location::DetailSchema", + "description": null, + "schema": { + "type": "object", + "properties": { + "description": { + "type": "string", + "description": "What the player sees, hears and smells standing in this place right now. Describe THIS place only -- not what neighbours it, not what is visible out of a window or across the way, because the world around it can move. Second person. One paragraph, 4 to 6 sentences.", + "maxLength": 1200 + }, + "lore": { + "type": "string", + "description": "What this place is, who made it and what happened here. Written for the game engine rather than the player. One paragraph, 3 to 5 sentences.", + "maxLength": 900 + }, + "name": { + "type": "string", + "description": "What this room is called -- ONLY when the instructions above ask you to name it. Leave this out entirely otherwise. A short noun phrase a player would type to walk into it, carrying the article English wants on it: \"the counting room\". Never the name of the building it is in, never a name this story has already given to a room, a person or a thing, and never a comma.", + "maxLength": 60 + }, + "items": { + "type": "array", + "description": "Portable things lying loose in this place that a player could pick up and carry away. Empty is the right answer for most rooms.", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "What the thing is called, as a player would type it to pick it up. A short noun phrase, 1 to 4 words, lower case unless it is a proper name. Never the name of a person or of a place.", + "maxLength": 60 + }, + "description": { + "type": "string", + "description": "What it is and what state it is in, consistent with the description of the room you just wrote. One or two sentences.", + "maxLength": 400 + }, + "use_kind": { + "type": "string", + "enum": [ + "ordinary", + "food", + "drink", + "healing", + "firestarter", + "lever", + "lockpick", + "key" + ], + "description": "The engine's physical profile: ordinary unless it is food, a drink, a healing dose, a firestarter, a prying lever, lockpicks, or a key. Choose healing only for an actual restorative dose allowed by this world. Profiles supply behavior from code, never from the description." + }, + "combustible": { + "type": "boolean", + "description": "True only if this portable thing can be destroyed by an ordinary carried firestarter: dry paper, cloth or wood. False for stone, metal, liquids, or anything uncertain." + }, + "readable": { + "type": "boolean", + "description": "True only if this thing has WRITING on it that a player could read: a note, a letter, a label, a docket, a page, a sign, an inscription. False for everything else, which is most things." + }, + "inscription": { + "type": "string", + "description": "The words written on it, exactly as they appear, and only when `readable` is true. Write what is actually on the thing -- what a player would read off it -- not a description of it. A few words, a line, or a few short lines; well under the limit, and finished rather than trailing off. Leave this out entirely when nothing is written on it.", + "maxLength": 400 + } + }, + "required": [ + "name", + "description", + "use_kind", + "combustible", + "readable" + ], + "additionalProperties": false + }, + "maxItems": 3 + }, + "people": { + "type": "array", + "description": "The person who is in this place right now. Write the one the instructions describe.", + "items": { + "type": "object", + "properties": { + "fullname": { + "type": "string", + "description": "Their full name, as a player would type it to speak to them. 2 or 3 words.", + "maxLength": 60 + }, + "nickname": { + "type": "string", + "description": "What they are called to their face. 1 or 2 words.", + "maxLength": 30 + }, + "appearance": { + "type": "string", + "description": "What somebody walking in sees of them, consistent with the room you just wrote and with the race and age you were given for them. One or two sentences.", + "maxLength": 300 + }, + "personality": { + "type": "string", + "description": "How they behave and how they treat a stranger. One or two sentences.", + "maxLength": 300 + }, + "backstory": { + "type": "string", + "description": "The life history that brought them here, the repeated choices it taught them, and why today's presence presses on an enduring aim. Third person, by name. Two or three sentences.", + "maxLength": 450 + }, + "likes": { + "type": "string", + "description": "A comma separated list of 2 or 3 things they enjoy.", + "maxLength": 160 + }, + "dislikes": { + "type": "string", + "description": "A comma separated list of 2 or 3 things they cannot stand.", + "maxLength": 160 + }, + "fears": { + "type": "string", + "description": "A comma separated list of 1 or 2 things they are afraid of.", + "maxLength": 160 + }, + "conscious_desire": { + "type": "string", + "description": "The future, standing, relationship, legacy, place or way of life they knowingly organize their choices around and would name if asked what they want from their life. Make it specific to this person and world. Do not substitute the clue, deadline, delivery, payment or room they are dealing with today. One sentence, third person, by name.", + "maxLength": 180 + }, + "unconscious_desire": { + "type": "string", + "description": "The deeper reward their choices have pursued for years and they would deny: the recognition, absolution, dependence, belonging, power or intimacy beneath the conscious account. It explains a repeated pattern in the backstory and pulls at an angle to the conscious desire, not merely today's hidden motive. One sentence, third person, by name.", + "maxLength": 180 + }, + "recognized_need": { + "type": "string", + "description": "The duty, oath, debt, craft, family burden or bodily discipline they believe they must keep over years whether they want to or not. It predates today's assignment, survives it, and can repeatedly obstruct the conscious desire. One sentence, third person, by name.", + "maxLength": 180 + }, + "unrecognized_need": { + "type": "string", + "description": "The enduring change, truth or relationship their life requires and their repeated pattern prevents them from seeing. A reader can infer it from the backstory. It cannot be completed by one confession, realization or errand: it demands a new way of choosing across the story. If they never move toward it, pursuit of the conscious desire ruins them. It must not be the conscious desire said twice. One sentence, third person, by name.", + "maxLength": 180 + }, + "desire_pursuit": { + "type": "string", + "enum": [ + "keep", + "obtain", + "reach", + "attend", + "avoid", + "withhold", + "offer" + ], + "description": "Which listed ENGINE ACT could repeatedly advance or protect the CONSCIOUS DESIRE in ordinary rooms. The label is the next-step expression of the larger desire, not its timescale." + }, + "need_pursuit": { + "type": "string", + "enum": [ + "keep", + "obtain", + "reach", + "attend", + "avoid", + "withhold", + "offer" + ], + "description": "Which listed ENGINE ACT could repeatedly move them toward the UNRECOGNIZED NEED in ordinary rooms. It may match desire_pursuit, but a different label should reflect a real conflict rather than manufactured variety." + } + }, + "required": [ + "fullname", + "nickname", + "appearance", + "personality", + "backstory", + "likes", + "dislikes", + "fears", + "conscious_desire", + "unconscious_desire", + "recognized_need", + "unrecognized_need", + "desire_pursuit", + "need_pursuit" + ], + "additionalProperties": false + }, + "minItems": 1, + "maxItems": 1 + } + }, + "required": [ + "description", + "lore", + "people" + ], + "additionalProperties": false, + "strict": true + } + } + }, + { + "purpose": "arrival", + "system": "You narrate a text adventure. You write the moment a player walks into a\nplace: what reaches them first, in the second person and the present\ntense. You never break character, never offer a numbered menu, and never\nmention that you are a narrator.\n\nDO NOT INCLUDE EMOJIS IN YOUR RESPONSE.\n", + "user": "## Universe Details\nphysics: Ordinary physics.\ntechnology: Lanterns, iron keys and hand tools.\n\n## Story Details\ntitle: A Deeper Floor (engine sweep)\ngenre: dungeon crawl\nsummary: Odile looks into an upper floor and the bottom one.\n\n## The Place\nname: The Upper Floor room 1\ndescription: A low chamber with a bedroll by the wall.\nlore: Somebody sleeps here between delves.\nways out: The Landing\n\n## Who Is Here\nOdile (Odile), Delvers -- the player, the one arriving\nWenna Cole (Wenna), Delvers\n\n## Just Before This\nThe player has come from The Landing. You arrive at The Bottom Floor room 1. You are unhurt. Also here: Hollin Graves. Nobody else is alive here. Fixed here, and not takeable: rock shelf, rubble, stalagmite, pool. Lying here: bone, rusted hinge. You are carrying: nothing.\n\n## Instructions\n- The player has never been here. This is the first time they have set\n foot in it\n- Narrate discovery: what catches them on the way in, before they\n have made sense of the rest\n- Address the player as \"you\", in the present tense\n- One paragraph. Do not re-describe the place item by item -- the\n description above is already what is here, and your job is the moment\n of coming into it\n- Anyone listed above is here; write them as already present, not as\n arriving. Do not add a person who is not on that list\n- Do not name a way out that is not on the list above\n- Respect the stated length of each field\n\n## Current State On Arrival\nThe place description is the world's original account. These current records\ntake precedence over its claims about people, items and wounds. Narrate the\nrecorded crossing result as part of this arrival.\nYou are unhurt.\nAlso here: Wenna Cole. Nobody else is alive here.\nFixed here, and not takeable: rock shelf, rubble, pool, ledge.\nLying here: bone, candle stub, shard of pottery, coin.\nYou are carrying: nothing.\n", + "schema": { + "name": "Scene::Schema", + "description": null, + "schema": { + "type": "object", + "properties": { + "description": { + "type": "string", + "description": "What the player experiences as they arrive here, right now. Second person, present tense. One paragraph, 3 to 5 sentences.", + "maxLength": 900 + }, + "summary": { + "type": "string", + "description": "What happened in this moment, for the game engine rather than the player. Third person. One sentence.", + "maxLength": 200 + } + }, + "required": [ + "description", + "summary" + ], + "additionalProperties": false, + "strict": true + } + } + } + ], + "written": { + "locations": { + "rows": [ + { + "id": 1000000002, + "story_id": 1000000001, + "name": "The Upper Floor", + "description": "The upper level of the dungeon, one chamber just under the landing.", + "lore": "Delvers pass through here on their way to somewhere worse.", + "last_protagonist_visit": null, + "parent_location_id": null, + "detail_level": "realized", + "teaser": "The upper level of the dungeon, one chamber deep.", + "mobile": 0, + "danger": "safe", + "hazard": null, + "hazard_die": null, + "x": null, + "y": null, + "z": null, + "width": 6, + "depth": 6, + "population": null, + "generation_checkpoint": null, + "surface": null, + "kind": "warren", + "density": null + }, + { + "id": 1000000003, + "story_id": 1000000001, + "name": "The Bottom Floor", + "description": "The bottom level of the dungeon, one chamber cut far down.", + "lore": "Whatever lives this deep has lived here a long time.", + "last_protagonist_visit": null, + "parent_location_id": null, + "detail_level": "realized", + "teaser": "The bottom level of the dungeon, one chamber deep.", + "mobile": 0, + "danger": "safe", + "hazard": null, + "hazard_die": null, + "x": null, + "y": null, + "z": null, + "width": 6, + "depth": 6, + "population": null, + "generation_checkpoint": null, + "surface": null, + "kind": "warren", + "density": null + }, + { + "id": 1000000004, + "story_id": 1000000001, + "name": "The Bottom Floor room 1", + "description": "A wide chamber where somebody has made a camp among the stones.", + "lore": "Delvers who stay down here too long stop going back up.", + "last_protagonist_visit": "2026-10-30 08:01:30", + "parent_location_id": 1000000003, + "detail_level": "realized", + "teaser": "A room inside The Bottom Floor, 6 by 6 paces on storey 0.", + "mobile": 0, + "danger": "safe", + "hazard": null, + "hazard_die": null, + "x": 0, + "y": 0, + "z": 0, + "width": 6, + "depth": 6, + "population": null, + "generation_checkpoint": null, + "surface": null, + "kind": "cave", + "density": null + }, + { + "id": 1000000005, + "story_id": 1000000001, + "name": "The Upper Floor room 1", + "description": "A low chamber with a bedroll by the wall.", + "lore": "Somebody sleeps here between delves.", + "last_protagonist_visit": "2026-10-30 08:03:00", + "parent_location_id": 1000000002, + "detail_level": "realized", + "teaser": "A room inside The Upper Floor, 6 by 6 paces on storey 0.", + "mobile": 0, + "danger": "uneasy", + "hazard": null, + "hazard_die": null, + "x": 0, + "y": 0, + "z": 0, + "width": 6, + "depth": 6, + "population": null, + "generation_checkpoint": null, + "surface": null, + "kind": "cave", + "density": null + } + ], + "removed": [] + }, + "location_connections": { + "rows": [ + { + "id": 1000000005, + "location_id": 1000000004, + "connected_location_id": 1000000001, + "distance": "adjacent", + "time_to_travel": "about a minute", + "travel_method": "taking stairs", + "hazard": null, + "hazard_die": null, + "barrier": "open", + "key_template_id": null + }, + { + "id": 1000000006, + "location_id": 1000000001, + "connected_location_id": 1000000004, + "distance": "adjacent", + "time_to_travel": "about a minute", + "travel_method": "taking stairs", + "hazard": null, + "hazard_die": null, + "barrier": "open", + "key_template_id": null + }, + { + "id": 1000000007, + "location_id": 1000000005, + "connected_location_id": 1000000001, + "distance": "adjacent", + "time_to_travel": "about a minute", + "travel_method": "taking stairs", + "hazard": null, + "hazard_die": null, + "barrier": "open", + "key_template_id": null + }, + { + "id": 1000000008, + "location_id": 1000000001, + "connected_location_id": 1000000005, + "distance": "adjacent", + "time_to_travel": "about a minute", + "travel_method": "taking stairs", + "hazard": null, + "hazard_die": null, + "barrier": "open", + "key_template_id": null + } + ], + "removed": [ + 1000000001, + 1000000002, + 1000000003, + 1000000004 + ] + }, + "characters": { + "rows": [ + { + "id": 1000000002, + "story_id": 1000000001, + "fullname": "Hollin Graves", + "nickname": "Hollin", + "age": 25, + "sex": "trans man", + "personality": "Watchful and slow to speak.", + "appearance": "A delver's coat gone grey with stone dust.", + "likes": "The quiet down here.", + "dislikes": "Lanterns from above.", + "fears": "The surface.", + "backstory": "A delver who stopped going back up years ago.", + "is_companion": 0, + "race_id": 1000000001, + "is_protagonist": 0, + "location_id": 1000000004, + "deliberately_absent": 0, + "level": 4, + "hit_die": 6, + "strength": 13, + "dexterity": 11, + "will": 10, + "hostile": 0, + "x": 0, + "y": 3, + "conscious_desire": "", + "unconscious_desire": "", + "recognized_need": "", + "unrecognized_need": "", + "desire_pursuit": null, + "need_pursuit": null, + "posted_to_id": null + }, + { + "id": 1000000003, + "story_id": 1000000001, + "fullname": "Wenna Cole", + "nickname": "Wenna", + "age": 75, + "sex": "female", + "personality": "Cheerful and talkative.", + "appearance": "A patched cloak and a sack of candles.", + "likes": "Company.", + "dislikes": "Silence.", + "fears": "Being left in the dark.", + "backstory": "A candle-seller who follows the delvers down.", + "is_companion": 0, + "race_id": 1000000001, + "is_protagonist": 0, + "location_id": 1000000005, + "deliberately_absent": 0, + "level": 1, + "hit_die": 8, + "strength": 9, + "dexterity": 13, + "will": 12, + "hostile": 0, + "x": 3, + "y": 3, + "conscious_desire": "", + "unconscious_desire": "", + "recognized_need": "", + "unrecognized_need": "", + "desire_pursuit": null, + "need_pursuit": null, + "posted_to_id": null + } + ], + "removed": [] + }, + "items": { + "rows": [ + { + "id": 1000000001, + "character_id": null, + "name": "rock shelf", + "description": "The rock shelf, fixed in place.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "immovable", + "x": 5, + "y": 2, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "top", + "within_id": null, + "how": null, + "kit_key": "cave/rock shelf", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "stone", + "wear": null, + "worn": 0 + }, + { + "id": 1000000002, + "character_id": null, + "name": "rubble", + "description": "The rubble, fixed in place.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "immovable", + "x": 4, + "y": 0, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "hollow", + "within_id": null, + "how": null, + "kit_key": "cave/rubble", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000003, + "character_id": null, + "name": "stalagmite", + "description": "The stalagmite, fixed in place.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "immovable", + "x": 1, + "y": 5, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "nothing", + "within_id": null, + "how": null, + "kit_key": "cave/stalagmite", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "stone", + "wear": null, + "worn": 0 + }, + { + "id": 1000000004, + "character_id": null, + "name": "pool", + "description": "The pool, fixed in place.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "immovable", + "x": 0, + "y": 4, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "nothing", + "within_id": null, + "how": null, + "kit_key": "cave/pool", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000005, + "character_id": null, + "name": "bone", + "description": "The bone, in the rubble.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000002, + "how": "in", + "kit_key": "cave/rubble/bone", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000006, + "character_id": null, + "name": "rusted hinge", + "description": "The rusted hinge, in the rubble.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000002, + "how": "in", + "kit_key": "cave/rubble/rusted hinge", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "metal", + "wear": null, + "worn": 0 + }, + { + "id": 1000000007, + "character_id": null, + "name": "rock shelf", + "description": "The rock shelf, fixed in place.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000001, + "bulk": "immovable", + "x": 5, + "y": 2, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "top", + "within_id": null, + "how": null, + "kit_key": "cave/rock shelf", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "stone", + "wear": null, + "worn": 0 + }, + { + "id": 1000000008, + "character_id": null, + "name": "rubble", + "description": "The rubble, fixed in place.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000002, + "bulk": "immovable", + "x": 4, + "y": 0, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "hollow", + "within_id": null, + "how": null, + "kit_key": "cave/rubble", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000009, + "character_id": null, + "name": "stalagmite", + "description": "The stalagmite, fixed in place.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000003, + "bulk": "immovable", + "x": 1, + "y": 5, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "nothing", + "within_id": null, + "how": null, + "kit_key": "cave/stalagmite", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "stone", + "wear": null, + "worn": 0 + }, + { + "id": 1000000010, + "character_id": null, + "name": "pool", + "description": "The pool, fixed in place.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000004, + "bulk": "immovable", + "x": 0, + "y": 4, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "nothing", + "within_id": null, + "how": null, + "kit_key": "cave/pool", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000011, + "character_id": null, + "name": "bone", + "description": "The bone, in the rubble.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000005, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000008, + "how": "in", + "kit_key": "cave/rubble/bone", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000012, + "character_id": null, + "name": "rusted hinge", + "description": "The rusted hinge, in the rubble.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000006, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000008, + "how": "in", + "kit_key": "cave/rubble/rusted hinge", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "metal", + "wear": null, + "worn": 0 + }, + { + "id": 1000000013, + "character_id": null, + "name": "rock shelf", + "description": "The rock shelf, fixed in place.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "immovable", + "x": 2, + "y": 3, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "top", + "within_id": null, + "how": null, + "kit_key": "cave/rock shelf", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "stone", + "wear": null, + "worn": 0 + }, + { + "id": 1000000014, + "character_id": null, + "name": "rubble", + "description": "The rubble, fixed in place.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "immovable", + "x": 1, + "y": 3, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "hollow", + "within_id": null, + "how": null, + "kit_key": "cave/rubble", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000015, + "character_id": null, + "name": "pool", + "description": "The pool, fixed in place.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "immovable", + "x": 1, + "y": 2, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "nothing", + "within_id": null, + "how": null, + "kit_key": "cave/pool", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000016, + "character_id": null, + "name": "ledge", + "description": "The ledge, fixed in place.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "immovable", + "x": 0, + "y": 3, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "top", + "within_id": null, + "how": null, + "kit_key": "cave/ledge", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000017, + "character_id": null, + "name": "bone", + "description": "The bone, on the rock shelf.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000013, + "how": "on", + "kit_key": "cave/rock shelf/bone", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000018, + "character_id": null, + "name": "candle stub", + "description": "The candle stub, on the rock shelf.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 1, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000013, + "how": "on", + "kit_key": "cave/rock shelf/candle stub", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000019, + "character_id": null, + "name": "shard of pottery", + "description": "The shard of pottery, in the rubble.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000014, + "how": "in", + "kit_key": "cave/rubble/shard of pottery", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000020, + "character_id": null, + "name": "coin", + "description": "The coin, in the rubble.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000014, + "how": "in", + "kit_key": "cave/rubble/coin", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "metal", + "wear": null, + "worn": 0 + }, + { + "id": 1000000021, + "character_id": null, + "name": "rock shelf", + "description": "The rock shelf, fixed in place.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000013, + "bulk": "immovable", + "x": 2, + "y": 3, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "top", + "within_id": null, + "how": null, + "kit_key": "cave/rock shelf", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "stone", + "wear": null, + "worn": 0 + }, + { + "id": 1000000022, + "character_id": null, + "name": "rubble", + "description": "The rubble, fixed in place.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000014, + "bulk": "immovable", + "x": 1, + "y": 3, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "hollow", + "within_id": null, + "how": null, + "kit_key": "cave/rubble", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000023, + "character_id": null, + "name": "pool", + "description": "The pool, fixed in place.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000015, + "bulk": "immovable", + "x": 1, + "y": 2, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "nothing", + "within_id": null, + "how": null, + "kit_key": "cave/pool", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000024, + "character_id": null, + "name": "ledge", + "description": "The ledge, fixed in place.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000016, + "bulk": "immovable", + "x": 0, + "y": 3, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "top", + "within_id": null, + "how": null, + "kit_key": "cave/ledge", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000025, + "character_id": null, + "name": "bone", + "description": "The bone, on the rock shelf.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000017, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000021, + "how": "on", + "kit_key": "cave/rock shelf/bone", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000026, + "character_id": null, + "name": "candle stub", + "description": "The candle stub, on the rock shelf.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000018, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 1, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000021, + "how": "on", + "kit_key": "cave/rock shelf/candle stub", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000027, + "character_id": null, + "name": "shard of pottery", + "description": "The shard of pottery, in the rubble.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000019, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000022, + "how": "in", + "kit_key": "cave/rubble/shard of pottery", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000028, + "character_id": null, + "name": "coin", + "description": "The coin, in the rubble.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000020, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000022, + "how": "in", + "kit_key": "cave/rubble/coin", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "metal", + "wear": null, + "worn": 0 + } + ], + "removed": [] + }, + "quest_steps": { + "rows": [], + "removed": [] + }, + "scenes": { + "rows": [ + { + "id": 1000000002, + "story_id": 1000000001, + "location_id": 1000000004, + "previous_scene_id": 1000000001, + "description": "You arrive at The Bottom Floor room 1. You are unhurt. Also here: Hollin Graves. Nobody else is alive here. Fixed here, and not takeable: rock shelf, rubble, stalagmite, pool. Lying here: bone, rusted hinge. You are carrying: nothing.", + "story_timestamp": "2026-10-30 08:01:30", + "summary": "You arrive at The Bottom Floor room 1. You are unhurt. Also here: Hollin Graves. Nobody else is alive here. Fixed here, and not takeable: rock shelf, rubble, stalagmite, pool. Lying here: bone, rusted hinge. You are carrying: nothing.", + "is_opening": 0, + "typed": "move The Bottom Floor", + "resolved_action": "move", + "acted_on_type": "Location", + "acted_on_id": 1000000003, + "resolved_by": "grammar", + "engine_fallback": 1, + "engine_fact": "You are unhurt.\nAlso here: Hollin Graves. Nobody else is alive here.\nFixed here, and not takeable: rock shelf, rubble, stalagmite, pool.\nLying here: bone, rusted hinge.\nYou are carrying: nothing." + }, + { + "id": 1000000003, + "story_id": 1000000001, + "location_id": 1000000005, + "previous_scene_id": 1000000002, + "description": "You arrive at The Upper Floor room 1. You are unhurt. Also here: Wenna Cole. Nobody else is alive here. Fixed here, and not takeable: rock shelf, rubble, pool, ledge. Lying here: bone, candle stub, shard of pottery, coin. You are carrying: nothing.", + "story_timestamp": "2026-10-30 08:03:00", + "summary": "You arrive at The Upper Floor room 1. You are unhurt. Also here: Wenna Cole. Nobody else is alive here. Fixed here, and not takeable: rock shelf, rubble, pool, ledge. Lying here: bone, candle stub, shard of pottery, coin. You are carrying: nothing.", + "is_opening": 0, + "typed": "move The Upper Floor", + "resolved_action": "move", + "acted_on_type": "Location", + "acted_on_id": 1000000002, + "resolved_by": "grammar", + "engine_fallback": 1, + "engine_fact": "You are unhurt.\nAlso here: Wenna Cole. Nobody else is alive here.\nFixed here, and not takeable: rock shelf, rubble, pool, ledge.\nLying here: bone, candle stub, shard of pottery, coin.\nYou are carrying: nothing." + } + ], + "removed": [] + }, + "characters_scenes": { + "rows": [ + { + "character_id": 1000000001, + "scene_id": 1000000002 + }, + { + "character_id": 1000000002, + "scene_id": 1000000002 + }, + { + "character_id": 1000000001, + "scene_id": 1000000003 + }, + { + "character_id": 1000000003, + "scene_id": 1000000003 + } + ], + "removed": [] + }, + "interactions": { + "rows": [], + "removed": [] + }, + "playthrough_endings": { + "rows": [], + "removed": [] + }, + "playthrough_beats": { + "rows": [], + "removed": [] + }, + "playthrough_npc_states": { + "rows": [], + "removed": [] + }, + "world_events": { + "rows": [], + "removed": [] + } + } + } + ] +} diff --git a/parity/records/a-guard-waits-at-the-stairs.json b/parity/records/a-guard-waits-at-the-stairs.json new file mode 100644 index 0000000..b9e392a --- /dev/null +++ b/parity/records/a-guard-waits-at-the-stairs.json @@ -0,0 +1,15319 @@ +{ + "script": "a-guard-waits-at-the-stairs", + "steps": [ + { + "step": "step 1 into-the-upper-floor", + "requests": [ + { + "purpose": "location", + "system": "You build the rooms of a text adventure one at a time. You write places a\nplayer can stand in and walk out of: concrete, specific, and consistent\nwith the world they belong to. Every exit you name is somewhere the player\ncould actually go.\n\nDO NOT INCLUDE EMOJIS IN YOUR RESPONSE.\n", + "user": "## Universe Details\nphysics: Ordinary physics.\ntechnology: Lanterns, iron keys and hand tools.\ngeographies: A dungeon cut down into black stone under a lantern-lit landing.\nraces: Delvers, Stairwardens\ncivilizations: Delvers who sell what they bring up, and the things that keep the stairs.\n\n\n## Story Details\ntitle: A Guarded Stair (engine sweep)\ngenre: dungeon crawl\npreface: Every stair down in the dungeon has something standing at the top of it.\nsummary: Odile goes down past the thing that keeps the stair.\n\n\n## The Place\nname: The Upper Floor\nteaser: The first level of the dungeon, with a stair somewhere that goes further down.\n\n## Instructions\nWrite this place out in full. It is a BUILDING -- somewhere with rooms\ninside it that a player walks into and moves around in.\n- The description is what the player reads as they come in. Address them as \"you\"\n- Describe what is here now, not the history -- the history is the lore\n- Stay consistent with the universe and with the teaser above\n- Do NOT describe the floor plan: how many rooms there are, where the stairs\n are and which door leads where are the game's to decide, out of the answers\n below, and it will tell you room by room as the player reaches them\n- Do not name anybody standing here and do not list anything lying here.\n People and things belong to the rooms, and each room is written as it is\n reached\n- Respect the stated length of each field\n\n## What Kind Of Building This Is\nThe game lays the inside out itself -- every room, every door, every stair\n-- from the answers to these, and then writes each room as the player\nreaches it. You are choosing what KIND of place this is, not drawing it.\n- Answer for the place you have just described and for the story it stands in\n- Every one of them can be left out, and the quietest answer is usually the\n right one: one floor, nothing underneath, nothing dangerous, nothing that\n hurts you\n- A HAZARD is not atmosphere. It takes hit points off everybody who walks\n through those rooms, every turn in some cases, so pick one only for a\n place that really is flooded, unlit, silent or airless\n- A GRADIENT is only worth saying when the place itself makes it true: a\n cellar that gets worse the further down you go, a tower that gets worse\n the higher you climb\n", + "schema": { + "name": "Location::PlaceSchema", + "description": null, + "schema": { + "type": "object", + "properties": { + "description": { + "type": "string", + "description": "What the player sees, hears and smells standing in this place right now. Describe THIS place only -- not what neighbours it, not what is visible out of a window or across the way, because the world around it can move. Second person. One paragraph, 4 to 6 sentences.", + "maxLength": 1200 + }, + "lore": { + "type": "string", + "description": "What this place is, who made it and what happened here. Written for the game engine rather than the player. One paragraph, 3 to 5 sentences.", + "maxLength": 900 + }, + "place_kind": { + "type": "string", + "enum": [ + "house", + "farmhouse", + "mansion", + "apartment block", + "inn", + "shop", + "office building", + "guildhall", + "workshop", + "smithy", + "mill", + "factory", + "warehouse", + "barn", + "temple", + "library", + "school", + "hospital", + "theatre", + "bathhouse", + "barracks", + "fortress", + "jail", + "tower", + "lighthouse", + "warren", + "ruin", + "spaceship", + "space station" + ], + "description": "What sort of place with rooms this is -- a building, a ship, a station -- as the game will lay out and furnish its rooms: pick the closest. The game decides which rooms it has and what stands in each; you describe it from outside and at its way in." + }, + "parameters": { + "type": "object", + "properties": { + "storeys_above": { + "type": "string", + "enum": [ + "ground floor only", + "one storey up", + "two storeys up" + ], + "description": "How far up it goes, counting the ground floor. Most buildings are one floor." + }, + "storeys_below": { + "type": "string", + "enum": [ + "none", + "a cellar", + "two levels down", + "deep" + ], + "description": "How far down it goes below the ground floor. Most buildings have nothing under them." + }, + "danger": { + "type": "string", + "enum": [ + "safe", + "uneasy", + "dangerous" + ], + "description": "How likely a room of it is to hold something that means the player harm. Most buildings are safe." + }, + "gradient": { + "type": "string", + "enum": [ + "the same throughout", + "worse the deeper you go", + "worse the higher you climb" + ], + "description": "Whether that gets worse in one direction. Only say so when the place itself makes it true -- a cellar that gets worse the further down you go, a tower that gets worse the higher you climb." + }, + "hazard": { + "type": "string", + "enum": [ + "none", + "flooded", + "unlit", + "silent", + "airless" + ], + "description": "What standing in its rooms does to a person, if anything. NONE is the right answer for almost every building: this is not atmosphere, it is a place that takes hit points off whoever walks through it." + } + }, + "required": [], + "additionalProperties": false, + "description": "What kind of building this is. Every field is optional; leave one out and the game takes the quietest option. Answer for the place you have just described." + } + }, + "required": [ + "description", + "lore" + ], + "additionalProperties": false, + "strict": true + } + } + }, + { + "purpose": "location", + "system": "You build the rooms of a text adventure one at a time. You write places a\nplayer can stand in and walk out of: concrete, specific, and consistent\nwith the world they belong to. Every exit you name is somewhere the player\ncould actually go.\n\nDO NOT INCLUDE EMOJIS IN YOUR RESPONSE.\n", + "user": "## Universe Details\nphysics: Ordinary physics.\ntechnology: Lanterns, iron keys and hand tools.\ngeographies: A dungeon cut down into black stone under a lantern-lit landing.\nraces: Delvers, Stairwardens\ncivilizations: Delvers who sell what they bring up, and the things that keep the stairs.\n\n\n## Story Details\ntitle: A Guarded Stair (engine sweep)\ngenre: dungeon crawl\npreface: Every stair down in the dungeon has something standing at the top of it.\nsummary: Odile goes down past the thing that keeps the stair.\n\n\n## The Place\nname: The Upper Floor room 1\nteaser: A room inside The Upper Floor, 9 by 5 paces on storey 0.\n## Where This Room Is\nThe game's own records of this room, already decided and not yours to\nchange. Write the room around them: do not contradict a measurement, and\ndo not give it a way out this list does not have.\nThis room is 9 by 5 paces -- about 14 by 8 metres. It is on storey 0 of The Upper Floor, which is 12 by 12 paces across; storey 0 is the ground floor. Its ways out: a door in the east wall, to The Upper Floor room 2; a way out to The Landing, which is outside The Upper Floor. Those are every way out of this room.\n## Already Here, Decided By The Game\nThe game's own records of what is in this room, already decided and not yours\nto change, and it will tell the player so. Write the room around them, and do\nnot add another piece of furniture or fixed thing a player could reach for.\nFixed in place: rock shelf, rubble, pool, ledge.\nOn the rock shelf: bone, candle stub.\nIn the rubble: shard of pottery, coin.\nDo not list any of them again as a thing lying here.\n## Instructions\nWrite this place out in full.\n- The description is what the player reads on arrival. Address them as \"you\"\n- Describe what is here now, not the history -- the history is the lore\n- Stay consistent with the universe and with the teaser above\n- Respect the stated length of each field\n- NAME THIS ROOM. It is one room inside The Upper Floor. The player reads the room and\n the place together -- \"the of The Upper Floor\" -- so name the ROOM only,\n and never put the place's own name into it. A short noun phrase carrying the\n article English wants on it, 2 to 4 words: \"the counting room\", \"the cold\n store\", \"the harbourmaster's office\". Name it for what the floor plan above\n says this room is and for what you have just described standing in it. Never\n a comma in it\n\n## What Is Lying Here\nList AT MOST 3 portable things a player could pick up and carry away, besides the ones Already Here above: those are written already, and a thing named again is not a new one.\n- Nothing is the right answer for most rooms. An empty list is a complete answer\n- Only loose, portable things. Not the door, not the floor, not the machinery\n bolted to it -- something a person could put in a pocket or under an arm\n- Each one must be consistent with the description you just wrote, and worth\n the player noticing\n- Pick its use_kind from the supplied physical profiles. Ordinary is the\n default. Food and drink can be consumed without healing; healing is only\n a restorative dose this world permits. Firestarters burn combustible\n objects, levers pry jammed passages, lockpicks try locks, and a key only\n opens a passage explicitly matched to it. Mark combustible only when an\n ordinary firestarter can destroy the object. The engine owns the effects\n and their amounts; its description cannot grant extra powers\n- If a thing has WRITING on it -- a note, a letter, a handbill, a label, a\n docket, a page, a sign -- mark it readable and WRITE OUT WHAT IS WRITTEN\n ON IT, exactly as it appears on the thing. The words themselves, not a\n description of them, and short enough to finish -- a few words, a line,\n or a few short lines. The game keeps those words and a player reading it\n twice reads the same ones\n- Never name it after a person or after a place. Already spoken for in this story, so do not reuse: Odile, Brask the Stairwarden, stair key\n\n## Who Is Here\nWrite EXACTLY 1 person who is in this place right now.\n- Anyone you write is somebody the player can walk up to and talk to, so they\n have to have a reason to be standing here and an enduring aim that today's\n reason serves, threatens or exposes\n- Do not write the player, and do not write somebody passing through\n- Never give them the name of a place, of a thing, or any name already\n spoken for above\n\nWho they are is already decided. Write these people and do not change them:\n the 1st is Delvers, about 23, female\n\n## Their Four Objects of Desire\n\nWrite four STORY-SCALE things this person is after, then pick two TURN-SCALE labels.\nWork from everything above -- especially the years in their backstory, their repeated\nchoices, what they fear losing, the future they imagine, and where today's situation\npresses on that life.\n\nAn object of desire must satisfy both tests:\n1. It was shaping this person before the opening scene.\n2. Finishing today's errand would not finish it; it can drive choices across the\n whole story.\n\nThe two labels are picked from this list and nothing else:\n\n keep They are holding on to something they already have.\n obtain They are trying to get hold of something they do not have.\n reach They are trying to get to a particular place.\n attend They are trying to stay near a particular person.\n avoid They are trying to get away from a person or a place.\n withhold They are trying to stop somebody else getting something.\n offer They are trying to put something into somebody else's hands.\n\nconscious_desire\n The future, standing, relationship, legacy, place or way of life they knowingly\n organize their choices around and would name if asked what they want from their\n life. Make it specific to this person and world. Do not substitute the clue,\n deadline, delivery, payment or room they are dealing with today. One sentence.\n\nunconscious_desire\n The deeper reward their choices have pursued for years and they would deny: the\n recognition, absolution, dependence, belonging, power or intimacy beneath the\n conscious account. It must explain a repeated pattern in the backstory and pull at\n an angle to the conscious desire, not merely restate today's hidden motive. One\n sentence.\n\nrecognized_need\n The duty, oath, debt, craft, family burden or bodily discipline they believe they\n must keep over years whether they want to or not. It predates today's assignment,\n survives it, and can repeatedly obstruct the conscious desire. One sentence.\n\nunrecognized_need\n The enduring change, truth or relationship their life requires and their repeated\n pattern prevents them from seeing. A reader can infer it from the backstory. It\n cannot be completed by one confession, realization or errand: it demands a new way\n of choosing across the story. If they never move toward it, pursuit of the\n conscious desire ruins them. One sentence.\n\ndesire_pursuit\n Pick the listed ENGINE ACT that could repeatedly advance or protect the conscious\n desire in ordinary rooms. The label is the next-step expression of the larger\n desire, not its timescale.\n\nneed_pursuit\n Pick the listed ENGINE ACT that could repeatedly move them toward the unrecognized\n need in ordinary rooms. It may match desire_pursuit, but a different label should\n reflect a real conflict rather than manufactured variety.\n\nRules:\n- Every field names something to move TOWARD. State what lies beyond a fear, refusal\n or escape.\n- The object is larger than any single room act. Picking something up, handing it\n over, walking, waiting or staying near somebody may advance, protect, rehearse or\n betray it; that act must not become the whole desire.\n- Use today's room as pressure or evidence, not as the horizon of the person's life.\n- Keep the four consistent with the backstory, fears and dislikes. A fear is often\n the shadow of an object, but is not the object itself.\n- The conscious desire and unrecognized need must not be the same thing said twice.\n- Write in the third person, by name.\n- Do not resolve any object or say how the story ends.\n- Respect the stated length of each field.\n", + "schema": { + "name": "Location::DetailSchema", + "description": null, + "schema": { + "type": "object", + "properties": { + "description": { + "type": "string", + "description": "What the player sees, hears and smells standing in this place right now. Describe THIS place only -- not what neighbours it, not what is visible out of a window or across the way, because the world around it can move. Second person. One paragraph, 4 to 6 sentences.", + "maxLength": 1200 + }, + "lore": { + "type": "string", + "description": "What this place is, who made it and what happened here. Written for the game engine rather than the player. One paragraph, 3 to 5 sentences.", + "maxLength": 900 + }, + "name": { + "type": "string", + "description": "What this room is called -- ONLY when the instructions above ask you to name it. Leave this out entirely otherwise. A short noun phrase a player would type to walk into it, carrying the article English wants on it: \"the counting room\". Never the name of the building it is in, never a name this story has already given to a room, a person or a thing, and never a comma.", + "maxLength": 60 + }, + "items": { + "type": "array", + "description": "Portable things lying loose in this place that a player could pick up and carry away. Empty is the right answer for most rooms.", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "What the thing is called, as a player would type it to pick it up. A short noun phrase, 1 to 4 words, lower case unless it is a proper name. Never the name of a person or of a place.", + "maxLength": 60 + }, + "description": { + "type": "string", + "description": "What it is and what state it is in, consistent with the description of the room you just wrote. One or two sentences.", + "maxLength": 400 + }, + "use_kind": { + "type": "string", + "enum": [ + "ordinary", + "food", + "drink", + "healing", + "firestarter", + "lever", + "lockpick", + "key" + ], + "description": "The engine's physical profile: ordinary unless it is food, a drink, a healing dose, a firestarter, a prying lever, lockpicks, or a key. Choose healing only for an actual restorative dose allowed by this world. Profiles supply behavior from code, never from the description." + }, + "combustible": { + "type": "boolean", + "description": "True only if this portable thing can be destroyed by an ordinary carried firestarter: dry paper, cloth or wood. False for stone, metal, liquids, or anything uncertain." + }, + "readable": { + "type": "boolean", + "description": "True only if this thing has WRITING on it that a player could read: a note, a letter, a label, a docket, a page, a sign, an inscription. False for everything else, which is most things." + }, + "inscription": { + "type": "string", + "description": "The words written on it, exactly as they appear, and only when `readable` is true. Write what is actually on the thing -- what a player would read off it -- not a description of it. A few words, a line, or a few short lines; well under the limit, and finished rather than trailing off. Leave this out entirely when nothing is written on it.", + "maxLength": 400 + } + }, + "required": [ + "name", + "description", + "use_kind", + "combustible", + "readable" + ], + "additionalProperties": false + }, + "maxItems": 3 + }, + "people": { + "type": "array", + "description": "The person who is in this place right now. Write the one the instructions describe.", + "items": { + "type": "object", + "properties": { + "fullname": { + "type": "string", + "description": "Their full name, as a player would type it to speak to them. 2 or 3 words.", + "maxLength": 60 + }, + "nickname": { + "type": "string", + "description": "What they are called to their face. 1 or 2 words.", + "maxLength": 30 + }, + "appearance": { + "type": "string", + "description": "What somebody walking in sees of them, consistent with the room you just wrote and with the race and age you were given for them. One or two sentences.", + "maxLength": 300 + }, + "personality": { + "type": "string", + "description": "How they behave and how they treat a stranger. One or two sentences.", + "maxLength": 300 + }, + "backstory": { + "type": "string", + "description": "The life history that brought them here, the repeated choices it taught them, and why today's presence presses on an enduring aim. Third person, by name. Two or three sentences.", + "maxLength": 450 + }, + "likes": { + "type": "string", + "description": "A comma separated list of 2 or 3 things they enjoy.", + "maxLength": 160 + }, + "dislikes": { + "type": "string", + "description": "A comma separated list of 2 or 3 things they cannot stand.", + "maxLength": 160 + }, + "fears": { + "type": "string", + "description": "A comma separated list of 1 or 2 things they are afraid of.", + "maxLength": 160 + }, + "conscious_desire": { + "type": "string", + "description": "The future, standing, relationship, legacy, place or way of life they knowingly organize their choices around and would name if asked what they want from their life. Make it specific to this person and world. Do not substitute the clue, deadline, delivery, payment or room they are dealing with today. One sentence, third person, by name.", + "maxLength": 180 + }, + "unconscious_desire": { + "type": "string", + "description": "The deeper reward their choices have pursued for years and they would deny: the recognition, absolution, dependence, belonging, power or intimacy beneath the conscious account. It explains a repeated pattern in the backstory and pulls at an angle to the conscious desire, not merely today's hidden motive. One sentence, third person, by name.", + "maxLength": 180 + }, + "recognized_need": { + "type": "string", + "description": "The duty, oath, debt, craft, family burden or bodily discipline they believe they must keep over years whether they want to or not. It predates today's assignment, survives it, and can repeatedly obstruct the conscious desire. One sentence, third person, by name.", + "maxLength": 180 + }, + "unrecognized_need": { + "type": "string", + "description": "The enduring change, truth or relationship their life requires and their repeated pattern prevents them from seeing. A reader can infer it from the backstory. It cannot be completed by one confession, realization or errand: it demands a new way of choosing across the story. If they never move toward it, pursuit of the conscious desire ruins them. It must not be the conscious desire said twice. One sentence, third person, by name.", + "maxLength": 180 + }, + "desire_pursuit": { + "type": "string", + "enum": [ + "keep", + "obtain", + "reach", + "attend", + "avoid", + "withhold", + "offer" + ], + "description": "Which listed ENGINE ACT could repeatedly advance or protect the CONSCIOUS DESIRE in ordinary rooms. The label is the next-step expression of the larger desire, not its timescale." + }, + "need_pursuit": { + "type": "string", + "enum": [ + "keep", + "obtain", + "reach", + "attend", + "avoid", + "withhold", + "offer" + ], + "description": "Which listed ENGINE ACT could repeatedly move them toward the UNRECOGNIZED NEED in ordinary rooms. It may match desire_pursuit, but a different label should reflect a real conflict rather than manufactured variety." + } + }, + "required": [ + "fullname", + "nickname", + "appearance", + "personality", + "backstory", + "likes", + "dislikes", + "fears", + "conscious_desire", + "unconscious_desire", + "recognized_need", + "unrecognized_need", + "desire_pursuit", + "need_pursuit" + ], + "additionalProperties": false + }, + "minItems": 1, + "maxItems": 1 + } + }, + "required": [ + "description", + "lore", + "people" + ], + "additionalProperties": false, + "strict": true + } + } + }, + { + "purpose": "arrival", + "system": "You narrate a text adventure. You write the moment a player walks into a\nplace: what reaches them first, in the second person and the present\ntense. You never break character, never offer a numbered menu, and never\nmention that you are a narrator.\n\nDO NOT INCLUDE EMOJIS IN YOUR RESPONSE.\n", + "user": "## Universe Details\nphysics: Ordinary physics.\ntechnology: Lanterns, iron keys and hand tools.\n\n## Story Details\ntitle: A Guarded Stair (engine sweep)\ngenre: dungeon crawl\nsummary: Odile goes down past the thing that keeps the stair.\n\n## The Place\nname: The Upper Floor room 1\ndescription: A low chamber at the foot of the stair.\nlore: Delvers rest here before going on.\nways out: The Upper Floor room 2, The Landing\n\n## Who Is Here\nOdile (Odile), Delvers -- the player, the one arriving\n\n## Just Before This\nThe player has come from The Landing. Odile reaches the landing.\n\n## Instructions\n- The player has never been here. This is the first time they have set\n foot in it\n- Narrate discovery: what catches them on the way in, before they\n have made sense of the rest\n- Address the player as \"you\", in the present tense\n- One paragraph. Do not re-describe the place item by item -- the\n description above is already what is here, and your job is the moment\n of coming into it\n- Anyone listed above is here; write them as already present, not as\n arriving. Do not add a person who is not on that list\n- Do not name a way out that is not on the list above\n- Respect the stated length of each field\n\n## Current State On Arrival\nThe place description is the world's original account. These current records\ntake precedence over its claims about people, items and wounds. Narrate the\nrecorded crossing result as part of this arrival.\nYou are unhurt.\nNobody else is alive here.\nFixed here, and not takeable: rock shelf, rubble, pool, ledge.\nLying here: bone, candle stub, shard of pottery, coin.\nYou are carrying: nothing.\n", + "schema": { + "name": "Scene::Schema", + "description": null, + "schema": { + "type": "object", + "properties": { + "description": { + "type": "string", + "description": "What the player experiences as they arrive here, right now. Second person, present tense. One paragraph, 3 to 5 sentences.", + "maxLength": 900 + }, + "summary": { + "type": "string", + "description": "What happened in this moment, for the game engine rather than the player. Third person. One sentence.", + "maxLength": 200 + } + }, + "required": [ + "description", + "summary" + ], + "additionalProperties": false, + "strict": true + } + } + } + ], + "written": { + "locations": { + "rows": [ + { + "id": 1000000002, + "story_id": 1000000001, + "name": "The Upper Floor", + "description": "A level of black stone passages lit by nothing you can see.", + "lore": "The first floor was cut by people nobody remembers.", + "last_protagonist_visit": null, + "parent_location_id": null, + "detail_level": "realized", + "teaser": "The first level of the dungeon, with a stair somewhere that goes further down.", + "mobile": 0, + "danger": "safe", + "hazard": null, + "hazard_die": null, + "x": null, + "y": null, + "z": null, + "width": 12, + "depth": 12, + "population": null, + "generation_checkpoint": null, + "surface": null, + "kind": "warren", + "density": null + }, + { + "id": 1000000004, + "story_id": 1000000001, + "name": "The Upper Floor room 1", + "description": "A low chamber at the foot of the stair.", + "lore": "Delvers rest here before going on.", + "last_protagonist_visit": "2026-10-30 08:01:30", + "parent_location_id": 1000000002, + "detail_level": "realized", + "teaser": "A room inside The Upper Floor, 9 by 5 paces on storey 0.", + "mobile": 0, + "danger": "uneasy", + "hazard": null, + "hazard_die": null, + "x": 0, + "y": 0, + "z": 0, + "width": 9, + "depth": 5, + "population": null, + "generation_checkpoint": null, + "surface": null, + "kind": "cave", + "density": null + }, + { + "id": 1000000005, + "story_id": 1000000001, + "name": "The Upper Floor room 2", + "description": null, + "lore": null, + "last_protagonist_visit": null, + "parent_location_id": 1000000002, + "detail_level": "stub", + "teaser": "A room inside The Upper Floor, 3 by 5 paces on storey 0.", + "mobile": 0, + "danger": "dangerous", + "hazard": null, + "hazard_die": null, + "x": 9, + "y": 0, + "z": 0, + "width": 3, + "depth": 5, + "population": null, + "generation_checkpoint": null, + "surface": null, + "kind": "cave", + "density": null + }, + { + "id": 1000000006, + "story_id": 1000000001, + "name": "The Upper Floor room 3", + "description": null, + "lore": null, + "last_protagonist_visit": null, + "parent_location_id": 1000000002, + "detail_level": "stub", + "teaser": "A room inside The Upper Floor, 3 by 7 paces on storey 0.", + "mobile": 0, + "danger": "uneasy", + "hazard": null, + "hazard_die": null, + "x": 9, + "y": 5, + "z": 0, + "width": 3, + "depth": 7, + "population": null, + "generation_checkpoint": null, + "surface": null, + "kind": "cave", + "density": null + } + ], + "removed": [] + }, + "location_connections": { + "rows": [ + { + "id": 1000000005, + "location_id": 1000000004, + "connected_location_id": 1000000005, + "distance": "adjacent", + "time_to_travel": "about a minute", + "travel_method": "walking", + "hazard": null, + "hazard_die": null, + "barrier": "open", + "key_template_id": null + }, + { + "id": 1000000006, + "location_id": 1000000005, + "connected_location_id": 1000000004, + "distance": "adjacent", + "time_to_travel": "about a minute", + "travel_method": "walking", + "hazard": null, + "hazard_die": null, + "barrier": "open", + "key_template_id": null + }, + { + "id": 1000000007, + "location_id": 1000000005, + "connected_location_id": 1000000006, + "distance": "adjacent", + "time_to_travel": "about a minute", + "travel_method": "walking", + "hazard": null, + "hazard_die": null, + "barrier": "open", + "key_template_id": null + }, + { + "id": 1000000008, + "location_id": 1000000006, + "connected_location_id": 1000000005, + "distance": "adjacent", + "time_to_travel": "about a minute", + "travel_method": "walking", + "hazard": null, + "hazard_die": null, + "barrier": "open", + "key_template_id": null + }, + { + "id": 1000000009, + "location_id": 1000000004, + "connected_location_id": 1000000001, + "distance": "adjacent", + "time_to_travel": "about a minute", + "travel_method": "taking stairs", + "hazard": null, + "hazard_die": null, + "barrier": "open", + "key_template_id": null + }, + { + "id": 1000000010, + "location_id": 1000000001, + "connected_location_id": 1000000004, + "distance": "adjacent", + "time_to_travel": "about a minute", + "travel_method": "taking stairs", + "hazard": null, + "hazard_die": null, + "barrier": "open", + "key_template_id": null + }, + { + "id": 1000000011, + "location_id": 1000000005, + "connected_location_id": 1000000003, + "distance": "adjacent", + "time_to_travel": "about a minute", + "travel_method": "taking stairs", + "hazard": null, + "hazard_die": null, + "barrier": "keyed", + "key_template_id": 1000000001 + }, + { + "id": 1000000012, + "location_id": 1000000003, + "connected_location_id": 1000000005, + "distance": "adjacent", + "time_to_travel": "about a minute", + "travel_method": "taking stairs", + "hazard": null, + "hazard_die": null, + "barrier": "keyed", + "key_template_id": 1000000001 + } + ], + "removed": [ + 1000000001, + 1000000002, + 1000000003, + 1000000004 + ] + }, + "characters": { + "rows": [ + { + "id": 1000000002, + "story_id": 1000000001, + "fullname": "Brask the Stairwarden", + "nickname": "Brask", + "age": 90, + "sex": "male", + "personality": "Silent and patient.", + "appearance": "A grey, stooped thing with a long iron key on a cord.", + "likes": "Stillness.", + "dislikes": "Footsteps on the stair.", + "fears": "Nothing.", + "backstory": "He has kept the stair down for longer than the delvers have come.", + "is_companion": 0, + "race_id": 1000000002, + "is_protagonist": 0, + "location_id": 1000000005, + "deliberately_absent": 0, + "level": 1, + "hit_die": 6, + "strength": 8, + "dexterity": 8, + "will": 8, + "hostile": 1, + "x": 9, + "y": 3, + "conscious_desire": null, + "unconscious_desire": null, + "recognized_need": null, + "unrecognized_need": null, + "desire_pursuit": null, + "need_pursuit": null, + "posted_to_id": 1000000002 + } + ], + "removed": [] + }, + "items": { + "rows": [ + { + "id": 1000000002, + "character_id": null, + "name": "rock shelf", + "description": "The rock shelf, fixed in place.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "immovable", + "x": 4, + "y": 0, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "top", + "within_id": null, + "how": null, + "kit_key": "cave/rock shelf", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "stone", + "wear": null, + "worn": 0 + }, + { + "id": 1000000003, + "character_id": null, + "name": "rubble", + "description": "The rubble, fixed in place.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "immovable", + "x": 1, + "y": 3, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "hollow", + "within_id": null, + "how": null, + "kit_key": "cave/rubble", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000004, + "character_id": null, + "name": "pool", + "description": "The pool, fixed in place.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "immovable", + "x": 0, + "y": 4, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "nothing", + "within_id": null, + "how": null, + "kit_key": "cave/pool", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000005, + "character_id": null, + "name": "ledge", + "description": "The ledge, fixed in place.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "immovable", + "x": 7, + "y": 3, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "top", + "within_id": null, + "how": null, + "kit_key": "cave/ledge", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000006, + "character_id": null, + "name": "bone", + "description": "The bone, on the rock shelf.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000002, + "how": "on", + "kit_key": "cave/rock shelf/bone", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000007, + "character_id": null, + "name": "candle stub", + "description": "The candle stub, on the rock shelf.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 1, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000002, + "how": "on", + "kit_key": "cave/rock shelf/candle stub", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000008, + "character_id": null, + "name": "shard of pottery", + "description": "The shard of pottery, in the rubble.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000003, + "how": "in", + "kit_key": "cave/rubble/shard of pottery", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000009, + "character_id": null, + "name": "coin", + "description": "The coin, in the rubble.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000003, + "how": "in", + "kit_key": "cave/rubble/coin", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "metal", + "wear": null, + "worn": 0 + }, + { + "id": 1000000010, + "character_id": null, + "name": "rock shelf", + "description": "The rock shelf, fixed in place.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000002, + "bulk": "immovable", + "x": 4, + "y": 0, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "top", + "within_id": null, + "how": null, + "kit_key": "cave/rock shelf", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "stone", + "wear": null, + "worn": 0 + }, + { + "id": 1000000011, + "character_id": null, + "name": "rubble", + "description": "The rubble, fixed in place.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000003, + "bulk": "immovable", + "x": 1, + "y": 3, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "hollow", + "within_id": null, + "how": null, + "kit_key": "cave/rubble", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000012, + "character_id": null, + "name": "pool", + "description": "The pool, fixed in place.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000004, + "bulk": "immovable", + "x": 0, + "y": 4, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "nothing", + "within_id": null, + "how": null, + "kit_key": "cave/pool", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000013, + "character_id": null, + "name": "ledge", + "description": "The ledge, fixed in place.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000005, + "bulk": "immovable", + "x": 7, + "y": 3, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "top", + "within_id": null, + "how": null, + "kit_key": "cave/ledge", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000014, + "character_id": null, + "name": "bone", + "description": "The bone, on the rock shelf.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000006, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000010, + "how": "on", + "kit_key": "cave/rock shelf/bone", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000015, + "character_id": null, + "name": "candle stub", + "description": "The candle stub, on the rock shelf.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000007, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 1, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000010, + "how": "on", + "kit_key": "cave/rock shelf/candle stub", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000016, + "character_id": null, + "name": "shard of pottery", + "description": "The shard of pottery, in the rubble.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000008, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000011, + "how": "in", + "kit_key": "cave/rubble/shard of pottery", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000017, + "character_id": null, + "name": "coin", + "description": "The coin, in the rubble.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000009, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000011, + "how": "in", + "kit_key": "cave/rubble/coin", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "metal", + "wear": null, + "worn": 0 + } + ], + "removed": [] + }, + "quest_steps": { + "rows": [], + "removed": [] + }, + "scenes": { + "rows": [ + { + "id": 1000000002, + "story_id": 1000000001, + "location_id": 1000000004, + "previous_scene_id": 1000000001, + "description": "You arrive at The Upper Floor room 1. You are unhurt. Nobody else is alive here. Fixed here, and not takeable: rock shelf, rubble, pool, ledge. Lying here: bone, candle stub, shard of pottery, coin. You are carrying: nothing.", + "story_timestamp": "2026-10-30 08:01:30", + "summary": "You arrive at The Upper Floor room 1. You are unhurt. Nobody else is alive here. Fixed here, and not takeable: rock shelf, rubble, pool, ledge. Lying here: bone, candle stub, shard of pottery, coin. You are carrying: nothing.", + "is_opening": 0, + "typed": "move The Upper Floor", + "resolved_action": "move", + "acted_on_type": "Location", + "acted_on_id": 1000000002, + "resolved_by": "grammar", + "engine_fallback": 1, + "engine_fact": "You are unhurt.\nNobody else is alive here.\nFixed here, and not takeable: rock shelf, rubble, pool, ledge.\nLying here: bone, candle stub, shard of pottery, coin.\nYou are carrying: nothing." + } + ], + "removed": [] + }, + "characters_scenes": { + "rows": [ + { + "character_id": 1000000001, + "scene_id": 1000000002 + } + ], + "removed": [] + }, + "interactions": { + "rows": [], + "removed": [] + }, + "playthrough_endings": { + "rows": [], + "removed": [] + }, + "playthrough_beats": { + "rows": [], + "removed": [] + }, + "playthrough_npc_states": { + "rows": [], + "removed": [] + }, + "world_events": { + "rows": [], + "removed": [] + } + } + }, + { + "step": "step 2 the-guard-stands-on-the-way-down", + "requests": [ + { + "purpose": "location", + "system": "You build the rooms of a text adventure one at a time. You write places a\nplayer can stand in and walk out of: concrete, specific, and consistent\nwith the world they belong to. Every exit you name is somewhere the player\ncould actually go.\n\nDO NOT INCLUDE EMOJIS IN YOUR RESPONSE.\n", + "user": "## Universe Details\nphysics: Ordinary physics.\ntechnology: Lanterns, iron keys and hand tools.\ngeographies: A dungeon cut down into black stone under a lantern-lit landing.\nraces: Delvers, Stairwardens\ncivilizations: Delvers who sell what they bring up, and the things that keep the stairs.\n\n\n## Story Details\ntitle: A Guarded Stair (engine sweep)\ngenre: dungeon crawl\npreface: Every stair down in the dungeon has something standing at the top of it.\nsummary: Odile goes down past the thing that keeps the stair.\n\n\n## The Place\nname: The Upper Floor room 2\nteaser: A room inside The Upper Floor, 3 by 5 paces on storey 0.\n## Where This Room Is\nThe game's own records of this room, already decided and not yours to\nchange. Write the room around them: do not contradict a measurement, and\ndo not give it a way out this list does not have.\nThis room is 3 by 5 paces -- about 5 by 8 metres. It is on storey 0 of The Upper Floor, which is 12 by 12 paces across; storey 0 is the ground floor. Its ways out: a door in the south wall, to The Upper Floor room 3; a door in the west wall, to The Upper Floor room 1; a way out to The Lower Floor, which is outside The Upper Floor. Those are every way out of this room.\n## Already Here, Decided By The Game\nThe game's own records of what is in this room, already decided and not yours\nto change, and it will tell the player so. Write the room around them, and do\nnot add another piece of furniture or fixed thing a player could reach for.\nFixed in place: rock shelf, rubble, stalagmite, pool, crack (unsearched).\nOn the rock shelf: pebble, bone.\nIn the rubble: rusted hinge, coin.\nNobody has searched the crack yet, so do not say what is in it.\nDo not list any of them again as a thing lying here.\n## Already Standing Here, Decided By The Game\nThe game's own records of who is in this room, already decided and not yours\nto change. Write the room with them in it, as they are, and do not write any\nof them again as a new person.\n Brask the Stairwarden, Stairwardens, about 90, male: A grey, stooped thing with a long iron key on a cord.\n## Instructions\nWrite this place out in full.\n- The description is what the player reads on arrival. Address them as \"you\"\n- Describe what is here now, not the history -- the history is the lore\n- Stay consistent with the universe and with the teaser above\n- Respect the stated length of each field\n- NAME THIS ROOM. It is one room inside The Upper Floor. The player reads the room and\n the place together -- \"the of The Upper Floor\" -- so name the ROOM only,\n and never put the place's own name into it. A short noun phrase carrying the\n article English wants on it, 2 to 4 words: \"the counting room\", \"the cold\n store\", \"the harbourmaster's office\". Name it for what the floor plan above\n says this room is and for what you have just described standing in it. Never\n a comma in it\n\n## What Is Lying Here\nList AT MOST 3 portable things a player could pick up and carry away, besides the ones Already Here above: those are written already, and a thing named again is not a new one.\n- Nothing is the right answer for most rooms. An empty list is a complete answer\n- Only loose, portable things. Not the door, not the floor, not the machinery\n bolted to it -- something a person could put in a pocket or under an arm\n- Each one must be consistent with the description you just wrote, and worth\n the player noticing\n- Pick its use_kind from the supplied physical profiles. Ordinary is the\n default. Food and drink can be consumed without healing; healing is only\n a restorative dose this world permits. Firestarters burn combustible\n objects, levers pry jammed passages, lockpicks try locks, and a key only\n opens a passage explicitly matched to it. Mark combustible only when an\n ordinary firestarter can destroy the object. The engine owns the effects\n and their amounts; its description cannot grant extra powers\n- If a thing has WRITING on it -- a note, a letter, a handbill, a label, a\n docket, a page, a sign -- mark it readable and WRITE OUT WHAT IS WRITTEN\n ON IT, exactly as it appears on the thing. The words themselves, not a\n description of them, and short enough to finish -- a few words, a line,\n or a few short lines. The game keeps those words and a player reading it\n twice reads the same ones\n- Never name it after a person or after a place. Already spoken for in this story, so do not reuse: Odile, Brask the Stairwarden, stair key\n\n## Who Is Here\nWrite NOBODY new into this place. The people Already Standing Here above are\neveryone who is here.\n", + "schema": { + "name": "Location::DetailSchema", + "description": null, + "schema": { + "type": "object", + "properties": { + "description": { + "type": "string", + "description": "What the player sees, hears and smells standing in this place right now. Describe THIS place only -- not what neighbours it, not what is visible out of a window or across the way, because the world around it can move. Second person. One paragraph, 4 to 6 sentences.", + "maxLength": 1200 + }, + "lore": { + "type": "string", + "description": "What this place is, who made it and what happened here. Written for the game engine rather than the player. One paragraph, 3 to 5 sentences.", + "maxLength": 900 + }, + "name": { + "type": "string", + "description": "What this room is called -- ONLY when the instructions above ask you to name it. Leave this out entirely otherwise. A short noun phrase a player would type to walk into it, carrying the article English wants on it: \"the counting room\". Never the name of the building it is in, never a name this story has already given to a room, a person or a thing, and never a comma.", + "maxLength": 60 + }, + "items": { + "type": "array", + "description": "Portable things lying loose in this place that a player could pick up and carry away. Empty is the right answer for most rooms.", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "What the thing is called, as a player would type it to pick it up. A short noun phrase, 1 to 4 words, lower case unless it is a proper name. Never the name of a person or of a place.", + "maxLength": 60 + }, + "description": { + "type": "string", + "description": "What it is and what state it is in, consistent with the description of the room you just wrote. One or two sentences.", + "maxLength": 400 + }, + "use_kind": { + "type": "string", + "enum": [ + "ordinary", + "food", + "drink", + "healing", + "firestarter", + "lever", + "lockpick", + "key" + ], + "description": "The engine's physical profile: ordinary unless it is food, a drink, a healing dose, a firestarter, a prying lever, lockpicks, or a key. Choose healing only for an actual restorative dose allowed by this world. Profiles supply behavior from code, never from the description." + }, + "combustible": { + "type": "boolean", + "description": "True only if this portable thing can be destroyed by an ordinary carried firestarter: dry paper, cloth or wood. False for stone, metal, liquids, or anything uncertain." + }, + "readable": { + "type": "boolean", + "description": "True only if this thing has WRITING on it that a player could read: a note, a letter, a label, a docket, a page, a sign, an inscription. False for everything else, which is most things." + }, + "inscription": { + "type": "string", + "description": "The words written on it, exactly as they appear, and only when `readable` is true. Write what is actually on the thing -- what a player would read off it -- not a description of it. A few words, a line, or a few short lines; well under the limit, and finished rather than trailing off. Leave this out entirely when nothing is written on it.", + "maxLength": 400 + } + }, + "required": [ + "name", + "description", + "use_kind", + "combustible", + "readable" + ], + "additionalProperties": false + }, + "maxItems": 3 + }, + "people": { + "type": "array", + "description": "People who are in this place right now.", + "items": { + "type": "object", + "properties": { + "fullname": { + "type": "string", + "description": "Their full name, as a player would type it to speak to them. 2 or 3 words.", + "maxLength": 60 + }, + "nickname": { + "type": "string", + "description": "What they are called to their face. 1 or 2 words.", + "maxLength": 30 + }, + "appearance": { + "type": "string", + "description": "What somebody walking in sees of them, consistent with the room you just wrote and with the race and age you were given for them. One or two sentences.", + "maxLength": 300 + }, + "personality": { + "type": "string", + "description": "How they behave and how they treat a stranger. One or two sentences.", + "maxLength": 300 + }, + "backstory": { + "type": "string", + "description": "The life history that brought them here, the repeated choices it taught them, and why today's presence presses on an enduring aim. Third person, by name. Two or three sentences.", + "maxLength": 450 + }, + "likes": { + "type": "string", + "description": "A comma separated list of 2 or 3 things they enjoy.", + "maxLength": 160 + }, + "dislikes": { + "type": "string", + "description": "A comma separated list of 2 or 3 things they cannot stand.", + "maxLength": 160 + }, + "fears": { + "type": "string", + "description": "A comma separated list of 1 or 2 things they are afraid of.", + "maxLength": 160 + }, + "conscious_desire": { + "type": "string", + "description": "The future, standing, relationship, legacy, place or way of life they knowingly organize their choices around and would name if asked what they want from their life. Make it specific to this person and world. Do not substitute the clue, deadline, delivery, payment or room they are dealing with today. One sentence, third person, by name.", + "maxLength": 180 + }, + "unconscious_desire": { + "type": "string", + "description": "The deeper reward their choices have pursued for years and they would deny: the recognition, absolution, dependence, belonging, power or intimacy beneath the conscious account. It explains a repeated pattern in the backstory and pulls at an angle to the conscious desire, not merely today's hidden motive. One sentence, third person, by name.", + "maxLength": 180 + }, + "recognized_need": { + "type": "string", + "description": "The duty, oath, debt, craft, family burden or bodily discipline they believe they must keep over years whether they want to or not. It predates today's assignment, survives it, and can repeatedly obstruct the conscious desire. One sentence, third person, by name.", + "maxLength": 180 + }, + "unrecognized_need": { + "type": "string", + "description": "The enduring change, truth or relationship their life requires and their repeated pattern prevents them from seeing. A reader can infer it from the backstory. It cannot be completed by one confession, realization or errand: it demands a new way of choosing across the story. If they never move toward it, pursuit of the conscious desire ruins them. It must not be the conscious desire said twice. One sentence, third person, by name.", + "maxLength": 180 + }, + "desire_pursuit": { + "type": "string", + "enum": [ + "keep", + "obtain", + "reach", + "attend", + "avoid", + "withhold", + "offer" + ], + "description": "Which listed ENGINE ACT could repeatedly advance or protect the CONSCIOUS DESIRE in ordinary rooms. The label is the next-step expression of the larger desire, not its timescale." + }, + "need_pursuit": { + "type": "string", + "enum": [ + "keep", + "obtain", + "reach", + "attend", + "avoid", + "withhold", + "offer" + ], + "description": "Which listed ENGINE ACT could repeatedly move them toward the UNRECOGNIZED NEED in ordinary rooms. It may match desire_pursuit, but a different label should reflect a real conflict rather than manufactured variety." + } + }, + "required": [ + "fullname", + "nickname", + "appearance", + "personality", + "backstory", + "likes", + "dislikes", + "fears", + "conscious_desire", + "unconscious_desire", + "recognized_need", + "unrecognized_need", + "desire_pursuit", + "need_pursuit" + ], + "additionalProperties": false + }, + "maxItems": 3 + } + }, + "required": [ + "description", + "lore" + ], + "additionalProperties": false, + "strict": true + } + } + }, + { + "purpose": "arrival", + "system": "You narrate a text adventure. You write the moment a player walks into a\nplace: what reaches them first, in the second person and the present\ntense. You never break character, never offer a numbered menu, and never\nmention that you are a narrator.\n\nDO NOT INCLUDE EMOJIS IN YOUR RESPONSE.\n", + "user": "## Universe Details\nphysics: Ordinary physics.\ntechnology: Lanterns, iron keys and hand tools.\n\n## Story Details\ntitle: A Guarded Stair (engine sweep)\ngenre: dungeon crawl\nsummary: Odile goes down past the thing that keeps the stair.\n\n## The Place\nname: The Upper Floor room 2\ndescription: A bare chamber with a stair cut down into its floor.\nlore: The stair down is older than the rest.\nways out: The Upper Floor room 1, The Upper Floor room 3, The Lower Floor\n\n## Who Is Here\nOdile (Odile), Delvers -- the player, the one arriving\nBrask the Stairwarden (Brask), Stairwardens\n\n## Just Before This\nThe player has come from The Upper Floor room 1. You arrive at The Upper Floor room 1. You are unhurt. Nobody else is alive here. Fixed here, and not takeable: rock shelf, rubble, pool, ledge. Lying here: bone, candle stub, shard of pottery, coin. You are carrying: nothing.\n\n## Instructions\n- The player has never been here. This is the first time they have set\n foot in it\n- Narrate discovery: what catches them on the way in, before they\n have made sense of the rest\n- Address the player as \"you\", in the present tense\n- One paragraph. Do not re-describe the place item by item -- the\n description above is already what is here, and your job is the moment\n of coming into it\n- Anyone listed above is here; write them as already present, not as\n arriving. Do not add a person who is not on that list\n- Do not name a way out that is not on the list above\n- Respect the stated length of each field\n\n## Current State On Arrival\nThe place description is the world's original account. These current records\ntake precedence over its claims about people, items and wounds. Narrate the\nrecorded crossing result as part of this arrival.\nYou are unhurt.\nAlso here: Brask the Stairwarden. Nobody else is alive here.\nFixed here, and not takeable: rock shelf, rubble, stalagmite, pool, crack.\nLying here: pebble, bone, rusted hinge, coin.\nYou are carrying: nothing.\n", + "schema": { + "name": "Scene::Schema", + "description": null, + "schema": { + "type": "object", + "properties": { + "description": { + "type": "string", + "description": "What the player experiences as they arrive here, right now. Second person, present tense. One paragraph, 3 to 5 sentences.", + "maxLength": 900 + }, + "summary": { + "type": "string", + "description": "What happened in this moment, for the game engine rather than the player. Third person. One sentence.", + "maxLength": 200 + } + }, + "required": [ + "description", + "summary" + ], + "additionalProperties": false, + "strict": true + } + } + } + ], + "written": { + "locations": { + "rows": [ + { + "id": 1000000002, + "story_id": 1000000001, + "name": "The Upper Floor", + "description": "A level of black stone passages lit by nothing you can see.", + "lore": "The first floor was cut by people nobody remembers.", + "last_protagonist_visit": null, + "parent_location_id": null, + "detail_level": "realized", + "teaser": "The first level of the dungeon, with a stair somewhere that goes further down.", + "mobile": 0, + "danger": "safe", + "hazard": null, + "hazard_die": null, + "x": null, + "y": null, + "z": null, + "width": 12, + "depth": 12, + "population": null, + "generation_checkpoint": null, + "surface": null, + "kind": "warren", + "density": null + }, + { + "id": 1000000004, + "story_id": 1000000001, + "name": "The Upper Floor room 1", + "description": "A low chamber at the foot of the stair.", + "lore": "Delvers rest here before going on.", + "last_protagonist_visit": "2026-10-30 08:01:30", + "parent_location_id": 1000000002, + "detail_level": "realized", + "teaser": "A room inside The Upper Floor, 9 by 5 paces on storey 0.", + "mobile": 0, + "danger": "uneasy", + "hazard": null, + "hazard_die": null, + "x": 0, + "y": 0, + "z": 0, + "width": 9, + "depth": 5, + "population": null, + "generation_checkpoint": null, + "surface": null, + "kind": "cave", + "density": null + }, + { + "id": 1000000005, + "story_id": 1000000001, + "name": "The Upper Floor room 2", + "description": "A bare chamber with a stair cut down into its floor.", + "lore": "The stair down is older than the rest.", + "last_protagonist_visit": "2026-10-30 08:02:30", + "parent_location_id": 1000000002, + "detail_level": "realized", + "teaser": "A room inside The Upper Floor, 3 by 5 paces on storey 0.", + "mobile": 0, + "danger": "dangerous", + "hazard": null, + "hazard_die": null, + "x": 9, + "y": 0, + "z": 0, + "width": 3, + "depth": 5, + "population": null, + "generation_checkpoint": null, + "surface": null, + "kind": "cave", + "density": null + }, + { + "id": 1000000006, + "story_id": 1000000001, + "name": "The Upper Floor room 3", + "description": null, + "lore": null, + "last_protagonist_visit": null, + "parent_location_id": 1000000002, + "detail_level": "stub", + "teaser": "A room inside The Upper Floor, 3 by 7 paces on storey 0.", + "mobile": 0, + "danger": "uneasy", + "hazard": null, + "hazard_die": null, + "x": 9, + "y": 5, + "z": 0, + "width": 3, + "depth": 7, + "population": null, + "generation_checkpoint": null, + "surface": null, + "kind": "cave", + "density": null + } + ], + "removed": [] + }, + "location_connections": { + "rows": [ + { + "id": 1000000005, + "location_id": 1000000004, + "connected_location_id": 1000000005, + "distance": "adjacent", + "time_to_travel": "about a minute", + "travel_method": "walking", + "hazard": null, + "hazard_die": null, + "barrier": "open", + "key_template_id": null + }, + { + "id": 1000000006, + "location_id": 1000000005, + "connected_location_id": 1000000004, + "distance": "adjacent", + "time_to_travel": "about a minute", + "travel_method": "walking", + "hazard": null, + "hazard_die": null, + "barrier": "open", + "key_template_id": null + }, + { + "id": 1000000007, + "location_id": 1000000005, + "connected_location_id": 1000000006, + "distance": "adjacent", + "time_to_travel": "about a minute", + "travel_method": "walking", + "hazard": null, + "hazard_die": null, + "barrier": "open", + "key_template_id": null + }, + { + "id": 1000000008, + "location_id": 1000000006, + "connected_location_id": 1000000005, + "distance": "adjacent", + "time_to_travel": "about a minute", + "travel_method": "walking", + "hazard": null, + "hazard_die": null, + "barrier": "open", + "key_template_id": null + }, + { + "id": 1000000009, + "location_id": 1000000004, + "connected_location_id": 1000000001, + "distance": "adjacent", + "time_to_travel": "about a minute", + "travel_method": "taking stairs", + "hazard": null, + "hazard_die": null, + "barrier": "open", + "key_template_id": null + }, + { + "id": 1000000010, + "location_id": 1000000001, + "connected_location_id": 1000000004, + "distance": "adjacent", + "time_to_travel": "about a minute", + "travel_method": "taking stairs", + "hazard": null, + "hazard_die": null, + "barrier": "open", + "key_template_id": null + }, + { + "id": 1000000011, + "location_id": 1000000005, + "connected_location_id": 1000000003, + "distance": "adjacent", + "time_to_travel": "about a minute", + "travel_method": "taking stairs", + "hazard": null, + "hazard_die": null, + "barrier": "keyed", + "key_template_id": 1000000001 + }, + { + "id": 1000000012, + "location_id": 1000000003, + "connected_location_id": 1000000005, + "distance": "adjacent", + "time_to_travel": "about a minute", + "travel_method": "taking stairs", + "hazard": null, + "hazard_die": null, + "barrier": "keyed", + "key_template_id": 1000000001 + } + ], + "removed": [ + 1000000001, + 1000000002, + 1000000003, + 1000000004 + ] + }, + "characters": { + "rows": [ + { + "id": 1000000002, + "story_id": 1000000001, + "fullname": "Brask the Stairwarden", + "nickname": "Brask", + "age": 90, + "sex": "male", + "personality": "Silent and patient.", + "appearance": "A grey, stooped thing with a long iron key on a cord.", + "likes": "Stillness.", + "dislikes": "Footsteps on the stair.", + "fears": "Nothing.", + "backstory": "He has kept the stair down for longer than the delvers have come.", + "is_companion": 0, + "race_id": 1000000002, + "is_protagonist": 0, + "location_id": 1000000005, + "deliberately_absent": 0, + "level": 1, + "hit_die": 6, + "strength": 8, + "dexterity": 8, + "will": 8, + "hostile": 1, + "x": 9, + "y": 3, + "conscious_desire": null, + "unconscious_desire": null, + "recognized_need": null, + "unrecognized_need": null, + "desire_pursuit": null, + "need_pursuit": null, + "posted_to_id": 1000000002 + } + ], + "removed": [] + }, + "items": { + "rows": [ + { + "id": 1000000002, + "character_id": null, + "name": "rock shelf", + "description": "The rock shelf, fixed in place.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "immovable", + "x": 4, + "y": 0, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "top", + "within_id": null, + "how": null, + "kit_key": "cave/rock shelf", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "stone", + "wear": null, + "worn": 0 + }, + { + "id": 1000000003, + "character_id": null, + "name": "rubble", + "description": "The rubble, fixed in place.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "immovable", + "x": 1, + "y": 3, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "hollow", + "within_id": null, + "how": null, + "kit_key": "cave/rubble", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000004, + "character_id": null, + "name": "pool", + "description": "The pool, fixed in place.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "immovable", + "x": 0, + "y": 4, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "nothing", + "within_id": null, + "how": null, + "kit_key": "cave/pool", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000005, + "character_id": null, + "name": "ledge", + "description": "The ledge, fixed in place.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "immovable", + "x": 7, + "y": 3, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "top", + "within_id": null, + "how": null, + "kit_key": "cave/ledge", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000006, + "character_id": null, + "name": "bone", + "description": "The bone, on the rock shelf.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000002, + "how": "on", + "kit_key": "cave/rock shelf/bone", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000007, + "character_id": null, + "name": "candle stub", + "description": "The candle stub, on the rock shelf.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 1, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000002, + "how": "on", + "kit_key": "cave/rock shelf/candle stub", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000008, + "character_id": null, + "name": "shard of pottery", + "description": "The shard of pottery, in the rubble.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000003, + "how": "in", + "kit_key": "cave/rubble/shard of pottery", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000009, + "character_id": null, + "name": "coin", + "description": "The coin, in the rubble.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000003, + "how": "in", + "kit_key": "cave/rubble/coin", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "metal", + "wear": null, + "worn": 0 + }, + { + "id": 1000000010, + "character_id": null, + "name": "rock shelf", + "description": "The rock shelf, fixed in place.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000002, + "bulk": "immovable", + "x": 4, + "y": 0, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "top", + "within_id": null, + "how": null, + "kit_key": "cave/rock shelf", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "stone", + "wear": null, + "worn": 0 + }, + { + "id": 1000000011, + "character_id": null, + "name": "rubble", + "description": "The rubble, fixed in place.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000003, + "bulk": "immovable", + "x": 1, + "y": 3, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "hollow", + "within_id": null, + "how": null, + "kit_key": "cave/rubble", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000012, + "character_id": null, + "name": "pool", + "description": "The pool, fixed in place.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000004, + "bulk": "immovable", + "x": 0, + "y": 4, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "nothing", + "within_id": null, + "how": null, + "kit_key": "cave/pool", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000013, + "character_id": null, + "name": "ledge", + "description": "The ledge, fixed in place.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000005, + "bulk": "immovable", + "x": 7, + "y": 3, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "top", + "within_id": null, + "how": null, + "kit_key": "cave/ledge", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000014, + "character_id": null, + "name": "bone", + "description": "The bone, on the rock shelf.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000006, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000010, + "how": "on", + "kit_key": "cave/rock shelf/bone", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000015, + "character_id": null, + "name": "candle stub", + "description": "The candle stub, on the rock shelf.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000007, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 1, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000010, + "how": "on", + "kit_key": "cave/rock shelf/candle stub", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000016, + "character_id": null, + "name": "shard of pottery", + "description": "The shard of pottery, in the rubble.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000008, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000011, + "how": "in", + "kit_key": "cave/rubble/shard of pottery", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000017, + "character_id": null, + "name": "coin", + "description": "The coin, in the rubble.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000009, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000011, + "how": "in", + "kit_key": "cave/rubble/coin", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "metal", + "wear": null, + "worn": 0 + }, + { + "id": 1000000018, + "character_id": null, + "name": "rock shelf", + "description": "The rock shelf, fixed in place.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "immovable", + "x": 9, + "y": 3, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "top", + "within_id": null, + "how": null, + "kit_key": "cave/rock shelf", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "stone", + "wear": null, + "worn": 0 + }, + { + "id": 1000000019, + "character_id": null, + "name": "rubble", + "description": "The rubble, fixed in place.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "immovable", + "x": 10, + "y": 1, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "hollow", + "within_id": null, + "how": null, + "kit_key": "cave/rubble", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000020, + "character_id": null, + "name": "stalagmite", + "description": "The stalagmite, fixed in place.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "immovable", + "x": 11, + "y": 0, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "nothing", + "within_id": null, + "how": null, + "kit_key": "cave/stalagmite", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "stone", + "wear": null, + "worn": 0 + }, + { + "id": 1000000021, + "character_id": null, + "name": "pool", + "description": "The pool, fixed in place.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "immovable", + "x": 10, + "y": 1, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "nothing", + "within_id": null, + "how": null, + "kit_key": "cave/pool", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000022, + "character_id": null, + "name": "crack", + "description": "The crack, fixed in place.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "immovable", + "x": 10, + "y": 2, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "closed", + "within_id": null, + "how": null, + "kit_key": "cave/crack", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000023, + "character_id": null, + "name": "pebble", + "description": "The pebble, on the rock shelf.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000018, + "how": "on", + "kit_key": "cave/rock shelf/pebble", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "stone", + "wear": null, + "worn": 0 + }, + { + "id": 1000000024, + "character_id": null, + "name": "bone", + "description": "The bone, on the rock shelf.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000018, + "how": "on", + "kit_key": "cave/rock shelf/bone", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000025, + "character_id": null, + "name": "rusted hinge", + "description": "The rusted hinge, in the rubble.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000019, + "how": "in", + "kit_key": "cave/rubble/rusted hinge", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "metal", + "wear": null, + "worn": 0 + }, + { + "id": 1000000026, + "character_id": null, + "name": "coin", + "description": "The coin, in the rubble.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000019, + "how": "in", + "kit_key": "cave/rubble/coin", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "metal", + "wear": null, + "worn": 0 + }, + { + "id": 1000000027, + "character_id": null, + "name": "rock shelf", + "description": "The rock shelf, fixed in place.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000018, + "bulk": "immovable", + "x": 9, + "y": 3, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "top", + "within_id": null, + "how": null, + "kit_key": "cave/rock shelf", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "stone", + "wear": null, + "worn": 0 + }, + { + "id": 1000000028, + "character_id": null, + "name": "rubble", + "description": "The rubble, fixed in place.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000019, + "bulk": "immovable", + "x": 10, + "y": 1, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "hollow", + "within_id": null, + "how": null, + "kit_key": "cave/rubble", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000029, + "character_id": null, + "name": "stalagmite", + "description": "The stalagmite, fixed in place.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000020, + "bulk": "immovable", + "x": 11, + "y": 0, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "nothing", + "within_id": null, + "how": null, + "kit_key": "cave/stalagmite", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "stone", + "wear": null, + "worn": 0 + }, + { + "id": 1000000030, + "character_id": null, + "name": "pool", + "description": "The pool, fixed in place.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000021, + "bulk": "immovable", + "x": 10, + "y": 1, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "nothing", + "within_id": null, + "how": null, + "kit_key": "cave/pool", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000031, + "character_id": null, + "name": "crack", + "description": "The crack, fixed in place.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000022, + "bulk": "immovable", + "x": 10, + "y": 2, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "closed", + "within_id": null, + "how": null, + "kit_key": "cave/crack", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000032, + "character_id": null, + "name": "pebble", + "description": "The pebble, on the rock shelf.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000023, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000027, + "how": "on", + "kit_key": "cave/rock shelf/pebble", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "stone", + "wear": null, + "worn": 0 + }, + { + "id": 1000000033, + "character_id": null, + "name": "bone", + "description": "The bone, on the rock shelf.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000024, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000027, + "how": "on", + "kit_key": "cave/rock shelf/bone", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000034, + "character_id": null, + "name": "rusted hinge", + "description": "The rusted hinge, in the rubble.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000025, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000028, + "how": "in", + "kit_key": "cave/rubble/rusted hinge", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "metal", + "wear": null, + "worn": 0 + }, + { + "id": 1000000035, + "character_id": null, + "name": "coin", + "description": "The coin, in the rubble.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000026, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000028, + "how": "in", + "kit_key": "cave/rubble/coin", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "metal", + "wear": null, + "worn": 0 + }, + { + "id": 1000000036, + "character_id": 1000000002, + "name": "stair key", + "description": "A long iron key on a cord, cold to hold.", + "properties": "{}", + "location_id": null, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000001, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "key", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": null, + "how": null, + "kit_key": null, + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + } + ], + "removed": [] + }, + "quest_steps": { + "rows": [], + "removed": [] + }, + "scenes": { + "rows": [ + { + "id": 1000000002, + "story_id": 1000000001, + "location_id": 1000000004, + "previous_scene_id": 1000000001, + "description": "You arrive at The Upper Floor room 1. You are unhurt. Nobody else is alive here. Fixed here, and not takeable: rock shelf, rubble, pool, ledge. Lying here: bone, candle stub, shard of pottery, coin. You are carrying: nothing.", + "story_timestamp": "2026-10-30 08:01:30", + "summary": "You arrive at The Upper Floor room 1. You are unhurt. Nobody else is alive here. Fixed here, and not takeable: rock shelf, rubble, pool, ledge. Lying here: bone, candle stub, shard of pottery, coin. You are carrying: nothing.", + "is_opening": 0, + "typed": "move The Upper Floor", + "resolved_action": "move", + "acted_on_type": "Location", + "acted_on_id": 1000000002, + "resolved_by": "grammar", + "engine_fallback": 1, + "engine_fact": "You are unhurt.\nNobody else is alive here.\nFixed here, and not takeable: rock shelf, rubble, pool, ledge.\nLying here: bone, candle stub, shard of pottery, coin.\nYou are carrying: nothing." + }, + { + "id": 1000000003, + "story_id": 1000000001, + "location_id": 1000000005, + "previous_scene_id": 1000000002, + "description": "You arrive at The Upper Floor room 2. You are unhurt. Also here: Brask the Stairwarden. Nobody else is alive here. Fixed here, and not takeable: rock shelf, rubble, stalagmite, pool, crack. Lying here: pebble, bone, rusted hinge, coin. You are carrying: nothing.", + "story_timestamp": "2026-10-30 08:02:30", + "summary": "You arrive at The Upper Floor room 2. You are unhurt. Also here: Brask the Stairwarden. Nobody else is alive here. Fixed here, and not takeable: rock shelf, rubble, stalagmite, pool, crack. Lying here: pebble, bone, rusted hinge, coin. You are carrying: nothing.", + "is_opening": 0, + "typed": "move The Upper Floor room 2", + "resolved_action": "move", + "acted_on_type": "Location", + "acted_on_id": 1000000005, + "resolved_by": "grammar", + "engine_fallback": 1, + "engine_fact": "You are unhurt.\nAlso here: Brask the Stairwarden. Nobody else is alive here.\nFixed here, and not takeable: rock shelf, rubble, stalagmite, pool, crack.\nLying here: pebble, bone, rusted hinge, coin.\nYou are carrying: nothing." + } + ], + "removed": [] + }, + "characters_scenes": { + "rows": [ + { + "character_id": 1000000001, + "scene_id": 1000000002 + }, + { + "character_id": 1000000001, + "scene_id": 1000000003 + }, + { + "character_id": 1000000002, + "scene_id": 1000000003 + } + ], + "removed": [] + }, + "interactions": { + "rows": [], + "removed": [] + }, + "playthrough_endings": { + "rows": [], + "removed": [] + }, + "playthrough_beats": { + "rows": [], + "removed": [] + }, + "playthrough_npc_states": { + "rows": [], + "removed": [] + }, + "world_events": { + "rows": [], + "removed": [] + } + } + }, + { + "step": "step 3 kill-the-guard", + "requests": [], + "written": { + "locations": { + "rows": [ + { + "id": 1000000002, + "story_id": 1000000001, + "name": "The Upper Floor", + "description": "A level of black stone passages lit by nothing you can see.", + "lore": "The first floor was cut by people nobody remembers.", + "last_protagonist_visit": null, + "parent_location_id": null, + "detail_level": "realized", + "teaser": "The first level of the dungeon, with a stair somewhere that goes further down.", + "mobile": 0, + "danger": "safe", + "hazard": null, + "hazard_die": null, + "x": null, + "y": null, + "z": null, + "width": 12, + "depth": 12, + "population": null, + "generation_checkpoint": null, + "surface": null, + "kind": "warren", + "density": null + }, + { + "id": 1000000004, + "story_id": 1000000001, + "name": "The Upper Floor room 1", + "description": "A low chamber at the foot of the stair.", + "lore": "Delvers rest here before going on.", + "last_protagonist_visit": "2026-10-30 08:01:30", + "parent_location_id": 1000000002, + "detail_level": "realized", + "teaser": "A room inside The Upper Floor, 9 by 5 paces on storey 0.", + "mobile": 0, + "danger": "uneasy", + "hazard": null, + "hazard_die": null, + "x": 0, + "y": 0, + "z": 0, + "width": 9, + "depth": 5, + "population": null, + "generation_checkpoint": null, + "surface": null, + "kind": "cave", + "density": null + }, + { + "id": 1000000005, + "story_id": 1000000001, + "name": "The Upper Floor room 2", + "description": "A bare chamber with a stair cut down into its floor.", + "lore": "The stair down is older than the rest.", + "last_protagonist_visit": "2026-10-30 08:07:30", + "parent_location_id": 1000000002, + "detail_level": "realized", + "teaser": "A room inside The Upper Floor, 3 by 5 paces on storey 0.", + "mobile": 0, + "danger": "dangerous", + "hazard": null, + "hazard_die": null, + "x": 9, + "y": 0, + "z": 0, + "width": 3, + "depth": 5, + "population": null, + "generation_checkpoint": null, + "surface": null, + "kind": "cave", + "density": null + }, + { + "id": 1000000006, + "story_id": 1000000001, + "name": "The Upper Floor room 3", + "description": null, + "lore": null, + "last_protagonist_visit": null, + "parent_location_id": 1000000002, + "detail_level": "stub", + "teaser": "A room inside The Upper Floor, 3 by 7 paces on storey 0.", + "mobile": 0, + "danger": "uneasy", + "hazard": null, + "hazard_die": null, + "x": 9, + "y": 5, + "z": 0, + "width": 3, + "depth": 7, + "population": null, + "generation_checkpoint": null, + "surface": null, + "kind": "cave", + "density": null + } + ], + "removed": [] + }, + "location_connections": { + "rows": [ + { + "id": 1000000005, + "location_id": 1000000004, + "connected_location_id": 1000000005, + "distance": "adjacent", + "time_to_travel": "about a minute", + "travel_method": "walking", + "hazard": null, + "hazard_die": null, + "barrier": "open", + "key_template_id": null + }, + { + "id": 1000000006, + "location_id": 1000000005, + "connected_location_id": 1000000004, + "distance": "adjacent", + "time_to_travel": "about a minute", + "travel_method": "walking", + "hazard": null, + "hazard_die": null, + "barrier": "open", + "key_template_id": null + }, + { + "id": 1000000007, + "location_id": 1000000005, + "connected_location_id": 1000000006, + "distance": "adjacent", + "time_to_travel": "about a minute", + "travel_method": "walking", + "hazard": null, + "hazard_die": null, + "barrier": "open", + "key_template_id": null + }, + { + "id": 1000000008, + "location_id": 1000000006, + "connected_location_id": 1000000005, + "distance": "adjacent", + "time_to_travel": "about a minute", + "travel_method": "walking", + "hazard": null, + "hazard_die": null, + "barrier": "open", + "key_template_id": null + }, + { + "id": 1000000009, + "location_id": 1000000004, + "connected_location_id": 1000000001, + "distance": "adjacent", + "time_to_travel": "about a minute", + "travel_method": "taking stairs", + "hazard": null, + "hazard_die": null, + "barrier": "open", + "key_template_id": null + }, + { + "id": 1000000010, + "location_id": 1000000001, + "connected_location_id": 1000000004, + "distance": "adjacent", + "time_to_travel": "about a minute", + "travel_method": "taking stairs", + "hazard": null, + "hazard_die": null, + "barrier": "open", + "key_template_id": null + }, + { + "id": 1000000011, + "location_id": 1000000005, + "connected_location_id": 1000000003, + "distance": "adjacent", + "time_to_travel": "about a minute", + "travel_method": "taking stairs", + "hazard": null, + "hazard_die": null, + "barrier": "keyed", + "key_template_id": 1000000001 + }, + { + "id": 1000000012, + "location_id": 1000000003, + "connected_location_id": 1000000005, + "distance": "adjacent", + "time_to_travel": "about a minute", + "travel_method": "taking stairs", + "hazard": null, + "hazard_die": null, + "barrier": "keyed", + "key_template_id": 1000000001 + } + ], + "removed": [ + 1000000001, + 1000000002, + 1000000003, + 1000000004 + ] + }, + "characters": { + "rows": [ + { + "id": 1000000002, + "story_id": 1000000001, + "fullname": "Brask the Stairwarden", + "nickname": "Brask", + "age": 90, + "sex": "male", + "personality": "Silent and patient.", + "appearance": "A grey, stooped thing with a long iron key on a cord.", + "likes": "Stillness.", + "dislikes": "Footsteps on the stair.", + "fears": "Nothing.", + "backstory": "He has kept the stair down for longer than the delvers have come.", + "is_companion": 0, + "race_id": 1000000002, + "is_protagonist": 0, + "location_id": 1000000005, + "deliberately_absent": 0, + "level": 1, + "hit_die": 6, + "strength": 8, + "dexterity": 8, + "will": 8, + "hostile": 1, + "x": 9, + "y": 3, + "conscious_desire": null, + "unconscious_desire": null, + "recognized_need": null, + "unrecognized_need": null, + "desire_pursuit": null, + "need_pursuit": null, + "posted_to_id": 1000000002 + } + ], + "removed": [] + }, + "items": { + "rows": [ + { + "id": 1000000002, + "character_id": null, + "name": "rock shelf", + "description": "The rock shelf, fixed in place.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "immovable", + "x": 4, + "y": 0, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "top", + "within_id": null, + "how": null, + "kit_key": "cave/rock shelf", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "stone", + "wear": null, + "worn": 0 + }, + { + "id": 1000000003, + "character_id": null, + "name": "rubble", + "description": "The rubble, fixed in place.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "immovable", + "x": 1, + "y": 3, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "hollow", + "within_id": null, + "how": null, + "kit_key": "cave/rubble", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000004, + "character_id": null, + "name": "pool", + "description": "The pool, fixed in place.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "immovable", + "x": 0, + "y": 4, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "nothing", + "within_id": null, + "how": null, + "kit_key": "cave/pool", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000005, + "character_id": null, + "name": "ledge", + "description": "The ledge, fixed in place.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "immovable", + "x": 7, + "y": 3, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "top", + "within_id": null, + "how": null, + "kit_key": "cave/ledge", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000006, + "character_id": null, + "name": "bone", + "description": "The bone, on the rock shelf.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000002, + "how": "on", + "kit_key": "cave/rock shelf/bone", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000007, + "character_id": null, + "name": "candle stub", + "description": "The candle stub, on the rock shelf.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 1, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000002, + "how": "on", + "kit_key": "cave/rock shelf/candle stub", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000008, + "character_id": null, + "name": "shard of pottery", + "description": "The shard of pottery, in the rubble.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000003, + "how": "in", + "kit_key": "cave/rubble/shard of pottery", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000009, + "character_id": null, + "name": "coin", + "description": "The coin, in the rubble.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000003, + "how": "in", + "kit_key": "cave/rubble/coin", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "metal", + "wear": null, + "worn": 0 + }, + { + "id": 1000000010, + "character_id": null, + "name": "rock shelf", + "description": "The rock shelf, fixed in place.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000002, + "bulk": "immovable", + "x": 4, + "y": 0, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "top", + "within_id": null, + "how": null, + "kit_key": "cave/rock shelf", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "stone", + "wear": null, + "worn": 0 + }, + { + "id": 1000000011, + "character_id": null, + "name": "rubble", + "description": "The rubble, fixed in place.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000003, + "bulk": "immovable", + "x": 1, + "y": 3, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "hollow", + "within_id": null, + "how": null, + "kit_key": "cave/rubble", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000012, + "character_id": null, + "name": "pool", + "description": "The pool, fixed in place.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000004, + "bulk": "immovable", + "x": 0, + "y": 4, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "nothing", + "within_id": null, + "how": null, + "kit_key": "cave/pool", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000013, + "character_id": null, + "name": "ledge", + "description": "The ledge, fixed in place.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000005, + "bulk": "immovable", + "x": 7, + "y": 3, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "top", + "within_id": null, + "how": null, + "kit_key": "cave/ledge", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000014, + "character_id": null, + "name": "bone", + "description": "The bone, on the rock shelf.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000006, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000010, + "how": "on", + "kit_key": "cave/rock shelf/bone", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000015, + "character_id": null, + "name": "candle stub", + "description": "The candle stub, on the rock shelf.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000007, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 1, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000010, + "how": "on", + "kit_key": "cave/rock shelf/candle stub", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000016, + "character_id": null, + "name": "shard of pottery", + "description": "The shard of pottery, in the rubble.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000008, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000011, + "how": "in", + "kit_key": "cave/rubble/shard of pottery", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000017, + "character_id": null, + "name": "coin", + "description": "The coin, in the rubble.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000009, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000011, + "how": "in", + "kit_key": "cave/rubble/coin", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "metal", + "wear": null, + "worn": 0 + }, + { + "id": 1000000018, + "character_id": null, + "name": "rock shelf", + "description": "The rock shelf, fixed in place.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "immovable", + "x": 9, + "y": 3, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "top", + "within_id": null, + "how": null, + "kit_key": "cave/rock shelf", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "stone", + "wear": null, + "worn": 0 + }, + { + "id": 1000000019, + "character_id": null, + "name": "rubble", + "description": "The rubble, fixed in place.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "immovable", + "x": 10, + "y": 1, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "hollow", + "within_id": null, + "how": null, + "kit_key": "cave/rubble", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000020, + "character_id": null, + "name": "stalagmite", + "description": "The stalagmite, fixed in place.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "immovable", + "x": 11, + "y": 0, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "nothing", + "within_id": null, + "how": null, + "kit_key": "cave/stalagmite", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "stone", + "wear": null, + "worn": 0 + }, + { + "id": 1000000021, + "character_id": null, + "name": "pool", + "description": "The pool, fixed in place.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "immovable", + "x": 10, + "y": 1, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "nothing", + "within_id": null, + "how": null, + "kit_key": "cave/pool", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000022, + "character_id": null, + "name": "crack", + "description": "The crack, fixed in place.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "immovable", + "x": 10, + "y": 2, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "closed", + "within_id": null, + "how": null, + "kit_key": "cave/crack", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000023, + "character_id": null, + "name": "pebble", + "description": "The pebble, on the rock shelf.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000018, + "how": "on", + "kit_key": "cave/rock shelf/pebble", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "stone", + "wear": null, + "worn": 0 + }, + { + "id": 1000000024, + "character_id": null, + "name": "bone", + "description": "The bone, on the rock shelf.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000018, + "how": "on", + "kit_key": "cave/rock shelf/bone", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000025, + "character_id": null, + "name": "rusted hinge", + "description": "The rusted hinge, in the rubble.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000019, + "how": "in", + "kit_key": "cave/rubble/rusted hinge", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "metal", + "wear": null, + "worn": 0 + }, + { + "id": 1000000026, + "character_id": null, + "name": "coin", + "description": "The coin, in the rubble.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000019, + "how": "in", + "kit_key": "cave/rubble/coin", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "metal", + "wear": null, + "worn": 0 + }, + { + "id": 1000000027, + "character_id": null, + "name": "rock shelf", + "description": "The rock shelf, fixed in place.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000018, + "bulk": "immovable", + "x": 9, + "y": 3, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "top", + "within_id": null, + "how": null, + "kit_key": "cave/rock shelf", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "stone", + "wear": null, + "worn": 0 + }, + { + "id": 1000000028, + "character_id": null, + "name": "rubble", + "description": "The rubble, fixed in place.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000019, + "bulk": "immovable", + "x": 10, + "y": 1, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "hollow", + "within_id": null, + "how": null, + "kit_key": "cave/rubble", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000029, + "character_id": null, + "name": "stalagmite", + "description": "The stalagmite, fixed in place.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000020, + "bulk": "immovable", + "x": 11, + "y": 0, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "nothing", + "within_id": null, + "how": null, + "kit_key": "cave/stalagmite", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "stone", + "wear": null, + "worn": 0 + }, + { + "id": 1000000030, + "character_id": null, + "name": "pool", + "description": "The pool, fixed in place.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000021, + "bulk": "immovable", + "x": 10, + "y": 1, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "nothing", + "within_id": null, + "how": null, + "kit_key": "cave/pool", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000031, + "character_id": null, + "name": "crack", + "description": "The crack, fixed in place.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000022, + "bulk": "immovable", + "x": 10, + "y": 2, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "closed", + "within_id": null, + "how": null, + "kit_key": "cave/crack", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000032, + "character_id": null, + "name": "pebble", + "description": "The pebble, on the rock shelf.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000023, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000027, + "how": "on", + "kit_key": "cave/rock shelf/pebble", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "stone", + "wear": null, + "worn": 0 + }, + { + "id": 1000000033, + "character_id": null, + "name": "bone", + "description": "The bone, on the rock shelf.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000024, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000027, + "how": "on", + "kit_key": "cave/rock shelf/bone", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000034, + "character_id": null, + "name": "rusted hinge", + "description": "The rusted hinge, in the rubble.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000025, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000028, + "how": "in", + "kit_key": "cave/rubble/rusted hinge", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "metal", + "wear": null, + "worn": 0 + }, + { + "id": 1000000035, + "character_id": null, + "name": "coin", + "description": "The coin, in the rubble.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000026, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000028, + "how": "in", + "kit_key": "cave/rubble/coin", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "metal", + "wear": null, + "worn": 0 + }, + { + "id": 1000000036, + "character_id": null, + "name": "stair key", + "description": "A long iron key on a cord, cold to hold.", + "properties": "{}", + "location_id": 1000000005, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000001, + "bulk": "handy", + "x": 9, + "y": 0, + "use_kind": "key", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": null, + "how": null, + "kit_key": null, + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + } + ], + "removed": [] + }, + "quest_steps": { + "rows": [], + "removed": [] + }, + "scenes": { + "rows": [ + { + "id": 1000000002, + "story_id": 1000000001, + "location_id": 1000000004, + "previous_scene_id": 1000000001, + "description": "You arrive at The Upper Floor room 1. You are unhurt. Nobody else is alive here. Fixed here, and not takeable: rock shelf, rubble, pool, ledge. Lying here: bone, candle stub, shard of pottery, coin. You are carrying: nothing.", + "story_timestamp": "2026-10-30 08:01:30", + "summary": "You arrive at The Upper Floor room 1. You are unhurt. Nobody else is alive here. Fixed here, and not takeable: rock shelf, rubble, pool, ledge. Lying here: bone, candle stub, shard of pottery, coin. You are carrying: nothing.", + "is_opening": 0, + "typed": "move The Upper Floor", + "resolved_action": "move", + "acted_on_type": "Location", + "acted_on_id": 1000000002, + "resolved_by": "grammar", + "engine_fallback": 1, + "engine_fact": "You are unhurt.\nNobody else is alive here.\nFixed here, and not takeable: rock shelf, rubble, pool, ledge.\nLying here: bone, candle stub, shard of pottery, coin.\nYou are carrying: nothing." + }, + { + "id": 1000000003, + "story_id": 1000000001, + "location_id": 1000000005, + "previous_scene_id": 1000000002, + "description": "You arrive at The Upper Floor room 2. You are unhurt. Also here: Brask the Stairwarden. Nobody else is alive here. Fixed here, and not takeable: rock shelf, rubble, stalagmite, pool, crack. Lying here: pebble, bone, rusted hinge, coin. You are carrying: nothing.", + "story_timestamp": "2026-10-30 08:02:30", + "summary": "You arrive at The Upper Floor room 2. You are unhurt. Also here: Brask the Stairwarden. Nobody else is alive here. Fixed here, and not takeable: rock shelf, rubble, stalagmite, pool, crack. Lying here: pebble, bone, rusted hinge, coin. You are carrying: nothing.", + "is_opening": 0, + "typed": "move The Upper Floor room 2", + "resolved_action": "move", + "acted_on_type": "Location", + "acted_on_id": 1000000005, + "resolved_by": "grammar", + "engine_fallback": 1, + "engine_fact": "You are unhurt.\nAlso here: Brask the Stairwarden. Nobody else is alive here.\nFixed here, and not takeable: rock shelf, rubble, stalagmite, pool, crack.\nLying here: pebble, bone, rusted hinge, coin.\nYou are carrying: nothing." + }, + { + "id": 1000000004, + "story_id": 1000000001, + "location_id": 1000000005, + "previous_scene_id": 1000000003, + "description": "The fight in The Upper Floor room 2 is over after 1 round. Brask the Stairwarden is dead. 1 blow landed: Odile hit Brask the Stairwarden for 7 (round 1); Brask the Stairwarden is dead.", + "story_timestamp": "2026-10-30 08:07:30", + "summary": "A fight in The Upper Floor room 2: 1 blow over 1 round. Brask the Stairwarden is dead.", + "is_opening": 0, + "typed": null, + "resolved_action": "attack", + "acted_on_type": "Character", + "acted_on_id": 1000000002, + "resolved_by": null, + "engine_fallback": 0, + "engine_fact": null + } + ], + "removed": [] + }, + "characters_scenes": { + "rows": [ + { + "character_id": 1000000001, + "scene_id": 1000000002 + }, + { + "character_id": 1000000001, + "scene_id": 1000000003 + }, + { + "character_id": 1000000002, + "scene_id": 1000000003 + }, + { + "character_id": 1000000001, + "scene_id": 1000000004 + } + ], + "removed": [] + }, + "interactions": { + "rows": [], + "removed": [] + }, + "playthrough_endings": { + "rows": [], + "removed": [] + }, + "playthrough_beats": { + "rows": [], + "removed": [] + }, + "playthrough_npc_states": { + "rows": [ + { + "id": 1000000001, + "playthrough_id": 1000000001, + "character_id": 1000000002, + "location_id": 1000000005, + "following": 0, + "ceasefire": 0, + "peace_after_blow_id": 0, + "noticed_at": null + } + ], + "removed": [] + }, + "world_events": { + "rows": [], + "removed": [] + } + } + }, + { + "step": "step 4 the-stair-key-off-the-floor", + "requests": [], + "written": { + "locations": { + "rows": [ + { + "id": 1000000002, + "story_id": 1000000001, + "name": "The Upper Floor", + "description": "A level of black stone passages lit by nothing you can see.", + "lore": "The first floor was cut by people nobody remembers.", + "last_protagonist_visit": null, + "parent_location_id": null, + "detail_level": "realized", + "teaser": "The first level of the dungeon, with a stair somewhere that goes further down.", + "mobile": 0, + "danger": "safe", + "hazard": null, + "hazard_die": null, + "x": null, + "y": null, + "z": null, + "width": 12, + "depth": 12, + "population": null, + "generation_checkpoint": null, + "surface": null, + "kind": "warren", + "density": null + }, + { + "id": 1000000004, + "story_id": 1000000001, + "name": "The Upper Floor room 1", + "description": "A low chamber at the foot of the stair.", + "lore": "Delvers rest here before going on.", + "last_protagonist_visit": "2026-10-30 08:01:30", + "parent_location_id": 1000000002, + "detail_level": "realized", + "teaser": "A room inside The Upper Floor, 9 by 5 paces on storey 0.", + "mobile": 0, + "danger": "uneasy", + "hazard": null, + "hazard_die": null, + "x": 0, + "y": 0, + "z": 0, + "width": 9, + "depth": 5, + "population": null, + "generation_checkpoint": null, + "surface": null, + "kind": "cave", + "density": null + }, + { + "id": 1000000005, + "story_id": 1000000001, + "name": "The Upper Floor room 2", + "description": "A bare chamber with a stair cut down into its floor.", + "lore": "The stair down is older than the rest.", + "last_protagonist_visit": "2026-10-30 08:07:30", + "parent_location_id": 1000000002, + "detail_level": "realized", + "teaser": "A room inside The Upper Floor, 3 by 5 paces on storey 0.", + "mobile": 0, + "danger": "dangerous", + "hazard": null, + "hazard_die": null, + "x": 9, + "y": 0, + "z": 0, + "width": 3, + "depth": 5, + "population": null, + "generation_checkpoint": null, + "surface": null, + "kind": "cave", + "density": null + }, + { + "id": 1000000006, + "story_id": 1000000001, + "name": "The Upper Floor room 3", + "description": null, + "lore": null, + "last_protagonist_visit": null, + "parent_location_id": 1000000002, + "detail_level": "stub", + "teaser": "A room inside The Upper Floor, 3 by 7 paces on storey 0.", + "mobile": 0, + "danger": "uneasy", + "hazard": null, + "hazard_die": null, + "x": 9, + "y": 5, + "z": 0, + "width": 3, + "depth": 7, + "population": null, + "generation_checkpoint": null, + "surface": null, + "kind": "cave", + "density": null + } + ], + "removed": [] + }, + "location_connections": { + "rows": [ + { + "id": 1000000005, + "location_id": 1000000004, + "connected_location_id": 1000000005, + "distance": "adjacent", + "time_to_travel": "about a minute", + "travel_method": "walking", + "hazard": null, + "hazard_die": null, + "barrier": "open", + "key_template_id": null + }, + { + "id": 1000000006, + "location_id": 1000000005, + "connected_location_id": 1000000004, + "distance": "adjacent", + "time_to_travel": "about a minute", + "travel_method": "walking", + "hazard": null, + "hazard_die": null, + "barrier": "open", + "key_template_id": null + }, + { + "id": 1000000007, + "location_id": 1000000005, + "connected_location_id": 1000000006, + "distance": "adjacent", + "time_to_travel": "about a minute", + "travel_method": "walking", + "hazard": null, + "hazard_die": null, + "barrier": "open", + "key_template_id": null + }, + { + "id": 1000000008, + "location_id": 1000000006, + "connected_location_id": 1000000005, + "distance": "adjacent", + "time_to_travel": "about a minute", + "travel_method": "walking", + "hazard": null, + "hazard_die": null, + "barrier": "open", + "key_template_id": null + }, + { + "id": 1000000009, + "location_id": 1000000004, + "connected_location_id": 1000000001, + "distance": "adjacent", + "time_to_travel": "about a minute", + "travel_method": "taking stairs", + "hazard": null, + "hazard_die": null, + "barrier": "open", + "key_template_id": null + }, + { + "id": 1000000010, + "location_id": 1000000001, + "connected_location_id": 1000000004, + "distance": "adjacent", + "time_to_travel": "about a minute", + "travel_method": "taking stairs", + "hazard": null, + "hazard_die": null, + "barrier": "open", + "key_template_id": null + }, + { + "id": 1000000011, + "location_id": 1000000005, + "connected_location_id": 1000000003, + "distance": "adjacent", + "time_to_travel": "about a minute", + "travel_method": "taking stairs", + "hazard": null, + "hazard_die": null, + "barrier": "keyed", + "key_template_id": 1000000001 + }, + { + "id": 1000000012, + "location_id": 1000000003, + "connected_location_id": 1000000005, + "distance": "adjacent", + "time_to_travel": "about a minute", + "travel_method": "taking stairs", + "hazard": null, + "hazard_die": null, + "barrier": "keyed", + "key_template_id": 1000000001 + } + ], + "removed": [ + 1000000001, + 1000000002, + 1000000003, + 1000000004 + ] + }, + "characters": { + "rows": [ + { + "id": 1000000002, + "story_id": 1000000001, + "fullname": "Brask the Stairwarden", + "nickname": "Brask", + "age": 90, + "sex": "male", + "personality": "Silent and patient.", + "appearance": "A grey, stooped thing with a long iron key on a cord.", + "likes": "Stillness.", + "dislikes": "Footsteps on the stair.", + "fears": "Nothing.", + "backstory": "He has kept the stair down for longer than the delvers have come.", + "is_companion": 0, + "race_id": 1000000002, + "is_protagonist": 0, + "location_id": 1000000005, + "deliberately_absent": 0, + "level": 1, + "hit_die": 6, + "strength": 8, + "dexterity": 8, + "will": 8, + "hostile": 1, + "x": 9, + "y": 3, + "conscious_desire": null, + "unconscious_desire": null, + "recognized_need": null, + "unrecognized_need": null, + "desire_pursuit": null, + "need_pursuit": null, + "posted_to_id": 1000000002 + } + ], + "removed": [] + }, + "items": { + "rows": [ + { + "id": 1000000002, + "character_id": null, + "name": "rock shelf", + "description": "The rock shelf, fixed in place.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "immovable", + "x": 4, + "y": 0, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "top", + "within_id": null, + "how": null, + "kit_key": "cave/rock shelf", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "stone", + "wear": null, + "worn": 0 + }, + { + "id": 1000000003, + "character_id": null, + "name": "rubble", + "description": "The rubble, fixed in place.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "immovable", + "x": 1, + "y": 3, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "hollow", + "within_id": null, + "how": null, + "kit_key": "cave/rubble", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000004, + "character_id": null, + "name": "pool", + "description": "The pool, fixed in place.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "immovable", + "x": 0, + "y": 4, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "nothing", + "within_id": null, + "how": null, + "kit_key": "cave/pool", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000005, + "character_id": null, + "name": "ledge", + "description": "The ledge, fixed in place.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "immovable", + "x": 7, + "y": 3, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "top", + "within_id": null, + "how": null, + "kit_key": "cave/ledge", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000006, + "character_id": null, + "name": "bone", + "description": "The bone, on the rock shelf.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000002, + "how": "on", + "kit_key": "cave/rock shelf/bone", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000007, + "character_id": null, + "name": "candle stub", + "description": "The candle stub, on the rock shelf.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 1, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000002, + "how": "on", + "kit_key": "cave/rock shelf/candle stub", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000008, + "character_id": null, + "name": "shard of pottery", + "description": "The shard of pottery, in the rubble.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000003, + "how": "in", + "kit_key": "cave/rubble/shard of pottery", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000009, + "character_id": null, + "name": "coin", + "description": "The coin, in the rubble.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000003, + "how": "in", + "kit_key": "cave/rubble/coin", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "metal", + "wear": null, + "worn": 0 + }, + { + "id": 1000000010, + "character_id": null, + "name": "rock shelf", + "description": "The rock shelf, fixed in place.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000002, + "bulk": "immovable", + "x": 4, + "y": 0, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "top", + "within_id": null, + "how": null, + "kit_key": "cave/rock shelf", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "stone", + "wear": null, + "worn": 0 + }, + { + "id": 1000000011, + "character_id": null, + "name": "rubble", + "description": "The rubble, fixed in place.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000003, + "bulk": "immovable", + "x": 1, + "y": 3, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "hollow", + "within_id": null, + "how": null, + "kit_key": "cave/rubble", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000012, + "character_id": null, + "name": "pool", + "description": "The pool, fixed in place.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000004, + "bulk": "immovable", + "x": 0, + "y": 4, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "nothing", + "within_id": null, + "how": null, + "kit_key": "cave/pool", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000013, + "character_id": null, + "name": "ledge", + "description": "The ledge, fixed in place.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000005, + "bulk": "immovable", + "x": 7, + "y": 3, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "top", + "within_id": null, + "how": null, + "kit_key": "cave/ledge", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000014, + "character_id": null, + "name": "bone", + "description": "The bone, on the rock shelf.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000006, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000010, + "how": "on", + "kit_key": "cave/rock shelf/bone", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000015, + "character_id": null, + "name": "candle stub", + "description": "The candle stub, on the rock shelf.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000007, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 1, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000010, + "how": "on", + "kit_key": "cave/rock shelf/candle stub", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000016, + "character_id": null, + "name": "shard of pottery", + "description": "The shard of pottery, in the rubble.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000008, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000011, + "how": "in", + "kit_key": "cave/rubble/shard of pottery", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000017, + "character_id": null, + "name": "coin", + "description": "The coin, in the rubble.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000009, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000011, + "how": "in", + "kit_key": "cave/rubble/coin", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "metal", + "wear": null, + "worn": 0 + }, + { + "id": 1000000018, + "character_id": null, + "name": "rock shelf", + "description": "The rock shelf, fixed in place.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "immovable", + "x": 9, + "y": 3, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "top", + "within_id": null, + "how": null, + "kit_key": "cave/rock shelf", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "stone", + "wear": null, + "worn": 0 + }, + { + "id": 1000000019, + "character_id": null, + "name": "rubble", + "description": "The rubble, fixed in place.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "immovable", + "x": 10, + "y": 1, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "hollow", + "within_id": null, + "how": null, + "kit_key": "cave/rubble", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000020, + "character_id": null, + "name": "stalagmite", + "description": "The stalagmite, fixed in place.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "immovable", + "x": 11, + "y": 0, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "nothing", + "within_id": null, + "how": null, + "kit_key": "cave/stalagmite", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "stone", + "wear": null, + "worn": 0 + }, + { + "id": 1000000021, + "character_id": null, + "name": "pool", + "description": "The pool, fixed in place.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "immovable", + "x": 10, + "y": 1, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "nothing", + "within_id": null, + "how": null, + "kit_key": "cave/pool", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000022, + "character_id": null, + "name": "crack", + "description": "The crack, fixed in place.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "immovable", + "x": 10, + "y": 2, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "closed", + "within_id": null, + "how": null, + "kit_key": "cave/crack", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000023, + "character_id": null, + "name": "pebble", + "description": "The pebble, on the rock shelf.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000018, + "how": "on", + "kit_key": "cave/rock shelf/pebble", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "stone", + "wear": null, + "worn": 0 + }, + { + "id": 1000000024, + "character_id": null, + "name": "bone", + "description": "The bone, on the rock shelf.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000018, + "how": "on", + "kit_key": "cave/rock shelf/bone", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000025, + "character_id": null, + "name": "rusted hinge", + "description": "The rusted hinge, in the rubble.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000019, + "how": "in", + "kit_key": "cave/rubble/rusted hinge", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "metal", + "wear": null, + "worn": 0 + }, + { + "id": 1000000026, + "character_id": null, + "name": "coin", + "description": "The coin, in the rubble.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000019, + "how": "in", + "kit_key": "cave/rubble/coin", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "metal", + "wear": null, + "worn": 0 + }, + { + "id": 1000000027, + "character_id": null, + "name": "rock shelf", + "description": "The rock shelf, fixed in place.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000018, + "bulk": "immovable", + "x": 9, + "y": 3, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "top", + "within_id": null, + "how": null, + "kit_key": "cave/rock shelf", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "stone", + "wear": null, + "worn": 0 + }, + { + "id": 1000000028, + "character_id": null, + "name": "rubble", + "description": "The rubble, fixed in place.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000019, + "bulk": "immovable", + "x": 10, + "y": 1, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "hollow", + "within_id": null, + "how": null, + "kit_key": "cave/rubble", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000029, + "character_id": null, + "name": "stalagmite", + "description": "The stalagmite, fixed in place.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000020, + "bulk": "immovable", + "x": 11, + "y": 0, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "nothing", + "within_id": null, + "how": null, + "kit_key": "cave/stalagmite", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "stone", + "wear": null, + "worn": 0 + }, + { + "id": 1000000030, + "character_id": null, + "name": "pool", + "description": "The pool, fixed in place.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000021, + "bulk": "immovable", + "x": 10, + "y": 1, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "nothing", + "within_id": null, + "how": null, + "kit_key": "cave/pool", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000031, + "character_id": null, + "name": "crack", + "description": "The crack, fixed in place.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000022, + "bulk": "immovable", + "x": 10, + "y": 2, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "closed", + "within_id": null, + "how": null, + "kit_key": "cave/crack", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000032, + "character_id": null, + "name": "pebble", + "description": "The pebble, on the rock shelf.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000023, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000027, + "how": "on", + "kit_key": "cave/rock shelf/pebble", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "stone", + "wear": null, + "worn": 0 + }, + { + "id": 1000000033, + "character_id": null, + "name": "bone", + "description": "The bone, on the rock shelf.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000024, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000027, + "how": "on", + "kit_key": "cave/rock shelf/bone", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000034, + "character_id": null, + "name": "rusted hinge", + "description": "The rusted hinge, in the rubble.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000025, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000028, + "how": "in", + "kit_key": "cave/rubble/rusted hinge", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "metal", + "wear": null, + "worn": 0 + }, + { + "id": 1000000035, + "character_id": null, + "name": "coin", + "description": "The coin, in the rubble.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000026, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000028, + "how": "in", + "kit_key": "cave/rubble/coin", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "metal", + "wear": null, + "worn": 0 + }, + { + "id": 1000000036, + "character_id": null, + "name": "stair key", + "description": "A long iron key on a cord, cold to hold.", + "properties": "{}", + "location_id": null, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000001, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "key", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": null, + "how": null, + "kit_key": null, + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + } + ], + "removed": [] + }, + "quest_steps": { + "rows": [], + "removed": [] + }, + "scenes": { + "rows": [ + { + "id": 1000000002, + "story_id": 1000000001, + "location_id": 1000000004, + "previous_scene_id": 1000000001, + "description": "You arrive at The Upper Floor room 1. You are unhurt. Nobody else is alive here. Fixed here, and not takeable: rock shelf, rubble, pool, ledge. Lying here: bone, candle stub, shard of pottery, coin. You are carrying: nothing.", + "story_timestamp": "2026-10-30 08:01:30", + "summary": "You arrive at The Upper Floor room 1. You are unhurt. Nobody else is alive here. Fixed here, and not takeable: rock shelf, rubble, pool, ledge. Lying here: bone, candle stub, shard of pottery, coin. You are carrying: nothing.", + "is_opening": 0, + "typed": "move The Upper Floor", + "resolved_action": "move", + "acted_on_type": "Location", + "acted_on_id": 1000000002, + "resolved_by": "grammar", + "engine_fallback": 1, + "engine_fact": "You are unhurt.\nNobody else is alive here.\nFixed here, and not takeable: rock shelf, rubble, pool, ledge.\nLying here: bone, candle stub, shard of pottery, coin.\nYou are carrying: nothing." + }, + { + "id": 1000000003, + "story_id": 1000000001, + "location_id": 1000000005, + "previous_scene_id": 1000000002, + "description": "You arrive at The Upper Floor room 2. You are unhurt. Also here: Brask the Stairwarden. Nobody else is alive here. Fixed here, and not takeable: rock shelf, rubble, stalagmite, pool, crack. Lying here: pebble, bone, rusted hinge, coin. You are carrying: nothing.", + "story_timestamp": "2026-10-30 08:02:30", + "summary": "You arrive at The Upper Floor room 2. You are unhurt. Also here: Brask the Stairwarden. Nobody else is alive here. Fixed here, and not takeable: rock shelf, rubble, stalagmite, pool, crack. Lying here: pebble, bone, rusted hinge, coin. You are carrying: nothing.", + "is_opening": 0, + "typed": "move The Upper Floor room 2", + "resolved_action": "move", + "acted_on_type": "Location", + "acted_on_id": 1000000005, + "resolved_by": "grammar", + "engine_fallback": 1, + "engine_fact": "You are unhurt.\nAlso here: Brask the Stairwarden. Nobody else is alive here.\nFixed here, and not takeable: rock shelf, rubble, stalagmite, pool, crack.\nLying here: pebble, bone, rusted hinge, coin.\nYou are carrying: nothing." + }, + { + "id": 1000000004, + "story_id": 1000000001, + "location_id": 1000000005, + "previous_scene_id": 1000000003, + "description": "The fight in The Upper Floor room 2 is over after 1 round. Brask the Stairwarden is dead. 1 blow landed: Odile hit Brask the Stairwarden for 7 (round 1); Brask the Stairwarden is dead.", + "story_timestamp": "2026-10-30 08:07:30", + "summary": "A fight in The Upper Floor room 2: 1 blow over 1 round. Brask the Stairwarden is dead.", + "is_opening": 0, + "typed": null, + "resolved_action": "attack", + "acted_on_type": "Character", + "acted_on_id": 1000000002, + "resolved_by": null, + "engine_fallback": 0, + "engine_fact": null + } + ], + "removed": [] + }, + "characters_scenes": { + "rows": [ + { + "character_id": 1000000001, + "scene_id": 1000000002 + }, + { + "character_id": 1000000001, + "scene_id": 1000000003 + }, + { + "character_id": 1000000002, + "scene_id": 1000000003 + }, + { + "character_id": 1000000001, + "scene_id": 1000000004 + } + ], + "removed": [] + }, + "interactions": { + "rows": [], + "removed": [] + }, + "playthrough_endings": { + "rows": [], + "removed": [] + }, + "playthrough_beats": { + "rows": [], + "removed": [] + }, + "playthrough_npc_states": { + "rows": [ + { + "id": 1000000001, + "playthrough_id": 1000000001, + "character_id": 1000000002, + "location_id": 1000000005, + "following": 0, + "ceasefire": 0, + "peace_after_blow_id": 0, + "noticed_at": null + } + ], + "removed": [] + }, + "world_events": { + "rows": [], + "removed": [] + } + } + }, + { + "step": "step 5 the-stair-down-is-locked", + "requests": [], + "written": { + "locations": { + "rows": [ + { + "id": 1000000002, + "story_id": 1000000001, + "name": "The Upper Floor", + "description": "A level of black stone passages lit by nothing you can see.", + "lore": "The first floor was cut by people nobody remembers.", + "last_protagonist_visit": null, + "parent_location_id": null, + "detail_level": "realized", + "teaser": "The first level of the dungeon, with a stair somewhere that goes further down.", + "mobile": 0, + "danger": "safe", + "hazard": null, + "hazard_die": null, + "x": null, + "y": null, + "z": null, + "width": 12, + "depth": 12, + "population": null, + "generation_checkpoint": null, + "surface": null, + "kind": "warren", + "density": null + }, + { + "id": 1000000004, + "story_id": 1000000001, + "name": "The Upper Floor room 1", + "description": "A low chamber at the foot of the stair.", + "lore": "Delvers rest here before going on.", + "last_protagonist_visit": "2026-10-30 08:01:30", + "parent_location_id": 1000000002, + "detail_level": "realized", + "teaser": "A room inside The Upper Floor, 9 by 5 paces on storey 0.", + "mobile": 0, + "danger": "uneasy", + "hazard": null, + "hazard_die": null, + "x": 0, + "y": 0, + "z": 0, + "width": 9, + "depth": 5, + "population": null, + "generation_checkpoint": null, + "surface": null, + "kind": "cave", + "density": null + }, + { + "id": 1000000005, + "story_id": 1000000001, + "name": "The Upper Floor room 2", + "description": "A bare chamber with a stair cut down into its floor.", + "lore": "The stair down is older than the rest.", + "last_protagonist_visit": "2026-10-30 08:07:30", + "parent_location_id": 1000000002, + "detail_level": "realized", + "teaser": "A room inside The Upper Floor, 3 by 5 paces on storey 0.", + "mobile": 0, + "danger": "dangerous", + "hazard": null, + "hazard_die": null, + "x": 9, + "y": 0, + "z": 0, + "width": 3, + "depth": 5, + "population": null, + "generation_checkpoint": null, + "surface": null, + "kind": "cave", + "density": null + }, + { + "id": 1000000006, + "story_id": 1000000001, + "name": "The Upper Floor room 3", + "description": null, + "lore": null, + "last_protagonist_visit": null, + "parent_location_id": 1000000002, + "detail_level": "stub", + "teaser": "A room inside The Upper Floor, 3 by 7 paces on storey 0.", + "mobile": 0, + "danger": "uneasy", + "hazard": null, + "hazard_die": null, + "x": 9, + "y": 5, + "z": 0, + "width": 3, + "depth": 7, + "population": null, + "generation_checkpoint": null, + "surface": null, + "kind": "cave", + "density": null + } + ], + "removed": [] + }, + "location_connections": { + "rows": [ + { + "id": 1000000005, + "location_id": 1000000004, + "connected_location_id": 1000000005, + "distance": "adjacent", + "time_to_travel": "about a minute", + "travel_method": "walking", + "hazard": null, + "hazard_die": null, + "barrier": "open", + "key_template_id": null + }, + { + "id": 1000000006, + "location_id": 1000000005, + "connected_location_id": 1000000004, + "distance": "adjacent", + "time_to_travel": "about a minute", + "travel_method": "walking", + "hazard": null, + "hazard_die": null, + "barrier": "open", + "key_template_id": null + }, + { + "id": 1000000007, + "location_id": 1000000005, + "connected_location_id": 1000000006, + "distance": "adjacent", + "time_to_travel": "about a minute", + "travel_method": "walking", + "hazard": null, + "hazard_die": null, + "barrier": "open", + "key_template_id": null + }, + { + "id": 1000000008, + "location_id": 1000000006, + "connected_location_id": 1000000005, + "distance": "adjacent", + "time_to_travel": "about a minute", + "travel_method": "walking", + "hazard": null, + "hazard_die": null, + "barrier": "open", + "key_template_id": null + }, + { + "id": 1000000009, + "location_id": 1000000004, + "connected_location_id": 1000000001, + "distance": "adjacent", + "time_to_travel": "about a minute", + "travel_method": "taking stairs", + "hazard": null, + "hazard_die": null, + "barrier": "open", + "key_template_id": null + }, + { + "id": 1000000010, + "location_id": 1000000001, + "connected_location_id": 1000000004, + "distance": "adjacent", + "time_to_travel": "about a minute", + "travel_method": "taking stairs", + "hazard": null, + "hazard_die": null, + "barrier": "open", + "key_template_id": null + }, + { + "id": 1000000011, + "location_id": 1000000005, + "connected_location_id": 1000000003, + "distance": "adjacent", + "time_to_travel": "about a minute", + "travel_method": "taking stairs", + "hazard": null, + "hazard_die": null, + "barrier": "keyed", + "key_template_id": 1000000001 + }, + { + "id": 1000000012, + "location_id": 1000000003, + "connected_location_id": 1000000005, + "distance": "adjacent", + "time_to_travel": "about a minute", + "travel_method": "taking stairs", + "hazard": null, + "hazard_die": null, + "barrier": "keyed", + "key_template_id": 1000000001 + } + ], + "removed": [ + 1000000001, + 1000000002, + 1000000003, + 1000000004 + ] + }, + "characters": { + "rows": [ + { + "id": 1000000002, + "story_id": 1000000001, + "fullname": "Brask the Stairwarden", + "nickname": "Brask", + "age": 90, + "sex": "male", + "personality": "Silent and patient.", + "appearance": "A grey, stooped thing with a long iron key on a cord.", + "likes": "Stillness.", + "dislikes": "Footsteps on the stair.", + "fears": "Nothing.", + "backstory": "He has kept the stair down for longer than the delvers have come.", + "is_companion": 0, + "race_id": 1000000002, + "is_protagonist": 0, + "location_id": 1000000005, + "deliberately_absent": 0, + "level": 1, + "hit_die": 6, + "strength": 8, + "dexterity": 8, + "will": 8, + "hostile": 1, + "x": 9, + "y": 3, + "conscious_desire": null, + "unconscious_desire": null, + "recognized_need": null, + "unrecognized_need": null, + "desire_pursuit": null, + "need_pursuit": null, + "posted_to_id": 1000000002 + } + ], + "removed": [] + }, + "items": { + "rows": [ + { + "id": 1000000002, + "character_id": null, + "name": "rock shelf", + "description": "The rock shelf, fixed in place.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "immovable", + "x": 4, + "y": 0, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "top", + "within_id": null, + "how": null, + "kit_key": "cave/rock shelf", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "stone", + "wear": null, + "worn": 0 + }, + { + "id": 1000000003, + "character_id": null, + "name": "rubble", + "description": "The rubble, fixed in place.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "immovable", + "x": 1, + "y": 3, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "hollow", + "within_id": null, + "how": null, + "kit_key": "cave/rubble", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000004, + "character_id": null, + "name": "pool", + "description": "The pool, fixed in place.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "immovable", + "x": 0, + "y": 4, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "nothing", + "within_id": null, + "how": null, + "kit_key": "cave/pool", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000005, + "character_id": null, + "name": "ledge", + "description": "The ledge, fixed in place.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "immovable", + "x": 7, + "y": 3, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "top", + "within_id": null, + "how": null, + "kit_key": "cave/ledge", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000006, + "character_id": null, + "name": "bone", + "description": "The bone, on the rock shelf.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000002, + "how": "on", + "kit_key": "cave/rock shelf/bone", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000007, + "character_id": null, + "name": "candle stub", + "description": "The candle stub, on the rock shelf.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 1, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000002, + "how": "on", + "kit_key": "cave/rock shelf/candle stub", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000008, + "character_id": null, + "name": "shard of pottery", + "description": "The shard of pottery, in the rubble.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000003, + "how": "in", + "kit_key": "cave/rubble/shard of pottery", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000009, + "character_id": null, + "name": "coin", + "description": "The coin, in the rubble.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000003, + "how": "in", + "kit_key": "cave/rubble/coin", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "metal", + "wear": null, + "worn": 0 + }, + { + "id": 1000000010, + "character_id": null, + "name": "rock shelf", + "description": "The rock shelf, fixed in place.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000002, + "bulk": "immovable", + "x": 4, + "y": 0, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "top", + "within_id": null, + "how": null, + "kit_key": "cave/rock shelf", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "stone", + "wear": null, + "worn": 0 + }, + { + "id": 1000000011, + "character_id": null, + "name": "rubble", + "description": "The rubble, fixed in place.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000003, + "bulk": "immovable", + "x": 1, + "y": 3, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "hollow", + "within_id": null, + "how": null, + "kit_key": "cave/rubble", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000012, + "character_id": null, + "name": "pool", + "description": "The pool, fixed in place.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000004, + "bulk": "immovable", + "x": 0, + "y": 4, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "nothing", + "within_id": null, + "how": null, + "kit_key": "cave/pool", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000013, + "character_id": null, + "name": "ledge", + "description": "The ledge, fixed in place.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000005, + "bulk": "immovable", + "x": 7, + "y": 3, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "top", + "within_id": null, + "how": null, + "kit_key": "cave/ledge", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000014, + "character_id": null, + "name": "bone", + "description": "The bone, on the rock shelf.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000006, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000010, + "how": "on", + "kit_key": "cave/rock shelf/bone", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000015, + "character_id": null, + "name": "candle stub", + "description": "The candle stub, on the rock shelf.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000007, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 1, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000010, + "how": "on", + "kit_key": "cave/rock shelf/candle stub", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000016, + "character_id": null, + "name": "shard of pottery", + "description": "The shard of pottery, in the rubble.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000008, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000011, + "how": "in", + "kit_key": "cave/rubble/shard of pottery", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000017, + "character_id": null, + "name": "coin", + "description": "The coin, in the rubble.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000009, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000011, + "how": "in", + "kit_key": "cave/rubble/coin", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "metal", + "wear": null, + "worn": 0 + }, + { + "id": 1000000018, + "character_id": null, + "name": "rock shelf", + "description": "The rock shelf, fixed in place.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "immovable", + "x": 9, + "y": 3, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "top", + "within_id": null, + "how": null, + "kit_key": "cave/rock shelf", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "stone", + "wear": null, + "worn": 0 + }, + { + "id": 1000000019, + "character_id": null, + "name": "rubble", + "description": "The rubble, fixed in place.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "immovable", + "x": 10, + "y": 1, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "hollow", + "within_id": null, + "how": null, + "kit_key": "cave/rubble", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000020, + "character_id": null, + "name": "stalagmite", + "description": "The stalagmite, fixed in place.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "immovable", + "x": 11, + "y": 0, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "nothing", + "within_id": null, + "how": null, + "kit_key": "cave/stalagmite", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "stone", + "wear": null, + "worn": 0 + }, + { + "id": 1000000021, + "character_id": null, + "name": "pool", + "description": "The pool, fixed in place.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "immovable", + "x": 10, + "y": 1, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "nothing", + "within_id": null, + "how": null, + "kit_key": "cave/pool", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000022, + "character_id": null, + "name": "crack", + "description": "The crack, fixed in place.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "immovable", + "x": 10, + "y": 2, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "closed", + "within_id": null, + "how": null, + "kit_key": "cave/crack", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000023, + "character_id": null, + "name": "pebble", + "description": "The pebble, on the rock shelf.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000018, + "how": "on", + "kit_key": "cave/rock shelf/pebble", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "stone", + "wear": null, + "worn": 0 + }, + { + "id": 1000000024, + "character_id": null, + "name": "bone", + "description": "The bone, on the rock shelf.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000018, + "how": "on", + "kit_key": "cave/rock shelf/bone", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000025, + "character_id": null, + "name": "rusted hinge", + "description": "The rusted hinge, in the rubble.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000019, + "how": "in", + "kit_key": "cave/rubble/rusted hinge", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "metal", + "wear": null, + "worn": 0 + }, + { + "id": 1000000026, + "character_id": null, + "name": "coin", + "description": "The coin, in the rubble.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000019, + "how": "in", + "kit_key": "cave/rubble/coin", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "metal", + "wear": null, + "worn": 0 + }, + { + "id": 1000000027, + "character_id": null, + "name": "rock shelf", + "description": "The rock shelf, fixed in place.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000018, + "bulk": "immovable", + "x": 9, + "y": 3, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "top", + "within_id": null, + "how": null, + "kit_key": "cave/rock shelf", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "stone", + "wear": null, + "worn": 0 + }, + { + "id": 1000000028, + "character_id": null, + "name": "rubble", + "description": "The rubble, fixed in place.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000019, + "bulk": "immovable", + "x": 10, + "y": 1, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "hollow", + "within_id": null, + "how": null, + "kit_key": "cave/rubble", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000029, + "character_id": null, + "name": "stalagmite", + "description": "The stalagmite, fixed in place.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000020, + "bulk": "immovable", + "x": 11, + "y": 0, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "nothing", + "within_id": null, + "how": null, + "kit_key": "cave/stalagmite", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "stone", + "wear": null, + "worn": 0 + }, + { + "id": 1000000030, + "character_id": null, + "name": "pool", + "description": "The pool, fixed in place.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000021, + "bulk": "immovable", + "x": 10, + "y": 1, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "nothing", + "within_id": null, + "how": null, + "kit_key": "cave/pool", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000031, + "character_id": null, + "name": "crack", + "description": "The crack, fixed in place.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000022, + "bulk": "immovable", + "x": 10, + "y": 2, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "closed", + "within_id": null, + "how": null, + "kit_key": "cave/crack", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000032, + "character_id": null, + "name": "pebble", + "description": "The pebble, on the rock shelf.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000023, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000027, + "how": "on", + "kit_key": "cave/rock shelf/pebble", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "stone", + "wear": null, + "worn": 0 + }, + { + "id": 1000000033, + "character_id": null, + "name": "bone", + "description": "The bone, on the rock shelf.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000024, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000027, + "how": "on", + "kit_key": "cave/rock shelf/bone", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000034, + "character_id": null, + "name": "rusted hinge", + "description": "The rusted hinge, in the rubble.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000025, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000028, + "how": "in", + "kit_key": "cave/rubble/rusted hinge", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "metal", + "wear": null, + "worn": 0 + }, + { + "id": 1000000035, + "character_id": null, + "name": "coin", + "description": "The coin, in the rubble.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000026, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000028, + "how": "in", + "kit_key": "cave/rubble/coin", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "metal", + "wear": null, + "worn": 0 + }, + { + "id": 1000000036, + "character_id": null, + "name": "stair key", + "description": "A long iron key on a cord, cold to hold.", + "properties": "{}", + "location_id": null, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000001, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "key", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": null, + "how": null, + "kit_key": null, + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + } + ], + "removed": [] + }, + "quest_steps": { + "rows": [], + "removed": [] + }, + "scenes": { + "rows": [ + { + "id": 1000000002, + "story_id": 1000000001, + "location_id": 1000000004, + "previous_scene_id": 1000000001, + "description": "You arrive at The Upper Floor room 1. You are unhurt. Nobody else is alive here. Fixed here, and not takeable: rock shelf, rubble, pool, ledge. Lying here: bone, candle stub, shard of pottery, coin. You are carrying: nothing.", + "story_timestamp": "2026-10-30 08:01:30", + "summary": "You arrive at The Upper Floor room 1. You are unhurt. Nobody else is alive here. Fixed here, and not takeable: rock shelf, rubble, pool, ledge. Lying here: bone, candle stub, shard of pottery, coin. You are carrying: nothing.", + "is_opening": 0, + "typed": "move The Upper Floor", + "resolved_action": "move", + "acted_on_type": "Location", + "acted_on_id": 1000000002, + "resolved_by": "grammar", + "engine_fallback": 1, + "engine_fact": "You are unhurt.\nNobody else is alive here.\nFixed here, and not takeable: rock shelf, rubble, pool, ledge.\nLying here: bone, candle stub, shard of pottery, coin.\nYou are carrying: nothing." + }, + { + "id": 1000000003, + "story_id": 1000000001, + "location_id": 1000000005, + "previous_scene_id": 1000000002, + "description": "You arrive at The Upper Floor room 2. You are unhurt. Also here: Brask the Stairwarden. Nobody else is alive here. Fixed here, and not takeable: rock shelf, rubble, stalagmite, pool, crack. Lying here: pebble, bone, rusted hinge, coin. You are carrying: nothing.", + "story_timestamp": "2026-10-30 08:02:30", + "summary": "You arrive at The Upper Floor room 2. You are unhurt. Also here: Brask the Stairwarden. Nobody else is alive here. Fixed here, and not takeable: rock shelf, rubble, stalagmite, pool, crack. Lying here: pebble, bone, rusted hinge, coin. You are carrying: nothing.", + "is_opening": 0, + "typed": "move The Upper Floor room 2", + "resolved_action": "move", + "acted_on_type": "Location", + "acted_on_id": 1000000005, + "resolved_by": "grammar", + "engine_fallback": 1, + "engine_fact": "You are unhurt.\nAlso here: Brask the Stairwarden. Nobody else is alive here.\nFixed here, and not takeable: rock shelf, rubble, stalagmite, pool, crack.\nLying here: pebble, bone, rusted hinge, coin.\nYou are carrying: nothing." + }, + { + "id": 1000000004, + "story_id": 1000000001, + "location_id": 1000000005, + "previous_scene_id": 1000000003, + "description": "The fight in The Upper Floor room 2 is over after 1 round. Brask the Stairwarden is dead. 1 blow landed: Odile hit Brask the Stairwarden for 7 (round 1); Brask the Stairwarden is dead.", + "story_timestamp": "2026-10-30 08:07:30", + "summary": "A fight in The Upper Floor room 2: 1 blow over 1 round. Brask the Stairwarden is dead.", + "is_opening": 0, + "typed": null, + "resolved_action": "attack", + "acted_on_type": "Character", + "acted_on_id": 1000000002, + "resolved_by": null, + "engine_fallback": 0, + "engine_fact": null + } + ], + "removed": [] + }, + "characters_scenes": { + "rows": [ + { + "character_id": 1000000001, + "scene_id": 1000000002 + }, + { + "character_id": 1000000001, + "scene_id": 1000000003 + }, + { + "character_id": 1000000002, + "scene_id": 1000000003 + }, + { + "character_id": 1000000001, + "scene_id": 1000000004 + } + ], + "removed": [] + }, + "interactions": { + "rows": [], + "removed": [] + }, + "playthrough_endings": { + "rows": [], + "removed": [] + }, + "playthrough_beats": { + "rows": [], + "removed": [] + }, + "playthrough_npc_states": { + "rows": [ + { + "id": 1000000001, + "playthrough_id": 1000000001, + "character_id": 1000000002, + "location_id": 1000000005, + "following": 0, + "ceasefire": 0, + "peace_after_blow_id": 0, + "noticed_at": null + } + ], + "removed": [] + }, + "world_events": { + "rows": [], + "removed": [] + } + } + }, + { + "step": "step 6 his-key-opens-it", + "requests": [], + "written": { + "locations": { + "rows": [ + { + "id": 1000000002, + "story_id": 1000000001, + "name": "The Upper Floor", + "description": "A level of black stone passages lit by nothing you can see.", + "lore": "The first floor was cut by people nobody remembers.", + "last_protagonist_visit": null, + "parent_location_id": null, + "detail_level": "realized", + "teaser": "The first level of the dungeon, with a stair somewhere that goes further down.", + "mobile": 0, + "danger": "safe", + "hazard": null, + "hazard_die": null, + "x": null, + "y": null, + "z": null, + "width": 12, + "depth": 12, + "population": null, + "generation_checkpoint": null, + "surface": null, + "kind": "warren", + "density": null + }, + { + "id": 1000000004, + "story_id": 1000000001, + "name": "The Upper Floor room 1", + "description": "A low chamber at the foot of the stair.", + "lore": "Delvers rest here before going on.", + "last_protagonist_visit": "2026-10-30 08:01:30", + "parent_location_id": 1000000002, + "detail_level": "realized", + "teaser": "A room inside The Upper Floor, 9 by 5 paces on storey 0.", + "mobile": 0, + "danger": "uneasy", + "hazard": null, + "hazard_die": null, + "x": 0, + "y": 0, + "z": 0, + "width": 9, + "depth": 5, + "population": null, + "generation_checkpoint": null, + "surface": null, + "kind": "cave", + "density": null + }, + { + "id": 1000000005, + "story_id": 1000000001, + "name": "The Upper Floor room 2", + "description": "A bare chamber with a stair cut down into its floor.", + "lore": "The stair down is older than the rest.", + "last_protagonist_visit": "2026-10-30 08:07:30", + "parent_location_id": 1000000002, + "detail_level": "realized", + "teaser": "A room inside The Upper Floor, 3 by 5 paces on storey 0.", + "mobile": 0, + "danger": "dangerous", + "hazard": null, + "hazard_die": null, + "x": 9, + "y": 0, + "z": 0, + "width": 3, + "depth": 5, + "population": null, + "generation_checkpoint": null, + "surface": null, + "kind": "cave", + "density": null + }, + { + "id": 1000000006, + "story_id": 1000000001, + "name": "The Upper Floor room 3", + "description": null, + "lore": null, + "last_protagonist_visit": null, + "parent_location_id": 1000000002, + "detail_level": "stub", + "teaser": "A room inside The Upper Floor, 3 by 7 paces on storey 0.", + "mobile": 0, + "danger": "uneasy", + "hazard": null, + "hazard_die": null, + "x": 9, + "y": 5, + "z": 0, + "width": 3, + "depth": 7, + "population": null, + "generation_checkpoint": null, + "surface": null, + "kind": "cave", + "density": null + } + ], + "removed": [] + }, + "location_connections": { + "rows": [ + { + "id": 1000000005, + "location_id": 1000000004, + "connected_location_id": 1000000005, + "distance": "adjacent", + "time_to_travel": "about a minute", + "travel_method": "walking", + "hazard": null, + "hazard_die": null, + "barrier": "open", + "key_template_id": null + }, + { + "id": 1000000006, + "location_id": 1000000005, + "connected_location_id": 1000000004, + "distance": "adjacent", + "time_to_travel": "about a minute", + "travel_method": "walking", + "hazard": null, + "hazard_die": null, + "barrier": "open", + "key_template_id": null + }, + { + "id": 1000000007, + "location_id": 1000000005, + "connected_location_id": 1000000006, + "distance": "adjacent", + "time_to_travel": "about a minute", + "travel_method": "walking", + "hazard": null, + "hazard_die": null, + "barrier": "open", + "key_template_id": null + }, + { + "id": 1000000008, + "location_id": 1000000006, + "connected_location_id": 1000000005, + "distance": "adjacent", + "time_to_travel": "about a minute", + "travel_method": "walking", + "hazard": null, + "hazard_die": null, + "barrier": "open", + "key_template_id": null + }, + { + "id": 1000000009, + "location_id": 1000000004, + "connected_location_id": 1000000001, + "distance": "adjacent", + "time_to_travel": "about a minute", + "travel_method": "taking stairs", + "hazard": null, + "hazard_die": null, + "barrier": "open", + "key_template_id": null + }, + { + "id": 1000000010, + "location_id": 1000000001, + "connected_location_id": 1000000004, + "distance": "adjacent", + "time_to_travel": "about a minute", + "travel_method": "taking stairs", + "hazard": null, + "hazard_die": null, + "barrier": "open", + "key_template_id": null + }, + { + "id": 1000000011, + "location_id": 1000000005, + "connected_location_id": 1000000003, + "distance": "adjacent", + "time_to_travel": "about a minute", + "travel_method": "taking stairs", + "hazard": null, + "hazard_die": null, + "barrier": "keyed", + "key_template_id": 1000000001 + }, + { + "id": 1000000012, + "location_id": 1000000003, + "connected_location_id": 1000000005, + "distance": "adjacent", + "time_to_travel": "about a minute", + "travel_method": "taking stairs", + "hazard": null, + "hazard_die": null, + "barrier": "keyed", + "key_template_id": 1000000001 + } + ], + "removed": [ + 1000000001, + 1000000002, + 1000000003, + 1000000004 + ] + }, + "characters": { + "rows": [ + { + "id": 1000000002, + "story_id": 1000000001, + "fullname": "Brask the Stairwarden", + "nickname": "Brask", + "age": 90, + "sex": "male", + "personality": "Silent and patient.", + "appearance": "A grey, stooped thing with a long iron key on a cord.", + "likes": "Stillness.", + "dislikes": "Footsteps on the stair.", + "fears": "Nothing.", + "backstory": "He has kept the stair down for longer than the delvers have come.", + "is_companion": 0, + "race_id": 1000000002, + "is_protagonist": 0, + "location_id": 1000000005, + "deliberately_absent": 0, + "level": 1, + "hit_die": 6, + "strength": 8, + "dexterity": 8, + "will": 8, + "hostile": 1, + "x": 9, + "y": 3, + "conscious_desire": null, + "unconscious_desire": null, + "recognized_need": null, + "unrecognized_need": null, + "desire_pursuit": null, + "need_pursuit": null, + "posted_to_id": 1000000002 + } + ], + "removed": [] + }, + "items": { + "rows": [ + { + "id": 1000000002, + "character_id": null, + "name": "rock shelf", + "description": "The rock shelf, fixed in place.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "immovable", + "x": 4, + "y": 0, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "top", + "within_id": null, + "how": null, + "kit_key": "cave/rock shelf", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "stone", + "wear": null, + "worn": 0 + }, + { + "id": 1000000003, + "character_id": null, + "name": "rubble", + "description": "The rubble, fixed in place.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "immovable", + "x": 1, + "y": 3, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "hollow", + "within_id": null, + "how": null, + "kit_key": "cave/rubble", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000004, + "character_id": null, + "name": "pool", + "description": "The pool, fixed in place.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "immovable", + "x": 0, + "y": 4, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "nothing", + "within_id": null, + "how": null, + "kit_key": "cave/pool", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000005, + "character_id": null, + "name": "ledge", + "description": "The ledge, fixed in place.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "immovable", + "x": 7, + "y": 3, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "top", + "within_id": null, + "how": null, + "kit_key": "cave/ledge", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000006, + "character_id": null, + "name": "bone", + "description": "The bone, on the rock shelf.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000002, + "how": "on", + "kit_key": "cave/rock shelf/bone", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000007, + "character_id": null, + "name": "candle stub", + "description": "The candle stub, on the rock shelf.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 1, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000002, + "how": "on", + "kit_key": "cave/rock shelf/candle stub", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000008, + "character_id": null, + "name": "shard of pottery", + "description": "The shard of pottery, in the rubble.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000003, + "how": "in", + "kit_key": "cave/rubble/shard of pottery", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000009, + "character_id": null, + "name": "coin", + "description": "The coin, in the rubble.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000003, + "how": "in", + "kit_key": "cave/rubble/coin", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "metal", + "wear": null, + "worn": 0 + }, + { + "id": 1000000010, + "character_id": null, + "name": "rock shelf", + "description": "The rock shelf, fixed in place.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000002, + "bulk": "immovable", + "x": 4, + "y": 0, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "top", + "within_id": null, + "how": null, + "kit_key": "cave/rock shelf", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "stone", + "wear": null, + "worn": 0 + }, + { + "id": 1000000011, + "character_id": null, + "name": "rubble", + "description": "The rubble, fixed in place.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000003, + "bulk": "immovable", + "x": 1, + "y": 3, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "hollow", + "within_id": null, + "how": null, + "kit_key": "cave/rubble", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000012, + "character_id": null, + "name": "pool", + "description": "The pool, fixed in place.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000004, + "bulk": "immovable", + "x": 0, + "y": 4, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "nothing", + "within_id": null, + "how": null, + "kit_key": "cave/pool", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000013, + "character_id": null, + "name": "ledge", + "description": "The ledge, fixed in place.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000005, + "bulk": "immovable", + "x": 7, + "y": 3, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "top", + "within_id": null, + "how": null, + "kit_key": "cave/ledge", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000014, + "character_id": null, + "name": "bone", + "description": "The bone, on the rock shelf.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000006, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000010, + "how": "on", + "kit_key": "cave/rock shelf/bone", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000015, + "character_id": null, + "name": "candle stub", + "description": "The candle stub, on the rock shelf.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000007, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 1, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000010, + "how": "on", + "kit_key": "cave/rock shelf/candle stub", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000016, + "character_id": null, + "name": "shard of pottery", + "description": "The shard of pottery, in the rubble.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000008, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000011, + "how": "in", + "kit_key": "cave/rubble/shard of pottery", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000017, + "character_id": null, + "name": "coin", + "description": "The coin, in the rubble.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000009, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000011, + "how": "in", + "kit_key": "cave/rubble/coin", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "metal", + "wear": null, + "worn": 0 + }, + { + "id": 1000000018, + "character_id": null, + "name": "rock shelf", + "description": "The rock shelf, fixed in place.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "immovable", + "x": 9, + "y": 3, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "top", + "within_id": null, + "how": null, + "kit_key": "cave/rock shelf", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "stone", + "wear": null, + "worn": 0 + }, + { + "id": 1000000019, + "character_id": null, + "name": "rubble", + "description": "The rubble, fixed in place.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "immovable", + "x": 10, + "y": 1, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "hollow", + "within_id": null, + "how": null, + "kit_key": "cave/rubble", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000020, + "character_id": null, + "name": "stalagmite", + "description": "The stalagmite, fixed in place.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "immovable", + "x": 11, + "y": 0, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "nothing", + "within_id": null, + "how": null, + "kit_key": "cave/stalagmite", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "stone", + "wear": null, + "worn": 0 + }, + { + "id": 1000000021, + "character_id": null, + "name": "pool", + "description": "The pool, fixed in place.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "immovable", + "x": 10, + "y": 1, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "nothing", + "within_id": null, + "how": null, + "kit_key": "cave/pool", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000022, + "character_id": null, + "name": "crack", + "description": "The crack, fixed in place.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "immovable", + "x": 10, + "y": 2, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "closed", + "within_id": null, + "how": null, + "kit_key": "cave/crack", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000023, + "character_id": null, + "name": "pebble", + "description": "The pebble, on the rock shelf.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000018, + "how": "on", + "kit_key": "cave/rock shelf/pebble", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "stone", + "wear": null, + "worn": 0 + }, + { + "id": 1000000024, + "character_id": null, + "name": "bone", + "description": "The bone, on the rock shelf.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000018, + "how": "on", + "kit_key": "cave/rock shelf/bone", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000025, + "character_id": null, + "name": "rusted hinge", + "description": "The rusted hinge, in the rubble.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000019, + "how": "in", + "kit_key": "cave/rubble/rusted hinge", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "metal", + "wear": null, + "worn": 0 + }, + { + "id": 1000000026, + "character_id": null, + "name": "coin", + "description": "The coin, in the rubble.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000019, + "how": "in", + "kit_key": "cave/rubble/coin", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "metal", + "wear": null, + "worn": 0 + }, + { + "id": 1000000027, + "character_id": null, + "name": "rock shelf", + "description": "The rock shelf, fixed in place.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000018, + "bulk": "immovable", + "x": 9, + "y": 3, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "top", + "within_id": null, + "how": null, + "kit_key": "cave/rock shelf", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "stone", + "wear": null, + "worn": 0 + }, + { + "id": 1000000028, + "character_id": null, + "name": "rubble", + "description": "The rubble, fixed in place.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000019, + "bulk": "immovable", + "x": 10, + "y": 1, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "hollow", + "within_id": null, + "how": null, + "kit_key": "cave/rubble", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000029, + "character_id": null, + "name": "stalagmite", + "description": "The stalagmite, fixed in place.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000020, + "bulk": "immovable", + "x": 11, + "y": 0, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "nothing", + "within_id": null, + "how": null, + "kit_key": "cave/stalagmite", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "stone", + "wear": null, + "worn": 0 + }, + { + "id": 1000000030, + "character_id": null, + "name": "pool", + "description": "The pool, fixed in place.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000021, + "bulk": "immovable", + "x": 10, + "y": 1, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "nothing", + "within_id": null, + "how": null, + "kit_key": "cave/pool", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000031, + "character_id": null, + "name": "crack", + "description": "The crack, fixed in place.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000022, + "bulk": "immovable", + "x": 10, + "y": 2, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "closed", + "within_id": null, + "how": null, + "kit_key": "cave/crack", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000032, + "character_id": null, + "name": "pebble", + "description": "The pebble, on the rock shelf.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000023, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000027, + "how": "on", + "kit_key": "cave/rock shelf/pebble", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "stone", + "wear": null, + "worn": 0 + }, + { + "id": 1000000033, + "character_id": null, + "name": "bone", + "description": "The bone, on the rock shelf.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000024, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000027, + "how": "on", + "kit_key": "cave/rock shelf/bone", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000034, + "character_id": null, + "name": "rusted hinge", + "description": "The rusted hinge, in the rubble.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000025, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000028, + "how": "in", + "kit_key": "cave/rubble/rusted hinge", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "metal", + "wear": null, + "worn": 0 + }, + { + "id": 1000000035, + "character_id": null, + "name": "coin", + "description": "The coin, in the rubble.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000026, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000028, + "how": "in", + "kit_key": "cave/rubble/coin", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "metal", + "wear": null, + "worn": 0 + }, + { + "id": 1000000036, + "character_id": null, + "name": "stair key", + "description": "A long iron key on a cord, cold to hold.", + "properties": "{}", + "location_id": null, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000001, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "key", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": null, + "how": null, + "kit_key": null, + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + } + ], + "removed": [] + }, + "quest_steps": { + "rows": [], + "removed": [] + }, + "scenes": { + "rows": [ + { + "id": 1000000002, + "story_id": 1000000001, + "location_id": 1000000004, + "previous_scene_id": 1000000001, + "description": "You arrive at The Upper Floor room 1. You are unhurt. Nobody else is alive here. Fixed here, and not takeable: rock shelf, rubble, pool, ledge. Lying here: bone, candle stub, shard of pottery, coin. You are carrying: nothing.", + "story_timestamp": "2026-10-30 08:01:30", + "summary": "You arrive at The Upper Floor room 1. You are unhurt. Nobody else is alive here. Fixed here, and not takeable: rock shelf, rubble, pool, ledge. Lying here: bone, candle stub, shard of pottery, coin. You are carrying: nothing.", + "is_opening": 0, + "typed": "move The Upper Floor", + "resolved_action": "move", + "acted_on_type": "Location", + "acted_on_id": 1000000002, + "resolved_by": "grammar", + "engine_fallback": 1, + "engine_fact": "You are unhurt.\nNobody else is alive here.\nFixed here, and not takeable: rock shelf, rubble, pool, ledge.\nLying here: bone, candle stub, shard of pottery, coin.\nYou are carrying: nothing." + }, + { + "id": 1000000003, + "story_id": 1000000001, + "location_id": 1000000005, + "previous_scene_id": 1000000002, + "description": "You arrive at The Upper Floor room 2. You are unhurt. Also here: Brask the Stairwarden. Nobody else is alive here. Fixed here, and not takeable: rock shelf, rubble, stalagmite, pool, crack. Lying here: pebble, bone, rusted hinge, coin. You are carrying: nothing.", + "story_timestamp": "2026-10-30 08:02:30", + "summary": "You arrive at The Upper Floor room 2. You are unhurt. Also here: Brask the Stairwarden. Nobody else is alive here. Fixed here, and not takeable: rock shelf, rubble, stalagmite, pool, crack. Lying here: pebble, bone, rusted hinge, coin. You are carrying: nothing.", + "is_opening": 0, + "typed": "move The Upper Floor room 2", + "resolved_action": "move", + "acted_on_type": "Location", + "acted_on_id": 1000000005, + "resolved_by": "grammar", + "engine_fallback": 1, + "engine_fact": "You are unhurt.\nAlso here: Brask the Stairwarden. Nobody else is alive here.\nFixed here, and not takeable: rock shelf, rubble, stalagmite, pool, crack.\nLying here: pebble, bone, rusted hinge, coin.\nYou are carrying: nothing." + }, + { + "id": 1000000004, + "story_id": 1000000001, + "location_id": 1000000005, + "previous_scene_id": 1000000003, + "description": "The fight in The Upper Floor room 2 is over after 1 round. Brask the Stairwarden is dead. 1 blow landed: Odile hit Brask the Stairwarden for 7 (round 1); Brask the Stairwarden is dead.", + "story_timestamp": "2026-10-30 08:07:30", + "summary": "A fight in The Upper Floor room 2: 1 blow over 1 round. Brask the Stairwarden is dead.", + "is_opening": 0, + "typed": null, + "resolved_action": "attack", + "acted_on_type": "Character", + "acted_on_id": 1000000002, + "resolved_by": null, + "engine_fallback": 0, + "engine_fact": null + } + ], + "removed": [] + }, + "characters_scenes": { + "rows": [ + { + "character_id": 1000000001, + "scene_id": 1000000002 + }, + { + "character_id": 1000000001, + "scene_id": 1000000003 + }, + { + "character_id": 1000000002, + "scene_id": 1000000003 + }, + { + "character_id": 1000000001, + "scene_id": 1000000004 + } + ], + "removed": [] + }, + "interactions": { + "rows": [], + "removed": [] + }, + "playthrough_endings": { + "rows": [], + "removed": [] + }, + "playthrough_beats": { + "rows": [], + "removed": [] + }, + "playthrough_npc_states": { + "rows": [ + { + "id": 1000000001, + "playthrough_id": 1000000001, + "character_id": 1000000002, + "location_id": 1000000005, + "following": 0, + "ceasefire": 0, + "peace_after_blow_id": 0, + "noticed_at": null + } + ], + "removed": [] + }, + "world_events": { + "rows": [], + "removed": [] + } + } + }, + { + "step": "step 7 down-to-the-lower-floor", + "requests": [ + { + "purpose": "location", + "system": "You build the rooms of a text adventure one at a time. You write places a\nplayer can stand in and walk out of: concrete, specific, and consistent\nwith the world they belong to. Every exit you name is somewhere the player\ncould actually go.\n\nDO NOT INCLUDE EMOJIS IN YOUR RESPONSE.\n", + "user": "## Universe Details\nphysics: Ordinary physics.\ntechnology: Lanterns, iron keys and hand tools.\ngeographies: A dungeon cut down into black stone under a lantern-lit landing.\nraces: Delvers, Stairwardens\ncivilizations: Delvers who sell what they bring up, and the things that keep the stairs.\n\n\n## Story Details\ntitle: A Guarded Stair (engine sweep)\ngenre: dungeon crawl\npreface: Every stair down in the dungeon has something standing at the top of it.\nsummary: Odile goes down past the thing that keeps the stair.\n\n\n## The Place\nname: The Lower Floor\nteaser: The second level of the dungeon, colder and quieter than the first.\n\n## Instructions\nWrite this place out in full. It is a BUILDING -- somewhere with rooms\ninside it that a player walks into and moves around in.\n- The description is what the player reads as they come in. Address them as \"you\"\n- Describe what is here now, not the history -- the history is the lore\n- Stay consistent with the universe and with the teaser above\n- Do NOT describe the floor plan: how many rooms there are, where the stairs\n are and which door leads where are the game's to decide, out of the answers\n below, and it will tell you room by room as the player reaches them\n- Do not name anybody standing here and do not list anything lying here.\n People and things belong to the rooms, and each room is written as it is\n reached\n- Respect the stated length of each field\n\n## What Kind Of Building This Is\nThe game lays the inside out itself -- every room, every door, every stair\n-- from the answers to these, and then writes each room as the player\nreaches it. You are choosing what KIND of place this is, not drawing it.\n- Answer for the place you have just described and for the story it stands in\n- Every one of them can be left out, and the quietest answer is usually the\n right one: one floor, nothing underneath, nothing dangerous, nothing that\n hurts you\n- A HAZARD is not atmosphere. It takes hit points off everybody who walks\n through those rooms, every turn in some cases, so pick one only for a\n place that really is flooded, unlit, silent or airless\n- A GRADIENT is only worth saying when the place itself makes it true: a\n cellar that gets worse the further down you go, a tower that gets worse\n the higher you climb\n", + "schema": { + "name": "Location::PlaceSchema", + "description": null, + "schema": { + "type": "object", + "properties": { + "description": { + "type": "string", + "description": "What the player sees, hears and smells standing in this place right now. Describe THIS place only -- not what neighbours it, not what is visible out of a window or across the way, because the world around it can move. Second person. One paragraph, 4 to 6 sentences.", + "maxLength": 1200 + }, + "lore": { + "type": "string", + "description": "What this place is, who made it and what happened here. Written for the game engine rather than the player. One paragraph, 3 to 5 sentences.", + "maxLength": 900 + }, + "place_kind": { + "type": "string", + "enum": [ + "house", + "farmhouse", + "mansion", + "apartment block", + "inn", + "shop", + "office building", + "guildhall", + "workshop", + "smithy", + "mill", + "factory", + "warehouse", + "barn", + "temple", + "library", + "school", + "hospital", + "theatre", + "bathhouse", + "barracks", + "fortress", + "jail", + "tower", + "lighthouse", + "warren", + "ruin", + "spaceship", + "space station" + ], + "description": "What sort of place with rooms this is -- a building, a ship, a station -- as the game will lay out and furnish its rooms: pick the closest. The game decides which rooms it has and what stands in each; you describe it from outside and at its way in." + }, + "parameters": { + "type": "object", + "properties": { + "storeys_above": { + "type": "string", + "enum": [ + "ground floor only", + "one storey up", + "two storeys up" + ], + "description": "How far up it goes, counting the ground floor. Most buildings are one floor." + }, + "storeys_below": { + "type": "string", + "enum": [ + "none", + "a cellar", + "two levels down", + "deep" + ], + "description": "How far down it goes below the ground floor. Most buildings have nothing under them." + }, + "danger": { + "type": "string", + "enum": [ + "safe", + "uneasy", + "dangerous" + ], + "description": "How likely a room of it is to hold something that means the player harm. Most buildings are safe." + }, + "gradient": { + "type": "string", + "enum": [ + "the same throughout", + "worse the deeper you go", + "worse the higher you climb" + ], + "description": "Whether that gets worse in one direction. Only say so when the place itself makes it true -- a cellar that gets worse the further down you go, a tower that gets worse the higher you climb." + }, + "hazard": { + "type": "string", + "enum": [ + "none", + "flooded", + "unlit", + "silent", + "airless" + ], + "description": "What standing in its rooms does to a person, if anything. NONE is the right answer for almost every building: this is not atmosphere, it is a place that takes hit points off whoever walks through it." + } + }, + "required": [], + "additionalProperties": false, + "description": "What kind of building this is. Every field is optional; leave one out and the game takes the quietest option. Answer for the place you have just described." + } + }, + "required": [ + "description", + "lore" + ], + "additionalProperties": false, + "strict": true + } + } + }, + { + "purpose": "location", + "system": "You build the rooms of a text adventure one at a time. You write places a\nplayer can stand in and walk out of: concrete, specific, and consistent\nwith the world they belong to. Every exit you name is somewhere the player\ncould actually go.\n\nDO NOT INCLUDE EMOJIS IN YOUR RESPONSE.\n", + "user": "## Universe Details\nphysics: Ordinary physics.\ntechnology: Lanterns, iron keys and hand tools.\ngeographies: A dungeon cut down into black stone under a lantern-lit landing.\nraces: Delvers, Stairwardens\ncivilizations: Delvers who sell what they bring up, and the things that keep the stairs.\n\n\n## Story Details\ntitle: A Guarded Stair (engine sweep)\ngenre: dungeon crawl\npreface: Every stair down in the dungeon has something standing at the top of it.\nsummary: Odile goes down past the thing that keeps the stair.\n\n\n## The Place\nname: The Lower Floor room 1\nteaser: A room inside The Lower Floor, 9 by 6 paces on storey 0.\n## Where This Room Is\nThe game's own records of this room, already decided and not yours to\nchange. Write the room around them: do not contradict a measurement, and\ndo not give it a way out this list does not have.\nThis room is 9 by 6 paces -- about 14 by 9 metres. It is on storey 0 of The Lower Floor, which is 12 by 6 paces across; storey 0 is the ground floor. Its ways out: a door in the east wall, to The Lower Floor room 2; a way out to The Upper Floor room 2, which is outside The Lower Floor. Those are every way out of this room.\n## Already Here, Decided By The Game\nThe game's own records of what is in this room, already decided and not yours\nto change, and it will tell the player so. Write the room around them, and do\nnot add another piece of furniture or fixed thing a player could reach for.\nFixed in place: rock shelf, rubble, pool.\nOn the rock shelf: candle stub, bone.\nIn the rubble: shard of pottery.\nDo not list any of them again as a thing lying here.\n## Instructions\nWrite this place out in full.\n- The description is what the player reads on arrival. Address them as \"you\"\n- Describe what is here now, not the history -- the history is the lore\n- Stay consistent with the universe and with the teaser above\n- Respect the stated length of each field\n- NAME THIS ROOM. It is one room inside The Lower Floor. The player reads the room and\n the place together -- \"the of The Lower Floor\" -- so name the ROOM only,\n and never put the place's own name into it. A short noun phrase carrying the\n article English wants on it, 2 to 4 words: \"the counting room\", \"the cold\n store\", \"the harbourmaster's office\". Name it for what the floor plan above\n says this room is and for what you have just described standing in it. Never\n a comma in it\n\n## What Is Lying Here\nList AT MOST 3 portable things a player could pick up and carry away, besides the ones Already Here above: those are written already, and a thing named again is not a new one.\n- Nothing is the right answer for most rooms. An empty list is a complete answer\n- Only loose, portable things. Not the door, not the floor, not the machinery\n bolted to it -- something a person could put in a pocket or under an arm\n- Each one must be consistent with the description you just wrote, and worth\n the player noticing\n- Pick its use_kind from the supplied physical profiles. Ordinary is the\n default. Food and drink can be consumed without healing; healing is only\n a restorative dose this world permits. Firestarters burn combustible\n objects, levers pry jammed passages, lockpicks try locks, and a key only\n opens a passage explicitly matched to it. Mark combustible only when an\n ordinary firestarter can destroy the object. The engine owns the effects\n and their amounts; its description cannot grant extra powers\n- If a thing has WRITING on it -- a note, a letter, a handbill, a label, a\n docket, a page, a sign -- mark it readable and WRITE OUT WHAT IS WRITTEN\n ON IT, exactly as it appears on the thing. The words themselves, not a\n description of them, and short enough to finish -- a few words, a line,\n or a few short lines. The game keeps those words and a player reading it\n twice reads the same ones\n- Never name it after a person or after a place. Already spoken for in this story, so do not reuse: Odile, Brask the Stairwarden, stair key\n\n## Who Is Here\nWrite NOBODY into this place. There is nobody here, and the description\nshould read like somewhere nobody is standing.\n", + "schema": { + "name": "Location::DetailSchema", + "description": null, + "schema": { + "type": "object", + "properties": { + "description": { + "type": "string", + "description": "What the player sees, hears and smells standing in this place right now. Describe THIS place only -- not what neighbours it, not what is visible out of a window or across the way, because the world around it can move. Second person. One paragraph, 4 to 6 sentences.", + "maxLength": 1200 + }, + "lore": { + "type": "string", + "description": "What this place is, who made it and what happened here. Written for the game engine rather than the player. One paragraph, 3 to 5 sentences.", + "maxLength": 900 + }, + "name": { + "type": "string", + "description": "What this room is called -- ONLY when the instructions above ask you to name it. Leave this out entirely otherwise. A short noun phrase a player would type to walk into it, carrying the article English wants on it: \"the counting room\". Never the name of the building it is in, never a name this story has already given to a room, a person or a thing, and never a comma.", + "maxLength": 60 + }, + "items": { + "type": "array", + "description": "Portable things lying loose in this place that a player could pick up and carry away. Empty is the right answer for most rooms.", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "What the thing is called, as a player would type it to pick it up. A short noun phrase, 1 to 4 words, lower case unless it is a proper name. Never the name of a person or of a place.", + "maxLength": 60 + }, + "description": { + "type": "string", + "description": "What it is and what state it is in, consistent with the description of the room you just wrote. One or two sentences.", + "maxLength": 400 + }, + "use_kind": { + "type": "string", + "enum": [ + "ordinary", + "food", + "drink", + "healing", + "firestarter", + "lever", + "lockpick", + "key" + ], + "description": "The engine's physical profile: ordinary unless it is food, a drink, a healing dose, a firestarter, a prying lever, lockpicks, or a key. Choose healing only for an actual restorative dose allowed by this world. Profiles supply behavior from code, never from the description." + }, + "combustible": { + "type": "boolean", + "description": "True only if this portable thing can be destroyed by an ordinary carried firestarter: dry paper, cloth or wood. False for stone, metal, liquids, or anything uncertain." + }, + "readable": { + "type": "boolean", + "description": "True only if this thing has WRITING on it that a player could read: a note, a letter, a label, a docket, a page, a sign, an inscription. False for everything else, which is most things." + }, + "inscription": { + "type": "string", + "description": "The words written on it, exactly as they appear, and only when `readable` is true. Write what is actually on the thing -- what a player would read off it -- not a description of it. A few words, a line, or a few short lines; well under the limit, and finished rather than trailing off. Leave this out entirely when nothing is written on it.", + "maxLength": 400 + } + }, + "required": [ + "name", + "description", + "use_kind", + "combustible", + "readable" + ], + "additionalProperties": false + }, + "maxItems": 3 + }, + "people": { + "type": "array", + "description": "People who are in this place right now.", + "items": { + "type": "object", + "properties": { + "fullname": { + "type": "string", + "description": "Their full name, as a player would type it to speak to them. 2 or 3 words.", + "maxLength": 60 + }, + "nickname": { + "type": "string", + "description": "What they are called to their face. 1 or 2 words.", + "maxLength": 30 + }, + "appearance": { + "type": "string", + "description": "What somebody walking in sees of them, consistent with the room you just wrote and with the race and age you were given for them. One or two sentences.", + "maxLength": 300 + }, + "personality": { + "type": "string", + "description": "How they behave and how they treat a stranger. One or two sentences.", + "maxLength": 300 + }, + "backstory": { + "type": "string", + "description": "The life history that brought them here, the repeated choices it taught them, and why today's presence presses on an enduring aim. Third person, by name. Two or three sentences.", + "maxLength": 450 + }, + "likes": { + "type": "string", + "description": "A comma separated list of 2 or 3 things they enjoy.", + "maxLength": 160 + }, + "dislikes": { + "type": "string", + "description": "A comma separated list of 2 or 3 things they cannot stand.", + "maxLength": 160 + }, + "fears": { + "type": "string", + "description": "A comma separated list of 1 or 2 things they are afraid of.", + "maxLength": 160 + }, + "conscious_desire": { + "type": "string", + "description": "The future, standing, relationship, legacy, place or way of life they knowingly organize their choices around and would name if asked what they want from their life. Make it specific to this person and world. Do not substitute the clue, deadline, delivery, payment or room they are dealing with today. One sentence, third person, by name.", + "maxLength": 180 + }, + "unconscious_desire": { + "type": "string", + "description": "The deeper reward their choices have pursued for years and they would deny: the recognition, absolution, dependence, belonging, power or intimacy beneath the conscious account. It explains a repeated pattern in the backstory and pulls at an angle to the conscious desire, not merely today's hidden motive. One sentence, third person, by name.", + "maxLength": 180 + }, + "recognized_need": { + "type": "string", + "description": "The duty, oath, debt, craft, family burden or bodily discipline they believe they must keep over years whether they want to or not. It predates today's assignment, survives it, and can repeatedly obstruct the conscious desire. One sentence, third person, by name.", + "maxLength": 180 + }, + "unrecognized_need": { + "type": "string", + "description": "The enduring change, truth or relationship their life requires and their repeated pattern prevents them from seeing. A reader can infer it from the backstory. It cannot be completed by one confession, realization or errand: it demands a new way of choosing across the story. If they never move toward it, pursuit of the conscious desire ruins them. It must not be the conscious desire said twice. One sentence, third person, by name.", + "maxLength": 180 + }, + "desire_pursuit": { + "type": "string", + "enum": [ + "keep", + "obtain", + "reach", + "attend", + "avoid", + "withhold", + "offer" + ], + "description": "Which listed ENGINE ACT could repeatedly advance or protect the CONSCIOUS DESIRE in ordinary rooms. The label is the next-step expression of the larger desire, not its timescale." + }, + "need_pursuit": { + "type": "string", + "enum": [ + "keep", + "obtain", + "reach", + "attend", + "avoid", + "withhold", + "offer" + ], + "description": "Which listed ENGINE ACT could repeatedly move them toward the UNRECOGNIZED NEED in ordinary rooms. It may match desire_pursuit, but a different label should reflect a real conflict rather than manufactured variety." + } + }, + "required": [ + "fullname", + "nickname", + "appearance", + "personality", + "backstory", + "likes", + "dislikes", + "fears", + "conscious_desire", + "unconscious_desire", + "recognized_need", + "unrecognized_need", + "desire_pursuit", + "need_pursuit" + ], + "additionalProperties": false + }, + "maxItems": 3 + } + }, + "required": [ + "description", + "lore" + ], + "additionalProperties": false, + "strict": true + } + } + }, + { + "purpose": "arrival", + "system": "You narrate a text adventure. You write the moment a player walks into a\nplace: what reaches them first, in the second person and the present\ntense. You never break character, never offer a numbered menu, and never\nmention that you are a narrator.\n\nDO NOT INCLUDE EMOJIS IN YOUR RESPONSE.\n", + "user": "## Universe Details\nphysics: Ordinary physics.\ntechnology: Lanterns, iron keys and hand tools.\n\n## Story Details\ntitle: A Guarded Stair (engine sweep)\ngenre: dungeon crawl\nsummary: Odile goes down past the thing that keeps the stair.\n\n## The Place\nname: The Lower Floor room 1\ndescription: The foot of the stair, cold and unlit.\nlore: The stair comes down here from the floor above.\nways out: The Lower Floor room 2, The Upper Floor room 2\n\n## Who Is Here\nOdile (Odile), Delvers -- the player, the one arriving\n\n## Just Before This\nThe player has come from The Upper Floor room 2. A fight in The Upper Floor room 2: 1 blow over 1 round. Brask the Stairwarden is dead.\n\n## Instructions\n- The player has never been here. This is the first time they have set\n foot in it\n- Narrate discovery: what catches them on the way in, before they\n have made sense of the rest\n- Address the player as \"you\", in the present tense\n- One paragraph. Do not re-describe the place item by item -- the\n description above is already what is here, and your job is the moment\n of coming into it\n- Anyone listed above is here; write them as already present, not as\n arriving. Do not add a person who is not on that list\n- Do not name a way out that is not on the list above\n- Respect the stated length of each field\n\n## Current State On Arrival\nThe place description is the world's original account. These current records\ntake precedence over its claims about people, items and wounds. Narrate the\nrecorded crossing result as part of this arrival.\nYou are unhurt.\nNobody else is alive here.\nFixed here, and not takeable: rock shelf, rubble, pool.\nLying here: candle stub, bone, shard of pottery.\nYou are carrying: stair key.\n", + "schema": { + "name": "Scene::Schema", + "description": null, + "schema": { + "type": "object", + "properties": { + "description": { + "type": "string", + "description": "What the player experiences as they arrive here, right now. Second person, present tense. One paragraph, 3 to 5 sentences.", + "maxLength": 900 + }, + "summary": { + "type": "string", + "description": "What happened in this moment, for the game engine rather than the player. Third person. One sentence.", + "maxLength": 200 + } + }, + "required": [ + "description", + "summary" + ], + "additionalProperties": false, + "strict": true + } + } + } + ], + "written": { + "locations": { + "rows": [ + { + "id": 1000000002, + "story_id": 1000000001, + "name": "The Upper Floor", + "description": "A level of black stone passages lit by nothing you can see.", + "lore": "The first floor was cut by people nobody remembers.", + "last_protagonist_visit": null, + "parent_location_id": null, + "detail_level": "realized", + "teaser": "The first level of the dungeon, with a stair somewhere that goes further down.", + "mobile": 0, + "danger": "safe", + "hazard": null, + "hazard_die": null, + "x": null, + "y": null, + "z": null, + "width": 12, + "depth": 12, + "population": null, + "generation_checkpoint": null, + "surface": null, + "kind": "warren", + "density": null + }, + { + "id": 1000000003, + "story_id": 1000000001, + "name": "The Lower Floor", + "description": "A colder level, the air still and the dark close.", + "lore": "Few delvers come this far.", + "last_protagonist_visit": null, + "parent_location_id": null, + "detail_level": "realized", + "teaser": "The second level of the dungeon, colder and quieter than the first.", + "mobile": 0, + "danger": "safe", + "hazard": null, + "hazard_die": null, + "x": null, + "y": null, + "z": null, + "width": 12, + "depth": 6, + "population": null, + "generation_checkpoint": null, + "surface": null, + "kind": "warren", + "density": null + }, + { + "id": 1000000004, + "story_id": 1000000001, + "name": "The Upper Floor room 1", + "description": "A low chamber at the foot of the stair.", + "lore": "Delvers rest here before going on.", + "last_protagonist_visit": "2026-10-30 08:01:30", + "parent_location_id": 1000000002, + "detail_level": "realized", + "teaser": "A room inside The Upper Floor, 9 by 5 paces on storey 0.", + "mobile": 0, + "danger": "uneasy", + "hazard": null, + "hazard_die": null, + "x": 0, + "y": 0, + "z": 0, + "width": 9, + "depth": 5, + "population": null, + "generation_checkpoint": null, + "surface": null, + "kind": "cave", + "density": null + }, + { + "id": 1000000005, + "story_id": 1000000001, + "name": "The Upper Floor room 2", + "description": "A bare chamber with a stair cut down into its floor.", + "lore": "The stair down is older than the rest.", + "last_protagonist_visit": "2026-10-30 08:07:30", + "parent_location_id": 1000000002, + "detail_level": "realized", + "teaser": "A room inside The Upper Floor, 3 by 5 paces on storey 0.", + "mobile": 0, + "danger": "dangerous", + "hazard": null, + "hazard_die": null, + "x": 9, + "y": 0, + "z": 0, + "width": 3, + "depth": 5, + "population": null, + "generation_checkpoint": null, + "surface": null, + "kind": "cave", + "density": null + }, + { + "id": 1000000006, + "story_id": 1000000001, + "name": "The Upper Floor room 3", + "description": null, + "lore": null, + "last_protagonist_visit": null, + "parent_location_id": 1000000002, + "detail_level": "stub", + "teaser": "A room inside The Upper Floor, 3 by 7 paces on storey 0.", + "mobile": 0, + "danger": "uneasy", + "hazard": null, + "hazard_die": null, + "x": 9, + "y": 5, + "z": 0, + "width": 3, + "depth": 7, + "population": null, + "generation_checkpoint": null, + "surface": null, + "kind": "cave", + "density": null + }, + { + "id": 1000000007, + "story_id": 1000000001, + "name": "The Lower Floor room 1", + "description": "The foot of the stair, cold and unlit.", + "lore": "The stair comes down here from the floor above.", + "last_protagonist_visit": "2026-10-30 08:09:00", + "parent_location_id": 1000000003, + "detail_level": "realized", + "teaser": "A room inside The Lower Floor, 9 by 6 paces on storey 0.", + "mobile": 0, + "danger": "uneasy", + "hazard": null, + "hazard_die": null, + "x": 0, + "y": 0, + "z": 0, + "width": 9, + "depth": 6, + "population": null, + "generation_checkpoint": null, + "surface": null, + "kind": "cave", + "density": null + }, + { + "id": 1000000008, + "story_id": 1000000001, + "name": "The Lower Floor room 2", + "description": null, + "lore": null, + "last_protagonist_visit": null, + "parent_location_id": 1000000003, + "detail_level": "stub", + "teaser": "A room inside The Lower Floor, 3 by 6 paces on storey 0.", + "mobile": 0, + "danger": "dangerous", + "hazard": null, + "hazard_die": null, + "x": 9, + "y": 0, + "z": 0, + "width": 3, + "depth": 6, + "population": null, + "generation_checkpoint": null, + "surface": null, + "kind": "cave", + "density": null + } + ], + "removed": [] + }, + "location_connections": { + "rows": [ + { + "id": 1000000005, + "location_id": 1000000004, + "connected_location_id": 1000000005, + "distance": "adjacent", + "time_to_travel": "about a minute", + "travel_method": "walking", + "hazard": null, + "hazard_die": null, + "barrier": "open", + "key_template_id": null + }, + { + "id": 1000000006, + "location_id": 1000000005, + "connected_location_id": 1000000004, + "distance": "adjacent", + "time_to_travel": "about a minute", + "travel_method": "walking", + "hazard": null, + "hazard_die": null, + "barrier": "open", + "key_template_id": null + }, + { + "id": 1000000007, + "location_id": 1000000005, + "connected_location_id": 1000000006, + "distance": "adjacent", + "time_to_travel": "about a minute", + "travel_method": "walking", + "hazard": null, + "hazard_die": null, + "barrier": "open", + "key_template_id": null + }, + { + "id": 1000000008, + "location_id": 1000000006, + "connected_location_id": 1000000005, + "distance": "adjacent", + "time_to_travel": "about a minute", + "travel_method": "walking", + "hazard": null, + "hazard_die": null, + "barrier": "open", + "key_template_id": null + }, + { + "id": 1000000009, + "location_id": 1000000004, + "connected_location_id": 1000000001, + "distance": "adjacent", + "time_to_travel": "about a minute", + "travel_method": "taking stairs", + "hazard": null, + "hazard_die": null, + "barrier": "open", + "key_template_id": null + }, + { + "id": 1000000010, + "location_id": 1000000001, + "connected_location_id": 1000000004, + "distance": "adjacent", + "time_to_travel": "about a minute", + "travel_method": "taking stairs", + "hazard": null, + "hazard_die": null, + "barrier": "open", + "key_template_id": null + }, + { + "id": 1000000013, + "location_id": 1000000007, + "connected_location_id": 1000000008, + "distance": "adjacent", + "time_to_travel": "about a minute", + "travel_method": "walking", + "hazard": null, + "hazard_die": null, + "barrier": "open", + "key_template_id": null + }, + { + "id": 1000000014, + "location_id": 1000000008, + "connected_location_id": 1000000007, + "distance": "adjacent", + "time_to_travel": "about a minute", + "travel_method": "walking", + "hazard": null, + "hazard_die": null, + "barrier": "open", + "key_template_id": null + }, + { + "id": 1000000015, + "location_id": 1000000007, + "connected_location_id": 1000000005, + "distance": "adjacent", + "time_to_travel": "about a minute", + "travel_method": "taking stairs", + "hazard": null, + "hazard_die": null, + "barrier": "keyed", + "key_template_id": 1000000001 + }, + { + "id": 1000000016, + "location_id": 1000000005, + "connected_location_id": 1000000007, + "distance": "adjacent", + "time_to_travel": "about a minute", + "travel_method": "taking stairs", + "hazard": null, + "hazard_die": null, + "barrier": "keyed", + "key_template_id": 1000000001 + } + ], + "removed": [ + 1000000001, + 1000000002, + 1000000003, + 1000000004 + ] + }, + "characters": { + "rows": [ + { + "id": 1000000002, + "story_id": 1000000001, + "fullname": "Brask the Stairwarden", + "nickname": "Brask", + "age": 90, + "sex": "male", + "personality": "Silent and patient.", + "appearance": "A grey, stooped thing with a long iron key on a cord.", + "likes": "Stillness.", + "dislikes": "Footsteps on the stair.", + "fears": "Nothing.", + "backstory": "He has kept the stair down for longer than the delvers have come.", + "is_companion": 0, + "race_id": 1000000002, + "is_protagonist": 0, + "location_id": 1000000005, + "deliberately_absent": 0, + "level": 1, + "hit_die": 6, + "strength": 8, + "dexterity": 8, + "will": 8, + "hostile": 1, + "x": 9, + "y": 3, + "conscious_desire": null, + "unconscious_desire": null, + "recognized_need": null, + "unrecognized_need": null, + "desire_pursuit": null, + "need_pursuit": null, + "posted_to_id": 1000000002 + } + ], + "removed": [] + }, + "items": { + "rows": [ + { + "id": 1000000002, + "character_id": null, + "name": "rock shelf", + "description": "The rock shelf, fixed in place.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "immovable", + "x": 4, + "y": 0, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "top", + "within_id": null, + "how": null, + "kit_key": "cave/rock shelf", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "stone", + "wear": null, + "worn": 0 + }, + { + "id": 1000000003, + "character_id": null, + "name": "rubble", + "description": "The rubble, fixed in place.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "immovable", + "x": 1, + "y": 3, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "hollow", + "within_id": null, + "how": null, + "kit_key": "cave/rubble", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000004, + "character_id": null, + "name": "pool", + "description": "The pool, fixed in place.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "immovable", + "x": 0, + "y": 4, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "nothing", + "within_id": null, + "how": null, + "kit_key": "cave/pool", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000005, + "character_id": null, + "name": "ledge", + "description": "The ledge, fixed in place.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "immovable", + "x": 7, + "y": 3, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "top", + "within_id": null, + "how": null, + "kit_key": "cave/ledge", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000006, + "character_id": null, + "name": "bone", + "description": "The bone, on the rock shelf.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000002, + "how": "on", + "kit_key": "cave/rock shelf/bone", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000007, + "character_id": null, + "name": "candle stub", + "description": "The candle stub, on the rock shelf.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 1, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000002, + "how": "on", + "kit_key": "cave/rock shelf/candle stub", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000008, + "character_id": null, + "name": "shard of pottery", + "description": "The shard of pottery, in the rubble.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000003, + "how": "in", + "kit_key": "cave/rubble/shard of pottery", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000009, + "character_id": null, + "name": "coin", + "description": "The coin, in the rubble.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000003, + "how": "in", + "kit_key": "cave/rubble/coin", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "metal", + "wear": null, + "worn": 0 + }, + { + "id": 1000000010, + "character_id": null, + "name": "rock shelf", + "description": "The rock shelf, fixed in place.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000002, + "bulk": "immovable", + "x": 4, + "y": 0, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "top", + "within_id": null, + "how": null, + "kit_key": "cave/rock shelf", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "stone", + "wear": null, + "worn": 0 + }, + { + "id": 1000000011, + "character_id": null, + "name": "rubble", + "description": "The rubble, fixed in place.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000003, + "bulk": "immovable", + "x": 1, + "y": 3, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "hollow", + "within_id": null, + "how": null, + "kit_key": "cave/rubble", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000012, + "character_id": null, + "name": "pool", + "description": "The pool, fixed in place.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000004, + "bulk": "immovable", + "x": 0, + "y": 4, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "nothing", + "within_id": null, + "how": null, + "kit_key": "cave/pool", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000013, + "character_id": null, + "name": "ledge", + "description": "The ledge, fixed in place.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000005, + "bulk": "immovable", + "x": 7, + "y": 3, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "top", + "within_id": null, + "how": null, + "kit_key": "cave/ledge", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000014, + "character_id": null, + "name": "bone", + "description": "The bone, on the rock shelf.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000006, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000010, + "how": "on", + "kit_key": "cave/rock shelf/bone", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000015, + "character_id": null, + "name": "candle stub", + "description": "The candle stub, on the rock shelf.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000007, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 1, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000010, + "how": "on", + "kit_key": "cave/rock shelf/candle stub", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000016, + "character_id": null, + "name": "shard of pottery", + "description": "The shard of pottery, in the rubble.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000008, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000011, + "how": "in", + "kit_key": "cave/rubble/shard of pottery", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000017, + "character_id": null, + "name": "coin", + "description": "The coin, in the rubble.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000009, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000011, + "how": "in", + "kit_key": "cave/rubble/coin", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "metal", + "wear": null, + "worn": 0 + }, + { + "id": 1000000018, + "character_id": null, + "name": "rock shelf", + "description": "The rock shelf, fixed in place.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "immovable", + "x": 9, + "y": 3, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "top", + "within_id": null, + "how": null, + "kit_key": "cave/rock shelf", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "stone", + "wear": null, + "worn": 0 + }, + { + "id": 1000000019, + "character_id": null, + "name": "rubble", + "description": "The rubble, fixed in place.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "immovable", + "x": 10, + "y": 1, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "hollow", + "within_id": null, + "how": null, + "kit_key": "cave/rubble", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000020, + "character_id": null, + "name": "stalagmite", + "description": "The stalagmite, fixed in place.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "immovable", + "x": 11, + "y": 0, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "nothing", + "within_id": null, + "how": null, + "kit_key": "cave/stalagmite", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "stone", + "wear": null, + "worn": 0 + }, + { + "id": 1000000021, + "character_id": null, + "name": "pool", + "description": "The pool, fixed in place.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "immovable", + "x": 10, + "y": 1, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "nothing", + "within_id": null, + "how": null, + "kit_key": "cave/pool", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000022, + "character_id": null, + "name": "crack", + "description": "The crack, fixed in place.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "immovable", + "x": 10, + "y": 2, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "closed", + "within_id": null, + "how": null, + "kit_key": "cave/crack", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000023, + "character_id": null, + "name": "pebble", + "description": "The pebble, on the rock shelf.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000018, + "how": "on", + "kit_key": "cave/rock shelf/pebble", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "stone", + "wear": null, + "worn": 0 + }, + { + "id": 1000000024, + "character_id": null, + "name": "bone", + "description": "The bone, on the rock shelf.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000018, + "how": "on", + "kit_key": "cave/rock shelf/bone", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000025, + "character_id": null, + "name": "rusted hinge", + "description": "The rusted hinge, in the rubble.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000019, + "how": "in", + "kit_key": "cave/rubble/rusted hinge", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "metal", + "wear": null, + "worn": 0 + }, + { + "id": 1000000026, + "character_id": null, + "name": "coin", + "description": "The coin, in the rubble.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000019, + "how": "in", + "kit_key": "cave/rubble/coin", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "metal", + "wear": null, + "worn": 0 + }, + { + "id": 1000000027, + "character_id": null, + "name": "rock shelf", + "description": "The rock shelf, fixed in place.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000018, + "bulk": "immovable", + "x": 9, + "y": 3, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "top", + "within_id": null, + "how": null, + "kit_key": "cave/rock shelf", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "stone", + "wear": null, + "worn": 0 + }, + { + "id": 1000000028, + "character_id": null, + "name": "rubble", + "description": "The rubble, fixed in place.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000019, + "bulk": "immovable", + "x": 10, + "y": 1, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "hollow", + "within_id": null, + "how": null, + "kit_key": "cave/rubble", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000029, + "character_id": null, + "name": "stalagmite", + "description": "The stalagmite, fixed in place.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000020, + "bulk": "immovable", + "x": 11, + "y": 0, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "nothing", + "within_id": null, + "how": null, + "kit_key": "cave/stalagmite", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "stone", + "wear": null, + "worn": 0 + }, + { + "id": 1000000030, + "character_id": null, + "name": "pool", + "description": "The pool, fixed in place.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000021, + "bulk": "immovable", + "x": 10, + "y": 1, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "nothing", + "within_id": null, + "how": null, + "kit_key": "cave/pool", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000031, + "character_id": null, + "name": "crack", + "description": "The crack, fixed in place.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000022, + "bulk": "immovable", + "x": 10, + "y": 2, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "closed", + "within_id": null, + "how": null, + "kit_key": "cave/crack", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000032, + "character_id": null, + "name": "pebble", + "description": "The pebble, on the rock shelf.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000023, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000027, + "how": "on", + "kit_key": "cave/rock shelf/pebble", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "stone", + "wear": null, + "worn": 0 + }, + { + "id": 1000000033, + "character_id": null, + "name": "bone", + "description": "The bone, on the rock shelf.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000024, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000027, + "how": "on", + "kit_key": "cave/rock shelf/bone", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000034, + "character_id": null, + "name": "rusted hinge", + "description": "The rusted hinge, in the rubble.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000025, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000028, + "how": "in", + "kit_key": "cave/rubble/rusted hinge", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "metal", + "wear": null, + "worn": 0 + }, + { + "id": 1000000035, + "character_id": null, + "name": "coin", + "description": "The coin, in the rubble.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000026, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000028, + "how": "in", + "kit_key": "cave/rubble/coin", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "metal", + "wear": null, + "worn": 0 + }, + { + "id": 1000000036, + "character_id": null, + "name": "stair key", + "description": "A long iron key on a cord, cold to hold.", + "properties": "{}", + "location_id": null, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000001, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "key", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": null, + "how": null, + "kit_key": null, + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000037, + "character_id": null, + "name": "rock shelf", + "description": "The rock shelf, fixed in place.", + "properties": null, + "location_id": 1000000007, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "immovable", + "x": 5, + "y": 4, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "top", + "within_id": null, + "how": null, + "kit_key": "cave/rock shelf", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "stone", + "wear": null, + "worn": 0 + }, + { + "id": 1000000038, + "character_id": null, + "name": "rubble", + "description": "The rubble, fixed in place.", + "properties": null, + "location_id": 1000000007, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "immovable", + "x": 1, + "y": 4, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "hollow", + "within_id": null, + "how": null, + "kit_key": "cave/rubble", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000039, + "character_id": null, + "name": "pool", + "description": "The pool, fixed in place.", + "properties": null, + "location_id": 1000000007, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "immovable", + "x": 0, + "y": 4, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "nothing", + "within_id": null, + "how": null, + "kit_key": "cave/pool", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000040, + "character_id": null, + "name": "candle stub", + "description": "The candle stub, on the rock shelf.", + "properties": null, + "location_id": 1000000007, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 1, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000037, + "how": "on", + "kit_key": "cave/rock shelf/candle stub", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000041, + "character_id": null, + "name": "bone", + "description": "The bone, on the rock shelf.", + "properties": null, + "location_id": 1000000007, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000037, + "how": "on", + "kit_key": "cave/rock shelf/bone", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000042, + "character_id": null, + "name": "shard of pottery", + "description": "The shard of pottery, in the rubble.", + "properties": null, + "location_id": 1000000007, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000038, + "how": "in", + "kit_key": "cave/rubble/shard of pottery", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000043, + "character_id": null, + "name": "rock shelf", + "description": "The rock shelf, fixed in place.", + "properties": null, + "location_id": 1000000007, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000037, + "bulk": "immovable", + "x": 5, + "y": 4, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "top", + "within_id": null, + "how": null, + "kit_key": "cave/rock shelf", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "stone", + "wear": null, + "worn": 0 + }, + { + "id": 1000000044, + "character_id": null, + "name": "rubble", + "description": "The rubble, fixed in place.", + "properties": null, + "location_id": 1000000007, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000038, + "bulk": "immovable", + "x": 1, + "y": 4, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "hollow", + "within_id": null, + "how": null, + "kit_key": "cave/rubble", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000045, + "character_id": null, + "name": "pool", + "description": "The pool, fixed in place.", + "properties": null, + "location_id": 1000000007, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000039, + "bulk": "immovable", + "x": 0, + "y": 4, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "nothing", + "within_id": null, + "how": null, + "kit_key": "cave/pool", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000046, + "character_id": null, + "name": "candle stub", + "description": "The candle stub, on the rock shelf.", + "properties": null, + "location_id": 1000000007, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000040, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 1, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000043, + "how": "on", + "kit_key": "cave/rock shelf/candle stub", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000047, + "character_id": null, + "name": "bone", + "description": "The bone, on the rock shelf.", + "properties": null, + "location_id": 1000000007, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000041, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000043, + "how": "on", + "kit_key": "cave/rock shelf/bone", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000048, + "character_id": null, + "name": "shard of pottery", + "description": "The shard of pottery, in the rubble.", + "properties": null, + "location_id": 1000000007, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000042, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000044, + "how": "in", + "kit_key": "cave/rubble/shard of pottery", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + } + ], + "removed": [] + }, + "quest_steps": { + "rows": [], + "removed": [] + }, + "scenes": { + "rows": [ + { + "id": 1000000002, + "story_id": 1000000001, + "location_id": 1000000004, + "previous_scene_id": 1000000001, + "description": "You arrive at The Upper Floor room 1. You are unhurt. Nobody else is alive here. Fixed here, and not takeable: rock shelf, rubble, pool, ledge. Lying here: bone, candle stub, shard of pottery, coin. You are carrying: nothing.", + "story_timestamp": "2026-10-30 08:01:30", + "summary": "You arrive at The Upper Floor room 1. You are unhurt. Nobody else is alive here. Fixed here, and not takeable: rock shelf, rubble, pool, ledge. Lying here: bone, candle stub, shard of pottery, coin. You are carrying: nothing.", + "is_opening": 0, + "typed": "move The Upper Floor", + "resolved_action": "move", + "acted_on_type": "Location", + "acted_on_id": 1000000002, + "resolved_by": "grammar", + "engine_fallback": 1, + "engine_fact": "You are unhurt.\nNobody else is alive here.\nFixed here, and not takeable: rock shelf, rubble, pool, ledge.\nLying here: bone, candle stub, shard of pottery, coin.\nYou are carrying: nothing." + }, + { + "id": 1000000003, + "story_id": 1000000001, + "location_id": 1000000005, + "previous_scene_id": 1000000002, + "description": "You arrive at The Upper Floor room 2. You are unhurt. Also here: Brask the Stairwarden. Nobody else is alive here. Fixed here, and not takeable: rock shelf, rubble, stalagmite, pool, crack. Lying here: pebble, bone, rusted hinge, coin. You are carrying: nothing.", + "story_timestamp": "2026-10-30 08:02:30", + "summary": "You arrive at The Upper Floor room 2. You are unhurt. Also here: Brask the Stairwarden. Nobody else is alive here. Fixed here, and not takeable: rock shelf, rubble, stalagmite, pool, crack. Lying here: pebble, bone, rusted hinge, coin. You are carrying: nothing.", + "is_opening": 0, + "typed": "move The Upper Floor room 2", + "resolved_action": "move", + "acted_on_type": "Location", + "acted_on_id": 1000000005, + "resolved_by": "grammar", + "engine_fallback": 1, + "engine_fact": "You are unhurt.\nAlso here: Brask the Stairwarden. Nobody else is alive here.\nFixed here, and not takeable: rock shelf, rubble, stalagmite, pool, crack.\nLying here: pebble, bone, rusted hinge, coin.\nYou are carrying: nothing." + }, + { + "id": 1000000004, + "story_id": 1000000001, + "location_id": 1000000005, + "previous_scene_id": 1000000003, + "description": "The fight in The Upper Floor room 2 is over after 1 round. Brask the Stairwarden is dead. 1 blow landed: Odile hit Brask the Stairwarden for 7 (round 1); Brask the Stairwarden is dead.", + "story_timestamp": "2026-10-30 08:07:30", + "summary": "A fight in The Upper Floor room 2: 1 blow over 1 round. Brask the Stairwarden is dead.", + "is_opening": 0, + "typed": null, + "resolved_action": "attack", + "acted_on_type": "Character", + "acted_on_id": 1000000002, + "resolved_by": null, + "engine_fallback": 0, + "engine_fact": null + }, + { + "id": 1000000005, + "story_id": 1000000001, + "location_id": 1000000007, + "previous_scene_id": 1000000004, + "description": "You arrive at The Lower Floor room 1. You are unhurt. Nobody else is alive here. Fixed here, and not takeable: rock shelf, rubble, pool. Lying here: candle stub, bone, shard of pottery. You are carrying: stair key.", + "story_timestamp": "2026-10-30 08:09:00", + "summary": "You arrive at The Lower Floor room 1. You are unhurt. Nobody else is alive here. Fixed here, and not takeable: rock shelf, rubble, pool. Lying here: candle stub, bone, shard of pottery. You are carrying: stair key.", + "is_opening": 0, + "typed": "move The Lower Floor", + "resolved_action": "move", + "acted_on_type": "Location", + "acted_on_id": 1000000003, + "resolved_by": "grammar", + "engine_fallback": 1, + "engine_fact": "You are unhurt.\nNobody else is alive here.\nFixed here, and not takeable: rock shelf, rubble, pool.\nLying here: candle stub, bone, shard of pottery.\nYou are carrying: stair key." + } + ], + "removed": [] + }, + "characters_scenes": { + "rows": [ + { + "character_id": 1000000001, + "scene_id": 1000000002 + }, + { + "character_id": 1000000001, + "scene_id": 1000000003 + }, + { + "character_id": 1000000002, + "scene_id": 1000000003 + }, + { + "character_id": 1000000001, + "scene_id": 1000000004 + }, + { + "character_id": 1000000001, + "scene_id": 1000000005 + } + ], + "removed": [] + }, + "interactions": { + "rows": [], + "removed": [] + }, + "playthrough_endings": { + "rows": [], + "removed": [] + }, + "playthrough_beats": { + "rows": [], + "removed": [] + }, + "playthrough_npc_states": { + "rows": [ + { + "id": 1000000001, + "playthrough_id": 1000000001, + "character_id": 1000000002, + "location_id": 1000000005, + "following": 0, + "ceasefire": 0, + "peace_after_blow_id": 0, + "noticed_at": null + } + ], + "removed": [] + }, + "world_events": { + "rows": [], + "removed": [] + } + } + }, + { + "step": "step 8 a-second-game-walks-onto-the-laid-out-floor (second)", + "requests": [], + "written": { + "locations": { + "rows": [ + { + "id": 1000000002, + "story_id": 1000000001, + "name": "The Upper Floor", + "description": "A level of black stone passages lit by nothing you can see.", + "lore": "The first floor was cut by people nobody remembers.", + "last_protagonist_visit": null, + "parent_location_id": null, + "detail_level": "realized", + "teaser": "The first level of the dungeon, with a stair somewhere that goes further down.", + "mobile": 0, + "danger": "safe", + "hazard": null, + "hazard_die": null, + "x": null, + "y": null, + "z": null, + "width": 12, + "depth": 12, + "population": null, + "generation_checkpoint": null, + "surface": null, + "kind": "warren", + "density": null + }, + { + "id": 1000000003, + "story_id": 1000000001, + "name": "The Lower Floor", + "description": "A colder level, the air still and the dark close.", + "lore": "Few delvers come this far.", + "last_protagonist_visit": null, + "parent_location_id": null, + "detail_level": "realized", + "teaser": "The second level of the dungeon, colder and quieter than the first.", + "mobile": 0, + "danger": "safe", + "hazard": null, + "hazard_die": null, + "x": null, + "y": null, + "z": null, + "width": 12, + "depth": 6, + "population": null, + "generation_checkpoint": null, + "surface": null, + "kind": "warren", + "density": null + }, + { + "id": 1000000004, + "story_id": 1000000001, + "name": "The Upper Floor room 1", + "description": "A low chamber at the foot of the stair.", + "lore": "Delvers rest here before going on.", + "last_protagonist_visit": "2026-10-30 08:01:30", + "parent_location_id": 1000000002, + "detail_level": "realized", + "teaser": "A room inside The Upper Floor, 9 by 5 paces on storey 0.", + "mobile": 0, + "danger": "uneasy", + "hazard": null, + "hazard_die": null, + "x": 0, + "y": 0, + "z": 0, + "width": 9, + "depth": 5, + "population": null, + "generation_checkpoint": null, + "surface": null, + "kind": "cave", + "density": null + }, + { + "id": 1000000005, + "story_id": 1000000001, + "name": "The Upper Floor room 2", + "description": "A bare chamber with a stair cut down into its floor.", + "lore": "The stair down is older than the rest.", + "last_protagonist_visit": "2026-10-30 08:07:30", + "parent_location_id": 1000000002, + "detail_level": "realized", + "teaser": "A room inside The Upper Floor, 3 by 5 paces on storey 0.", + "mobile": 0, + "danger": "dangerous", + "hazard": null, + "hazard_die": null, + "x": 9, + "y": 0, + "z": 0, + "width": 3, + "depth": 5, + "population": null, + "generation_checkpoint": null, + "surface": null, + "kind": "cave", + "density": null + }, + { + "id": 1000000006, + "story_id": 1000000001, + "name": "The Upper Floor room 3", + "description": null, + "lore": null, + "last_protagonist_visit": null, + "parent_location_id": 1000000002, + "detail_level": "stub", + "teaser": "A room inside The Upper Floor, 3 by 7 paces on storey 0.", + "mobile": 0, + "danger": "uneasy", + "hazard": null, + "hazard_die": null, + "x": 9, + "y": 5, + "z": 0, + "width": 3, + "depth": 7, + "population": null, + "generation_checkpoint": null, + "surface": null, + "kind": "cave", + "density": null + }, + { + "id": 1000000007, + "story_id": 1000000001, + "name": "The Lower Floor room 1", + "description": "The foot of the stair, cold and unlit.", + "lore": "The stair comes down here from the floor above.", + "last_protagonist_visit": "2026-10-30 08:09:00", + "parent_location_id": 1000000003, + "detail_level": "realized", + "teaser": "A room inside The Lower Floor, 9 by 6 paces on storey 0.", + "mobile": 0, + "danger": "uneasy", + "hazard": null, + "hazard_die": null, + "x": 0, + "y": 0, + "z": 0, + "width": 9, + "depth": 6, + "population": null, + "generation_checkpoint": null, + "surface": null, + "kind": "cave", + "density": null + }, + { + "id": 1000000008, + "story_id": 1000000001, + "name": "The Lower Floor room 2", + "description": null, + "lore": null, + "last_protagonist_visit": null, + "parent_location_id": 1000000003, + "detail_level": "stub", + "teaser": "A room inside The Lower Floor, 3 by 6 paces on storey 0.", + "mobile": 0, + "danger": "dangerous", + "hazard": null, + "hazard_die": null, + "x": 9, + "y": 0, + "z": 0, + "width": 3, + "depth": 6, + "population": null, + "generation_checkpoint": null, + "surface": null, + "kind": "cave", + "density": null + } + ], + "removed": [] + }, + "location_connections": { + "rows": [ + { + "id": 1000000005, + "location_id": 1000000004, + "connected_location_id": 1000000005, + "distance": "adjacent", + "time_to_travel": "about a minute", + "travel_method": "walking", + "hazard": null, + "hazard_die": null, + "barrier": "open", + "key_template_id": null + }, + { + "id": 1000000006, + "location_id": 1000000005, + "connected_location_id": 1000000004, + "distance": "adjacent", + "time_to_travel": "about a minute", + "travel_method": "walking", + "hazard": null, + "hazard_die": null, + "barrier": "open", + "key_template_id": null + }, + { + "id": 1000000007, + "location_id": 1000000005, + "connected_location_id": 1000000006, + "distance": "adjacent", + "time_to_travel": "about a minute", + "travel_method": "walking", + "hazard": null, + "hazard_die": null, + "barrier": "open", + "key_template_id": null + }, + { + "id": 1000000008, + "location_id": 1000000006, + "connected_location_id": 1000000005, + "distance": "adjacent", + "time_to_travel": "about a minute", + "travel_method": "walking", + "hazard": null, + "hazard_die": null, + "barrier": "open", + "key_template_id": null + }, + { + "id": 1000000009, + "location_id": 1000000004, + "connected_location_id": 1000000001, + "distance": "adjacent", + "time_to_travel": "about a minute", + "travel_method": "taking stairs", + "hazard": null, + "hazard_die": null, + "barrier": "open", + "key_template_id": null + }, + { + "id": 1000000010, + "location_id": 1000000001, + "connected_location_id": 1000000004, + "distance": "adjacent", + "time_to_travel": "about a minute", + "travel_method": "taking stairs", + "hazard": null, + "hazard_die": null, + "barrier": "open", + "key_template_id": null + }, + { + "id": 1000000013, + "location_id": 1000000007, + "connected_location_id": 1000000008, + "distance": "adjacent", + "time_to_travel": "about a minute", + "travel_method": "walking", + "hazard": null, + "hazard_die": null, + "barrier": "open", + "key_template_id": null + }, + { + "id": 1000000014, + "location_id": 1000000008, + "connected_location_id": 1000000007, + "distance": "adjacent", + "time_to_travel": "about a minute", + "travel_method": "walking", + "hazard": null, + "hazard_die": null, + "barrier": "open", + "key_template_id": null + }, + { + "id": 1000000015, + "location_id": 1000000007, + "connected_location_id": 1000000005, + "distance": "adjacent", + "time_to_travel": "about a minute", + "travel_method": "taking stairs", + "hazard": null, + "hazard_die": null, + "barrier": "keyed", + "key_template_id": 1000000001 + }, + { + "id": 1000000016, + "location_id": 1000000005, + "connected_location_id": 1000000007, + "distance": "adjacent", + "time_to_travel": "about a minute", + "travel_method": "taking stairs", + "hazard": null, + "hazard_die": null, + "barrier": "keyed", + "key_template_id": 1000000001 + } + ], + "removed": [ + 1000000001, + 1000000002, + 1000000003, + 1000000004 + ] + }, + "characters": { + "rows": [ + { + "id": 1000000002, + "story_id": 1000000001, + "fullname": "Brask the Stairwarden", + "nickname": "Brask", + "age": 90, + "sex": "male", + "personality": "Silent and patient.", + "appearance": "A grey, stooped thing with a long iron key on a cord.", + "likes": "Stillness.", + "dislikes": "Footsteps on the stair.", + "fears": "Nothing.", + "backstory": "He has kept the stair down for longer than the delvers have come.", + "is_companion": 0, + "race_id": 1000000002, + "is_protagonist": 0, + "location_id": 1000000005, + "deliberately_absent": 0, + "level": 1, + "hit_die": 6, + "strength": 8, + "dexterity": 8, + "will": 8, + "hostile": 1, + "x": 9, + "y": 3, + "conscious_desire": null, + "unconscious_desire": null, + "recognized_need": null, + "unrecognized_need": null, + "desire_pursuit": null, + "need_pursuit": null, + "posted_to_id": 1000000002 + } + ], + "removed": [] + }, + "items": { + "rows": [ + { + "id": 1000000002, + "character_id": null, + "name": "rock shelf", + "description": "The rock shelf, fixed in place.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "immovable", + "x": 4, + "y": 0, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "top", + "within_id": null, + "how": null, + "kit_key": "cave/rock shelf", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "stone", + "wear": null, + "worn": 0 + }, + { + "id": 1000000003, + "character_id": null, + "name": "rubble", + "description": "The rubble, fixed in place.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "immovable", + "x": 1, + "y": 3, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "hollow", + "within_id": null, + "how": null, + "kit_key": "cave/rubble", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000004, + "character_id": null, + "name": "pool", + "description": "The pool, fixed in place.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "immovable", + "x": 0, + "y": 4, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "nothing", + "within_id": null, + "how": null, + "kit_key": "cave/pool", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000005, + "character_id": null, + "name": "ledge", + "description": "The ledge, fixed in place.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "immovable", + "x": 7, + "y": 3, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "top", + "within_id": null, + "how": null, + "kit_key": "cave/ledge", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000006, + "character_id": null, + "name": "bone", + "description": "The bone, on the rock shelf.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000002, + "how": "on", + "kit_key": "cave/rock shelf/bone", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000007, + "character_id": null, + "name": "candle stub", + "description": "The candle stub, on the rock shelf.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 1, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000002, + "how": "on", + "kit_key": "cave/rock shelf/candle stub", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000008, + "character_id": null, + "name": "shard of pottery", + "description": "The shard of pottery, in the rubble.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000003, + "how": "in", + "kit_key": "cave/rubble/shard of pottery", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000009, + "character_id": null, + "name": "coin", + "description": "The coin, in the rubble.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000003, + "how": "in", + "kit_key": "cave/rubble/coin", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "metal", + "wear": null, + "worn": 0 + }, + { + "id": 1000000010, + "character_id": null, + "name": "rock shelf", + "description": "The rock shelf, fixed in place.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000002, + "bulk": "immovable", + "x": 4, + "y": 0, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "top", + "within_id": null, + "how": null, + "kit_key": "cave/rock shelf", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "stone", + "wear": null, + "worn": 0 + }, + { + "id": 1000000011, + "character_id": null, + "name": "rubble", + "description": "The rubble, fixed in place.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000003, + "bulk": "immovable", + "x": 1, + "y": 3, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "hollow", + "within_id": null, + "how": null, + "kit_key": "cave/rubble", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000012, + "character_id": null, + "name": "pool", + "description": "The pool, fixed in place.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000004, + "bulk": "immovable", + "x": 0, + "y": 4, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "nothing", + "within_id": null, + "how": null, + "kit_key": "cave/pool", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000013, + "character_id": null, + "name": "ledge", + "description": "The ledge, fixed in place.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000005, + "bulk": "immovable", + "x": 7, + "y": 3, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "top", + "within_id": null, + "how": null, + "kit_key": "cave/ledge", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000014, + "character_id": null, + "name": "bone", + "description": "The bone, on the rock shelf.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000006, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000010, + "how": "on", + "kit_key": "cave/rock shelf/bone", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000015, + "character_id": null, + "name": "candle stub", + "description": "The candle stub, on the rock shelf.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000007, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 1, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000010, + "how": "on", + "kit_key": "cave/rock shelf/candle stub", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000016, + "character_id": null, + "name": "shard of pottery", + "description": "The shard of pottery, in the rubble.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000008, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000011, + "how": "in", + "kit_key": "cave/rubble/shard of pottery", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000017, + "character_id": null, + "name": "coin", + "description": "The coin, in the rubble.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000009, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000011, + "how": "in", + "kit_key": "cave/rubble/coin", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "metal", + "wear": null, + "worn": 0 + }, + { + "id": 1000000018, + "character_id": null, + "name": "rock shelf", + "description": "The rock shelf, fixed in place.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "immovable", + "x": 9, + "y": 3, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "top", + "within_id": null, + "how": null, + "kit_key": "cave/rock shelf", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "stone", + "wear": null, + "worn": 0 + }, + { + "id": 1000000019, + "character_id": null, + "name": "rubble", + "description": "The rubble, fixed in place.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "immovable", + "x": 10, + "y": 1, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "hollow", + "within_id": null, + "how": null, + "kit_key": "cave/rubble", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000020, + "character_id": null, + "name": "stalagmite", + "description": "The stalagmite, fixed in place.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "immovable", + "x": 11, + "y": 0, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "nothing", + "within_id": null, + "how": null, + "kit_key": "cave/stalagmite", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "stone", + "wear": null, + "worn": 0 + }, + { + "id": 1000000021, + "character_id": null, + "name": "pool", + "description": "The pool, fixed in place.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "immovable", + "x": 10, + "y": 1, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "nothing", + "within_id": null, + "how": null, + "kit_key": "cave/pool", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000022, + "character_id": null, + "name": "crack", + "description": "The crack, fixed in place.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "immovable", + "x": 10, + "y": 2, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "closed", + "within_id": null, + "how": null, + "kit_key": "cave/crack", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000023, + "character_id": null, + "name": "pebble", + "description": "The pebble, on the rock shelf.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000018, + "how": "on", + "kit_key": "cave/rock shelf/pebble", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "stone", + "wear": null, + "worn": 0 + }, + { + "id": 1000000024, + "character_id": null, + "name": "bone", + "description": "The bone, on the rock shelf.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000018, + "how": "on", + "kit_key": "cave/rock shelf/bone", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000025, + "character_id": null, + "name": "rusted hinge", + "description": "The rusted hinge, in the rubble.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000019, + "how": "in", + "kit_key": "cave/rubble/rusted hinge", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "metal", + "wear": null, + "worn": 0 + }, + { + "id": 1000000026, + "character_id": null, + "name": "coin", + "description": "The coin, in the rubble.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000019, + "how": "in", + "kit_key": "cave/rubble/coin", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "metal", + "wear": null, + "worn": 0 + }, + { + "id": 1000000027, + "character_id": null, + "name": "rock shelf", + "description": "The rock shelf, fixed in place.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000018, + "bulk": "immovable", + "x": 9, + "y": 3, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "top", + "within_id": null, + "how": null, + "kit_key": "cave/rock shelf", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "stone", + "wear": null, + "worn": 0 + }, + { + "id": 1000000028, + "character_id": null, + "name": "rubble", + "description": "The rubble, fixed in place.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000019, + "bulk": "immovable", + "x": 10, + "y": 1, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "hollow", + "within_id": null, + "how": null, + "kit_key": "cave/rubble", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000029, + "character_id": null, + "name": "stalagmite", + "description": "The stalagmite, fixed in place.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000020, + "bulk": "immovable", + "x": 11, + "y": 0, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "nothing", + "within_id": null, + "how": null, + "kit_key": "cave/stalagmite", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "stone", + "wear": null, + "worn": 0 + }, + { + "id": 1000000030, + "character_id": null, + "name": "pool", + "description": "The pool, fixed in place.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000021, + "bulk": "immovable", + "x": 10, + "y": 1, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "nothing", + "within_id": null, + "how": null, + "kit_key": "cave/pool", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000031, + "character_id": null, + "name": "crack", + "description": "The crack, fixed in place.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000022, + "bulk": "immovable", + "x": 10, + "y": 2, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "closed", + "within_id": null, + "how": null, + "kit_key": "cave/crack", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000032, + "character_id": null, + "name": "pebble", + "description": "The pebble, on the rock shelf.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000023, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000027, + "how": "on", + "kit_key": "cave/rock shelf/pebble", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "stone", + "wear": null, + "worn": 0 + }, + { + "id": 1000000033, + "character_id": null, + "name": "bone", + "description": "The bone, on the rock shelf.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000024, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000027, + "how": "on", + "kit_key": "cave/rock shelf/bone", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000034, + "character_id": null, + "name": "rusted hinge", + "description": "The rusted hinge, in the rubble.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000025, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000028, + "how": "in", + "kit_key": "cave/rubble/rusted hinge", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "metal", + "wear": null, + "worn": 0 + }, + { + "id": 1000000035, + "character_id": null, + "name": "coin", + "description": "The coin, in the rubble.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000026, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000028, + "how": "in", + "kit_key": "cave/rubble/coin", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "metal", + "wear": null, + "worn": 0 + }, + { + "id": 1000000036, + "character_id": null, + "name": "stair key", + "description": "A long iron key on a cord, cold to hold.", + "properties": "{}", + "location_id": null, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000001, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "key", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": null, + "how": null, + "kit_key": null, + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000037, + "character_id": null, + "name": "rock shelf", + "description": "The rock shelf, fixed in place.", + "properties": null, + "location_id": 1000000007, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "immovable", + "x": 5, + "y": 4, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "top", + "within_id": null, + "how": null, + "kit_key": "cave/rock shelf", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "stone", + "wear": null, + "worn": 0 + }, + { + "id": 1000000038, + "character_id": null, + "name": "rubble", + "description": "The rubble, fixed in place.", + "properties": null, + "location_id": 1000000007, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "immovable", + "x": 1, + "y": 4, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "hollow", + "within_id": null, + "how": null, + "kit_key": "cave/rubble", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000039, + "character_id": null, + "name": "pool", + "description": "The pool, fixed in place.", + "properties": null, + "location_id": 1000000007, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "immovable", + "x": 0, + "y": 4, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "nothing", + "within_id": null, + "how": null, + "kit_key": "cave/pool", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000040, + "character_id": null, + "name": "candle stub", + "description": "The candle stub, on the rock shelf.", + "properties": null, + "location_id": 1000000007, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 1, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000037, + "how": "on", + "kit_key": "cave/rock shelf/candle stub", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000041, + "character_id": null, + "name": "bone", + "description": "The bone, on the rock shelf.", + "properties": null, + "location_id": 1000000007, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000037, + "how": "on", + "kit_key": "cave/rock shelf/bone", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000042, + "character_id": null, + "name": "shard of pottery", + "description": "The shard of pottery, in the rubble.", + "properties": null, + "location_id": 1000000007, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000038, + "how": "in", + "kit_key": "cave/rubble/shard of pottery", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000043, + "character_id": null, + "name": "rock shelf", + "description": "The rock shelf, fixed in place.", + "properties": null, + "location_id": 1000000007, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000037, + "bulk": "immovable", + "x": 5, + "y": 4, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "top", + "within_id": null, + "how": null, + "kit_key": "cave/rock shelf", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "stone", + "wear": null, + "worn": 0 + }, + { + "id": 1000000044, + "character_id": null, + "name": "rubble", + "description": "The rubble, fixed in place.", + "properties": null, + "location_id": 1000000007, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000038, + "bulk": "immovable", + "x": 1, + "y": 4, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "hollow", + "within_id": null, + "how": null, + "kit_key": "cave/rubble", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000045, + "character_id": null, + "name": "pool", + "description": "The pool, fixed in place.", + "properties": null, + "location_id": 1000000007, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000039, + "bulk": "immovable", + "x": 0, + "y": 4, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "nothing", + "within_id": null, + "how": null, + "kit_key": "cave/pool", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000046, + "character_id": null, + "name": "candle stub", + "description": "The candle stub, on the rock shelf.", + "properties": null, + "location_id": 1000000007, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000040, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 1, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000043, + "how": "on", + "kit_key": "cave/rock shelf/candle stub", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000047, + "character_id": null, + "name": "bone", + "description": "The bone, on the rock shelf.", + "properties": null, + "location_id": 1000000007, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000041, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000043, + "how": "on", + "kit_key": "cave/rock shelf/bone", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000048, + "character_id": null, + "name": "shard of pottery", + "description": "The shard of pottery, in the rubble.", + "properties": null, + "location_id": 1000000007, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000042, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000044, + "how": "in", + "kit_key": "cave/rubble/shard of pottery", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000049, + "character_id": null, + "name": "rock shelf", + "description": "The rock shelf, fixed in place.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": 1000000002, + "readable": 0, + "inscription": null, + "template_id": 1000000002, + "bulk": "immovable", + "x": 4, + "y": 0, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "top", + "within_id": null, + "how": null, + "kit_key": "cave/rock shelf", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "stone", + "wear": null, + "worn": 0 + }, + { + "id": 1000000050, + "character_id": null, + "name": "rubble", + "description": "The rubble, fixed in place.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": 1000000002, + "readable": 0, + "inscription": null, + "template_id": 1000000003, + "bulk": "immovable", + "x": 1, + "y": 3, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "hollow", + "within_id": null, + "how": null, + "kit_key": "cave/rubble", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000051, + "character_id": null, + "name": "pool", + "description": "The pool, fixed in place.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": 1000000002, + "readable": 0, + "inscription": null, + "template_id": 1000000004, + "bulk": "immovable", + "x": 0, + "y": 4, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "nothing", + "within_id": null, + "how": null, + "kit_key": "cave/pool", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000052, + "character_id": null, + "name": "ledge", + "description": "The ledge, fixed in place.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": 1000000002, + "readable": 0, + "inscription": null, + "template_id": 1000000005, + "bulk": "immovable", + "x": 7, + "y": 3, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "top", + "within_id": null, + "how": null, + "kit_key": "cave/ledge", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000053, + "character_id": null, + "name": "bone", + "description": "The bone, on the rock shelf.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": 1000000002, + "readable": 0, + "inscription": null, + "template_id": 1000000006, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000049, + "how": "on", + "kit_key": "cave/rock shelf/bone", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000054, + "character_id": null, + "name": "candle stub", + "description": "The candle stub, on the rock shelf.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": 1000000002, + "readable": 0, + "inscription": null, + "template_id": 1000000007, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 1, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000049, + "how": "on", + "kit_key": "cave/rock shelf/candle stub", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000055, + "character_id": null, + "name": "shard of pottery", + "description": "The shard of pottery, in the rubble.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": 1000000002, + "readable": 0, + "inscription": null, + "template_id": 1000000008, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000050, + "how": "in", + "kit_key": "cave/rubble/shard of pottery", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000056, + "character_id": null, + "name": "coin", + "description": "The coin, in the rubble.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": 1000000002, + "readable": 0, + "inscription": null, + "template_id": 1000000009, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000050, + "how": "in", + "kit_key": "cave/rubble/coin", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "metal", + "wear": null, + "worn": 0 + } + ], + "removed": [] + }, + "quest_steps": { + "rows": [], + "removed": [] + }, + "scenes": { + "rows": [ + { + "id": 1000000002, + "story_id": 1000000001, + "location_id": 1000000004, + "previous_scene_id": 1000000001, + "description": "You arrive at The Upper Floor room 1. You are unhurt. Nobody else is alive here. Fixed here, and not takeable: rock shelf, rubble, pool, ledge. Lying here: bone, candle stub, shard of pottery, coin. You are carrying: nothing.", + "story_timestamp": "2026-10-30 08:01:30", + "summary": "You arrive at The Upper Floor room 1. You are unhurt. Nobody else is alive here. Fixed here, and not takeable: rock shelf, rubble, pool, ledge. Lying here: bone, candle stub, shard of pottery, coin. You are carrying: nothing.", + "is_opening": 0, + "typed": "move The Upper Floor", + "resolved_action": "move", + "acted_on_type": "Location", + "acted_on_id": 1000000002, + "resolved_by": "grammar", + "engine_fallback": 1, + "engine_fact": "You are unhurt.\nNobody else is alive here.\nFixed here, and not takeable: rock shelf, rubble, pool, ledge.\nLying here: bone, candle stub, shard of pottery, coin.\nYou are carrying: nothing." + }, + { + "id": 1000000003, + "story_id": 1000000001, + "location_id": 1000000005, + "previous_scene_id": 1000000002, + "description": "You arrive at The Upper Floor room 2. You are unhurt. Also here: Brask the Stairwarden. Nobody else is alive here. Fixed here, and not takeable: rock shelf, rubble, stalagmite, pool, crack. Lying here: pebble, bone, rusted hinge, coin. You are carrying: nothing.", + "story_timestamp": "2026-10-30 08:02:30", + "summary": "You arrive at The Upper Floor room 2. You are unhurt. Also here: Brask the Stairwarden. Nobody else is alive here. Fixed here, and not takeable: rock shelf, rubble, stalagmite, pool, crack. Lying here: pebble, bone, rusted hinge, coin. You are carrying: nothing.", + "is_opening": 0, + "typed": "move The Upper Floor room 2", + "resolved_action": "move", + "acted_on_type": "Location", + "acted_on_id": 1000000005, + "resolved_by": "grammar", + "engine_fallback": 1, + "engine_fact": "You are unhurt.\nAlso here: Brask the Stairwarden. Nobody else is alive here.\nFixed here, and not takeable: rock shelf, rubble, stalagmite, pool, crack.\nLying here: pebble, bone, rusted hinge, coin.\nYou are carrying: nothing." + }, + { + "id": 1000000004, + "story_id": 1000000001, + "location_id": 1000000005, + "previous_scene_id": 1000000003, + "description": "The fight in The Upper Floor room 2 is over after 1 round. Brask the Stairwarden is dead. 1 blow landed: Odile hit Brask the Stairwarden for 7 (round 1); Brask the Stairwarden is dead.", + "story_timestamp": "2026-10-30 08:07:30", + "summary": "A fight in The Upper Floor room 2: 1 blow over 1 round. Brask the Stairwarden is dead.", + "is_opening": 0, + "typed": null, + "resolved_action": "attack", + "acted_on_type": "Character", + "acted_on_id": 1000000002, + "resolved_by": null, + "engine_fallback": 0, + "engine_fact": null + }, + { + "id": 1000000005, + "story_id": 1000000001, + "location_id": 1000000007, + "previous_scene_id": 1000000004, + "description": "You arrive at The Lower Floor room 1. You are unhurt. Nobody else is alive here. Fixed here, and not takeable: rock shelf, rubble, pool. Lying here: candle stub, bone, shard of pottery. You are carrying: stair key.", + "story_timestamp": "2026-10-30 08:09:00", + "summary": "You arrive at The Lower Floor room 1. You are unhurt. Nobody else is alive here. Fixed here, and not takeable: rock shelf, rubble, pool. Lying here: candle stub, bone, shard of pottery. You are carrying: stair key.", + "is_opening": 0, + "typed": "move The Lower Floor", + "resolved_action": "move", + "acted_on_type": "Location", + "acted_on_id": 1000000003, + "resolved_by": "grammar", + "engine_fallback": 1, + "engine_fact": "You are unhurt.\nNobody else is alive here.\nFixed here, and not takeable: rock shelf, rubble, pool.\nLying here: candle stub, bone, shard of pottery.\nYou are carrying: stair key." + } + ], + "removed": [] + }, + "characters_scenes": { + "rows": [ + { + "character_id": 1000000001, + "scene_id": 1000000002 + }, + { + "character_id": 1000000001, + "scene_id": 1000000003 + }, + { + "character_id": 1000000002, + "scene_id": 1000000003 + }, + { + "character_id": 1000000001, + "scene_id": 1000000004 + }, + { + "character_id": 1000000001, + "scene_id": 1000000005 + } + ], + "removed": [] + }, + "interactions": { + "rows": [], + "removed": [] + }, + "playthrough_endings": { + "rows": [], + "removed": [] + }, + "playthrough_beats": { + "rows": [], + "removed": [] + }, + "playthrough_npc_states": { + "rows": [ + { + "id": 1000000001, + "playthrough_id": 1000000001, + "character_id": 1000000002, + "location_id": 1000000005, + "following": 0, + "ceasefire": 0, + "peace_after_blow_id": 0, + "noticed_at": null + } + ], + "removed": [] + }, + "world_events": { + "rows": [], + "removed": [] + } + } + }, + { + "step": "step 9 and-finds-the-guard-where-the-layout-stood-him (second)", + "requests": [], + "written": { + "locations": { + "rows": [ + { + "id": 1000000002, + "story_id": 1000000001, + "name": "The Upper Floor", + "description": "A level of black stone passages lit by nothing you can see.", + "lore": "The first floor was cut by people nobody remembers.", + "last_protagonist_visit": null, + "parent_location_id": null, + "detail_level": "realized", + "teaser": "The first level of the dungeon, with a stair somewhere that goes further down.", + "mobile": 0, + "danger": "safe", + "hazard": null, + "hazard_die": null, + "x": null, + "y": null, + "z": null, + "width": 12, + "depth": 12, + "population": null, + "generation_checkpoint": null, + "surface": null, + "kind": "warren", + "density": null + }, + { + "id": 1000000003, + "story_id": 1000000001, + "name": "The Lower Floor", + "description": "A colder level, the air still and the dark close.", + "lore": "Few delvers come this far.", + "last_protagonist_visit": null, + "parent_location_id": null, + "detail_level": "realized", + "teaser": "The second level of the dungeon, colder and quieter than the first.", + "mobile": 0, + "danger": "safe", + "hazard": null, + "hazard_die": null, + "x": null, + "y": null, + "z": null, + "width": 12, + "depth": 6, + "population": null, + "generation_checkpoint": null, + "surface": null, + "kind": "warren", + "density": null + }, + { + "id": 1000000004, + "story_id": 1000000001, + "name": "The Upper Floor room 1", + "description": "A low chamber at the foot of the stair.", + "lore": "Delvers rest here before going on.", + "last_protagonist_visit": "2026-10-30 08:01:30", + "parent_location_id": 1000000002, + "detail_level": "realized", + "teaser": "A room inside The Upper Floor, 9 by 5 paces on storey 0.", + "mobile": 0, + "danger": "uneasy", + "hazard": null, + "hazard_die": null, + "x": 0, + "y": 0, + "z": 0, + "width": 9, + "depth": 5, + "population": null, + "generation_checkpoint": null, + "surface": null, + "kind": "cave", + "density": null + }, + { + "id": 1000000005, + "story_id": 1000000001, + "name": "The Upper Floor room 2", + "description": "A bare chamber with a stair cut down into its floor.", + "lore": "The stair down is older than the rest.", + "last_protagonist_visit": "2026-10-30 08:07:30", + "parent_location_id": 1000000002, + "detail_level": "realized", + "teaser": "A room inside The Upper Floor, 3 by 5 paces on storey 0.", + "mobile": 0, + "danger": "dangerous", + "hazard": null, + "hazard_die": null, + "x": 9, + "y": 0, + "z": 0, + "width": 3, + "depth": 5, + "population": null, + "generation_checkpoint": null, + "surface": null, + "kind": "cave", + "density": null + }, + { + "id": 1000000006, + "story_id": 1000000001, + "name": "The Upper Floor room 3", + "description": null, + "lore": null, + "last_protagonist_visit": null, + "parent_location_id": 1000000002, + "detail_level": "stub", + "teaser": "A room inside The Upper Floor, 3 by 7 paces on storey 0.", + "mobile": 0, + "danger": "uneasy", + "hazard": null, + "hazard_die": null, + "x": 9, + "y": 5, + "z": 0, + "width": 3, + "depth": 7, + "population": null, + "generation_checkpoint": null, + "surface": null, + "kind": "cave", + "density": null + }, + { + "id": 1000000007, + "story_id": 1000000001, + "name": "The Lower Floor room 1", + "description": "The foot of the stair, cold and unlit.", + "lore": "The stair comes down here from the floor above.", + "last_protagonist_visit": "2026-10-30 08:09:00", + "parent_location_id": 1000000003, + "detail_level": "realized", + "teaser": "A room inside The Lower Floor, 9 by 6 paces on storey 0.", + "mobile": 0, + "danger": "uneasy", + "hazard": null, + "hazard_die": null, + "x": 0, + "y": 0, + "z": 0, + "width": 9, + "depth": 6, + "population": null, + "generation_checkpoint": null, + "surface": null, + "kind": "cave", + "density": null + }, + { + "id": 1000000008, + "story_id": 1000000001, + "name": "The Lower Floor room 2", + "description": null, + "lore": null, + "last_protagonist_visit": null, + "parent_location_id": 1000000003, + "detail_level": "stub", + "teaser": "A room inside The Lower Floor, 3 by 6 paces on storey 0.", + "mobile": 0, + "danger": "dangerous", + "hazard": null, + "hazard_die": null, + "x": 9, + "y": 0, + "z": 0, + "width": 3, + "depth": 6, + "population": null, + "generation_checkpoint": null, + "surface": null, + "kind": "cave", + "density": null + } + ], + "removed": [] + }, + "location_connections": { + "rows": [ + { + "id": 1000000005, + "location_id": 1000000004, + "connected_location_id": 1000000005, + "distance": "adjacent", + "time_to_travel": "about a minute", + "travel_method": "walking", + "hazard": null, + "hazard_die": null, + "barrier": "open", + "key_template_id": null + }, + { + "id": 1000000006, + "location_id": 1000000005, + "connected_location_id": 1000000004, + "distance": "adjacent", + "time_to_travel": "about a minute", + "travel_method": "walking", + "hazard": null, + "hazard_die": null, + "barrier": "open", + "key_template_id": null + }, + { + "id": 1000000007, + "location_id": 1000000005, + "connected_location_id": 1000000006, + "distance": "adjacent", + "time_to_travel": "about a minute", + "travel_method": "walking", + "hazard": null, + "hazard_die": null, + "barrier": "open", + "key_template_id": null + }, + { + "id": 1000000008, + "location_id": 1000000006, + "connected_location_id": 1000000005, + "distance": "adjacent", + "time_to_travel": "about a minute", + "travel_method": "walking", + "hazard": null, + "hazard_die": null, + "barrier": "open", + "key_template_id": null + }, + { + "id": 1000000009, + "location_id": 1000000004, + "connected_location_id": 1000000001, + "distance": "adjacent", + "time_to_travel": "about a minute", + "travel_method": "taking stairs", + "hazard": null, + "hazard_die": null, + "barrier": "open", + "key_template_id": null + }, + { + "id": 1000000010, + "location_id": 1000000001, + "connected_location_id": 1000000004, + "distance": "adjacent", + "time_to_travel": "about a minute", + "travel_method": "taking stairs", + "hazard": null, + "hazard_die": null, + "barrier": "open", + "key_template_id": null + }, + { + "id": 1000000013, + "location_id": 1000000007, + "connected_location_id": 1000000008, + "distance": "adjacent", + "time_to_travel": "about a minute", + "travel_method": "walking", + "hazard": null, + "hazard_die": null, + "barrier": "open", + "key_template_id": null + }, + { + "id": 1000000014, + "location_id": 1000000008, + "connected_location_id": 1000000007, + "distance": "adjacent", + "time_to_travel": "about a minute", + "travel_method": "walking", + "hazard": null, + "hazard_die": null, + "barrier": "open", + "key_template_id": null + }, + { + "id": 1000000015, + "location_id": 1000000007, + "connected_location_id": 1000000005, + "distance": "adjacent", + "time_to_travel": "about a minute", + "travel_method": "taking stairs", + "hazard": null, + "hazard_die": null, + "barrier": "keyed", + "key_template_id": 1000000001 + }, + { + "id": 1000000016, + "location_id": 1000000005, + "connected_location_id": 1000000007, + "distance": "adjacent", + "time_to_travel": "about a minute", + "travel_method": "taking stairs", + "hazard": null, + "hazard_die": null, + "barrier": "keyed", + "key_template_id": 1000000001 + } + ], + "removed": [ + 1000000001, + 1000000002, + 1000000003, + 1000000004 + ] + }, + "characters": { + "rows": [ + { + "id": 1000000002, + "story_id": 1000000001, + "fullname": "Brask the Stairwarden", + "nickname": "Brask", + "age": 90, + "sex": "male", + "personality": "Silent and patient.", + "appearance": "A grey, stooped thing with a long iron key on a cord.", + "likes": "Stillness.", + "dislikes": "Footsteps on the stair.", + "fears": "Nothing.", + "backstory": "He has kept the stair down for longer than the delvers have come.", + "is_companion": 0, + "race_id": 1000000002, + "is_protagonist": 0, + "location_id": 1000000005, + "deliberately_absent": 0, + "level": 1, + "hit_die": 6, + "strength": 8, + "dexterity": 8, + "will": 8, + "hostile": 1, + "x": 9, + "y": 3, + "conscious_desire": null, + "unconscious_desire": null, + "recognized_need": null, + "unrecognized_need": null, + "desire_pursuit": null, + "need_pursuit": null, + "posted_to_id": 1000000002 + } + ], + "removed": [] + }, + "items": { + "rows": [ + { + "id": 1000000002, + "character_id": null, + "name": "rock shelf", + "description": "The rock shelf, fixed in place.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "immovable", + "x": 4, + "y": 0, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "top", + "within_id": null, + "how": null, + "kit_key": "cave/rock shelf", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "stone", + "wear": null, + "worn": 0 + }, + { + "id": 1000000003, + "character_id": null, + "name": "rubble", + "description": "The rubble, fixed in place.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "immovable", + "x": 1, + "y": 3, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "hollow", + "within_id": null, + "how": null, + "kit_key": "cave/rubble", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000004, + "character_id": null, + "name": "pool", + "description": "The pool, fixed in place.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "immovable", + "x": 0, + "y": 4, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "nothing", + "within_id": null, + "how": null, + "kit_key": "cave/pool", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000005, + "character_id": null, + "name": "ledge", + "description": "The ledge, fixed in place.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "immovable", + "x": 7, + "y": 3, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "top", + "within_id": null, + "how": null, + "kit_key": "cave/ledge", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000006, + "character_id": null, + "name": "bone", + "description": "The bone, on the rock shelf.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000002, + "how": "on", + "kit_key": "cave/rock shelf/bone", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000007, + "character_id": null, + "name": "candle stub", + "description": "The candle stub, on the rock shelf.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 1, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000002, + "how": "on", + "kit_key": "cave/rock shelf/candle stub", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000008, + "character_id": null, + "name": "shard of pottery", + "description": "The shard of pottery, in the rubble.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000003, + "how": "in", + "kit_key": "cave/rubble/shard of pottery", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000009, + "character_id": null, + "name": "coin", + "description": "The coin, in the rubble.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000003, + "how": "in", + "kit_key": "cave/rubble/coin", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "metal", + "wear": null, + "worn": 0 + }, + { + "id": 1000000010, + "character_id": null, + "name": "rock shelf", + "description": "The rock shelf, fixed in place.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000002, + "bulk": "immovable", + "x": 4, + "y": 0, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "top", + "within_id": null, + "how": null, + "kit_key": "cave/rock shelf", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "stone", + "wear": null, + "worn": 0 + }, + { + "id": 1000000011, + "character_id": null, + "name": "rubble", + "description": "The rubble, fixed in place.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000003, + "bulk": "immovable", + "x": 1, + "y": 3, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "hollow", + "within_id": null, + "how": null, + "kit_key": "cave/rubble", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000012, + "character_id": null, + "name": "pool", + "description": "The pool, fixed in place.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000004, + "bulk": "immovable", + "x": 0, + "y": 4, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "nothing", + "within_id": null, + "how": null, + "kit_key": "cave/pool", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000013, + "character_id": null, + "name": "ledge", + "description": "The ledge, fixed in place.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000005, + "bulk": "immovable", + "x": 7, + "y": 3, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "top", + "within_id": null, + "how": null, + "kit_key": "cave/ledge", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000014, + "character_id": null, + "name": "bone", + "description": "The bone, on the rock shelf.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000006, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000010, + "how": "on", + "kit_key": "cave/rock shelf/bone", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000015, + "character_id": null, + "name": "candle stub", + "description": "The candle stub, on the rock shelf.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000007, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 1, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000010, + "how": "on", + "kit_key": "cave/rock shelf/candle stub", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000016, + "character_id": null, + "name": "shard of pottery", + "description": "The shard of pottery, in the rubble.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000008, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000011, + "how": "in", + "kit_key": "cave/rubble/shard of pottery", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000017, + "character_id": null, + "name": "coin", + "description": "The coin, in the rubble.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000009, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000011, + "how": "in", + "kit_key": "cave/rubble/coin", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "metal", + "wear": null, + "worn": 0 + }, + { + "id": 1000000018, + "character_id": null, + "name": "rock shelf", + "description": "The rock shelf, fixed in place.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "immovable", + "x": 9, + "y": 3, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "top", + "within_id": null, + "how": null, + "kit_key": "cave/rock shelf", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "stone", + "wear": null, + "worn": 0 + }, + { + "id": 1000000019, + "character_id": null, + "name": "rubble", + "description": "The rubble, fixed in place.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "immovable", + "x": 10, + "y": 1, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "hollow", + "within_id": null, + "how": null, + "kit_key": "cave/rubble", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000020, + "character_id": null, + "name": "stalagmite", + "description": "The stalagmite, fixed in place.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "immovable", + "x": 11, + "y": 0, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "nothing", + "within_id": null, + "how": null, + "kit_key": "cave/stalagmite", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "stone", + "wear": null, + "worn": 0 + }, + { + "id": 1000000021, + "character_id": null, + "name": "pool", + "description": "The pool, fixed in place.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "immovable", + "x": 10, + "y": 1, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "nothing", + "within_id": null, + "how": null, + "kit_key": "cave/pool", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000022, + "character_id": null, + "name": "crack", + "description": "The crack, fixed in place.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "immovable", + "x": 10, + "y": 2, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "closed", + "within_id": null, + "how": null, + "kit_key": "cave/crack", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000023, + "character_id": null, + "name": "pebble", + "description": "The pebble, on the rock shelf.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000018, + "how": "on", + "kit_key": "cave/rock shelf/pebble", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "stone", + "wear": null, + "worn": 0 + }, + { + "id": 1000000024, + "character_id": null, + "name": "bone", + "description": "The bone, on the rock shelf.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000018, + "how": "on", + "kit_key": "cave/rock shelf/bone", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000025, + "character_id": null, + "name": "rusted hinge", + "description": "The rusted hinge, in the rubble.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000019, + "how": "in", + "kit_key": "cave/rubble/rusted hinge", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "metal", + "wear": null, + "worn": 0 + }, + { + "id": 1000000026, + "character_id": null, + "name": "coin", + "description": "The coin, in the rubble.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000019, + "how": "in", + "kit_key": "cave/rubble/coin", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "metal", + "wear": null, + "worn": 0 + }, + { + "id": 1000000027, + "character_id": null, + "name": "rock shelf", + "description": "The rock shelf, fixed in place.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000018, + "bulk": "immovable", + "x": 9, + "y": 3, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "top", + "within_id": null, + "how": null, + "kit_key": "cave/rock shelf", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "stone", + "wear": null, + "worn": 0 + }, + { + "id": 1000000028, + "character_id": null, + "name": "rubble", + "description": "The rubble, fixed in place.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000019, + "bulk": "immovable", + "x": 10, + "y": 1, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "hollow", + "within_id": null, + "how": null, + "kit_key": "cave/rubble", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000029, + "character_id": null, + "name": "stalagmite", + "description": "The stalagmite, fixed in place.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000020, + "bulk": "immovable", + "x": 11, + "y": 0, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "nothing", + "within_id": null, + "how": null, + "kit_key": "cave/stalagmite", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "stone", + "wear": null, + "worn": 0 + }, + { + "id": 1000000030, + "character_id": null, + "name": "pool", + "description": "The pool, fixed in place.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000021, + "bulk": "immovable", + "x": 10, + "y": 1, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "nothing", + "within_id": null, + "how": null, + "kit_key": "cave/pool", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000031, + "character_id": null, + "name": "crack", + "description": "The crack, fixed in place.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000022, + "bulk": "immovable", + "x": 10, + "y": 2, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "closed", + "within_id": null, + "how": null, + "kit_key": "cave/crack", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000032, + "character_id": null, + "name": "pebble", + "description": "The pebble, on the rock shelf.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000023, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000027, + "how": "on", + "kit_key": "cave/rock shelf/pebble", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "stone", + "wear": null, + "worn": 0 + }, + { + "id": 1000000033, + "character_id": null, + "name": "bone", + "description": "The bone, on the rock shelf.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000024, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000027, + "how": "on", + "kit_key": "cave/rock shelf/bone", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000034, + "character_id": null, + "name": "rusted hinge", + "description": "The rusted hinge, in the rubble.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000025, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000028, + "how": "in", + "kit_key": "cave/rubble/rusted hinge", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "metal", + "wear": null, + "worn": 0 + }, + { + "id": 1000000035, + "character_id": null, + "name": "coin", + "description": "The coin, in the rubble.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000026, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000028, + "how": "in", + "kit_key": "cave/rubble/coin", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "metal", + "wear": null, + "worn": 0 + }, + { + "id": 1000000036, + "character_id": null, + "name": "stair key", + "description": "A long iron key on a cord, cold to hold.", + "properties": "{}", + "location_id": null, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000001, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "key", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": null, + "how": null, + "kit_key": null, + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000037, + "character_id": null, + "name": "rock shelf", + "description": "The rock shelf, fixed in place.", + "properties": null, + "location_id": 1000000007, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "immovable", + "x": 5, + "y": 4, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "top", + "within_id": null, + "how": null, + "kit_key": "cave/rock shelf", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "stone", + "wear": null, + "worn": 0 + }, + { + "id": 1000000038, + "character_id": null, + "name": "rubble", + "description": "The rubble, fixed in place.", + "properties": null, + "location_id": 1000000007, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "immovable", + "x": 1, + "y": 4, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "hollow", + "within_id": null, + "how": null, + "kit_key": "cave/rubble", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000039, + "character_id": null, + "name": "pool", + "description": "The pool, fixed in place.", + "properties": null, + "location_id": 1000000007, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "immovable", + "x": 0, + "y": 4, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "nothing", + "within_id": null, + "how": null, + "kit_key": "cave/pool", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000040, + "character_id": null, + "name": "candle stub", + "description": "The candle stub, on the rock shelf.", + "properties": null, + "location_id": 1000000007, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 1, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000037, + "how": "on", + "kit_key": "cave/rock shelf/candle stub", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000041, + "character_id": null, + "name": "bone", + "description": "The bone, on the rock shelf.", + "properties": null, + "location_id": 1000000007, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000037, + "how": "on", + "kit_key": "cave/rock shelf/bone", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000042, + "character_id": null, + "name": "shard of pottery", + "description": "The shard of pottery, in the rubble.", + "properties": null, + "location_id": 1000000007, + "playthrough_id": null, + "readable": 0, + "inscription": null, + "template_id": null, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000038, + "how": "in", + "kit_key": "cave/rubble/shard of pottery", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000043, + "character_id": null, + "name": "rock shelf", + "description": "The rock shelf, fixed in place.", + "properties": null, + "location_id": 1000000007, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000037, + "bulk": "immovable", + "x": 5, + "y": 4, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "top", + "within_id": null, + "how": null, + "kit_key": "cave/rock shelf", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "stone", + "wear": null, + "worn": 0 + }, + { + "id": 1000000044, + "character_id": null, + "name": "rubble", + "description": "The rubble, fixed in place.", + "properties": null, + "location_id": 1000000007, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000038, + "bulk": "immovable", + "x": 1, + "y": 4, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "hollow", + "within_id": null, + "how": null, + "kit_key": "cave/rubble", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000045, + "character_id": null, + "name": "pool", + "description": "The pool, fixed in place.", + "properties": null, + "location_id": 1000000007, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000039, + "bulk": "immovable", + "x": 0, + "y": 4, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "nothing", + "within_id": null, + "how": null, + "kit_key": "cave/pool", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000046, + "character_id": null, + "name": "candle stub", + "description": "The candle stub, on the rock shelf.", + "properties": null, + "location_id": 1000000007, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000040, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 1, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000043, + "how": "on", + "kit_key": "cave/rock shelf/candle stub", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000047, + "character_id": null, + "name": "bone", + "description": "The bone, on the rock shelf.", + "properties": null, + "location_id": 1000000007, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000041, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000043, + "how": "on", + "kit_key": "cave/rock shelf/bone", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000048, + "character_id": null, + "name": "shard of pottery", + "description": "The shard of pottery, in the rubble.", + "properties": null, + "location_id": 1000000007, + "playthrough_id": 1000000001, + "readable": 0, + "inscription": null, + "template_id": 1000000042, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000044, + "how": "in", + "kit_key": "cave/rubble/shard of pottery", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000049, + "character_id": null, + "name": "rock shelf", + "description": "The rock shelf, fixed in place.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": 1000000002, + "readable": 0, + "inscription": null, + "template_id": 1000000002, + "bulk": "immovable", + "x": 4, + "y": 0, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "top", + "within_id": null, + "how": null, + "kit_key": "cave/rock shelf", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "stone", + "wear": null, + "worn": 0 + }, + { + "id": 1000000050, + "character_id": null, + "name": "rubble", + "description": "The rubble, fixed in place.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": 1000000002, + "readable": 0, + "inscription": null, + "template_id": 1000000003, + "bulk": "immovable", + "x": 1, + "y": 3, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "hollow", + "within_id": null, + "how": null, + "kit_key": "cave/rubble", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000051, + "character_id": null, + "name": "pool", + "description": "The pool, fixed in place.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": 1000000002, + "readable": 0, + "inscription": null, + "template_id": 1000000004, + "bulk": "immovable", + "x": 0, + "y": 4, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "nothing", + "within_id": null, + "how": null, + "kit_key": "cave/pool", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000052, + "character_id": null, + "name": "ledge", + "description": "The ledge, fixed in place.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": 1000000002, + "readable": 0, + "inscription": null, + "template_id": 1000000005, + "bulk": "immovable", + "x": 7, + "y": 3, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "top", + "within_id": null, + "how": null, + "kit_key": "cave/ledge", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000053, + "character_id": null, + "name": "bone", + "description": "The bone, on the rock shelf.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": 1000000002, + "readable": 0, + "inscription": null, + "template_id": 1000000006, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000049, + "how": "on", + "kit_key": "cave/rock shelf/bone", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000054, + "character_id": null, + "name": "candle stub", + "description": "The candle stub, on the rock shelf.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": 1000000002, + "readable": 0, + "inscription": null, + "template_id": 1000000007, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 1, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000049, + "how": "on", + "kit_key": "cave/rock shelf/candle stub", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000055, + "character_id": null, + "name": "shard of pottery", + "description": "The shard of pottery, in the rubble.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": 1000000002, + "readable": 0, + "inscription": null, + "template_id": 1000000008, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000050, + "how": "in", + "kit_key": "cave/rubble/shard of pottery", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000056, + "character_id": null, + "name": "coin", + "description": "The coin, in the rubble.", + "properties": null, + "location_id": 1000000004, + "playthrough_id": 1000000002, + "readable": 0, + "inscription": null, + "template_id": 1000000009, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000050, + "how": "in", + "kit_key": "cave/rubble/coin", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "metal", + "wear": null, + "worn": 0 + }, + { + "id": 1000000057, + "character_id": null, + "name": "rock shelf", + "description": "The rock shelf, fixed in place.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": 1000000002, + "readable": 0, + "inscription": null, + "template_id": 1000000018, + "bulk": "immovable", + "x": 9, + "y": 3, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "top", + "within_id": null, + "how": null, + "kit_key": "cave/rock shelf", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "stone", + "wear": null, + "worn": 0 + }, + { + "id": 1000000058, + "character_id": null, + "name": "rubble", + "description": "The rubble, fixed in place.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": 1000000002, + "readable": 0, + "inscription": null, + "template_id": 1000000019, + "bulk": "immovable", + "x": 10, + "y": 1, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "hollow", + "within_id": null, + "how": null, + "kit_key": "cave/rubble", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000059, + "character_id": null, + "name": "stalagmite", + "description": "The stalagmite, fixed in place.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": 1000000002, + "readable": 0, + "inscription": null, + "template_id": 1000000020, + "bulk": "immovable", + "x": 11, + "y": 0, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "nothing", + "within_id": null, + "how": null, + "kit_key": "cave/stalagmite", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "stone", + "wear": null, + "worn": 0 + }, + { + "id": 1000000060, + "character_id": null, + "name": "pool", + "description": "The pool, fixed in place.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": 1000000002, + "readable": 0, + "inscription": null, + "template_id": 1000000021, + "bulk": "immovable", + "x": 10, + "y": 1, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "nothing", + "within_id": null, + "how": null, + "kit_key": "cave/pool", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000061, + "character_id": null, + "name": "crack", + "description": "The crack, fixed in place.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": 1000000002, + "readable": 0, + "inscription": null, + "template_id": 1000000022, + "bulk": "immovable", + "x": 10, + "y": 2, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "fixture", + "holds": "closed", + "within_id": null, + "how": null, + "kit_key": "cave/crack", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000062, + "character_id": null, + "name": "pebble", + "description": "The pebble, on the rock shelf.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": 1000000002, + "readable": 0, + "inscription": null, + "template_id": 1000000023, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000057, + "how": "on", + "kit_key": "cave/rock shelf/pebble", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "stone", + "wear": null, + "worn": 0 + }, + { + "id": 1000000063, + "character_id": null, + "name": "bone", + "description": "The bone, on the rock shelf.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": 1000000002, + "readable": 0, + "inscription": null, + "template_id": 1000000024, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000057, + "how": "on", + "kit_key": "cave/rock shelf/bone", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + }, + { + "id": 1000000064, + "character_id": null, + "name": "rusted hinge", + "description": "The rusted hinge, in the rubble.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": 1000000002, + "readable": 0, + "inscription": null, + "template_id": 1000000025, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000058, + "how": "in", + "kit_key": "cave/rubble/rusted hinge", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "metal", + "wear": null, + "worn": 0 + }, + { + "id": 1000000065, + "character_id": null, + "name": "coin", + "description": "The coin, in the rubble.", + "properties": null, + "location_id": 1000000005, + "playthrough_id": 1000000002, + "readable": 0, + "inscription": null, + "template_id": 1000000026, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "ordinary", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": 1000000058, + "how": "in", + "kit_key": "cave/rubble/coin", + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": "metal", + "wear": null, + "worn": 0 + }, + { + "id": 1000000066, + "character_id": 1000000002, + "name": "stair key", + "description": "A long iron key on a cord, cold to hold.", + "properties": "{}", + "location_id": null, + "playthrough_id": 1000000002, + "readable": 0, + "inscription": null, + "template_id": 1000000001, + "bulk": "handy", + "x": null, + "y": null, + "use_kind": "key", + "combustible": 0, + "disposition": "intact", + "fragility": "sturdy", + "tier": "portable", + "holds": null, + "within_id": null, + "how": null, + "kit_key": null, + "noticed_at": null, + "searched_at": null, + "hold": null, + "material": null, + "wear": null, + "worn": 0 + } + ], + "removed": [] + }, + "quest_steps": { + "rows": [], + "removed": [] + }, + "scenes": { + "rows": [ + { + "id": 1000000002, + "story_id": 1000000001, + "location_id": 1000000004, + "previous_scene_id": 1000000001, + "description": "You arrive at The Upper Floor room 1. You are unhurt. Nobody else is alive here. Fixed here, and not takeable: rock shelf, rubble, pool, ledge. Lying here: bone, candle stub, shard of pottery, coin. You are carrying: nothing.", + "story_timestamp": "2026-10-30 08:01:30", + "summary": "You arrive at The Upper Floor room 1. You are unhurt. Nobody else is alive here. Fixed here, and not takeable: rock shelf, rubble, pool, ledge. Lying here: bone, candle stub, shard of pottery, coin. You are carrying: nothing.", + "is_opening": 0, + "typed": "move The Upper Floor", + "resolved_action": "move", + "acted_on_type": "Location", + "acted_on_id": 1000000002, + "resolved_by": "grammar", + "engine_fallback": 1, + "engine_fact": "You are unhurt.\nNobody else is alive here.\nFixed here, and not takeable: rock shelf, rubble, pool, ledge.\nLying here: bone, candle stub, shard of pottery, coin.\nYou are carrying: nothing." + }, + { + "id": 1000000003, + "story_id": 1000000001, + "location_id": 1000000005, + "previous_scene_id": 1000000002, + "description": "You arrive at The Upper Floor room 2. You are unhurt. Also here: Brask the Stairwarden. Nobody else is alive here. Fixed here, and not takeable: rock shelf, rubble, stalagmite, pool, crack. Lying here: pebble, bone, rusted hinge, coin. You are carrying: nothing.", + "story_timestamp": "2026-10-30 08:02:30", + "summary": "You arrive at The Upper Floor room 2. You are unhurt. Also here: Brask the Stairwarden. Nobody else is alive here. Fixed here, and not takeable: rock shelf, rubble, stalagmite, pool, crack. Lying here: pebble, bone, rusted hinge, coin. You are carrying: nothing.", + "is_opening": 0, + "typed": "move The Upper Floor room 2", + "resolved_action": "move", + "acted_on_type": "Location", + "acted_on_id": 1000000005, + "resolved_by": "grammar", + "engine_fallback": 1, + "engine_fact": "You are unhurt.\nAlso here: Brask the Stairwarden. Nobody else is alive here.\nFixed here, and not takeable: rock shelf, rubble, stalagmite, pool, crack.\nLying here: pebble, bone, rusted hinge, coin.\nYou are carrying: nothing." + }, + { + "id": 1000000004, + "story_id": 1000000001, + "location_id": 1000000005, + "previous_scene_id": 1000000003, + "description": "The fight in The Upper Floor room 2 is over after 1 round. Brask the Stairwarden is dead. 1 blow landed: Odile hit Brask the Stairwarden for 7 (round 1); Brask the Stairwarden is dead.", + "story_timestamp": "2026-10-30 08:07:30", + "summary": "A fight in The Upper Floor room 2: 1 blow over 1 round. Brask the Stairwarden is dead.", + "is_opening": 0, + "typed": null, + "resolved_action": "attack", + "acted_on_type": "Character", + "acted_on_id": 1000000002, + "resolved_by": null, + "engine_fallback": 0, + "engine_fact": null + }, + { + "id": 1000000005, + "story_id": 1000000001, + "location_id": 1000000007, + "previous_scene_id": 1000000004, + "description": "You arrive at The Lower Floor room 1. You are unhurt. Nobody else is alive here. Fixed here, and not takeable: rock shelf, rubble, pool. Lying here: candle stub, bone, shard of pottery. You are carrying: stair key.", + "story_timestamp": "2026-10-30 08:09:00", + "summary": "You arrive at The Lower Floor room 1. You are unhurt. Nobody else is alive here. Fixed here, and not takeable: rock shelf, rubble, pool. Lying here: candle stub, bone, shard of pottery. You are carrying: stair key.", + "is_opening": 0, + "typed": "move The Lower Floor", + "resolved_action": "move", + "acted_on_type": "Location", + "acted_on_id": 1000000003, + "resolved_by": "grammar", + "engine_fallback": 1, + "engine_fact": "You are unhurt.\nNobody else is alive here.\nFixed here, and not takeable: rock shelf, rubble, pool.\nLying here: candle stub, bone, shard of pottery.\nYou are carrying: stair key." + } + ], + "removed": [] + }, + "characters_scenes": { + "rows": [ + { + "character_id": 1000000001, + "scene_id": 1000000002 + }, + { + "character_id": 1000000001, + "scene_id": 1000000003 + }, + { + "character_id": 1000000002, + "scene_id": 1000000003 + }, + { + "character_id": 1000000001, + "scene_id": 1000000004 + }, + { + "character_id": 1000000001, + "scene_id": 1000000005 + } + ], + "removed": [] + }, + "interactions": { + "rows": [], + "removed": [] + }, + "playthrough_endings": { + "rows": [], + "removed": [] + }, + "playthrough_beats": { + "rows": [], + "removed": [] + }, + "playthrough_npc_states": { + "rows": [ + { + "id": 1000000001, + "playthrough_id": 1000000001, + "character_id": 1000000002, + "location_id": 1000000005, + "following": 0, + "ceasefire": 0, + "peace_after_blow_id": 0, + "noticed_at": null + } + ], + "removed": [] + }, + "world_events": { + "rows": [], + "removed": [] + } + } + } + ] +} diff --git a/parity/records/a-room-written-while-the-arc-waits.json b/parity/records/a-room-written-while-the-arc-waits.json index c726450..cbae720 100644 --- a/parity/records/a-room-written-while-the-arc-waits.json +++ b/parity/records/a-room-written-while-the-arc-waits.json @@ -504,7 +504,8 @@ "unrecognized_need": "", "will": 10, "x": 3, - "y": 7 + "y": 7, + "posted_to_id": null } ], "removed": [] @@ -859,7 +860,8 @@ "unrecognized_need": "", "will": 10, "x": 3, - "y": 7 + "y": 7, + "posted_to_id": null } ], "removed": [] @@ -1291,7 +1293,8 @@ "unrecognized_need": "", "will": 10, "x": 3, - "y": 7 + "y": 7, + "posted_to_id": null } ], "removed": [] @@ -2082,7 +2085,8 @@ "unrecognized_need": "", "will": 10, "x": 3, - "y": 7 + "y": 7, + "posted_to_id": null } ], "removed": [] @@ -2527,7 +2531,8 @@ "unrecognized_need": "", "will": 10, "x": 3, - "y": 7 + "y": 7, + "posted_to_id": null } ], "removed": [] diff --git a/parity/records/a-thirteenth-person-is-born.json b/parity/records/a-thirteenth-person-is-born.json index b306516..251b160 100644 --- a/parity/records/a-thirteenth-person-is-born.json +++ b/parity/records/a-thirteenth-person-is-born.json @@ -454,7 +454,8 @@ "recognized_need": "", "unrecognized_need": "", "desire_pursuit": null, - "need_pursuit": null + "need_pursuit": null, + "posted_to_id": null }, { "id": 1000000014, @@ -487,7 +488,8 @@ "recognized_need": "", "unrecognized_need": "", "desire_pursuit": null, - "need_pursuit": null + "need_pursuit": null, + "posted_to_id": null }, { "id": 1000000015, @@ -520,7 +522,8 @@ "recognized_need": "", "unrecognized_need": "", "desire_pursuit": null, - "need_pursuit": null + "need_pursuit": null, + "posted_to_id": null } ], "removed": [] diff --git a/parity/records/unfinished-realization-resumes-on-entry.json b/parity/records/unfinished-realization-resumes-on-entry.json index af620ab..289224f 100644 --- a/parity/records/unfinished-realization-resumes-on-entry.json +++ b/parity/records/unfinished-realization-resumes-on-entry.json @@ -424,7 +424,8 @@ "unrecognized_need": "", "will": 12, "x": 0, - "y": 3 + "y": 3, + "posted_to_id": null } ], "removed": [] @@ -746,7 +747,8 @@ "unrecognized_need": "", "will": 12, "x": 0, - "y": 3 + "y": 3, + "posted_to_id": null } ], "removed": [] @@ -948,7 +950,8 @@ "unrecognized_need": "", "will": 12, "x": 0, - "y": 3 + "y": 3, + "posted_to_id": null } ], "removed": [] @@ -1206,7 +1209,8 @@ "unrecognized_need": "", "will": 12, "x": 0, - "y": 3 + "y": 3, + "posted_to_id": null } ], "removed": [] @@ -1485,7 +1489,8 @@ "unrecognized_need": "", "will": 12, "x": 0, - "y": 3 + "y": 3, + "posted_to_id": null } ], "removed": [] diff --git a/parity/scripts/a-deeper-floor-births-stronger-people.yml b/parity/scripts/a-deeper-floor-births-stronger-people.yml new file mode 100644 index 0000000..7e9c661 --- /dev/null +++ b/parity/scripts/a-deeper-floor-births-stronger-people.yml @@ -0,0 +1,73 @@ +--- +story: A Deeper Floor +steps: +- id: down-to-the-bottom-floor + type: "/move The Bottom Floor" + browser: + token: into-the-bottom-floor + realizes: The Bottom Floor + replies: + - purpose: location + content: + description: The bottom level of the dungeon, one chamber cut far down. + lore: Whatever lives this deep has lived here a long time. + place_kind: warren + - purpose: location + content: + description: A wide chamber where somebody has made a camp among the stones. + lore: Delvers who stay down here too long stop going back up. + items: [] + people: + - fullname: Hollin Graves + nickname: Hollin + appearance: A delver's coat gone grey with stone dust. + personality: Watchful and slow to speak. + backstory: A delver who stopped going back up years ago. + likes: The quiet down here. + dislikes: Lanterns from above. + fears: The surface. + - purpose: arrival + unavailable: true + expect: + location: The Bottom Floor room 1 (realized) + present: + - Hollin Graves + hp_of: + Hollin Graves: 18 +- id: back-up-to-the-landing + type: "/move The Landing" + expect: + location: The Landing (realized) +- id: into-the-upper-floor + type: "/move The Upper Floor" + browser: + token: into-the-upper-floor + realizes: The Upper Floor + replies: + - purpose: location + content: + description: The upper level of the dungeon, one chamber just under the landing. + lore: Delvers pass through here on their way to somewhere worse. + place_kind: warren + - purpose: location + content: + description: A low chamber with a bedroll by the wall. + lore: Somebody sleeps here between delves. + items: [] + people: + - fullname: Wenna Cole + nickname: Wenna + appearance: A patched cloak and a sack of candles. + personality: Cheerful and talkative. + backstory: A candle-seller who follows the delvers down. + likes: Company. + dislikes: Silence. + fears: Being left in the dark. + - purpose: arrival + unavailable: true + expect: + location: The Upper Floor room 1 (realized) + present: + - Wenna Cole + hp_of: + Wenna Cole: 8 diff --git a/parity/scripts/a-guard-waits-at-the-stairs.yml b/parity/scripts/a-guard-waits-at-the-stairs.yml new file mode 100644 index 0000000..e96ae45 --- /dev/null +++ b/parity/scripts/a-guard-waits-at-the-stairs.yml @@ -0,0 +1,117 @@ +--- +story: A Guarded Stair +steps: +- id: into-the-upper-floor + type: "/move The Upper Floor" + browser: + token: into-the-upper-floor + realizes: The Upper Floor + replies: + - purpose: location + content: + description: A level of black stone passages lit by nothing you can see. + lore: The first floor was cut by people nobody remembers. + place_kind: warren + - purpose: location + content: + description: A low chamber at the foot of the stair. + lore: Delvers rest here before going on. + items: [] + people: [] + - purpose: arrival + unavailable: true + expect: + location: The Upper Floor room 1 (realized) + exits: + - The Landing (realized) + - The Upper Floor room 2 (stub) + present: [] +- id: the-guard-stands-on-the-way-down + type: "/move The Upper Floor room 2" + browser: + token: room-2 + realizes: The Upper Floor room 2 + replies: + - purpose: location + content: + description: A bare chamber with a stair cut down into its floor. + lore: The stair down is older than the rest. + items: [] + people: [] + - purpose: arrival + unavailable: true + expect: + location: The Upper Floor room 2 (realized) + exits: + - The Lower Floor (stub) + - The Upper Floor room 1 (realized) + - The Upper Floor room 3 (stub) + present: + - Brask the Stairwarden + foes: + - Brask the Stairwarden + hp_of: + Brask the Stairwarden: 6 +- id: kill-the-guard + type: "/attack Brask the Stairwarden" + expect: + changed: true + present: [] + foes: [] +- id: the-stair-key-off-the-floor + type: "/take stair key" + expect: + carrying: + - stair key +- id: the-stair-down-is-locked + type: "/move The Lower Floor" + expect: + location: The Upper Floor room 2 (realized) + refused: true + offers: locked +- id: his-key-opens-it + type: "/unlock The Lower Floor with stair key" + expect: + changed: true + refused: false +- id: down-to-the-lower-floor + type: "/move The Lower Floor" + browser: + token: into-the-lower-floor + realizes: The Lower Floor + replies: + - purpose: location + content: + description: A colder level, the air still and the dark close. + lore: Few delvers come this far. + place_kind: warren + - purpose: location + content: + description: The foot of the stair, cold and unlit. + lore: The stair comes down here from the floor above. + items: [] + people: [] + - purpose: arrival + unavailable: true + expect: + location: The Lower Floor room 1 (realized) + exits_include: + - The Upper Floor room 2 (realized) + refused: false +- id: a-second-game-walks-onto-the-laid-out-floor + player: second + type: "/move The Upper Floor room 1" + expect: + location: The Upper Floor room 1 (realized) + present: [] +- id: and-finds-the-guard-where-the-layout-stood-him + player: second + type: "/move The Upper Floor room 2" + expect: + location: The Upper Floor room 2 (realized) + present: + - Brask the Stairwarden + foes: + - Brask the Stairwarden + hp_of: + Brask the Stairwarden: 6 diff --git a/parity/worlds/a-bell-nobody-has-rung.sql b/parity/worlds/a-bell-nobody-has-rung.sql index b8ed67a..305fead 100644 --- a/parity/worlds/a-bell-nobody-has-rung.sql +++ b/parity/worlds/a-bell-nobody-has-rung.sql @@ -14,18 +14,6 @@ CREATE TABLE IF NOT EXISTS "ruby_llm_usages" ("id" integer PRIMARY KEY AUTOINCRE CREATE TABLE IF NOT EXISTS "ruby_llm_v2_backfills" ("completed" boolean DEFAULT FALSE NOT NULL, "last_id" integer, "task" varchar NOT NULL); CREATE TABLE IF NOT EXISTS "universes" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "civilizations" text, "created_at" datetime(6) NOT NULL, "economics" text, "geographies" text, "history" text, "physics" text, "politics" text, "religion" text, "technology" text, "updated_at" datetime(6) NOT NULL, "weapons" text, "gravity" varchar, "carrying" varchar); INSERT INTO universes VALUES(1000000001,'A small order of lay brothers.','2026-10-02 13:25:57.875879','The order trades honey for lamp oil.','A hill abbey and the valley below it.','The abbey bell has not rung in a generation.','Ordinary physics.','The prior settles every dispute.','The order keeps the hours by the bell.','Bells, candles and iron locks.','2026-10-02 13:25:57.875879','Walking staves.',NULL,NULL); -CREATE TABLE IF NOT EXISTS "characters" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "age" integer, "appearance" text, "backstory" text, "conscious_desire" text, "created_at" datetime(6) NOT NULL, "deliberately_absent" boolean DEFAULT FALSE NOT NULL, "desire_pursuit" varchar, "dexterity" integer, "dislikes" text, "fears" text, "fullname" varchar, "hit_die" integer, "hostile" boolean DEFAULT FALSE NOT NULL, "is_companion" boolean, "is_protagonist" boolean DEFAULT FALSE NOT NULL, "level" integer, "likes" text, "location_id" integer, "need_pursuit" varchar, "nickname" varchar, "personality" text, "race_id" integer NOT NULL, "recognized_need" text, "sex" varchar, "story_id" integer NOT NULL, "strength" integer, "unconscious_desire" text, "unrecognized_need" text, "updated_at" datetime(6) NOT NULL, "will" integer, "x" integer, "y" integer, CONSTRAINT "fk_rails_56a213cbe2" -FOREIGN KEY ("race_id") - REFERENCES "races" ("id") -, CONSTRAINT "fk_rails_0b4b445641" -FOREIGN KEY ("location_id") - REFERENCES "locations" ("id") -, CONSTRAINT "fk_rails_6608c248e8" -FOREIGN KEY ("story_id") - REFERENCES "stories" ("id") -); -INSERT INTO characters VALUES(1000000001,27,'A mended wool cloak.','A bell-founder''s daughter from the valley.',NULL,'2026-10-02 13:25:58.087091',0,NULL,12,'Silence kept for its own sake.','Heights.','Ilse',8,0,0,1,5,'Clear tones.',NULL,NULL,'Ilse','Curious and stubborn.',1000000001,NULL,'female',1000000001,11,NULL,NULL,'2026-10-02 13:25:58.087091',13,NULL,NULL); -INSERT INTO characters VALUES(1000000002,61,'A grey apron and a ring of keys.','The sexton, who has swept the hall for forty years.',NULL,'2026-10-02 13:25:58.123801',0,NULL,10,'Visitors who touch things.','The bell tower stair.','Oda',6,0,0,0,3,'A swept floor.',1000000001,NULL,'Oda','Patient and dry.',1000000001,NULL,'female',1000000001,9,NULL,NULL,'2026-10-02 13:25:58.123801',12,NULL,NULL); CREATE TABLE IF NOT EXISTS "chats" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "cancelled" boolean DEFAULT FALSE NOT NULL, "character_id" integer, "created_at" datetime(6) NOT NULL, "model_id_string" varchar, "player_id" integer, "playthrough_id" integer, "purpose" varchar, "ruby_llm_model_id" integer, "updated_at" datetime(6) NOT NULL, CONSTRAINT "fk_rails_415520c982" FOREIGN KEY ("playthrough_id") REFERENCES "playthroughs" ("id") @@ -417,6 +405,7 @@ INSERT INTO schema_migrations VALUES('20261025230000'); INSERT INTO schema_migrations VALUES('20261027120117'); INSERT INTO schema_migrations VALUES('20261030120000'); INSERT INTO schema_migrations VALUES('20261030200000'); +INSERT INTO schema_migrations VALUES('20261030230000'); CREATE TABLE IF NOT EXISTS "ar_internal_metadata" ("key" varchar NOT NULL PRIMARY KEY, "value" varchar, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL); INSERT INTO ar_internal_metadata VALUES('environment','test','2026-10-02 13:25:33.143313','2026-10-02 13:25:33.143316'); INSERT INTO ar_internal_metadata VALUES('schema_sha1','072abc2bfb80d111a1808df270e9210911f5e81d','2026-10-02 13:25:33.148224','2026-10-02 13:25:33.148228'); @@ -427,13 +416,28 @@ FOREIGN KEY ("playthrough_id") FOREIGN KEY ("quest_outcome_id") REFERENCES "quest_outcomes" ("id") ); -CREATE TABLE IF NOT EXISTS "floors" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "location_id" integer NOT NULL, "rooms" integer NOT NULL, "danger" varchar, "hazard" varchar, "down_location_id" integer, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, CONSTRAINT "fk_rails_ce8b005cb2" +CREATE TABLE IF NOT EXISTS "floors" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "location_id" integer NOT NULL, "rooms" integer NOT NULL, "danger" varchar, "hazard" varchar, "down_location_id" integer, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, "monster_level" integer, CONSTRAINT "fk_rails_ce8b005cb2" FOREIGN KEY ("location_id") REFERENCES "locations" ("id") ON DELETE CASCADE, CONSTRAINT "fk_rails_8a0268155a" FOREIGN KEY ("down_location_id") REFERENCES "locations" ("id") ON DELETE SET NULL); +CREATE TABLE IF NOT EXISTS "characters" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "age" integer, "appearance" text, "backstory" text, "conscious_desire" text, "created_at" datetime(6) NOT NULL, "deliberately_absent" boolean DEFAULT FALSE NOT NULL, "desire_pursuit" varchar, "dexterity" integer, "dislikes" text, "fears" text, "fullname" varchar, "hit_die" integer, "hostile" boolean DEFAULT FALSE NOT NULL, "is_companion" boolean, "is_protagonist" boolean DEFAULT FALSE NOT NULL, "level" integer, "likes" text, "location_id" integer, "need_pursuit" varchar, "nickname" varchar, "personality" text, "race_id" integer NOT NULL, "recognized_need" text, "sex" varchar, "story_id" integer NOT NULL, "strength" integer, "unconscious_desire" text, "unrecognized_need" text, "updated_at" datetime(6) NOT NULL, "will" integer, "x" integer, "y" integer, "posted_to_id" integer, CONSTRAINT "fk_rails_6608c248e8" +FOREIGN KEY ("story_id") + REFERENCES "stories" ("id") +, CONSTRAINT "fk_rails_0b4b445641" +FOREIGN KEY ("location_id") + REFERENCES "locations" ("id") +, CONSTRAINT "fk_rails_56a213cbe2" +FOREIGN KEY ("race_id") + REFERENCES "races" ("id") +, CONSTRAINT "fk_rails_4c54fc506e" +FOREIGN KEY ("posted_to_id") + REFERENCES "locations" ("id") + ON DELETE SET NULL); +INSERT INTO characters VALUES(1000000001,27,'A mended wool cloak.','A bell-founder''s daughter from the valley.',NULL,'2026-10-02 13:25:58.087091',0,NULL,12,'Silence kept for its own sake.','Heights.','Ilse',8,0,0,1,5,'Clear tones.',NULL,NULL,'Ilse','Curious and stubborn.',1000000001,NULL,'female',1000000001,11,NULL,NULL,'2026-10-02 13:25:58.087091',13,NULL,NULL,NULL); +INSERT INTO characters VALUES(1000000002,61,'A grey apron and a ring of keys.','The sexton, who has swept the hall for forty years.',NULL,'2026-10-02 13:25:58.123801',0,NULL,10,'Visitors who touch things.','The bell tower stair.','Oda',6,0,0,0,3,'A swept floor.',1000000001,NULL,'Oda','Patient and dry.',1000000001,NULL,'female',1000000001,9,NULL,NULL,'2026-10-02 13:25:58.123801',12,NULL,NULL,NULL); PRAGMA writable_schema=ON; CREATE TABLE IF NOT EXISTS sqlite_sequence(name,seq); DELETE FROM sqlite_sequence; @@ -446,7 +450,6 @@ INSERT INTO sqlite_sequence VALUES('ruby_llm_models',1000000000); INSERT INTO sqlite_sequence VALUES('ruby_llm_tool_calls',1000000000); INSERT INTO sqlite_sequence VALUES('ruby_llm_usages',1000000000); INSERT INTO sqlite_sequence VALUES('universes',1000000001); -INSERT INTO sqlite_sequence VALUES('characters',1000000002); INSERT INTO sqlite_sequence VALUES('chats',1000000000); INSERT INTO sqlite_sequence VALUES('interactions',1000000000); INSERT INTO sqlite_sequence VALUES('items',1000000002); @@ -481,6 +484,7 @@ INSERT INTO sqlite_sequence VALUES('stories',1000000001); INSERT INTO sqlite_sequence VALUES('system_one_receipts',1000000000); INSERT INTO sqlite_sequence VALUES('world_events',1000000000); INSERT INTO sqlite_sequence VALUES('world_mechanics',1000000000); +INSERT INTO sqlite_sequence VALUES('characters',1000000002); CREATE INDEX "index_characters_scenes_on_character_id_and_scene_id" ON "characters_scenes" ("character_id", "scene_id"); CREATE INDEX "index_characters_scenes_on_scene_id_and_character_id" ON "characters_scenes" ("scene_id", "character_id"); CREATE UNIQUE INDEX "index_players_on_name" ON "players" ("name"); @@ -501,13 +505,6 @@ CREATE INDEX "index_ruby_llm_usages_on_chat_type_and_chat_id" ON "ruby_llm_usage CREATE INDEX "index_ruby_llm_usages_on_message_type_and_message_id" ON "ruby_llm_usages" ("message_type", "message_id"); CREATE INDEX "index_ruby_llm_usages_on_status" ON "ruby_llm_usages" ("status"); CREATE UNIQUE INDEX "index_ruby_llm_v2_backfills_on_task" ON "ruby_llm_v2_backfills" ("task"); -CREATE UNIQUE INDEX "index_characters_on_story_id_and_lower_fullname" ON "characters" (story_id, LOWER(fullname)); -CREATE INDEX "index_characters_on_location_id_and_hostile" ON "characters" ("location_id", "hostile"); -CREATE INDEX "index_characters_on_location_id_and_id" ON "characters" ("location_id", "id"); -CREATE INDEX "index_characters_on_location_id" ON "characters" ("location_id"); -CREATE INDEX "index_characters_on_race_id" ON "characters" ("race_id"); -CREATE INDEX "index_characters_on_story_id_and_is_protagonist" ON "characters" ("story_id", "is_protagonist"); -CREATE INDEX "index_characters_on_story_id" ON "characters" ("story_id"); CREATE INDEX "index_chats_on_character_id" ON "chats" ("character_id"); CREATE INDEX "index_chats_on_player_id" ON "chats" ("player_id"); CREATE INDEX "index_chats_on_conversation_key" ON "chats" ("playthrough_id", "character_id", "purpose"); @@ -643,5 +640,13 @@ CREATE INDEX "index_playthrough_due_endings_on_quest_outcome_id" ON "playthrough CREATE INDEX "index_quest_steps_on_companion_id" ON "quest_steps" ("companion_id"); CREATE UNIQUE INDEX "index_floors_on_location_id" ON "floors" ("location_id"); CREATE INDEX "index_floors_on_down_location_id" ON "floors" ("down_location_id"); +CREATE UNIQUE INDEX "index_characters_on_story_id_and_lower_fullname" ON "characters" (story_id, LOWER(fullname)); +CREATE INDEX "index_characters_on_location_id_and_hostile" ON "characters" ("location_id", "hostile"); +CREATE INDEX "index_characters_on_location_id_and_id" ON "characters" ("location_id", "id"); +CREATE INDEX "index_characters_on_location_id" ON "characters" ("location_id"); +CREATE INDEX "index_characters_on_race_id" ON "characters" ("race_id"); +CREATE INDEX "index_characters_on_story_id_and_is_protagonist" ON "characters" ("story_id", "is_protagonist"); +CREATE INDEX "index_characters_on_story_id" ON "characters" ("story_id"); +CREATE INDEX "index_characters_on_posted_to_id" ON "characters" ("posted_to_id"); PRAGMA writable_schema=OFF; COMMIT; diff --git a/parity/worlds/a-chamber-of-one-room.sql b/parity/worlds/a-chamber-of-one-room.sql index a933032..bfb649d 100644 --- a/parity/worlds/a-chamber-of-one-room.sql +++ b/parity/worlds/a-chamber-of-one-room.sql @@ -13,17 +13,6 @@ CREATE TABLE IF NOT EXISTS "ruby_llm_usages" ("id" integer PRIMARY KEY AUTOINCRE CREATE TABLE IF NOT EXISTS "ruby_llm_v2_backfills" ("completed" boolean DEFAULT FALSE NOT NULL, "last_id" integer, "task" varchar NOT NULL); CREATE TABLE IF NOT EXISTS "universes" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "civilizations" text, "created_at" datetime(6) NOT NULL, "economics" text, "geographies" text, "history" text, "physics" text, "politics" text, "religion" text, "technology" text, "updated_at" datetime(6) NOT NULL, "weapons" text, "gravity" varchar, "carrying" varchar); INSERT INTO universes VALUES(1000000001,'A small working crew.','2026-10-02 13:25:37.988033','Freight contracts.','The decks of a cargo hauler.','The hauler has run the same route for twenty years.','Spin gravity on the outer decks, less toward the core.','The ship''s master has the last word aboard.','The crew keeps no faith.','Ladders, hatches and hand lamps.','2026-10-02 13:25:37.988033','Wrenches.',NULL,NULL); -CREATE TABLE IF NOT EXISTS "characters" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "age" integer, "appearance" text, "backstory" text, "conscious_desire" text, "created_at" datetime(6) NOT NULL, "deliberately_absent" boolean DEFAULT FALSE NOT NULL, "desire_pursuit" varchar, "dexterity" integer, "dislikes" text, "fears" text, "fullname" varchar, "hit_die" integer, "hostile" boolean DEFAULT FALSE NOT NULL, "is_companion" boolean, "is_protagonist" boolean DEFAULT FALSE NOT NULL, "level" integer, "likes" text, "location_id" integer, "need_pursuit" varchar, "nickname" varchar, "personality" text, "race_id" integer NOT NULL, "recognized_need" text, "sex" varchar, "story_id" integer NOT NULL, "strength" integer, "unconscious_desire" text, "unrecognized_need" text, "updated_at" datetime(6) NOT NULL, "will" integer, "x" integer, "y" integer, CONSTRAINT "fk_rails_56a213cbe2" -FOREIGN KEY ("race_id") - REFERENCES "races" ("id") -, CONSTRAINT "fk_rails_0b4b445641" -FOREIGN KEY ("location_id") - REFERENCES "locations" ("id") -, CONSTRAINT "fk_rails_6608c248e8" -FOREIGN KEY ("story_id") - REFERENCES "stories" ("id") -); -INSERT INTO characters VALUES(1000000001,30,'A patched jumpsuit.','An engineer on her third run aboard.',NULL,'2026-10-02 13:25:38.114411',0,NULL,12,'Guesswork.','A breach she cannot seal.','Kael',8,0,0,1,10,'A clean fault log.',NULL,NULL,'Kael','Careful and dry.',1000000001,NULL,'female',1000000001,12,NULL,NULL,'2026-10-02 13:25:38.114411',12,NULL,NULL); CREATE TABLE IF NOT EXISTS "chats" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "cancelled" boolean DEFAULT FALSE NOT NULL, "character_id" integer, "created_at" datetime(6) NOT NULL, "model_id_string" varchar, "player_id" integer, "playthrough_id" integer, "purpose" varchar, "ruby_llm_model_id" integer, "updated_at" datetime(6) NOT NULL, CONSTRAINT "fk_rails_415520c982" FOREIGN KEY ("playthrough_id") REFERENCES "playthroughs" ("id") @@ -405,6 +394,7 @@ INSERT INTO schema_migrations VALUES('20261025230000'); INSERT INTO schema_migrations VALUES('20261027120117'); INSERT INTO schema_migrations VALUES('20261030120000'); INSERT INTO schema_migrations VALUES('20261030200000'); +INSERT INTO schema_migrations VALUES('20261030230000'); CREATE TABLE IF NOT EXISTS "ar_internal_metadata" ("key" varchar NOT NULL PRIMARY KEY, "value" varchar, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL); INSERT INTO ar_internal_metadata VALUES('environment','test','2026-10-02 13:25:33.143313','2026-10-02 13:25:33.143316'); INSERT INTO ar_internal_metadata VALUES('schema_sha1','072abc2bfb80d111a1808df270e9210911f5e81d','2026-10-02 13:25:33.148224','2026-10-02 13:25:33.148228'); @@ -415,13 +405,27 @@ FOREIGN KEY ("playthrough_id") FOREIGN KEY ("quest_outcome_id") REFERENCES "quest_outcomes" ("id") ); -CREATE TABLE IF NOT EXISTS "floors" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "location_id" integer NOT NULL, "rooms" integer NOT NULL, "danger" varchar, "hazard" varchar, "down_location_id" integer, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, CONSTRAINT "fk_rails_ce8b005cb2" +CREATE TABLE IF NOT EXISTS "floors" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "location_id" integer NOT NULL, "rooms" integer NOT NULL, "danger" varchar, "hazard" varchar, "down_location_id" integer, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, "monster_level" integer, CONSTRAINT "fk_rails_ce8b005cb2" FOREIGN KEY ("location_id") REFERENCES "locations" ("id") ON DELETE CASCADE, CONSTRAINT "fk_rails_8a0268155a" FOREIGN KEY ("down_location_id") REFERENCES "locations" ("id") ON DELETE SET NULL); +CREATE TABLE IF NOT EXISTS "characters" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "age" integer, "appearance" text, "backstory" text, "conscious_desire" text, "created_at" datetime(6) NOT NULL, "deliberately_absent" boolean DEFAULT FALSE NOT NULL, "desire_pursuit" varchar, "dexterity" integer, "dislikes" text, "fears" text, "fullname" varchar, "hit_die" integer, "hostile" boolean DEFAULT FALSE NOT NULL, "is_companion" boolean, "is_protagonist" boolean DEFAULT FALSE NOT NULL, "level" integer, "likes" text, "location_id" integer, "need_pursuit" varchar, "nickname" varchar, "personality" text, "race_id" integer NOT NULL, "recognized_need" text, "sex" varchar, "story_id" integer NOT NULL, "strength" integer, "unconscious_desire" text, "unrecognized_need" text, "updated_at" datetime(6) NOT NULL, "will" integer, "x" integer, "y" integer, "posted_to_id" integer, CONSTRAINT "fk_rails_6608c248e8" +FOREIGN KEY ("story_id") + REFERENCES "stories" ("id") +, CONSTRAINT "fk_rails_0b4b445641" +FOREIGN KEY ("location_id") + REFERENCES "locations" ("id") +, CONSTRAINT "fk_rails_56a213cbe2" +FOREIGN KEY ("race_id") + REFERENCES "races" ("id") +, CONSTRAINT "fk_rails_4c54fc506e" +FOREIGN KEY ("posted_to_id") + REFERENCES "locations" ("id") + ON DELETE SET NULL); +INSERT INTO characters VALUES(1000000001,30,'A patched jumpsuit.','An engineer on her third run aboard.',NULL,'2026-10-02 13:25:38.114411',0,NULL,12,'Guesswork.','A breach she cannot seal.','Kael',8,0,0,1,10,'A clean fault log.',NULL,NULL,'Kael','Careful and dry.',1000000001,NULL,'female',1000000001,12,NULL,NULL,'2026-10-02 13:25:38.114411',12,NULL,NULL,NULL); PRAGMA writable_schema=ON; CREATE TABLE IF NOT EXISTS sqlite_sequence(name,seq); DELETE FROM sqlite_sequence; @@ -434,7 +438,6 @@ INSERT INTO sqlite_sequence VALUES('ruby_llm_models',1000000000); INSERT INTO sqlite_sequence VALUES('ruby_llm_tool_calls',1000000000); INSERT INTO sqlite_sequence VALUES('ruby_llm_usages',1000000000); INSERT INTO sqlite_sequence VALUES('universes',1000000001); -INSERT INTO sqlite_sequence VALUES('characters',1000000001); INSERT INTO sqlite_sequence VALUES('chats',1000000000); INSERT INTO sqlite_sequence VALUES('interactions',1000000000); INSERT INTO sqlite_sequence VALUES('items',1000000000); @@ -469,6 +472,7 @@ INSERT INTO sqlite_sequence VALUES('stories',1000000001); INSERT INTO sqlite_sequence VALUES('system_one_receipts',1000000000); INSERT INTO sqlite_sequence VALUES('world_events',1000000000); INSERT INTO sqlite_sequence VALUES('world_mechanics',1000000000); +INSERT INTO sqlite_sequence VALUES('characters',1000000001); CREATE INDEX "index_characters_scenes_on_character_id_and_scene_id" ON "characters_scenes" ("character_id", "scene_id"); CREATE INDEX "index_characters_scenes_on_scene_id_and_character_id" ON "characters_scenes" ("scene_id", "character_id"); CREATE UNIQUE INDEX "index_players_on_name" ON "players" ("name"); @@ -489,13 +493,6 @@ CREATE INDEX "index_ruby_llm_usages_on_chat_type_and_chat_id" ON "ruby_llm_usage CREATE INDEX "index_ruby_llm_usages_on_message_type_and_message_id" ON "ruby_llm_usages" ("message_type", "message_id"); CREATE INDEX "index_ruby_llm_usages_on_status" ON "ruby_llm_usages" ("status"); CREATE UNIQUE INDEX "index_ruby_llm_v2_backfills_on_task" ON "ruby_llm_v2_backfills" ("task"); -CREATE UNIQUE INDEX "index_characters_on_story_id_and_lower_fullname" ON "characters" (story_id, LOWER(fullname)); -CREATE INDEX "index_characters_on_location_id_and_hostile" ON "characters" ("location_id", "hostile"); -CREATE INDEX "index_characters_on_location_id_and_id" ON "characters" ("location_id", "id"); -CREATE INDEX "index_characters_on_location_id" ON "characters" ("location_id"); -CREATE INDEX "index_characters_on_race_id" ON "characters" ("race_id"); -CREATE INDEX "index_characters_on_story_id_and_is_protagonist" ON "characters" ("story_id", "is_protagonist"); -CREATE INDEX "index_characters_on_story_id" ON "characters" ("story_id"); CREATE INDEX "index_chats_on_character_id" ON "chats" ("character_id"); CREATE INDEX "index_chats_on_player_id" ON "chats" ("player_id"); CREATE INDEX "index_chats_on_conversation_key" ON "chats" ("playthrough_id", "character_id", "purpose"); @@ -631,5 +628,13 @@ CREATE INDEX "index_playthrough_due_endings_on_quest_outcome_id" ON "playthrough CREATE INDEX "index_quest_steps_on_companion_id" ON "quest_steps" ("companion_id"); CREATE UNIQUE INDEX "index_floors_on_location_id" ON "floors" ("location_id"); CREATE INDEX "index_floors_on_down_location_id" ON "floors" ("down_location_id"); +CREATE UNIQUE INDEX "index_characters_on_story_id_and_lower_fullname" ON "characters" (story_id, LOWER(fullname)); +CREATE INDEX "index_characters_on_location_id_and_hostile" ON "characters" ("location_id", "hostile"); +CREATE INDEX "index_characters_on_location_id_and_id" ON "characters" ("location_id", "id"); +CREATE INDEX "index_characters_on_location_id" ON "characters" ("location_id"); +CREATE INDEX "index_characters_on_race_id" ON "characters" ("race_id"); +CREATE INDEX "index_characters_on_story_id_and_is_protagonist" ON "characters" ("story_id", "is_protagonist"); +CREATE INDEX "index_characters_on_story_id" ON "characters" ("story_id"); +CREATE INDEX "index_characters_on_posted_to_id" ON "characters" ("posted_to_id"); PRAGMA writable_schema=OFF; COMMIT; diff --git a/parity/worlds/a-clerk-with-somewhere-to-be.sql b/parity/worlds/a-clerk-with-somewhere-to-be.sql index a2e9011..fc3bc63 100644 --- a/parity/worlds/a-clerk-with-somewhere-to-be.sql +++ b/parity/worlds/a-clerk-with-somewhere-to-be.sql @@ -14,18 +14,6 @@ CREATE TABLE IF NOT EXISTS "ruby_llm_usages" ("id" integer PRIMARY KEY AUTOINCRE CREATE TABLE IF NOT EXISTS "ruby_llm_v2_backfills" ("completed" boolean DEFAULT FALSE NOT NULL, "last_id" integer, "task" varchar NOT NULL); CREATE TABLE IF NOT EXISTS "universes" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "civilizations" text, "created_at" datetime(6) NOT NULL, "economics" text, "geographies" text, "history" text, "physics" text, "politics" text, "religion" text, "technology" text, "updated_at" datetime(6) NOT NULL, "weapons" text, "gravity" varchar, "carrying" varchar); INSERT INTO universes VALUES(1000000001,'A town that keeps its paperwork.','2026-10-02 13:25:43.207564','Fees, receipts and arrears.','A records office and the landing outside it.','The office has stood since the town did.','Ordinary physics.','A clerk of works answers to a council.','The town keeps a quiet calendar.','Ledgers, lamps and locked doors.','2026-10-02 13:25:43.207564','Nothing anybody carries.',NULL,NULL); -CREATE TABLE IF NOT EXISTS "characters" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "age" integer, "appearance" text, "backstory" text, "conscious_desire" text, "created_at" datetime(6) NOT NULL, "deliberately_absent" boolean DEFAULT FALSE NOT NULL, "desire_pursuit" varchar, "dexterity" integer, "dislikes" text, "fears" text, "fullname" varchar, "hit_die" integer, "hostile" boolean DEFAULT FALSE NOT NULL, "is_companion" boolean, "is_protagonist" boolean DEFAULT FALSE NOT NULL, "level" integer, "likes" text, "location_id" integer, "need_pursuit" varchar, "nickname" varchar, "personality" text, "race_id" integer NOT NULL, "recognized_need" text, "sex" varchar, "story_id" integer NOT NULL, "strength" integer, "unconscious_desire" text, "unrecognized_need" text, "updated_at" datetime(6) NOT NULL, "will" integer, "x" integer, "y" integer, CONSTRAINT "fk_rails_56a213cbe2" -FOREIGN KEY ("race_id") - REFERENCES "races" ("id") -, CONSTRAINT "fk_rails_0b4b445641" -FOREIGN KEY ("location_id") - REFERENCES "locations" ("id") -, CONSTRAINT "fk_rails_6608c248e8" -FOREIGN KEY ("story_id") - REFERENCES "stories" ("id") -); -INSERT INTO characters VALUES(1000000001,34,'A wet coat and a folded paper.','A tenant who has come for a receipt and will not leave without one.','Wick wants a dated receipt for the quarter''s rent, in his hand, before the office closes.','2026-10-02 13:25:43.372798',0,'obtain',12,'Being asked to come back tomorrow.','Paying twice for the same thing.','Wick',8,0,0,1,10,'A receipt with a date on it.',NULL,'offer','Wick','Patient and stubborn in equal measure.',1000000001,'Wick needs to keep every receipt he has ever been given, because it is the only argument he has.','male',1000000001,12,'Wick wants somebody behind that counter to know his name without being told it.','Wick needs to say out loud that he cannot pay next quarter, to somebody who can do something about it.','2026-10-02 13:25:43.372798',12,NULL,NULL); -INSERT INTO characters VALUES(1000000002,41,'An office coat and a ring of keys she keeps turning over.','A clerk of works who is due on the landing and has been due there for an hour.','Bell wants to be up on the landing where the survey is waiting, before the hour goes and the whole morning goes with it.','2026-10-02 13:25:43.419441',0,'reach',12,'Being caught on the stairs.','Being the one who held everybody up.','Bell',8,0,0,0,10,'A morning that runs to time.',1000000001,'attend','Bell','Brisk, decent, and already thinking about the next room.',1000000001,'Bell needs to answer whoever is at the counter first, because that is what the counter is for.','female',1000000001,12,'Bell wants one person in this building to say the delay was never hers.','Bell needs to stand still long enough to hear what the person at the counter actually came to say.','2026-10-02 13:25:43.419441',12,NULL,NULL); CREATE TABLE IF NOT EXISTS "chats" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "cancelled" boolean DEFAULT FALSE NOT NULL, "character_id" integer, "created_at" datetime(6) NOT NULL, "model_id_string" varchar, "player_id" integer, "playthrough_id" integer, "purpose" varchar, "ruby_llm_model_id" integer, "updated_at" datetime(6) NOT NULL, CONSTRAINT "fk_rails_415520c982" FOREIGN KEY ("playthrough_id") REFERENCES "playthroughs" ("id") @@ -408,6 +396,7 @@ INSERT INTO schema_migrations VALUES('20261025230000'); INSERT INTO schema_migrations VALUES('20261027120117'); INSERT INTO schema_migrations VALUES('20261030120000'); INSERT INTO schema_migrations VALUES('20261030200000'); +INSERT INTO schema_migrations VALUES('20261030230000'); CREATE TABLE IF NOT EXISTS "ar_internal_metadata" ("key" varchar NOT NULL PRIMARY KEY, "value" varchar, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL); INSERT INTO ar_internal_metadata VALUES('environment','test','2026-10-02 13:25:33.143313','2026-10-02 13:25:33.143316'); INSERT INTO ar_internal_metadata VALUES('schema_sha1','072abc2bfb80d111a1808df270e9210911f5e81d','2026-10-02 13:25:33.148224','2026-10-02 13:25:33.148228'); @@ -418,13 +407,28 @@ FOREIGN KEY ("playthrough_id") FOREIGN KEY ("quest_outcome_id") REFERENCES "quest_outcomes" ("id") ); -CREATE TABLE IF NOT EXISTS "floors" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "location_id" integer NOT NULL, "rooms" integer NOT NULL, "danger" varchar, "hazard" varchar, "down_location_id" integer, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, CONSTRAINT "fk_rails_ce8b005cb2" +CREATE TABLE IF NOT EXISTS "floors" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "location_id" integer NOT NULL, "rooms" integer NOT NULL, "danger" varchar, "hazard" varchar, "down_location_id" integer, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, "monster_level" integer, CONSTRAINT "fk_rails_ce8b005cb2" FOREIGN KEY ("location_id") REFERENCES "locations" ("id") ON DELETE CASCADE, CONSTRAINT "fk_rails_8a0268155a" FOREIGN KEY ("down_location_id") REFERENCES "locations" ("id") ON DELETE SET NULL); +CREATE TABLE IF NOT EXISTS "characters" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "age" integer, "appearance" text, "backstory" text, "conscious_desire" text, "created_at" datetime(6) NOT NULL, "deliberately_absent" boolean DEFAULT FALSE NOT NULL, "desire_pursuit" varchar, "dexterity" integer, "dislikes" text, "fears" text, "fullname" varchar, "hit_die" integer, "hostile" boolean DEFAULT FALSE NOT NULL, "is_companion" boolean, "is_protagonist" boolean DEFAULT FALSE NOT NULL, "level" integer, "likes" text, "location_id" integer, "need_pursuit" varchar, "nickname" varchar, "personality" text, "race_id" integer NOT NULL, "recognized_need" text, "sex" varchar, "story_id" integer NOT NULL, "strength" integer, "unconscious_desire" text, "unrecognized_need" text, "updated_at" datetime(6) NOT NULL, "will" integer, "x" integer, "y" integer, "posted_to_id" integer, CONSTRAINT "fk_rails_6608c248e8" +FOREIGN KEY ("story_id") + REFERENCES "stories" ("id") +, CONSTRAINT "fk_rails_0b4b445641" +FOREIGN KEY ("location_id") + REFERENCES "locations" ("id") +, CONSTRAINT "fk_rails_56a213cbe2" +FOREIGN KEY ("race_id") + REFERENCES "races" ("id") +, CONSTRAINT "fk_rails_4c54fc506e" +FOREIGN KEY ("posted_to_id") + REFERENCES "locations" ("id") + ON DELETE SET NULL); +INSERT INTO characters VALUES(1000000001,34,'A wet coat and a folded paper.','A tenant who has come for a receipt and will not leave without one.','Wick wants a dated receipt for the quarter''s rent, in his hand, before the office closes.','2026-10-02 13:25:43.372798',0,'obtain',12,'Being asked to come back tomorrow.','Paying twice for the same thing.','Wick',8,0,0,1,10,'A receipt with a date on it.',NULL,'offer','Wick','Patient and stubborn in equal measure.',1000000001,'Wick needs to keep every receipt he has ever been given, because it is the only argument he has.','male',1000000001,12,'Wick wants somebody behind that counter to know his name without being told it.','Wick needs to say out loud that he cannot pay next quarter, to somebody who can do something about it.','2026-10-02 13:25:43.372798',12,NULL,NULL,NULL); +INSERT INTO characters VALUES(1000000002,41,'An office coat and a ring of keys she keeps turning over.','A clerk of works who is due on the landing and has been due there for an hour.','Bell wants to be up on the landing where the survey is waiting, before the hour goes and the whole morning goes with it.','2026-10-02 13:25:43.419441',0,'reach',12,'Being caught on the stairs.','Being the one who held everybody up.','Bell',8,0,0,0,10,'A morning that runs to time.',1000000001,'attend','Bell','Brisk, decent, and already thinking about the next room.',1000000001,'Bell needs to answer whoever is at the counter first, because that is what the counter is for.','female',1000000001,12,'Bell wants one person in this building to say the delay was never hers.','Bell needs to stand still long enough to hear what the person at the counter actually came to say.','2026-10-02 13:25:43.419441',12,NULL,NULL,NULL); PRAGMA writable_schema=ON; CREATE TABLE IF NOT EXISTS sqlite_sequence(name,seq); DELETE FROM sqlite_sequence; @@ -437,7 +441,6 @@ INSERT INTO sqlite_sequence VALUES('ruby_llm_models',1000000000); INSERT INTO sqlite_sequence VALUES('ruby_llm_tool_calls',1000000000); INSERT INTO sqlite_sequence VALUES('ruby_llm_usages',1000000000); INSERT INTO sqlite_sequence VALUES('universes',1000000001); -INSERT INTO sqlite_sequence VALUES('characters',1000000002); INSERT INTO sqlite_sequence VALUES('chats',1000000000); INSERT INTO sqlite_sequence VALUES('interactions',1000000000); INSERT INTO sqlite_sequence VALUES('items',1000000001); @@ -472,6 +475,7 @@ INSERT INTO sqlite_sequence VALUES('stories',1000000001); INSERT INTO sqlite_sequence VALUES('system_one_receipts',1000000000); INSERT INTO sqlite_sequence VALUES('world_events',1000000000); INSERT INTO sqlite_sequence VALUES('world_mechanics',1000000000); +INSERT INTO sqlite_sequence VALUES('characters',1000000002); CREATE INDEX "index_characters_scenes_on_character_id_and_scene_id" ON "characters_scenes" ("character_id", "scene_id"); CREATE INDEX "index_characters_scenes_on_scene_id_and_character_id" ON "characters_scenes" ("scene_id", "character_id"); CREATE UNIQUE INDEX "index_players_on_name" ON "players" ("name"); @@ -492,13 +496,6 @@ CREATE INDEX "index_ruby_llm_usages_on_chat_type_and_chat_id" ON "ruby_llm_usage CREATE INDEX "index_ruby_llm_usages_on_message_type_and_message_id" ON "ruby_llm_usages" ("message_type", "message_id"); CREATE INDEX "index_ruby_llm_usages_on_status" ON "ruby_llm_usages" ("status"); CREATE UNIQUE INDEX "index_ruby_llm_v2_backfills_on_task" ON "ruby_llm_v2_backfills" ("task"); -CREATE UNIQUE INDEX "index_characters_on_story_id_and_lower_fullname" ON "characters" (story_id, LOWER(fullname)); -CREATE INDEX "index_characters_on_location_id_and_hostile" ON "characters" ("location_id", "hostile"); -CREATE INDEX "index_characters_on_location_id_and_id" ON "characters" ("location_id", "id"); -CREATE INDEX "index_characters_on_location_id" ON "characters" ("location_id"); -CREATE INDEX "index_characters_on_race_id" ON "characters" ("race_id"); -CREATE INDEX "index_characters_on_story_id_and_is_protagonist" ON "characters" ("story_id", "is_protagonist"); -CREATE INDEX "index_characters_on_story_id" ON "characters" ("story_id"); CREATE INDEX "index_chats_on_character_id" ON "chats" ("character_id"); CREATE INDEX "index_chats_on_player_id" ON "chats" ("player_id"); CREATE INDEX "index_chats_on_conversation_key" ON "chats" ("playthrough_id", "character_id", "purpose"); @@ -634,5 +631,13 @@ CREATE INDEX "index_playthrough_due_endings_on_quest_outcome_id" ON "playthrough CREATE INDEX "index_quest_steps_on_companion_id" ON "quest_steps" ("companion_id"); CREATE UNIQUE INDEX "index_floors_on_location_id" ON "floors" ("location_id"); CREATE INDEX "index_floors_on_down_location_id" ON "floors" ("down_location_id"); +CREATE UNIQUE INDEX "index_characters_on_story_id_and_lower_fullname" ON "characters" (story_id, LOWER(fullname)); +CREATE INDEX "index_characters_on_location_id_and_hostile" ON "characters" ("location_id", "hostile"); +CREATE INDEX "index_characters_on_location_id_and_id" ON "characters" ("location_id", "id"); +CREATE INDEX "index_characters_on_location_id" ON "characters" ("location_id"); +CREATE INDEX "index_characters_on_race_id" ON "characters" ("race_id"); +CREATE INDEX "index_characters_on_story_id_and_is_protagonist" ON "characters" ("story_id", "is_protagonist"); +CREATE INDEX "index_characters_on_story_id" ON "characters" ("story_id"); +CREATE INDEX "index_characters_on_posted_to_id" ON "characters" ("posted_to_id"); PRAGMA writable_schema=OFF; COMMIT; diff --git a/parity/worlds/a-conversation-at-the-gate.sql b/parity/worlds/a-conversation-at-the-gate.sql index b21ae98..fa88586 100644 --- a/parity/worlds/a-conversation-at-the-gate.sql +++ b/parity/worlds/a-conversation-at-the-gate.sql @@ -14,18 +14,6 @@ CREATE TABLE IF NOT EXISTS "ruby_llm_usages" ("id" integer PRIMARY KEY AUTOINCRE CREATE TABLE IF NOT EXISTS "ruby_llm_v2_backfills" ("completed" boolean DEFAULT FALSE NOT NULL, "last_id" integer, "task" varchar NOT NULL); CREATE TABLE IF NOT EXISTS "universes" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "civilizations" text, "created_at" datetime(6) NOT NULL, "economics" text, "geographies" text, "history" text, "physics" text, "politics" text, "religion" text, "technology" text, "updated_at" datetime(6) NOT NULL, "weapons" text, "gravity" varchar, "carrying" varchar); INSERT INTO universes VALUES(1000000001,'A small trading town.','2026-10-02 13:26:11.291907','Goods traded for coins.','A market and its courtyard.','Traders built the market together.','Ordinary physics.','A town council keeps the peace.','The town keeps its own festivals.','Keys, locks and lanterns.','2026-10-02 13:26:11.291907','Wooden staves.',NULL,NULL); -CREATE TABLE IF NOT EXISTS "characters" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "age" integer, "appearance" text, "backstory" text, "conscious_desire" text, "created_at" datetime(6) NOT NULL, "deliberately_absent" boolean DEFAULT FALSE NOT NULL, "desire_pursuit" varchar, "dexterity" integer, "dislikes" text, "fears" text, "fullname" varchar, "hit_die" integer, "hostile" boolean DEFAULT FALSE NOT NULL, "is_companion" boolean, "is_protagonist" boolean DEFAULT FALSE NOT NULL, "level" integer, "likes" text, "location_id" integer, "need_pursuit" varchar, "nickname" varchar, "personality" text, "race_id" integer NOT NULL, "recognized_need" text, "sex" varchar, "story_id" integer NOT NULL, "strength" integer, "unconscious_desire" text, "unrecognized_need" text, "updated_at" datetime(6) NOT NULL, "will" integer, "x" integer, "y" integer, CONSTRAINT "fk_rails_56a213cbe2" -FOREIGN KEY ("race_id") - REFERENCES "races" ("id") -, CONSTRAINT "fk_rails_0b4b445641" -FOREIGN KEY ("location_id") - REFERENCES "locations" ("id") -, CONSTRAINT "fk_rails_6608c248e8" -FOREIGN KEY ("story_id") - REFERENCES "stories" ("id") -); -INSERT INTO characters VALUES(1000000001,30,'A travel-worn coat.','A trader returning to the market.',NULL,'2026-10-02 13:26:11.419682',0,NULL,12,'Needless arguments.','Losing a friend.','Cal',8,0,0,1,10,'A fair bargain.',NULL,NULL,'Cal','Patient and practical.',1000000001,NULL,'male',1000000001,12,NULL,NULL,'2026-10-02 13:26:11.419682',12,NULL,NULL); -INSERT INTO characters VALUES(1000000002,32,'A blue coat and a wooden staff.','A gatekeeper who misunderstood Cal''s arrival.',NULL,'2026-10-02 13:26:11.466046',0,NULL,12,'Deception.','Betraying a trust.','Maren',8,1,0,0,10,'Clear agreements.',1000000001,NULL,'Maren','Willing to listen and keep a promise.',1000000001,NULL,'female',1000000001,12,NULL,NULL,'2026-10-02 13:26:11.466046',12,NULL,NULL); CREATE TABLE IF NOT EXISTS "chats" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "cancelled" boolean DEFAULT FALSE NOT NULL, "character_id" integer, "created_at" datetime(6) NOT NULL, "model_id_string" varchar, "player_id" integer, "playthrough_id" integer, "purpose" varchar, "ruby_llm_model_id" integer, "updated_at" datetime(6) NOT NULL, CONSTRAINT "fk_rails_415520c982" FOREIGN KEY ("playthrough_id") REFERENCES "playthroughs" ("id") @@ -408,6 +396,7 @@ INSERT INTO schema_migrations VALUES('20261025230000'); INSERT INTO schema_migrations VALUES('20261027120117'); INSERT INTO schema_migrations VALUES('20261030120000'); INSERT INTO schema_migrations VALUES('20261030200000'); +INSERT INTO schema_migrations VALUES('20261030230000'); CREATE TABLE IF NOT EXISTS "ar_internal_metadata" ("key" varchar NOT NULL PRIMARY KEY, "value" varchar, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL); INSERT INTO ar_internal_metadata VALUES('environment','test','2026-10-02 13:25:33.143313','2026-10-02 13:25:33.143316'); INSERT INTO ar_internal_metadata VALUES('schema_sha1','072abc2bfb80d111a1808df270e9210911f5e81d','2026-10-02 13:25:33.148224','2026-10-02 13:25:33.148228'); @@ -418,13 +407,28 @@ FOREIGN KEY ("playthrough_id") FOREIGN KEY ("quest_outcome_id") REFERENCES "quest_outcomes" ("id") ); -CREATE TABLE IF NOT EXISTS "floors" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "location_id" integer NOT NULL, "rooms" integer NOT NULL, "danger" varchar, "hazard" varchar, "down_location_id" integer, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, CONSTRAINT "fk_rails_ce8b005cb2" +CREATE TABLE IF NOT EXISTS "floors" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "location_id" integer NOT NULL, "rooms" integer NOT NULL, "danger" varchar, "hazard" varchar, "down_location_id" integer, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, "monster_level" integer, CONSTRAINT "fk_rails_ce8b005cb2" FOREIGN KEY ("location_id") REFERENCES "locations" ("id") ON DELETE CASCADE, CONSTRAINT "fk_rails_8a0268155a" FOREIGN KEY ("down_location_id") REFERENCES "locations" ("id") ON DELETE SET NULL); +CREATE TABLE IF NOT EXISTS "characters" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "age" integer, "appearance" text, "backstory" text, "conscious_desire" text, "created_at" datetime(6) NOT NULL, "deliberately_absent" boolean DEFAULT FALSE NOT NULL, "desire_pursuit" varchar, "dexterity" integer, "dislikes" text, "fears" text, "fullname" varchar, "hit_die" integer, "hostile" boolean DEFAULT FALSE NOT NULL, "is_companion" boolean, "is_protagonist" boolean DEFAULT FALSE NOT NULL, "level" integer, "likes" text, "location_id" integer, "need_pursuit" varchar, "nickname" varchar, "personality" text, "race_id" integer NOT NULL, "recognized_need" text, "sex" varchar, "story_id" integer NOT NULL, "strength" integer, "unconscious_desire" text, "unrecognized_need" text, "updated_at" datetime(6) NOT NULL, "will" integer, "x" integer, "y" integer, "posted_to_id" integer, CONSTRAINT "fk_rails_6608c248e8" +FOREIGN KEY ("story_id") + REFERENCES "stories" ("id") +, CONSTRAINT "fk_rails_0b4b445641" +FOREIGN KEY ("location_id") + REFERENCES "locations" ("id") +, CONSTRAINT "fk_rails_56a213cbe2" +FOREIGN KEY ("race_id") + REFERENCES "races" ("id") +, CONSTRAINT "fk_rails_4c54fc506e" +FOREIGN KEY ("posted_to_id") + REFERENCES "locations" ("id") + ON DELETE SET NULL); +INSERT INTO characters VALUES(1000000001,30,'A travel-worn coat.','A trader returning to the market.',NULL,'2026-10-02 13:26:11.419682',0,NULL,12,'Needless arguments.','Losing a friend.','Cal',8,0,0,1,10,'A fair bargain.',NULL,NULL,'Cal','Patient and practical.',1000000001,NULL,'male',1000000001,12,NULL,NULL,'2026-10-02 13:26:11.419682',12,NULL,NULL,NULL); +INSERT INTO characters VALUES(1000000002,32,'A blue coat and a wooden staff.','A gatekeeper who misunderstood Cal''s arrival.',NULL,'2026-10-02 13:26:11.466046',0,NULL,12,'Deception.','Betraying a trust.','Maren',8,1,0,0,10,'Clear agreements.',1000000001,NULL,'Maren','Willing to listen and keep a promise.',1000000001,NULL,'female',1000000001,12,NULL,NULL,'2026-10-02 13:26:11.466046',12,NULL,NULL,NULL); PRAGMA writable_schema=ON; CREATE TABLE IF NOT EXISTS sqlite_sequence(name,seq); DELETE FROM sqlite_sequence; @@ -437,7 +441,6 @@ INSERT INTO sqlite_sequence VALUES('ruby_llm_models',1000000000); INSERT INTO sqlite_sequence VALUES('ruby_llm_tool_calls',1000000000); INSERT INTO sqlite_sequence VALUES('ruby_llm_usages',1000000000); INSERT INTO sqlite_sequence VALUES('universes',1000000001); -INSERT INTO sqlite_sequence VALUES('characters',1000000002); INSERT INTO sqlite_sequence VALUES('chats',1000000000); INSERT INTO sqlite_sequence VALUES('interactions',1000000000); INSERT INTO sqlite_sequence VALUES('items',1000000001); @@ -472,6 +475,7 @@ INSERT INTO sqlite_sequence VALUES('stories',1000000001); INSERT INTO sqlite_sequence VALUES('system_one_receipts',1000000000); INSERT INTO sqlite_sequence VALUES('world_events',1000000000); INSERT INTO sqlite_sequence VALUES('world_mechanics',1000000000); +INSERT INTO sqlite_sequence VALUES('characters',1000000002); CREATE INDEX "index_characters_scenes_on_character_id_and_scene_id" ON "characters_scenes" ("character_id", "scene_id"); CREATE INDEX "index_characters_scenes_on_scene_id_and_character_id" ON "characters_scenes" ("scene_id", "character_id"); CREATE UNIQUE INDEX "index_players_on_name" ON "players" ("name"); @@ -492,13 +496,6 @@ CREATE INDEX "index_ruby_llm_usages_on_chat_type_and_chat_id" ON "ruby_llm_usage CREATE INDEX "index_ruby_llm_usages_on_message_type_and_message_id" ON "ruby_llm_usages" ("message_type", "message_id"); CREATE INDEX "index_ruby_llm_usages_on_status" ON "ruby_llm_usages" ("status"); CREATE UNIQUE INDEX "index_ruby_llm_v2_backfills_on_task" ON "ruby_llm_v2_backfills" ("task"); -CREATE UNIQUE INDEX "index_characters_on_story_id_and_lower_fullname" ON "characters" (story_id, LOWER(fullname)); -CREATE INDEX "index_characters_on_location_id_and_hostile" ON "characters" ("location_id", "hostile"); -CREATE INDEX "index_characters_on_location_id_and_id" ON "characters" ("location_id", "id"); -CREATE INDEX "index_characters_on_location_id" ON "characters" ("location_id"); -CREATE INDEX "index_characters_on_race_id" ON "characters" ("race_id"); -CREATE INDEX "index_characters_on_story_id_and_is_protagonist" ON "characters" ("story_id", "is_protagonist"); -CREATE INDEX "index_characters_on_story_id" ON "characters" ("story_id"); CREATE INDEX "index_chats_on_character_id" ON "chats" ("character_id"); CREATE INDEX "index_chats_on_player_id" ON "chats" ("player_id"); CREATE INDEX "index_chats_on_conversation_key" ON "chats" ("playthrough_id", "character_id", "purpose"); @@ -634,5 +631,13 @@ CREATE INDEX "index_playthrough_due_endings_on_quest_outcome_id" ON "playthrough CREATE INDEX "index_quest_steps_on_companion_id" ON "quest_steps" ("companion_id"); CREATE UNIQUE INDEX "index_floors_on_location_id" ON "floors" ("location_id"); CREATE INDEX "index_floors_on_down_location_id" ON "floors" ("down_location_id"); +CREATE UNIQUE INDEX "index_characters_on_story_id_and_lower_fullname" ON "characters" (story_id, LOWER(fullname)); +CREATE INDEX "index_characters_on_location_id_and_hostile" ON "characters" ("location_id", "hostile"); +CREATE INDEX "index_characters_on_location_id_and_id" ON "characters" ("location_id", "id"); +CREATE INDEX "index_characters_on_location_id" ON "characters" ("location_id"); +CREATE INDEX "index_characters_on_race_id" ON "characters" ("race_id"); +CREATE INDEX "index_characters_on_story_id_and_is_protagonist" ON "characters" ("story_id", "is_protagonist"); +CREATE INDEX "index_characters_on_story_id" ON "characters" ("story_id"); +CREATE INDEX "index_characters_on_posted_to_id" ON "characters" ("posted_to_id"); PRAGMA writable_schema=OFF; COMMIT; diff --git a/parity/worlds/a-crowded-cellar.sql b/parity/worlds/a-crowded-cellar.sql index 3948b09..41a7890 100644 --- a/parity/worlds/a-crowded-cellar.sql +++ b/parity/worlds/a-crowded-cellar.sql @@ -16,28 +16,6 @@ CREATE TABLE IF NOT EXISTS "ruby_llm_usages" ("id" integer PRIMARY KEY AUTOINCRE CREATE TABLE IF NOT EXISTS "ruby_llm_v2_backfills" ("task" varchar NOT NULL, "last_id" integer, "completed" boolean DEFAULT FALSE NOT NULL); CREATE TABLE IF NOT EXISTS "universes" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "physics" text, "technology" text, "weapons" text, "civilizations" text, "geographies" text, "history" text, "economics" text, "politics" text, "religion" text, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, "gravity" varchar, "carrying" varchar); INSERT INTO universes VALUES(1000000001,'Ordinary physics.','Lanterns, barrels and cart wheels.','Cudgels.','A market county.','A coaching inn on a wet road.','The inn has stood at the crossroads for a century.','Coin for beds, ale and stabling.','The county reeve keeps the road.','Harvest festivals.','2026-10-30 10:43:43.628832','2026-10-30 10:43:43.628832',NULL,NULL); -CREATE TABLE IF NOT EXISTS "characters" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "story_id" integer NOT NULL, "fullname" varchar, "nickname" varchar, "age" integer, "sex" varchar, "personality" text, "appearance" text, "likes" text, "dislikes" text, "fears" text, "backstory" text, "is_companion" boolean, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, "race_id" integer NOT NULL, "is_protagonist" boolean DEFAULT FALSE NOT NULL, "location_id" integer, "deliberately_absent" boolean DEFAULT FALSE NOT NULL, "level" integer, "hit_die" integer, "strength" integer, "dexterity" integer, "will" integer, "hostile" boolean DEFAULT FALSE NOT NULL, "x" integer, "y" integer, "conscious_desire" text, "unconscious_desire" text, "recognized_need" text, "unrecognized_need" text, "desire_pursuit" varchar, "need_pursuit" varchar, CONSTRAINT "fk_rails_56a213cbe2" -FOREIGN KEY ("race_id") - REFERENCES "races" ("id") -, CONSTRAINT "fk_rails_0b4b445641" -FOREIGN KEY ("location_id") - REFERENCES "locations" ("id") -, CONSTRAINT "fk_rails_6608c248e8" -FOREIGN KEY ("story_id") - REFERENCES "stories" ("id") -); -INSERT INTO characters VALUES(1000000001,1000000001,'Nell','Nell',33,'female','Plain-spoken.','A soaked cloak.','A dry corner.','Crowds.','Sleeping in the road.','A tinker caught out by the rain.',0,'2026-10-30 10:43:43.797067','2026-10-30 10:43:43.797067',1000000001,1,NULL,0,10,8,12,12,12,0,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL); -INSERT INTO characters VALUES(1000000002,1000000001,'Hob Allard','Hob',44,'male','Gruff and fair.','A stained apron.','A full house.','Unpaid tabs.','A cold hearth.','The innkeeper, who has run the Lantern for twenty years.',0,'2026-10-30 10:43:43.829152','2026-10-30 10:43:43.829152',1000000001,0,1000000001,0,3,8,12,12,12,0,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL); -INSERT INTO characters VALUES(1000000003,1000000001,'Ida Fenn','Ida',27,'female','Quick and wry.','Rolled sleeves.','Tips.','Spilled ale.','Deep water.','A barmaid saving for a boat of her own.',0,'2026-10-30 10:43:43.834649','2026-10-30 10:43:43.834649',1000000001,0,1000000001,0,3,8,12,12,12,0,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL); -INSERT INTO characters VALUES(1000000004,1000000001,'Moss Tarrow','Moss',51,'male','Slow and certain.','A floury shirt.','Fresh bread.','Haste.','Fire in the flue.','The cook, who came with the inn.',0,'2026-10-30 10:43:43.839521','2026-10-30 10:43:43.839521',1000000001,0,1000000002,0,3,8,12,12,12,0,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL); -INSERT INTO characters VALUES(1000000005,1000000001,'Pell Quill','Pell',19,'non-binary','Eager and clumsy.','Wet cuffs.','Praise.','Scouring pots.','The cook''s temper.','A scullion learning the trade.',0,'2026-10-30 10:43:43.851223','2026-10-30 10:43:43.851223',1000000001,0,1000000002,0,3,8,12,12,12,0,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL); -INSERT INTO characters VALUES(1000000006,1000000001,'Rue Ambry','Rue',36,'female','Tired and patient.','A dusty coat.','A hot meal.','Delays.','A lame horse.','A carter waiting for her supper.',0,'2026-10-30 10:43:43.857509','2026-10-30 10:43:43.857509',1000000001,0,1000000002,0,3,8,12,12,12,0,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL); -INSERT INTO characters VALUES(1000000007,1000000001,'Tam Brisk','Tam',23,'male','Cheerful and loud.','Straw in his hair.','Horses.','Tight fists.','Being kicked.','The ostler who minds the guests'' horses.',0,'2026-10-30 10:43:43.861686','2026-10-30 10:43:43.861686',1000000001,0,1000000003,0,3,8,12,12,12,0,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL); -INSERT INTO characters VALUES(1000000008,1000000001,'Vey Corran','Vey',40,'female','Watchful.','A long oilskin.','Open roads.','Fences.','Wolves.','A drover passing through with her herd.',0,'2026-10-30 10:43:43.865582','2026-10-30 10:43:43.865582',1000000001,0,1000000003,0,3,8,12,12,12,0,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL); -INSERT INTO characters VALUES(1000000009,1000000001,'Wick Dunmore','Wick',62,'male','Talkative.','A patched pack.','A good story.','Paying for a bed.','Dying alone.','An old pedlar sleeping in the hay to save a coin.',0,'2026-10-30 10:43:43.869814','2026-10-30 10:43:43.869814',1000000001,0,1000000003,0,3,8,12,12,12,0,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL); -INSERT INTO characters VALUES(1000000010,1000000001,'Yara Holt','Yara',31,'female','Restless.','Riding boots.','Speed.','Waiting.','A lost letter.','A courier resting between runs.',0,'2026-10-30 10:43:43.874924','2026-10-30 10:43:43.874924',1000000001,0,1000000004,0,3,8,12,12,12,0,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL); -INSERT INTO characters VALUES(1000000011,1000000001,'Zeb Marrow','Zeb',29,'male','Easygoing.','A green waistcoat.','Applause.','Silence.','A broken string.','A fiddler hired for the evening.',0,'2026-10-30 10:43:43.878653','2026-10-30 10:43:43.878653',1000000001,0,1000000004,0,3,8,12,12,12,0,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL); -INSERT INTO characters VALUES(1000000012,1000000001,'Ash Penrose','Ash',34,'trans man','Precise.','Ink on his fingers.','Straight lines.','Muddle.','Getting lost.','A surveyor laid up by the rain.',0,'2026-10-30 10:43:43.882211','2026-10-30 10:43:43.882211',1000000001,0,1000000004,0,3,8,12,12,12,0,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL); CREATE TABLE IF NOT EXISTS "chats" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "model_id_string" varchar, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, "ruby_llm_model_id" integer, "purpose" varchar, "playthrough_id" integer, "character_id" integer, "cancelled" boolean DEFAULT FALSE NOT NULL, "player_id" integer, CONSTRAINT "fk_rails_415520c982" FOREIGN KEY ("playthrough_id") REFERENCES "playthroughs" ("id") @@ -436,16 +414,42 @@ INSERT INTO schema_migrations VALUES('20250822184651'); INSERT INTO schema_migrations VALUES('20250822184650'); INSERT INTO schema_migrations VALUES('20250822184649'); INSERT INTO schema_migrations VALUES('20261030200000'); +INSERT INTO schema_migrations VALUES('20261030230000'); CREATE TABLE IF NOT EXISTS "ar_internal_metadata" ("key" varchar NOT NULL PRIMARY KEY, "value" varchar, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL); INSERT INTO ar_internal_metadata VALUES('environment','test','2026-10-30 10:43:42.317995','2026-10-30 10:43:42.317999'); INSERT INTO ar_internal_metadata VALUES('schema_sha1','ebdda9bf12dc2d5493a426a2e64de0ed551652b9','2026-10-30 10:43:42.324377','2026-10-30 10:43:42.324380'); -CREATE TABLE IF NOT EXISTS "floors" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "location_id" integer NOT NULL, "rooms" integer NOT NULL, "danger" varchar, "hazard" varchar, "down_location_id" integer, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, CONSTRAINT "fk_rails_ce8b005cb2" +CREATE TABLE IF NOT EXISTS "floors" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "location_id" integer NOT NULL, "rooms" integer NOT NULL, "danger" varchar, "hazard" varchar, "down_location_id" integer, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, "monster_level" integer, CONSTRAINT "fk_rails_ce8b005cb2" FOREIGN KEY ("location_id") REFERENCES "locations" ("id") ON DELETE CASCADE, CONSTRAINT "fk_rails_8a0268155a" FOREIGN KEY ("down_location_id") REFERENCES "locations" ("id") ON DELETE SET NULL); +CREATE TABLE IF NOT EXISTS "characters" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "story_id" integer NOT NULL, "fullname" varchar, "nickname" varchar, "age" integer, "sex" varchar, "personality" text, "appearance" text, "likes" text, "dislikes" text, "fears" text, "backstory" text, "is_companion" boolean, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, "race_id" integer NOT NULL, "is_protagonist" boolean DEFAULT FALSE NOT NULL, "location_id" integer, "deliberately_absent" boolean DEFAULT FALSE NOT NULL, "level" integer, "hit_die" integer, "strength" integer, "dexterity" integer, "will" integer, "hostile" boolean DEFAULT FALSE NOT NULL, "x" integer, "y" integer, "conscious_desire" text, "unconscious_desire" text, "recognized_need" text, "unrecognized_need" text, "desire_pursuit" varchar, "need_pursuit" varchar, "posted_to_id" integer, CONSTRAINT "fk_rails_6608c248e8" +FOREIGN KEY ("story_id") + REFERENCES "stories" ("id") +, CONSTRAINT "fk_rails_0b4b445641" +FOREIGN KEY ("location_id") + REFERENCES "locations" ("id") +, CONSTRAINT "fk_rails_56a213cbe2" +FOREIGN KEY ("race_id") + REFERENCES "races" ("id") +, CONSTRAINT "fk_rails_4c54fc506e" +FOREIGN KEY ("posted_to_id") + REFERENCES "locations" ("id") + ON DELETE SET NULL); +INSERT INTO characters VALUES(1000000001,1000000001,'Nell','Nell',33,'female','Plain-spoken.','A soaked cloak.','A dry corner.','Crowds.','Sleeping in the road.','A tinker caught out by the rain.',0,'2026-10-30 10:43:43.797067','2026-10-30 10:43:43.797067',1000000001,1,NULL,0,10,8,12,12,12,0,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL); +INSERT INTO characters VALUES(1000000002,1000000001,'Hob Allard','Hob',44,'male','Gruff and fair.','A stained apron.','A full house.','Unpaid tabs.','A cold hearth.','The innkeeper, who has run the Lantern for twenty years.',0,'2026-10-30 10:43:43.829152','2026-10-30 10:43:43.829152',1000000001,0,1000000001,0,3,8,12,12,12,0,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL); +INSERT INTO characters VALUES(1000000003,1000000001,'Ida Fenn','Ida',27,'female','Quick and wry.','Rolled sleeves.','Tips.','Spilled ale.','Deep water.','A barmaid saving for a boat of her own.',0,'2026-10-30 10:43:43.834649','2026-10-30 10:43:43.834649',1000000001,0,1000000001,0,3,8,12,12,12,0,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL); +INSERT INTO characters VALUES(1000000004,1000000001,'Moss Tarrow','Moss',51,'male','Slow and certain.','A floury shirt.','Fresh bread.','Haste.','Fire in the flue.','The cook, who came with the inn.',0,'2026-10-30 10:43:43.839521','2026-10-30 10:43:43.839521',1000000001,0,1000000002,0,3,8,12,12,12,0,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL); +INSERT INTO characters VALUES(1000000005,1000000001,'Pell Quill','Pell',19,'non-binary','Eager and clumsy.','Wet cuffs.','Praise.','Scouring pots.','The cook''s temper.','A scullion learning the trade.',0,'2026-10-30 10:43:43.851223','2026-10-30 10:43:43.851223',1000000001,0,1000000002,0,3,8,12,12,12,0,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL); +INSERT INTO characters VALUES(1000000006,1000000001,'Rue Ambry','Rue',36,'female','Tired and patient.','A dusty coat.','A hot meal.','Delays.','A lame horse.','A carter waiting for her supper.',0,'2026-10-30 10:43:43.857509','2026-10-30 10:43:43.857509',1000000001,0,1000000002,0,3,8,12,12,12,0,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL); +INSERT INTO characters VALUES(1000000007,1000000001,'Tam Brisk','Tam',23,'male','Cheerful and loud.','Straw in his hair.','Horses.','Tight fists.','Being kicked.','The ostler who minds the guests'' horses.',0,'2026-10-30 10:43:43.861686','2026-10-30 10:43:43.861686',1000000001,0,1000000003,0,3,8,12,12,12,0,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL); +INSERT INTO characters VALUES(1000000008,1000000001,'Vey Corran','Vey',40,'female','Watchful.','A long oilskin.','Open roads.','Fences.','Wolves.','A drover passing through with her herd.',0,'2026-10-30 10:43:43.865582','2026-10-30 10:43:43.865582',1000000001,0,1000000003,0,3,8,12,12,12,0,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL); +INSERT INTO characters VALUES(1000000009,1000000001,'Wick Dunmore','Wick',62,'male','Talkative.','A patched pack.','A good story.','Paying for a bed.','Dying alone.','An old pedlar sleeping in the hay to save a coin.',0,'2026-10-30 10:43:43.869814','2026-10-30 10:43:43.869814',1000000001,0,1000000003,0,3,8,12,12,12,0,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL); +INSERT INTO characters VALUES(1000000010,1000000001,'Yara Holt','Yara',31,'female','Restless.','Riding boots.','Speed.','Waiting.','A lost letter.','A courier resting between runs.',0,'2026-10-30 10:43:43.874924','2026-10-30 10:43:43.874924',1000000001,0,1000000004,0,3,8,12,12,12,0,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL); +INSERT INTO characters VALUES(1000000011,1000000001,'Zeb Marrow','Zeb',29,'male','Easygoing.','A green waistcoat.','Applause.','Silence.','A broken string.','A fiddler hired for the evening.',0,'2026-10-30 10:43:43.878653','2026-10-30 10:43:43.878653',1000000001,0,1000000004,0,3,8,12,12,12,0,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL); +INSERT INTO characters VALUES(1000000012,1000000001,'Ash Penrose','Ash',34,'trans man','Precise.','Ink on his fingers.','Straight lines.','Muddle.','Getting lost.','A surveyor laid up by the rain.',0,'2026-10-30 10:43:43.882211','2026-10-30 10:43:43.882211',1000000001,0,1000000004,0,3,8,12,12,12,0,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL); PRAGMA writable_schema=ON; CREATE TABLE IF NOT EXISTS sqlite_sequence(name,seq); DELETE FROM sqlite_sequence; @@ -459,7 +463,6 @@ INSERT INTO sqlite_sequence VALUES('ruby_llm_models',1000000000); INSERT INTO sqlite_sequence VALUES('ruby_llm_tool_calls',1000000000); INSERT INTO sqlite_sequence VALUES('ruby_llm_usages',1000000000); INSERT INTO sqlite_sequence VALUES('universes',1000000001); -INSERT INTO sqlite_sequence VALUES('characters',1000000012); INSERT INTO sqlite_sequence VALUES('chats',1000000000); INSERT INTO sqlite_sequence VALUES('interactions',1000000000); INSERT INTO sqlite_sequence VALUES('items',1000000000); @@ -495,6 +498,7 @@ INSERT INTO sqlite_sequence VALUES('stories',1000000001); INSERT INTO sqlite_sequence VALUES('system_one_receipts',1000000000); INSERT INTO sqlite_sequence VALUES('world_events',1000000000); INSERT INTO sqlite_sequence VALUES('world_mechanics',1000000000); +INSERT INTO sqlite_sequence VALUES('characters',1000000012); CREATE INDEX "index_characters_scenes_on_character_id_and_scene_id" ON "characters_scenes" ("character_id", "scene_id"); CREATE INDEX "index_characters_scenes_on_scene_id_and_character_id" ON "characters_scenes" ("scene_id", "character_id"); CREATE UNIQUE INDEX "index_players_on_name" ON "players" ("name"); @@ -516,13 +520,6 @@ CREATE INDEX "index_ruby_llm_usages_on_chat_type_and_chat_id" ON "ruby_llm_usage CREATE INDEX "index_ruby_llm_usages_on_message_type_and_message_id" ON "ruby_llm_usages" ("message_type", "message_id"); CREATE INDEX "index_ruby_llm_usages_on_status" ON "ruby_llm_usages" ("status"); CREATE UNIQUE INDEX "index_ruby_llm_v2_backfills_on_task" ON "ruby_llm_v2_backfills" ("task"); -CREATE UNIQUE INDEX "index_characters_on_story_id_and_lower_fullname" ON "characters" (story_id, LOWER(fullname)); -CREATE INDEX "index_characters_on_location_id_and_hostile" ON "characters" ("location_id", "hostile"); -CREATE INDEX "index_characters_on_location_id_and_id" ON "characters" ("location_id", "id"); -CREATE INDEX "index_characters_on_location_id" ON "characters" ("location_id"); -CREATE INDEX "index_characters_on_race_id" ON "characters" ("race_id"); -CREATE INDEX "index_characters_on_story_id_and_is_protagonist" ON "characters" ("story_id", "is_protagonist"); -CREATE INDEX "index_characters_on_story_id" ON "characters" ("story_id"); CREATE INDEX "index_chats_on_character_id" ON "chats" ("character_id"); CREATE INDEX "index_chats_on_player_id" ON "chats" ("player_id"); CREATE INDEX "index_chats_on_conversation_key" ON "chats" ("playthrough_id", "character_id", "purpose"); @@ -658,5 +655,13 @@ CREATE UNIQUE INDEX "index_world_mechanics_on_story_id_and_name" ON "world_mecha CREATE INDEX "index_world_mechanics_on_story_id" ON "world_mechanics" ("story_id"); CREATE UNIQUE INDEX "index_floors_on_location_id" ON "floors" ("location_id"); CREATE INDEX "index_floors_on_down_location_id" ON "floors" ("down_location_id"); +CREATE UNIQUE INDEX "index_characters_on_story_id_and_lower_fullname" ON "characters" (story_id, LOWER(fullname)); +CREATE INDEX "index_characters_on_location_id_and_hostile" ON "characters" ("location_id", "hostile"); +CREATE INDEX "index_characters_on_location_id_and_id" ON "characters" ("location_id", "id"); +CREATE INDEX "index_characters_on_location_id" ON "characters" ("location_id"); +CREATE INDEX "index_characters_on_race_id" ON "characters" ("race_id"); +CREATE INDEX "index_characters_on_story_id_and_is_protagonist" ON "characters" ("story_id", "is_protagonist"); +CREATE INDEX "index_characters_on_story_id" ON "characters" ("story_id"); +CREATE INDEX "index_characters_on_posted_to_id" ON "characters" ("posted_to_id"); PRAGMA writable_schema=OFF; COMMIT; diff --git a/parity/worlds/a-deeper-floor.sql b/parity/worlds/a-deeper-floor.sql new file mode 100644 index 0000000..9014fab --- /dev/null +++ b/parity/worlds/a-deeper-floor.sql @@ -0,0 +1,651 @@ +PRAGMA foreign_keys=OFF; +BEGIN TRANSACTION; +CREATE TABLE IF NOT EXISTS "characters_scenes" ("character_id" integer NOT NULL, "scene_id" integer NOT NULL); +INSERT INTO characters_scenes VALUES(1000000001,1000000001); +CREATE TABLE IF NOT EXISTS "lab_exits_vantages" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "world" varchar NOT NULL, "name" varchar NOT NULL, "teaser" text NOT NULL, "reached_from" varchar, "danger" varchar, "absent" text, "expects_inside_quantifier" varchar, "expects_population" text, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL); +CREATE TABLE IF NOT EXISTS "lab_realization_kinds" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "world" varchar NOT NULL, "name" varchar NOT NULL, "teaser" text NOT NULL, "reached_from" varchar, "inside" varchar, "danger" varchar, "population" varchar, "expects_inside" text, "expects_population" text, "expects_storeys_above" text, "expects_storeys_below" text, "expects_danger" text, "expects_gradient" text, "expects_hazard" text, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL); +CREATE TABLE IF NOT EXISTS "players" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "name" varchar NOT NULL, "token_digest" varchar NOT NULL, "monthly_limit_usd" decimal(10,4) DEFAULT 1.0 NOT NULL, "revoked_at" datetime(6), "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL); +CREATE TABLE IF NOT EXISTS "playthrough_visits" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "playthrough_id" integer NOT NULL, "location_id" integer NOT NULL, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL); +CREATE TABLE IF NOT EXISTS "refused_lines" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "typed" text NOT NULL, "state" json NOT NULL, "answers" json, "reading" json NOT NULL, "act_confidence" float, "thing_confidence" float, "refusal" json NOT NULL, "place" varchar, "weights" varchar, "playthrough_id" integer, "location_id" integer, "status" varchar DEFAULT 'new' NOT NULL, "label" json, "labelled_at" datetime(6), "note" text, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL); +CREATE TABLE IF NOT EXISTS "ruby_llm_batches" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "provider_batch_id" varchar NOT NULL, "provider" varchar NOT NULL, "status" varchar NOT NULL, "raw_status" varchar, "completed" boolean DEFAULT FALSE NOT NULL, "chat_type" varchar, "batch_protocol" varchar, "chat_ids" json DEFAULT '[]', "request_counts" json, "reported_cost" json, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL); +CREATE TABLE IF NOT EXISTS "ruby_llm_models" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "model_id" varchar NOT NULL, "name" varchar NOT NULL, "provider" varchar NOT NULL, "family" varchar, "model_created_at" datetime(6), "context_window" integer, "max_output_tokens" integer, "knowledge_cutoff" date, "modalities" json DEFAULT '{}', "capabilities" json DEFAULT '[]', "pricing" json DEFAULT '{}', "metadata" json DEFAULT '{}', "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, "unlisted_at" datetime(6)); +CREATE TABLE IF NOT EXISTS "ruby_llm_tool_calls" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "message_id" integer NOT NULL, "tool_call_id" varchar NOT NULL, "name" varchar NOT NULL, "arguments" json DEFAULT '{}', "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, "message_type" varchar NOT NULL, "result_type" varchar, "result_id" integer, "thought_signature" text, "approval" varchar, "remote" boolean DEFAULT FALSE NOT NULL); +CREATE TABLE IF NOT EXISTS "ruby_llm_usages" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "chat_type" varchar NOT NULL, "chat_id" integer NOT NULL, "message_type" varchar, "message_id" integer, "operation" varchar NOT NULL, "provider" varchar NOT NULL, "model" varchar NOT NULL, "status" varchar NOT NULL, "input_tokens" integer, "output_tokens" integer, "cache_read_tokens" integer, "cache_write_tokens" integer, "thinking_tokens" integer, "input_cost" decimal(16,10), "output_cost" decimal(16,10), "cache_read_cost" decimal(16,10), "cache_write_cost" decimal(16,10), "thinking_cost" decimal(16,10), "total_cost" decimal(16,10), "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, "latency_ms" integer, CONSTRAINT chk_rails_71abd85d6e CHECK (operation IN ('chat', 'embedding', 'moderation', 'image', 'speech', 'transcription', 'ocr', 'rerank')), CONSTRAINT chk_rails_f50895962a CHECK (status IN ('pending', 'succeeded', 'failed', 'cancelled'))); +CREATE TABLE IF NOT EXISTS "ruby_llm_v2_backfills" ("task" varchar NOT NULL, "last_id" integer, "completed" boolean DEFAULT FALSE NOT NULL); +CREATE TABLE IF NOT EXISTS "universes" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "physics" text, "technology" text, "weapons" text, "civilizations" text, "geographies" text, "history" text, "economics" text, "politics" text, "religion" text, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, "gravity" varchar, "carrying" varchar); +INSERT INTO universes VALUES(1000000001,'Ordinary physics.','Lanterns, iron keys and hand tools.','Short blades and cudgels.','Delvers who sell what they bring up.','A dungeon cut down into black stone under a lantern-lit landing.','The dungeon was cut long ago by people nobody remembers.','Coin for what comes up from below.','Nobody governs the dungeon.','Delvers leave a coin on the landing for luck.','2026-10-30 13:44:46.628920','2026-10-30 13:44:46.628920',NULL,NULL); +CREATE TABLE IF NOT EXISTS "characters" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "story_id" integer NOT NULL, "fullname" varchar, "nickname" varchar, "age" integer, "sex" varchar, "personality" text, "appearance" text, "likes" text, "dislikes" text, "fears" text, "backstory" text, "is_companion" boolean, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, "race_id" integer NOT NULL, "is_protagonist" boolean DEFAULT FALSE NOT NULL, "location_id" integer, "deliberately_absent" boolean DEFAULT FALSE NOT NULL, "level" integer, "hit_die" integer, "strength" integer, "dexterity" integer, "will" integer, "hostile" boolean DEFAULT FALSE NOT NULL, "x" integer, "y" integer, "conscious_desire" text, "unconscious_desire" text, "recognized_need" text, "unrecognized_need" text, "desire_pursuit" varchar, "need_pursuit" varchar, "posted_to_id" integer, CONSTRAINT "fk_rails_56a213cbe2" +FOREIGN KEY ("race_id") + REFERENCES "races" ("id") +, CONSTRAINT "fk_rails_0b4b445641" +FOREIGN KEY ("location_id") + REFERENCES "locations" ("id") +, CONSTRAINT "fk_rails_4c54fc506e" +FOREIGN KEY ("posted_to_id") + REFERENCES "locations" ("id") + ON DELETE SET NULL, CONSTRAINT "fk_rails_6608c248e8" +FOREIGN KEY ("story_id") + REFERENCES "stories" ("id") +); +INSERT INTO characters VALUES(1000000001,1000000001,'Odile','Odile',29,'female','Careful and stubborn.','A lantern on a pole and a short blade.','A clear way back.','Doors she cannot open.','The dark below the first floor.','A delver who has never been below the first floor.',0,'2026-10-30 13:44:46.796365','2026-10-30 13:44:46.796365',1000000001,1,NULL,0,3,8,12,12,12,0,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL); +CREATE TABLE IF NOT EXISTS "chats" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "model_id_string" varchar, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, "ruby_llm_model_id" integer, "purpose" varchar, "playthrough_id" integer, "character_id" integer, "cancelled" boolean DEFAULT FALSE NOT NULL, "player_id" integer, CONSTRAINT "fk_rails_415520c982" +FOREIGN KEY ("playthrough_id") + REFERENCES "playthroughs" ("id") +, CONSTRAINT "fk_rails_5c87bd0780" +FOREIGN KEY ("character_id") + REFERENCES "characters" ("id") +, CONSTRAINT "fk_rails_ea73c47d5b" +FOREIGN KEY ("player_id") + REFERENCES "players" ("id") +, CONSTRAINT "fk_rails_d8a12df187" +FOREIGN KEY ("ruby_llm_model_id") + REFERENCES "ruby_llm_models" ("id") +); +CREATE TABLE IF NOT EXISTS "floors" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "location_id" integer NOT NULL, "rooms" integer NOT NULL, "danger" varchar, "hazard" varchar, "down_location_id" integer, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, "monster_level" integer, CONSTRAINT "fk_rails_8a0268155a" +FOREIGN KEY ("down_location_id") + REFERENCES "locations" ("id") + ON DELETE SET NULL, CONSTRAINT "fk_rails_ce8b005cb2" +FOREIGN KEY ("location_id") + REFERENCES "locations" ("id") + ON DELETE CASCADE); +INSERT INTO floors VALUES(1000000001,1000000002,1,'uneasy','none',NULL,'2026-10-30 13:44:46.699801','2026-10-30 13:44:46.726626',NULL); +INSERT INTO floors VALUES(1000000002,1000000003,1,'uneasy','none',NULL,'2026-10-30 13:44:46.717364','2026-10-30 13:44:46.730299',4); +CREATE TABLE IF NOT EXISTS "interactions" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "character_id" integer NOT NULL, "scene_id" integer, "location_id" integer, "pre_thought" text, "pre_feeling" text, "action" text, "post_feeling" text, "post_thought" text, "inner_resolution" text, "summary" text, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, "user_input" text, "engine_action" varchar, "action_status" varchar, "action_fact" text, CONSTRAINT "fk_rails_294d4ae9f8" +FOREIGN KEY ("location_id") + REFERENCES "locations" ("id") +, CONSTRAINT "fk_rails_6b73a2cd17" +FOREIGN KEY ("character_id") + REFERENCES "characters" ("id") +, CONSTRAINT "fk_rails_b73583a008" +FOREIGN KEY ("scene_id") + REFERENCES "scenes" ("id") +); +CREATE TABLE IF NOT EXISTS "items" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "character_id" integer, "name" varchar, "description" text, "properties" text, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, "location_id" integer, "playthrough_id" integer, "readable" boolean DEFAULT FALSE NOT NULL, "inscription" text, "template_id" integer, "bulk" varchar DEFAULT 'handy' NOT NULL, "x" integer, "y" integer, "use_kind" varchar DEFAULT 'ordinary' NOT NULL, "combustible" boolean DEFAULT FALSE NOT NULL, "disposition" varchar DEFAULT 'intact' NOT NULL, "fragility" varchar DEFAULT 'sturdy' NOT NULL, "tier" varchar DEFAULT 'portable' NOT NULL, "holds" varchar, "within_id" integer, "how" varchar, "kit_key" varchar, "noticed_at" datetime(6), "searched_at" datetime(6), "hold" varchar, "material" varchar, "wear" varchar, "worn" boolean DEFAULT FALSE NOT NULL, CONSTRAINT "fk_rails_e8ed83a2e6" +FOREIGN KEY ("location_id") + REFERENCES "locations" ("id") +, CONSTRAINT "fk_rails_35423c7ef8" +FOREIGN KEY ("character_id") + REFERENCES "characters" ("id") +, CONSTRAINT "fk_rails_5248e92099" +FOREIGN KEY ("playthrough_id") + REFERENCES "playthroughs" ("id") +); +CREATE TABLE IF NOT EXISTS "lab_exits_judgements" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "vantage_id" integer NOT NULL, "name" varchar NOT NULL, "name_key" varchar NOT NULL, "expects_inside" text, "expects_population" text, "verdict" varchar, "aspects" text, "note" text, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, CONSTRAINT "fk_rails_cbf2b734ae" +FOREIGN KEY ("vantage_id") + REFERENCES "lab_exits_vantages" ("id") +); +CREATE TABLE IF NOT EXISTS "lab_exits_samples" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "vantage_id" integer NOT NULL, "row" json DEFAULT '{}' NOT NULL, "verdict" varchar, "aspects" text, "note" text, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, CONSTRAINT "fk_rails_3cc36a20a9" +FOREIGN KEY ("vantage_id") + REFERENCES "lab_exits_vantages" ("id") +); +CREATE TABLE IF NOT EXISTS "lab_realization_samples" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "kind_id" integer NOT NULL, "row" json DEFAULT '{}' NOT NULL, "verdict" varchar, "aspects" text, "note" text, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, CONSTRAINT "fk_rails_7bb6f06ff4" +FOREIGN KEY ("kind_id") + REFERENCES "lab_realization_kinds" ("id") +); +CREATE TABLE IF NOT EXISTS "location_connections" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "location_id" integer NOT NULL, "connected_location_id" integer NOT NULL, "distance" text, "time_to_travel" text, "travel_method" text, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, "hazard" varchar, "hazard_die" integer, "barrier" varchar DEFAULT 'open' NOT NULL, "key_template_id" integer, CONSTRAINT "fk_rails_99792c7943" +FOREIGN KEY ("location_id") + REFERENCES "locations" ("id") +, CONSTRAINT "fk_rails_34dc49debe" +FOREIGN KEY ("key_template_id") + REFERENCES "items" ("id") + ON DELETE SET NULL, CONSTRAINT "fk_rails_37a61eef17" +FOREIGN KEY ("connected_location_id") + REFERENCES "locations" ("id") +); +INSERT INTO location_connections VALUES(1000000001,1000000001,1000000002,'adjacent','about a minute','taking stairs','2026-10-30 13:44:46.887731','2026-10-30 13:44:46.887731',NULL,NULL,'open',NULL); +INSERT INTO location_connections VALUES(1000000002,1000000002,1000000001,'adjacent','about a minute','taking stairs','2026-10-30 13:44:46.895110','2026-10-30 13:44:46.895110',NULL,NULL,'open',NULL); +INSERT INTO location_connections VALUES(1000000003,1000000001,1000000003,'adjacent','about a minute','taking stairs','2026-10-30 13:44:46.896447','2026-10-30 13:44:46.896447',NULL,NULL,'open',NULL); +INSERT INTO location_connections VALUES(1000000004,1000000003,1000000001,'adjacent','about a minute','taking stairs','2026-10-30 13:44:46.898314','2026-10-30 13:44:46.898314',NULL,NULL,'open',NULL); +CREATE TABLE IF NOT EXISTS "locations" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "story_id" integer NOT NULL, "name" varchar, "description" text, "lore" text, "last_protagonist_visit" datetime(6), "parent_location_id" integer, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, "detail_level" varchar DEFAULT 'stub' NOT NULL, "teaser" text, "mobile" boolean DEFAULT FALSE NOT NULL, "danger" varchar DEFAULT 'safe' NOT NULL, "hazard" varchar, "hazard_die" integer, "x" integer, "y" integer, "z" integer, "width" integer, "depth" integer, "population" varchar, "generation_checkpoint" json, "surface" varchar, "kind" varchar, "density" varchar, CONSTRAINT "fk_rails_5bc98acf09" +FOREIGN KEY ("parent_location_id") + REFERENCES "locations" ("id") +, CONSTRAINT "fk_rails_fedd9b21a0" +FOREIGN KEY ("story_id") + REFERENCES "stories" ("id") +); +INSERT INTO locations VALUES(1000000001,1000000001,'The Landing','A square landing of black stone lit by one lantern, a stair going down on either side.','Delvers gather here before they go down.',NULL,NULL,'2026-10-30 13:44:46.673315','2026-10-30 13:44:46.673315','realized','A lantern-lit landing.',0,'safe',NULL,NULL,NULL,NULL,NULL,NULL,NULL,'nobody',NULL,NULL,NULL,NULL); +INSERT INTO locations VALUES(1000000002,1000000001,'The Upper Floor',NULL,NULL,NULL,NULL,'2026-10-30 13:44:46.692831','2026-10-30 13:44:46.692831','stub','The upper level of the dungeon, one chamber deep.',0,'safe',NULL,NULL,NULL,NULL,NULL,6,6,NULL,NULL,NULL,'warren',NULL); +INSERT INTO locations VALUES(1000000003,1000000001,'The Bottom Floor',NULL,NULL,NULL,NULL,'2026-10-30 13:44:46.711370','2026-10-30 13:44:46.711370','stub','The bottom level of the dungeon, one chamber deep.',0,'safe',NULL,NULL,NULL,NULL,NULL,6,6,NULL,NULL,NULL,'warren',NULL); +CREATE TABLE IF NOT EXISTS "locations_world_events" ("location_id" integer NOT NULL, "world_event_id" integer NOT NULL, CONSTRAINT "fk_rails_861146f35c" +FOREIGN KEY ("location_id") + REFERENCES "locations" ("id") +, CONSTRAINT "fk_rails_16317bfdf9" +FOREIGN KEY ("world_event_id") + REFERENCES "world_events" ("id") +); +CREATE TABLE IF NOT EXISTS "messages" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "chat_id" integer NOT NULL, "role" varchar, "content" text, "model_id_string" varchar, "input_tokens" integer, "output_tokens" integer, "tool_call_id" integer, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, "model_id" integer, "scene_id" integer, "content_raw" json, "cache_until_here" boolean DEFAULT FALSE NOT NULL, "finish_reason" varchar, "citations" json, "server_tool_calls" json, "raw_content" json, "raw_reasoning" json, CONSTRAINT "fk_rails_c02b47ad97" +FOREIGN KEY ("model_id") + REFERENCES "ruby_llm_models" ("id") +, CONSTRAINT "fk_rails_0f670de7ba" +FOREIGN KEY ("chat_id") + REFERENCES "chats" ("id") +, CONSTRAINT "fk_rails_5c4d747b35" +FOREIGN KEY ("scene_id") + REFERENCES "scenes" ("id") +); +CREATE TABLE IF NOT EXISTS "playthrough_beats" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "playthrough_id" integer NOT NULL, "quest_step_id" integer NOT NULL, "reached_at" datetime(6) NOT NULL, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, CONSTRAINT "fk_rails_d479d5ffac" +FOREIGN KEY ("playthrough_id") + REFERENCES "playthroughs" ("id") +, CONSTRAINT "fk_rails_c536cc5a9f" +FOREIGN KEY ("quest_step_id") + REFERENCES "quest_steps" ("id") +); +CREATE TABLE IF NOT EXISTS "playthrough_blows" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "playthrough_id" integer NOT NULL, "attacker_id" integer NOT NULL, "target_id" integer NOT NULL, "location_id" integer NOT NULL, "damage" integer NOT NULL, "hp_after" integer NOT NULL, "round" integer NOT NULL, "sequence" integer NOT NULL, "story_timestamp" datetime(6) NOT NULL, "scene_id" integer, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, CONSTRAINT "fk_rails_f0e8a189c5" +FOREIGN KEY ("playthrough_id") + REFERENCES "playthroughs" ("id") +, CONSTRAINT "fk_rails_9a2b0679db" +FOREIGN KEY ("target_id") + REFERENCES "characters" ("id") +, CONSTRAINT "fk_rails_d02f38aa6d" +FOREIGN KEY ("attacker_id") + REFERENCES "characters" ("id") +, CONSTRAINT "fk_rails_104e085e3d" +FOREIGN KEY ("location_id") + REFERENCES "locations" ("id") +, CONSTRAINT "fk_rails_83e55c02a1" +FOREIGN KEY ("scene_id") + REFERENCES "scenes" ("id") +); +CREATE TABLE IF NOT EXISTS "playthrough_commands" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "playthrough_id" integer NOT NULL, "request_token" varchar NOT NULL, "command" text NOT NULL, "status" varchar DEFAULT 'pending' NOT NULL, "result_scene_id" integer, "refusal" json DEFAULT '{}' NOT NULL, "error_kind" varchar, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, "journal" json DEFAULT '{}' NOT NULL, CONSTRAINT "fk_rails_53f23eb49f" +FOREIGN KEY ("playthrough_id") + REFERENCES "playthroughs" ("id") +, CONSTRAINT "fk_rails_69c7d16a1a" +FOREIGN KEY ("result_scene_id") + REFERENCES "scenes" ("id") +); +CREATE TABLE IF NOT EXISTS "playthrough_drifts" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "playthrough_id" integer NOT NULL, "scene_id" integer, "location_id" integer, "action" varchar NOT NULL, "command" text NOT NULL, "offered" text, "story_timestamp" datetime(6), "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, CONSTRAINT "fk_rails_018f29e0ac" +FOREIGN KEY ("playthrough_id") + REFERENCES "playthroughs" ("id") +, CONSTRAINT "fk_rails_094a605342" +FOREIGN KEY ("location_id") + REFERENCES "locations" ("id") +, CONSTRAINT "fk_rails_45044856a9" +FOREIGN KEY ("scene_id") + REFERENCES "scenes" ("id") +); +CREATE TABLE IF NOT EXISTS "playthrough_due_endings" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "playthrough_id" integer NOT NULL, "quest_outcome_id" integer NOT NULL, "due_at" datetime(6) NOT NULL, "declined_at" datetime(6), "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, CONSTRAINT "fk_rails_c471335e2f" +FOREIGN KEY ("playthrough_id") + REFERENCES "playthroughs" ("id") +, CONSTRAINT "fk_rails_b8e95530cf" +FOREIGN KEY ("quest_outcome_id") + REFERENCES "quest_outcomes" ("id") +); +CREATE TABLE IF NOT EXISTS "playthrough_endings" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "playthrough_id" integer NOT NULL, "quest_outcome_id" integer NOT NULL, "reached_at" datetime(6) NOT NULL, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, CONSTRAINT "fk_rails_762b78023d" +FOREIGN KEY ("playthrough_id") + REFERENCES "playthroughs" ("id") +, CONSTRAINT "fk_rails_2220d324b0" +FOREIGN KEY ("quest_outcome_id") + REFERENCES "quest_outcomes" ("id") +); +CREATE TABLE IF NOT EXISTS "playthrough_feedbacks" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "playthrough_id" integer NOT NULL, "scene_id" integer NOT NULL, "verdict" varchar NOT NULL, "note" text, "prose_model" varchar, "prose_purpose" varchar, "prose_models" text, "answering_models" text, "input_tokens" integer, "output_tokens" integer, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, "prose_prompt_digest" varchar, CONSTRAINT "fk_rails_99ee3b49ea" +FOREIGN KEY ("playthrough_id") + REFERENCES "playthroughs" ("id") +, CONSTRAINT "fk_rails_247709935a" +FOREIGN KEY ("scene_id") + REFERENCES "scenes" ("id") +); +CREATE TABLE IF NOT EXISTS "playthrough_npc_states" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "playthrough_id" integer NOT NULL, "character_id" integer NOT NULL, "location_id" integer, "following" boolean DEFAULT FALSE NOT NULL, "ceasefire" boolean DEFAULT FALSE NOT NULL, "peace_after_blow_id" integer DEFAULT 0 NOT NULL, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, "noticed_at" datetime(6), CONSTRAINT "fk_rails_ef100d7841" +FOREIGN KEY ("location_id") + REFERENCES "locations" ("id") +, CONSTRAINT "fk_rails_71a135780d" +FOREIGN KEY ("character_id") + REFERENCES "characters" ("id") +, CONSTRAINT "fk_rails_984efad0e9" +FOREIGN KEY ("playthrough_id") + REFERENCES "playthroughs" ("id") +); +CREATE TABLE IF NOT EXISTS "playthrough_overreaches" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "playthrough_id" integer NOT NULL, "scene_id" integer, "location_id" integer, "action" varchar NOT NULL, "command" text NOT NULL, "acted" text NOT NULL, "unacted" text NOT NULL, "story_timestamp" datetime(6), "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, CONSTRAINT "fk_rails_b903d1bc16" +FOREIGN KEY ("playthrough_id") + REFERENCES "playthroughs" ("id") +, CONSTRAINT "fk_rails_9aac75595a" +FOREIGN KEY ("location_id") + REFERENCES "locations" ("id") +, CONSTRAINT "fk_rails_879cc0f78c" +FOREIGN KEY ("scene_id") + REFERENCES "scenes" ("id") +); +CREATE TABLE IF NOT EXISTS "playthrough_paragraphs" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "playthrough_id" integer NOT NULL, "scene_id" integer NOT NULL, "author" varchar DEFAULT 'player' NOT NULL, "text" text NOT NULL, "audit" json, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, "consent" varchar DEFAULT 'none' NOT NULL, "requests" json, "prompt_digest" varchar, CONSTRAINT "fk_rails_db3983d32c" +FOREIGN KEY ("playthrough_id") + REFERENCES "playthroughs" ("id") +, CONSTRAINT "fk_rails_52ef8100a0" +FOREIGN KEY ("scene_id") + REFERENCES "scenes" ("id") +); +CREATE TABLE IF NOT EXISTS "playthrough_passages" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "playthrough_id" integer NOT NULL, "location_connection_id" integer NOT NULL, "opened_by_item_id" integer, "means" varchar NOT NULL, "opened_at" datetime(6) NOT NULL, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, CONSTRAINT "fk_rails_7bdf6a743a" +FOREIGN KEY ("location_connection_id") + REFERENCES "location_connections" ("id") + ON DELETE CASCADE, CONSTRAINT "fk_rails_75c2355411" +FOREIGN KEY ("opened_by_item_id") + REFERENCES "items" ("id") + ON DELETE SET NULL, CONSTRAINT "fk_rails_995c25e2fd" +FOREIGN KEY ("playthrough_id") + REFERENCES "playthroughs" ("id") + ON DELETE CASCADE); +CREATE TABLE IF NOT EXISTS "playthrough_tolls" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "playthrough_id" integer NOT NULL, "character_id" integer NOT NULL, "location_id" integer NOT NULL, "location_connection_id" integer, "hazard" varchar NOT NULL, "saved" boolean DEFAULT FALSE NOT NULL, "damage" integer NOT NULL, "hp_after" integer NOT NULL, "sequence" integer NOT NULL, "story_timestamp" datetime(6) NOT NULL, "scene_id" integer, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, CONSTRAINT "fk_rails_83dfd307ab" +FOREIGN KEY ("playthrough_id") + REFERENCES "playthroughs" ("id") +, CONSTRAINT "fk_rails_443984de3a" +FOREIGN KEY ("location_connection_id") + REFERENCES "location_connections" ("id") +, CONSTRAINT "fk_rails_8a2212da5c" +FOREIGN KEY ("character_id") + REFERENCES "characters" ("id") +, CONSTRAINT "fk_rails_6ec4904a86" +FOREIGN KEY ("location_id") + REFERENCES "locations" ("id") +, CONSTRAINT "fk_rails_af4dd03665" +FOREIGN KEY ("scene_id") + REFERENCES "scenes" ("id") +); +CREATE TABLE IF NOT EXISTS "playthrough_turn_events" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "playthrough_command_id" integer NOT NULL, "sequence" integer NOT NULL, "kind" varchar NOT NULL, "data" json DEFAULT '{}' NOT NULL, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, CONSTRAINT "fk_rails_c7a6770f24" +FOREIGN KEY ("playthrough_command_id") + REFERENCES "playthrough_commands" ("id") +); +CREATE TABLE IF NOT EXISTS "playthrough_vitals" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "playthrough_id" integer NOT NULL, "character_id" integer NOT NULL, "hp_current" integer NOT NULL, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, "provoked_at" datetime(6), CONSTRAINT "fk_rails_03cf4b1b8e" +FOREIGN KEY ("character_id") + REFERENCES "characters" ("id") +, CONSTRAINT "fk_rails_9956f79aa5" +FOREIGN KEY ("playthrough_id") + REFERENCES "playthroughs" ("id") +); +CREATE TABLE IF NOT EXISTS "playthrough_volitions" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "playthrough_id" integer NOT NULL, "character_id" integer NOT NULL, "location_id" integer NOT NULL, "scene_id" integer, "chosen" varchar NOT NULL, "status" varchar NOT NULL, "fact" text NOT NULL, "serves" varchar NOT NULL, "round" integer NOT NULL, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, "decided_by" varchar, "system_one_error" varchar, CONSTRAINT "fk_rails_55e6f9e865" +FOREIGN KEY ("playthrough_id") + REFERENCES "playthroughs" ("id") +, CONSTRAINT "fk_rails_0c408b6253" +FOREIGN KEY ("character_id") + REFERENCES "characters" ("id") +, CONSTRAINT "fk_rails_1824dea9f6" +FOREIGN KEY ("location_id") + REFERENCES "locations" ("id") +, CONSTRAINT "fk_rails_a054701564" +FOREIGN KEY ("scene_id") + REFERENCES "scenes" ("id") +); +CREATE TABLE IF NOT EXISTS "playthroughs" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "story_id" integer NOT NULL, "character_id" integer, "current_location_id" integer, "current_scene_id" integer, "token" varchar NOT NULL, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, "ended_at" datetime(6), "player_id" integer, "mode" varchar DEFAULT 'narrated' NOT NULL, CONSTRAINT "fk_rails_9b48509224" +FOREIGN KEY ("current_scene_id") + REFERENCES "scenes" ("id") +, CONSTRAINT "fk_rails_3a7e48fa36" +FOREIGN KEY ("current_location_id") + REFERENCES "locations" ("id") +, CONSTRAINT "fk_rails_5b54078c4b" +FOREIGN KEY ("character_id") + REFERENCES "characters" ("id") +, CONSTRAINT "fk_rails_ab75586f4b" +FOREIGN KEY ("player_id") + REFERENCES "players" ("id") +, CONSTRAINT "fk_rails_3d6d3a9c5a" +FOREIGN KEY ("story_id") + REFERENCES "stories" ("id") +); +CREATE TABLE IF NOT EXISTS "quest_outcomes" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "quest_id" integer NOT NULL, "name" varchar NOT NULL, "summary" text NOT NULL, "is_default" boolean DEFAULT FALSE NOT NULL, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, "condition" varchar, "minutes" integer, "ramification_summary" text, "ramification_minutes" integer, "step_position" integer, "character_id" integer, CONSTRAINT "fk_rails_acf8ece7c5" +FOREIGN KEY ("quest_id") + REFERENCES "quests" ("id") +); +CREATE TABLE IF NOT EXISTS "quest_steps" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "quest_id" integer NOT NULL, "position" integer NOT NULL, "summary" text NOT NULL, "trigger_kind" varchar NOT NULL, "target_name" varchar, "teaser" text, "target_type" varchar, "target_id" integer, "minutes" integer, "bound_at" datetime(6), "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, "companion_id" integer, CONSTRAINT "fk_rails_ba1603d17b" +FOREIGN KEY ("quest_id") + REFERENCES "quests" ("id") +); +CREATE TABLE IF NOT EXISTS "quests" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "story_id" integer NOT NULL, "parent_quest_id" integer, "title" varchar NOT NULL, "premise" text NOT NULL, "status" varchar DEFAULT 'open' NOT NULL, "contributes" boolean DEFAULT TRUE NOT NULL, "origin" varchar DEFAULT 'seeded' NOT NULL, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, CONSTRAINT "fk_rails_a64954ae79" +FOREIGN KEY ("parent_quest_id") + REFERENCES "quests" ("id") +, CONSTRAINT "fk_rails_fa77fc496b" +FOREIGN KEY ("story_id") + REFERENCES "stories" ("id") +); +CREATE TABLE IF NOT EXISTS "races" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "universe_id" integer NOT NULL, "name" varchar NOT NULL, "description" text NOT NULL, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, "monstrous" boolean DEFAULT FALSE NOT NULL, CONSTRAINT "fk_rails_c25ac61605" +FOREIGN KEY ("universe_id") + REFERENCES "universes" ("id") +); +INSERT INTO races VALUES(1000000001,1000000001,'Delvers','People who go down into the dungeon for a living.','2026-10-30 13:44:46.631781','2026-10-30 13:44:46.631781',0); +CREATE TABLE IF NOT EXISTS "relay_receipts" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "player_id" integer NOT NULL, "route" varchar NOT NULL, "model" varchar NOT NULL, "stream" boolean DEFAULT FALSE NOT NULL, "status" varchar DEFAULT 'open' NOT NULL, "reserved_usd" decimal(12,6) NOT NULL, "cost_usd" decimal(12,6), "cost_source" varchar, "input_tokens" integer, "output_tokens" integer, "upstream_status" integer, "finished_at" datetime(6), "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, CONSTRAINT "fk_rails_deae27bd71" +FOREIGN KEY ("player_id") + REFERENCES "players" ("id") +); +CREATE TABLE IF NOT EXISTS "scenes" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "story_id" integer NOT NULL, "location_id" integer NOT NULL, "previous_scene_id" integer, "description" text, "story_timestamp" datetime(6), "summary" text, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, "is_opening" boolean DEFAULT FALSE NOT NULL, "typed" text, "resolved_action" varchar, "acted_on_type" varchar, "acted_on_id" integer, "resolved_by" varchar, "engine_fallback" boolean DEFAULT FALSE NOT NULL, "engine_fact" text, CONSTRAINT "fk_rails_5cc24f985a" +FOREIGN KEY ("previous_scene_id") + REFERENCES "scenes" ("id") +, CONSTRAINT "fk_rails_abfe1c0369" +FOREIGN KEY ("location_id") + REFERENCES "locations" ("id") +, CONSTRAINT "fk_rails_38ea512481" +FOREIGN KEY ("story_id") + REFERENCES "stories" ("id") +); +INSERT INTO scenes VALUES(1000000001,1000000001,1000000001,NULL,'You stand on the lantern-lit landing between two stairs down.','2026-10-30 08:00:00','Odile reaches the landing.','2026-10-30 13:44:47.011572','2026-10-30 13:44:47.011572',1,NULL,NULL,NULL,NULL,NULL,0,NULL); +CREATE TABLE IF NOT EXISTS "stories" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "title" varchar, "genre" varchar, "preface" text, "summary" text, "start_time" datetime(6), "universe_id" integer NOT NULL, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, "generation_snapshot" text, "people_limit" integer, "things_limit" integer, CONSTRAINT "fk_rails_2a912ea846" +FOREIGN KEY ("universe_id") + REFERENCES "universes" ("id") +); +INSERT INTO stories VALUES(1000000001,'A Deeper Floor (engine sweep)','dungeon crawl','The deeper you go, the harder what lives there.','Odile looks into an upper floor and the bottom one.','2026-10-30 08:00:00',1000000001,'2026-10-30 13:44:46.650395','2026-10-30 13:44:46.650395',NULL,NULL,NULL); +CREATE TABLE IF NOT EXISTS "system_one_receipts" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "player_id" integer, "playthrough_id" integer, "purpose" varchar, "transport" varchar, "cost_usd" decimal(12,6) NOT NULL, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, "cost_source" varchar DEFAULT 'estimated' NOT NULL, "latency_ms" integer, CONSTRAINT "fk_rails_4ae941c854" +FOREIGN KEY ("player_id") + REFERENCES "players" ("id") +, CONSTRAINT "fk_rails_75847e1556" +FOREIGN KEY ("playthrough_id") + REFERENCES "playthroughs" ("id") +); +CREATE TABLE IF NOT EXISTS "world_events" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "world_mechanic_id" integer, "story_id" integer NOT NULL, "occurred_at" datetime(6) NOT NULL, "summary" text NOT NULL, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, "source" varchar NOT NULL, "playthrough_id" integer, "scheduled_for" datetime(6), "fired_at" datetime(6), CONSTRAINT "fk_rails_cafc0959e8" +FOREIGN KEY ("story_id") + REFERENCES "stories" ("id") +, CONSTRAINT "fk_rails_efc6cdec27" +FOREIGN KEY ("world_mechanic_id") + REFERENCES "world_mechanics" ("id") +); +CREATE TABLE IF NOT EXISTS "world_mechanics" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "story_id" integer NOT NULL, "name" varchar NOT NULL, "kind" varchar NOT NULL, "cadence" varchar NOT NULL, "description" text, "last_run_at" datetime(6), "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, CONSTRAINT "fk_rails_d7328bbbe5" +FOREIGN KEY ("story_id") + REFERENCES "stories" ("id") +); +CREATE TABLE IF NOT EXISTS "schema_migrations" ("version" varchar NOT NULL PRIMARY KEY); +INSERT INTO schema_migrations VALUES('20261030230000'); +INSERT INTO schema_migrations VALUES('20261030200000'); +INSERT INTO schema_migrations VALUES('20261030120000'); +INSERT INTO schema_migrations VALUES('20261029154235'); +INSERT INTO schema_migrations VALUES('20261027120117'); +INSERT INTO schema_migrations VALUES('20261025230000'); +INSERT INTO schema_migrations VALUES('20261025200000'); +INSERT INTO schema_migrations VALUES('20261025120000'); +INSERT INTO schema_migrations VALUES('20261024120000'); +INSERT INTO schema_migrations VALUES('20261015213000'); +INSERT INTO schema_migrations VALUES('20261015200000'); +INSERT INTO schema_migrations VALUES('20261015180000'); +INSERT INTO schema_migrations VALUES('20261015120000'); +INSERT INTO schema_migrations VALUES('20261010220000'); +INSERT INTO schema_migrations VALUES('20261010180000'); +INSERT INTO schema_migrations VALUES('20261010120000'); +INSERT INTO schema_migrations VALUES('20260929023507'); +INSERT INTO schema_migrations VALUES('20260928124453'); +INSERT INTO schema_migrations VALUES('20260928102823'); +INSERT INTO schema_migrations VALUES('20260928035434'); +INSERT INTO schema_migrations VALUES('20260928035429'); +INSERT INTO schema_migrations VALUES('20260928031026'); +INSERT INTO schema_migrations VALUES('20260928025950'); +INSERT INTO schema_migrations VALUES('20260928020741'); +INSERT INTO schema_migrations VALUES('20260927152056'); +INSERT INTO schema_migrations VALUES('20260927031841'); +INSERT INTO schema_migrations VALUES('20260927031839'); +INSERT INTO schema_migrations VALUES('20260927031837'); +INSERT INTO schema_migrations VALUES('20260927031835'); +INSERT INTO schema_migrations VALUES('20260927031834'); +INSERT INTO schema_migrations VALUES('20260926195041'); +INSERT INTO schema_migrations VALUES('20260922141619'); +INSERT INTO schema_migrations VALUES('20260922141618'); +INSERT INTO schema_migrations VALUES('20260922141617'); +INSERT INTO schema_migrations VALUES('20260919202606'); +INSERT INTO schema_migrations VALUES('20260919202605'); +INSERT INTO schema_migrations VALUES('20260917133823'); +INSERT INTO schema_migrations VALUES('20260910041334'); +INSERT INTO schema_migrations VALUES('20260910025040'); +INSERT INTO schema_migrations VALUES('20260909030346'); +INSERT INTO schema_migrations VALUES('20260909023028'); +INSERT INTO schema_migrations VALUES('20260909023014'); +INSERT INTO schema_migrations VALUES('20260908203558'); +INSERT INTO schema_migrations VALUES('20260908155546'); +INSERT INTO schema_migrations VALUES('20260908120000'); +INSERT INTO schema_migrations VALUES('20260907150000'); +INSERT INTO schema_migrations VALUES('20260907140000'); +INSERT INTO schema_migrations VALUES('20260907120000'); +INSERT INTO schema_migrations VALUES('20260906130000'); +INSERT INTO schema_migrations VALUES('20260906120000'); +INSERT INTO schema_migrations VALUES('20260905150000'); +INSERT INTO schema_migrations VALUES('20260905140000'); +INSERT INTO schema_migrations VALUES('20260905130000'); +INSERT INTO schema_migrations VALUES('20260905120000'); +INSERT INTO schema_migrations VALUES('20260905110000'); +INSERT INTO schema_migrations VALUES('20260905100001'); +INSERT INTO schema_migrations VALUES('20260905100000'); +INSERT INTO schema_migrations VALUES('20260905090100'); +INSERT INTO schema_migrations VALUES('20260905090000'); +INSERT INTO schema_migrations VALUES('20260904190000'); +INSERT INTO schema_migrations VALUES('20260904180000'); +INSERT INTO schema_migrations VALUES('20260904170000'); +INSERT INTO schema_migrations VALUES('20260904160000'); +INSERT INTO schema_migrations VALUES('20260904120000'); +INSERT INTO schema_migrations VALUES('20260903170000'); +INSERT INTO schema_migrations VALUES('20260903120000'); +INSERT INTO schema_migrations VALUES('20260902120000'); +INSERT INTO schema_migrations VALUES('20260901170000'); +INSERT INTO schema_migrations VALUES('20260901160000'); +INSERT INTO schema_migrations VALUES('20260901120000'); +INSERT INTO schema_migrations VALUES('20260831140000'); +INSERT INTO schema_migrations VALUES('20260831130000'); +INSERT INTO schema_migrations VALUES('20260831120000'); +INSERT INTO schema_migrations VALUES('20260831000000'); +INSERT INTO schema_migrations VALUES('20260830220946'); +INSERT INTO schema_migrations VALUES('20260830220945'); +INSERT INTO schema_migrations VALUES('20260830210000'); +INSERT INTO schema_migrations VALUES('20260830200001'); +INSERT INTO schema_migrations VALUES('20260830200000'); +INSERT INTO schema_migrations VALUES('20260830192215'); +INSERT INTO schema_migrations VALUES('20260830192214'); +INSERT INTO schema_migrations VALUES('20250824193519'); +INSERT INTO schema_migrations VALUES('20250823024100'); +INSERT INTO schema_migrations VALUES('20250823024050'); +INSERT INTO schema_migrations VALUES('20250823024037'); +INSERT INTO schema_migrations VALUES('20250823024029'); +INSERT INTO schema_migrations VALUES('20250823024021'); +INSERT INTO schema_migrations VALUES('20250823024013'); +INSERT INTO schema_migrations VALUES('20250823023950'); +INSERT INTO schema_migrations VALUES('20250823023942'); +INSERT INTO schema_migrations VALUES('20250823023925'); +INSERT INTO schema_migrations VALUES('20250822184651'); +INSERT INTO schema_migrations VALUES('20250822184650'); +INSERT INTO schema_migrations VALUES('20250822184649'); +CREATE TABLE IF NOT EXISTS "ar_internal_metadata" ("key" varchar NOT NULL PRIMARY KEY, "value" varchar, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL); +INSERT INTO ar_internal_metadata VALUES('environment','test','2026-10-30 13:44:43.769467','2026-10-30 13:44:43.769470'); +INSERT INTO ar_internal_metadata VALUES('schema_sha1','997d2947cb01ed87e8df6e9bddf8aab22ba21a27','2026-10-30 13:44:43.773358','2026-10-30 13:44:43.773359'); +PRAGMA writable_schema=ON; +CREATE TABLE IF NOT EXISTS sqlite_sequence(name,seq); +DELETE FROM sqlite_sequence; +INSERT INTO sqlite_sequence VALUES('lab_exits_vantages',1000000000); +INSERT INTO sqlite_sequence VALUES('lab_realization_kinds',1000000000); +INSERT INTO sqlite_sequence VALUES('players',1000000000); +INSERT INTO sqlite_sequence VALUES('playthrough_visits',1000000000); +INSERT INTO sqlite_sequence VALUES('refused_lines',1000000000); +INSERT INTO sqlite_sequence VALUES('ruby_llm_batches',1000000000); +INSERT INTO sqlite_sequence VALUES('ruby_llm_models',1000000000); +INSERT INTO sqlite_sequence VALUES('ruby_llm_tool_calls',1000000000); +INSERT INTO sqlite_sequence VALUES('ruby_llm_usages',1000000000); +INSERT INTO sqlite_sequence VALUES('universes',1000000001); +INSERT INTO sqlite_sequence VALUES('characters',1000000001); +INSERT INTO sqlite_sequence VALUES('chats',1000000000); +INSERT INTO sqlite_sequence VALUES('floors',1000000002); +INSERT INTO sqlite_sequence VALUES('interactions',1000000000); +INSERT INTO sqlite_sequence VALUES('items',1000000000); +INSERT INTO sqlite_sequence VALUES('lab_exits_judgements',1000000000); +INSERT INTO sqlite_sequence VALUES('lab_exits_samples',1000000000); +INSERT INTO sqlite_sequence VALUES('lab_realization_samples',1000000000); +INSERT INTO sqlite_sequence VALUES('location_connections',1000000004); +INSERT INTO sqlite_sequence VALUES('locations',1000000003); +INSERT INTO sqlite_sequence VALUES('messages',1000000000); +INSERT INTO sqlite_sequence VALUES('playthrough_beats',1000000000); +INSERT INTO sqlite_sequence VALUES('playthrough_blows',1000000000); +INSERT INTO sqlite_sequence VALUES('playthrough_commands',1000000000); +INSERT INTO sqlite_sequence VALUES('playthrough_drifts',1000000000); +INSERT INTO sqlite_sequence VALUES('playthrough_due_endings',1000000000); +INSERT INTO sqlite_sequence VALUES('playthrough_endings',1000000000); +INSERT INTO sqlite_sequence VALUES('playthrough_feedbacks',1000000000); +INSERT INTO sqlite_sequence VALUES('playthrough_npc_states',1000000000); +INSERT INTO sqlite_sequence VALUES('playthrough_overreaches',1000000000); +INSERT INTO sqlite_sequence VALUES('playthrough_paragraphs',1000000000); +INSERT INTO sqlite_sequence VALUES('playthrough_passages',1000000000); +INSERT INTO sqlite_sequence VALUES('playthrough_tolls',1000000000); +INSERT INTO sqlite_sequence VALUES('playthrough_turn_events',1000000000); +INSERT INTO sqlite_sequence VALUES('playthrough_vitals',1000000000); +INSERT INTO sqlite_sequence VALUES('playthrough_volitions',1000000000); +INSERT INTO sqlite_sequence VALUES('playthroughs',1000000000); +INSERT INTO sqlite_sequence VALUES('quest_outcomes',1000000000); +INSERT INTO sqlite_sequence VALUES('quest_steps',1000000000); +INSERT INTO sqlite_sequence VALUES('quests',1000000000); +INSERT INTO sqlite_sequence VALUES('races',1000000001); +INSERT INTO sqlite_sequence VALUES('relay_receipts',1000000000); +INSERT INTO sqlite_sequence VALUES('scenes',1000000001); +INSERT INTO sqlite_sequence VALUES('stories',1000000001); +INSERT INTO sqlite_sequence VALUES('system_one_receipts',1000000000); +INSERT INTO sqlite_sequence VALUES('world_events',1000000000); +INSERT INTO sqlite_sequence VALUES('world_mechanics',1000000000); +CREATE INDEX "index_characters_scenes_on_character_id_and_scene_id" ON "characters_scenes" ("character_id", "scene_id"); +CREATE INDEX "index_characters_scenes_on_scene_id_and_character_id" ON "characters_scenes" ("scene_id", "character_id"); +CREATE UNIQUE INDEX "index_players_on_name" ON "players" ("name"); +CREATE UNIQUE INDEX "index_players_on_token_digest" ON "players" ("token_digest"); +CREATE INDEX "index_playthrough_visits_on_location_id" ON "playthrough_visits" ("location_id"); +CREATE UNIQUE INDEX "index_playthrough_visits_on_playthrough_and_location" ON "playthrough_visits" ("playthrough_id", "location_id"); +CREATE INDEX "index_playthrough_visits_on_playthrough_id" ON "playthrough_visits" ("playthrough_id"); +CREATE INDEX "index_refused_lines_on_status" ON "refused_lines" ("status"); +CREATE UNIQUE INDEX "index_ruby_llm_batches_on_provider_and_provider_batch_id" ON "ruby_llm_batches" ("provider", "provider_batch_id"); +CREATE INDEX "index_ruby_llm_batches_on_status" ON "ruby_llm_batches" ("status"); +CREATE INDEX "index_ruby_llm_models_on_family" ON "ruby_llm_models" ("family"); +CREATE UNIQUE INDEX "index_ruby_llm_models_on_provider_and_model_id" ON "ruby_llm_models" ("provider", "model_id"); +CREATE INDEX "index_ruby_llm_models_on_provider" ON "ruby_llm_models" ("provider"); +CREATE INDEX "index_ruby_llm_tool_calls_on_message_type_and_message_id" ON "ruby_llm_tool_calls" ("message_type", "message_id"); +CREATE INDEX "index_ruby_llm_tool_calls_on_name" ON "ruby_llm_tool_calls" ("name"); +CREATE INDEX "index_ruby_llm_tool_calls_on_result_type_and_result_id" ON "ruby_llm_tool_calls" ("result_type", "result_id"); +CREATE UNIQUE INDEX "index_ruby_llm_tool_calls_on_tool_call_id" ON "ruby_llm_tool_calls" ("tool_call_id"); +CREATE INDEX "index_ruby_llm_usages_on_chat_type_and_chat_id" ON "ruby_llm_usages" ("chat_type", "chat_id"); +CREATE INDEX "index_ruby_llm_usages_on_message_type_and_message_id" ON "ruby_llm_usages" ("message_type", "message_id"); +CREATE INDEX "index_ruby_llm_usages_on_status" ON "ruby_llm_usages" ("status"); +CREATE UNIQUE INDEX "index_ruby_llm_v2_backfills_on_task" ON "ruby_llm_v2_backfills" ("task"); +CREATE UNIQUE INDEX "index_characters_on_story_id_and_lower_fullname" ON "characters" (story_id, LOWER(fullname)); +CREATE INDEX "index_characters_on_location_id_and_hostile" ON "characters" ("location_id", "hostile"); +CREATE INDEX "index_characters_on_location_id_and_id" ON "characters" ("location_id", "id"); +CREATE INDEX "index_characters_on_location_id" ON "characters" ("location_id"); +CREATE INDEX "index_characters_on_posted_to_id" ON "characters" ("posted_to_id"); +CREATE INDEX "index_characters_on_race_id" ON "characters" ("race_id"); +CREATE INDEX "index_characters_on_story_id_and_is_protagonist" ON "characters" ("story_id", "is_protagonist"); +CREATE INDEX "index_characters_on_story_id" ON "characters" ("story_id"); +CREATE INDEX "index_chats_on_character_id" ON "chats" ("character_id"); +CREATE INDEX "index_chats_on_player_id" ON "chats" ("player_id"); +CREATE INDEX "index_chats_on_conversation_key" ON "chats" ("playthrough_id", "character_id", "purpose"); +CREATE INDEX "index_chats_on_playthrough_id" ON "chats" ("playthrough_id"); +CREATE INDEX "index_chats_on_ruby_llm_model_id" ON "chats" ("ruby_llm_model_id"); +CREATE INDEX "index_floors_on_down_location_id" ON "floors" ("down_location_id"); +CREATE UNIQUE INDEX "index_floors_on_location_id" ON "floors" ("location_id"); +CREATE INDEX "index_interactions_on_character_id" ON "interactions" ("character_id"); +CREATE INDEX "index_interactions_on_location_id" ON "interactions" ("location_id"); +CREATE INDEX "index_interactions_on_scene_id" ON "interactions" ("scene_id"); +CREATE INDEX "index_items_on_character_id" ON "items" ("character_id"); +CREATE INDEX "index_items_on_location_id_and_character_id" ON "items" ("location_id", "character_id"); +CREATE INDEX "index_items_on_location_id" ON "items" ("location_id"); +CREATE INDEX "index_items_on_playthrough_id_and_id" ON "items" ("playthrough_id", "id"); +CREATE INDEX "index_items_on_playthrough_id_and_template_id" ON "items" ("playthrough_id", "template_id"); +CREATE INDEX "index_items_on_playthrough_id" ON "items" ("playthrough_id"); +CREATE INDEX "index_items_on_template_id" ON "items" ("template_id"); +CREATE INDEX "index_items_on_within_id" ON "items" ("within_id"); +CREATE UNIQUE INDEX "index_lab_exits_judgements_on_place" ON "lab_exits_judgements" ("vantage_id", "name_key"); +CREATE INDEX "index_lab_exits_judgements_on_vantage_id" ON "lab_exits_judgements" ("vantage_id"); +CREATE INDEX "index_lab_exits_samples_on_vantage_id" ON "lab_exits_samples" ("vantage_id"); +CREATE INDEX "index_lab_realization_samples_on_kind_id" ON "lab_realization_samples" ("kind_id"); +CREATE INDEX "index_location_connections_on_connected_location_id" ON "location_connections" ("connected_location_id"); +CREATE INDEX "index_location_connections_on_key_template_id" ON "location_connections" ("key_template_id"); +CREATE UNIQUE INDEX "idx_on_location_id_connected_location_id_a0efda2bf6" ON "location_connections" ("location_id", "connected_location_id"); +CREATE INDEX "index_location_connections_on_location_id" ON "location_connections" ("location_id"); +CREATE UNIQUE INDEX "index_locations_on_story_id_and_lower_name" ON "locations" (story_id, lower(name)); +CREATE INDEX "index_locations_on_parent_location_id" ON "locations" ("parent_location_id"); +CREATE INDEX "index_locations_on_story_id_and_detail_level" ON "locations" ("story_id", "detail_level"); +CREATE INDEX "index_locations_on_story_id" ON "locations" ("story_id"); +CREATE INDEX "index_locations_world_events_on_location_id" ON "locations_world_events" ("location_id"); +CREATE UNIQUE INDEX "index_locations_world_events_on_world_event_id_and_location_id" ON "locations_world_events" ("world_event_id", "location_id"); +CREATE INDEX "index_locations_world_events_on_world_event_id" ON "locations_world_events" ("world_event_id"); +CREATE INDEX "index_messages_on_chat_id" ON "messages" ("chat_id"); +CREATE INDEX "index_messages_on_model_id" ON "messages" ("model_id"); +CREATE INDEX "index_messages_on_scene_id" ON "messages" ("scene_id"); +CREATE INDEX "index_messages_on_tool_call_id" ON "messages" ("tool_call_id"); +CREATE UNIQUE INDEX "index_playthrough_beats_on_playthrough_id_and_quest_step_id" ON "playthrough_beats" ("playthrough_id", "quest_step_id"); +CREATE INDEX "index_playthrough_beats_on_playthrough_id" ON "playthrough_beats" ("playthrough_id"); +CREATE INDEX "index_playthrough_beats_on_quest_step_id" ON "playthrough_beats" ("quest_step_id"); +CREATE INDEX "index_playthrough_blows_on_attacker_id" ON "playthrough_blows" ("attacker_id"); +CREATE INDEX "index_playthrough_blows_on_location_id" ON "playthrough_blows" ("location_id"); +CREATE INDEX "index_playthrough_blows_on_playthrough_and_scene" ON "playthrough_blows" ("playthrough_id", "scene_id", "id"); +CREATE INDEX "index_playthrough_blows_on_playthrough_id" ON "playthrough_blows" ("playthrough_id"); +CREATE INDEX "index_playthrough_blows_on_scene_id" ON "playthrough_blows" ("scene_id"); +CREATE INDEX "index_playthrough_blows_on_target_id" ON "playthrough_blows" ("target_id"); +CREATE UNIQUE INDEX "index_playthrough_commands_on_submission" ON "playthrough_commands" ("playthrough_id", "request_token", "command"); +CREATE INDEX "index_playthrough_commands_on_playthrough_id" ON "playthrough_commands" ("playthrough_id"); +CREATE INDEX "index_playthrough_commands_on_result_scene_id" ON "playthrough_commands" ("result_scene_id"); +CREATE INDEX "index_playthrough_drifts_on_action" ON "playthrough_drifts" ("action"); +CREATE INDEX "index_playthrough_drifts_on_location_id" ON "playthrough_drifts" ("location_id"); +CREATE INDEX "index_playthrough_drifts_on_playthrough_id_and_story_timestamp" ON "playthrough_drifts" ("playthrough_id", "story_timestamp"); +CREATE INDEX "index_playthrough_drifts_on_playthrough_id" ON "playthrough_drifts" ("playthrough_id"); +CREATE INDEX "index_playthrough_drifts_on_scene_id" ON "playthrough_drifts" ("scene_id"); +CREATE UNIQUE INDEX "index_playthrough_due_endings_on_playthrough_id" ON "playthrough_due_endings" ("playthrough_id"); +CREATE INDEX "index_playthrough_due_endings_on_quest_outcome_id" ON "playthrough_due_endings" ("quest_outcome_id"); +CREATE UNIQUE INDEX "idx_on_playthrough_id_quest_outcome_id_7ea31b4171" ON "playthrough_endings" ("playthrough_id", "quest_outcome_id"); +CREATE INDEX "index_playthrough_endings_on_playthrough_id" ON "playthrough_endings" ("playthrough_id"); +CREATE INDEX "index_playthrough_endings_on_quest_outcome_id" ON "playthrough_endings" ("quest_outcome_id"); +CREATE UNIQUE INDEX "index_playthrough_feedbacks_on_playthrough_id_and_scene_id" ON "playthrough_feedbacks" ("playthrough_id", "scene_id"); +CREATE INDEX "index_playthrough_feedbacks_on_playthrough_id" ON "playthrough_feedbacks" ("playthrough_id"); +CREATE INDEX "index_playthrough_feedbacks_on_prose_model" ON "playthrough_feedbacks" ("prose_model"); +CREATE INDEX "index_playthrough_feedbacks_on_prose_prompt_digest" ON "playthrough_feedbacks" ("prose_prompt_digest"); +CREATE INDEX "index_playthrough_feedbacks_on_scene_id" ON "playthrough_feedbacks" ("scene_id"); +CREATE INDEX "index_playthrough_feedbacks_on_verdict" ON "playthrough_feedbacks" ("verdict"); +CREATE INDEX "index_playthrough_npc_states_on_character_id" ON "playthrough_npc_states" ("character_id"); +CREATE INDEX "index_playthrough_npc_states_on_location_id" ON "playthrough_npc_states" ("location_id"); +CREATE UNIQUE INDEX "idx_on_playthrough_id_character_id_008ae03862" ON "playthrough_npc_states" ("playthrough_id", "character_id"); +CREATE INDEX "index_playthrough_npc_states_on_playthrough_id" ON "playthrough_npc_states" ("playthrough_id"); +CREATE INDEX "index_playthrough_overreaches_on_action" ON "playthrough_overreaches" ("action"); +CREATE INDEX "index_playthrough_overreaches_on_location_id" ON "playthrough_overreaches" ("location_id"); +CREATE INDEX "idx_on_playthrough_id_story_timestamp_b54cd36315" ON "playthrough_overreaches" ("playthrough_id", "story_timestamp"); +CREATE INDEX "index_playthrough_overreaches_on_playthrough_id" ON "playthrough_overreaches" ("playthrough_id"); +CREATE INDEX "index_playthrough_overreaches_on_scene_id" ON "playthrough_overreaches" ("scene_id"); +CREATE INDEX "index_playthrough_paragraphs_on_playthrough_id" ON "playthrough_paragraphs" ("playthrough_id"); +CREATE UNIQUE INDEX "index_playthrough_paragraphs_on_scene_id_and_author" ON "playthrough_paragraphs" ("scene_id", "author"); +CREATE INDEX "index_playthrough_passages_on_location_connection_id" ON "playthrough_passages" ("location_connection_id"); +CREATE INDEX "index_playthrough_passages_on_opened_by_item_id" ON "playthrough_passages" ("opened_by_item_id"); +CREATE UNIQUE INDEX "idx_on_playthrough_id_location_connection_id_6d3a29d910" ON "playthrough_passages" ("playthrough_id", "location_connection_id"); +CREATE INDEX "index_playthrough_passages_on_playthrough_id" ON "playthrough_passages" ("playthrough_id"); +CREATE INDEX "index_playthrough_tolls_on_character_id" ON "playthrough_tolls" ("character_id"); +CREATE INDEX "index_playthrough_tolls_on_location_connection_id" ON "playthrough_tolls" ("location_connection_id"); +CREATE INDEX "index_playthrough_tolls_on_location_id" ON "playthrough_tolls" ("location_id"); +CREATE INDEX "index_playthrough_tolls_on_playthrough_and_scene" ON "playthrough_tolls" ("playthrough_id", "scene_id", "id"); +CREATE INDEX "index_playthrough_tolls_on_playthrough_id" ON "playthrough_tolls" ("playthrough_id"); +CREATE INDEX "index_playthrough_tolls_on_scene_id" ON "playthrough_tolls" ("scene_id"); +CREATE UNIQUE INDEX "index_playthrough_turn_events_on_command_and_sequence" ON "playthrough_turn_events" ("playthrough_command_id", "sequence"); +CREATE INDEX "index_playthrough_turn_events_on_playthrough_command_id" ON "playthrough_turn_events" ("playthrough_command_id"); +CREATE INDEX "index_playthrough_vitals_on_character_id" ON "playthrough_vitals" ("character_id"); +CREATE UNIQUE INDEX "index_playthrough_vitals_on_playthrough_and_character" ON "playthrough_vitals" ("playthrough_id", "character_id"); +CREATE INDEX "index_playthrough_vitals_on_playthrough_id" ON "playthrough_vitals" ("playthrough_id"); +CREATE INDEX "index_playthrough_volitions_on_character_id" ON "playthrough_volitions" ("character_id"); +CREATE INDEX "index_playthrough_volitions_on_location_id" ON "playthrough_volitions" ("location_id"); +CREATE INDEX "index_playthrough_volitions_on_playthrough_and_scene" ON "playthrough_volitions" ("playthrough_id", "scene_id", "id"); +CREATE INDEX "index_playthrough_volitions_on_playthrough_id" ON "playthrough_volitions" ("playthrough_id"); +CREATE INDEX "index_playthrough_volitions_on_scene_id" ON "playthrough_volitions" ("scene_id"); +CREATE INDEX "index_playthroughs_on_character_id" ON "playthroughs" ("character_id"); +CREATE INDEX "index_playthroughs_on_current_location_id" ON "playthroughs" ("current_location_id"); +CREATE INDEX "index_playthroughs_on_current_scene_id" ON "playthroughs" ("current_scene_id"); +CREATE INDEX "index_playthroughs_on_player_id" ON "playthroughs" ("player_id"); +CREATE INDEX "index_playthroughs_on_story_id" ON "playthroughs" ("story_id"); +CREATE UNIQUE INDEX "index_playthroughs_on_token" ON "playthroughs" ("token"); +CREATE INDEX "index_quest_outcomes_on_character_id" ON "quest_outcomes" ("character_id"); +CREATE UNIQUE INDEX "index_quest_outcomes_on_quest_id_and_name" ON "quest_outcomes" ("quest_id", "name"); +CREATE INDEX "index_quest_outcomes_on_quest_id" ON "quest_outcomes" ("quest_id"); +CREATE INDEX "index_quest_steps_on_companion_id" ON "quest_steps" ("companion_id"); +CREATE UNIQUE INDEX "index_quest_steps_on_quest_id_and_position" ON "quest_steps" ("quest_id", "position"); +CREATE INDEX "index_quest_steps_on_quest_id" ON "quest_steps" ("quest_id"); +CREATE INDEX "index_quest_steps_on_target" ON "quest_steps" ("target_type", "target_id"); +CREATE INDEX "index_quests_on_parent_quest_id" ON "quests" ("parent_quest_id"); +CREATE UNIQUE INDEX "index_quests_on_story_id_and_title" ON "quests" ("story_id", "title"); +CREATE INDEX "index_quests_on_story_id" ON "quests" ("story_id"); +CREATE UNIQUE INDEX "index_races_on_universe_id_and_name" ON "races" ("universe_id", "name"); +CREATE INDEX "index_races_on_universe_id" ON "races" ("universe_id"); +CREATE INDEX "index_relay_receipts_on_player_id_and_created_at" ON "relay_receipts" ("player_id", "created_at"); +CREATE INDEX "index_relay_receipts_on_player_id_and_status" ON "relay_receipts" ("player_id", "status"); +CREATE INDEX "index_relay_receipts_on_player_id" ON "relay_receipts" ("player_id"); +CREATE INDEX "index_scenes_on_acted_on" ON "scenes" ("acted_on_type", "acted_on_id"); +CREATE INDEX "index_scenes_on_location_id" ON "scenes" ("location_id"); +CREATE INDEX "index_scenes_on_previous_scene_id" ON "scenes" ("previous_scene_id"); +CREATE INDEX "index_scenes_on_story_id_and_is_opening" ON "scenes" ("story_id", "is_opening"); +CREATE INDEX "index_scenes_on_story_id_and_resolved_action" ON "scenes" ("story_id", "resolved_action"); +CREATE INDEX "index_scenes_on_story_id_and_story_timestamp" ON "scenes" ("story_id", "story_timestamp"); +CREATE INDEX "index_scenes_on_story_id" ON "scenes" ("story_id"); +CREATE INDEX "index_stories_on_universe_id" ON "stories" ("universe_id"); +CREATE INDEX "index_system_one_receipts_on_player_id_and_created_at" ON "system_one_receipts" ("player_id", "created_at"); +CREATE INDEX "index_system_one_receipts_on_player_id" ON "system_one_receipts" ("player_id"); +CREATE INDEX "index_system_one_receipts_on_playthrough_id" ON "system_one_receipts" ("playthrough_id"); +CREATE INDEX "index_world_events_on_playthrough_id" ON "world_events" ("playthrough_id"); +CREATE INDEX "index_world_events_on_story_id_and_occurred_at" ON "world_events" ("story_id", "occurred_at"); +CREATE INDEX "index_world_events_on_story_id_and_scheduled_for" ON "world_events" ("story_id", "scheduled_for"); +CREATE INDEX "index_world_events_on_story_id" ON "world_events" ("story_id"); +CREATE INDEX "index_world_events_on_world_mechanic_id" ON "world_events" ("world_mechanic_id"); +CREATE UNIQUE INDEX "index_world_mechanics_on_story_id_and_name" ON "world_mechanics" ("story_id", "name"); +CREATE INDEX "index_world_mechanics_on_story_id" ON "world_mechanics" ("story_id"); +PRAGMA writable_schema=OFF; +COMMIT; diff --git a/parity/worlds/a-dose-beyond-two-doors.sql b/parity/worlds/a-dose-beyond-two-doors.sql index be12c9e..551aea1 100644 --- a/parity/worlds/a-dose-beyond-two-doors.sql +++ b/parity/worlds/a-dose-beyond-two-doors.sql @@ -13,17 +13,6 @@ CREATE TABLE IF NOT EXISTS "ruby_llm_usages" ("id" integer PRIMARY KEY AUTOINCRE CREATE TABLE IF NOT EXISTS "ruby_llm_v2_backfills" ("completed" boolean DEFAULT FALSE NOT NULL, "last_id" integer, "task" varchar NOT NULL); CREATE TABLE IF NOT EXISTS "universes" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "civilizations" text, "created_at" datetime(6) NOT NULL, "economics" text, "geographies" text, "history" text, "physics" text, "politics" text, "religion" text, "technology" text, "updated_at" datetime(6) NOT NULL, "weapons" text, "gravity" varchar, "carrying" varchar); INSERT INTO universes VALUES(1000000001,'A small trading town.','2026-10-02 13:26:09.567495','Goods traded for coins.','A workshop, yard and storerooms.','Traders built the workshop together.','Ordinary physics.','A town council keeps the peace.','The town keeps its own festivals.','Keys, locks and hand tools.','2026-10-02 13:26:09.567495','Wooden staves.',NULL,NULL); -CREATE TABLE IF NOT EXISTS "characters" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "age" integer, "appearance" text, "backstory" text, "conscious_desire" text, "created_at" datetime(6) NOT NULL, "deliberately_absent" boolean DEFAULT FALSE NOT NULL, "desire_pursuit" varchar, "dexterity" integer, "dislikes" text, "fears" text, "fullname" varchar, "hit_die" integer, "hostile" boolean DEFAULT FALSE NOT NULL, "is_companion" boolean, "is_protagonist" boolean DEFAULT FALSE NOT NULL, "level" integer, "likes" text, "location_id" integer, "need_pursuit" varchar, "nickname" varchar, "personality" text, "race_id" integer NOT NULL, "recognized_need" text, "sex" varchar, "story_id" integer NOT NULL, "strength" integer, "unconscious_desire" text, "unrecognized_need" text, "updated_at" datetime(6) NOT NULL, "will" integer, "x" integer, "y" integer, CONSTRAINT "fk_rails_56a213cbe2" -FOREIGN KEY ("race_id") - REFERENCES "races" ("id") -, CONSTRAINT "fk_rails_0b4b445641" -FOREIGN KEY ("location_id") - REFERENCES "locations" ("id") -, CONSTRAINT "fk_rails_6608c248e8" -FOREIGN KEY ("story_id") - REFERENCES "stories" ("id") -); -INSERT INTO characters VALUES(1000000001,30,'A travel-worn coat.','A trader returning for supplies.',NULL,'2026-10-02 13:26:09.790338',0,NULL,12,'Waste.','Losing needed supplies.','Cal',8,0,0,1,3,'Well-kept tools.',NULL,NULL,'Cal','Patient and practical.',1000000001,NULL,'male',1000000001,3,NULL,NULL,'2026-10-02 13:26:09.790338',12,NULL,NULL); CREATE TABLE IF NOT EXISTS "chats" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "cancelled" boolean DEFAULT FALSE NOT NULL, "character_id" integer, "created_at" datetime(6) NOT NULL, "model_id_string" varchar, "player_id" integer, "playthrough_id" integer, "purpose" varchar, "ruby_llm_model_id" integer, "updated_at" datetime(6) NOT NULL, CONSTRAINT "fk_rails_415520c982" FOREIGN KEY ("playthrough_id") REFERENCES "playthroughs" ("id") @@ -415,6 +404,7 @@ INSERT INTO schema_migrations VALUES('20261025230000'); INSERT INTO schema_migrations VALUES('20261027120117'); INSERT INTO schema_migrations VALUES('20261030120000'); INSERT INTO schema_migrations VALUES('20261030200000'); +INSERT INTO schema_migrations VALUES('20261030230000'); CREATE TABLE IF NOT EXISTS "ar_internal_metadata" ("key" varchar NOT NULL PRIMARY KEY, "value" varchar, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL); INSERT INTO ar_internal_metadata VALUES('environment','test','2026-10-02 13:25:33.143313','2026-10-02 13:25:33.143316'); INSERT INTO ar_internal_metadata VALUES('schema_sha1','072abc2bfb80d111a1808df270e9210911f5e81d','2026-10-02 13:25:33.148224','2026-10-02 13:25:33.148228'); @@ -425,13 +415,27 @@ FOREIGN KEY ("playthrough_id") FOREIGN KEY ("quest_outcome_id") REFERENCES "quest_outcomes" ("id") ); -CREATE TABLE IF NOT EXISTS "floors" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "location_id" integer NOT NULL, "rooms" integer NOT NULL, "danger" varchar, "hazard" varchar, "down_location_id" integer, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, CONSTRAINT "fk_rails_ce8b005cb2" +CREATE TABLE IF NOT EXISTS "floors" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "location_id" integer NOT NULL, "rooms" integer NOT NULL, "danger" varchar, "hazard" varchar, "down_location_id" integer, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, "monster_level" integer, CONSTRAINT "fk_rails_ce8b005cb2" FOREIGN KEY ("location_id") REFERENCES "locations" ("id") ON DELETE CASCADE, CONSTRAINT "fk_rails_8a0268155a" FOREIGN KEY ("down_location_id") REFERENCES "locations" ("id") ON DELETE SET NULL); +CREATE TABLE IF NOT EXISTS "characters" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "age" integer, "appearance" text, "backstory" text, "conscious_desire" text, "created_at" datetime(6) NOT NULL, "deliberately_absent" boolean DEFAULT FALSE NOT NULL, "desire_pursuit" varchar, "dexterity" integer, "dislikes" text, "fears" text, "fullname" varchar, "hit_die" integer, "hostile" boolean DEFAULT FALSE NOT NULL, "is_companion" boolean, "is_protagonist" boolean DEFAULT FALSE NOT NULL, "level" integer, "likes" text, "location_id" integer, "need_pursuit" varchar, "nickname" varchar, "personality" text, "race_id" integer NOT NULL, "recognized_need" text, "sex" varchar, "story_id" integer NOT NULL, "strength" integer, "unconscious_desire" text, "unrecognized_need" text, "updated_at" datetime(6) NOT NULL, "will" integer, "x" integer, "y" integer, "posted_to_id" integer, CONSTRAINT "fk_rails_6608c248e8" +FOREIGN KEY ("story_id") + REFERENCES "stories" ("id") +, CONSTRAINT "fk_rails_0b4b445641" +FOREIGN KEY ("location_id") + REFERENCES "locations" ("id") +, CONSTRAINT "fk_rails_56a213cbe2" +FOREIGN KEY ("race_id") + REFERENCES "races" ("id") +, CONSTRAINT "fk_rails_4c54fc506e" +FOREIGN KEY ("posted_to_id") + REFERENCES "locations" ("id") + ON DELETE SET NULL); +INSERT INTO characters VALUES(1000000001,30,'A travel-worn coat.','A trader returning for supplies.',NULL,'2026-10-02 13:26:09.790338',0,NULL,12,'Waste.','Losing needed supplies.','Cal',8,0,0,1,3,'Well-kept tools.',NULL,NULL,'Cal','Patient and practical.',1000000001,NULL,'male',1000000001,3,NULL,NULL,'2026-10-02 13:26:09.790338',12,NULL,NULL,NULL); PRAGMA writable_schema=ON; CREATE TABLE IF NOT EXISTS sqlite_sequence(name,seq); DELETE FROM sqlite_sequence; @@ -444,7 +448,6 @@ INSERT INTO sqlite_sequence VALUES('ruby_llm_models',1000000000); INSERT INTO sqlite_sequence VALUES('ruby_llm_tool_calls',1000000000); INSERT INTO sqlite_sequence VALUES('ruby_llm_usages',1000000000); INSERT INTO sqlite_sequence VALUES('universes',1000000001); -INSERT INTO sqlite_sequence VALUES('characters',1000000001); INSERT INTO sqlite_sequence VALUES('chats',1000000000); INSERT INTO sqlite_sequence VALUES('interactions',1000000000); INSERT INTO sqlite_sequence VALUES('items',1000000004); @@ -479,6 +482,7 @@ INSERT INTO sqlite_sequence VALUES('stories',1000000001); INSERT INTO sqlite_sequence VALUES('system_one_receipts',1000000000); INSERT INTO sqlite_sequence VALUES('world_events',1000000000); INSERT INTO sqlite_sequence VALUES('world_mechanics',1000000000); +INSERT INTO sqlite_sequence VALUES('characters',1000000001); CREATE INDEX "index_characters_scenes_on_character_id_and_scene_id" ON "characters_scenes" ("character_id", "scene_id"); CREATE INDEX "index_characters_scenes_on_scene_id_and_character_id" ON "characters_scenes" ("scene_id", "character_id"); CREATE UNIQUE INDEX "index_players_on_name" ON "players" ("name"); @@ -499,13 +503,6 @@ CREATE INDEX "index_ruby_llm_usages_on_chat_type_and_chat_id" ON "ruby_llm_usage CREATE INDEX "index_ruby_llm_usages_on_message_type_and_message_id" ON "ruby_llm_usages" ("message_type", "message_id"); CREATE INDEX "index_ruby_llm_usages_on_status" ON "ruby_llm_usages" ("status"); CREATE UNIQUE INDEX "index_ruby_llm_v2_backfills_on_task" ON "ruby_llm_v2_backfills" ("task"); -CREATE UNIQUE INDEX "index_characters_on_story_id_and_lower_fullname" ON "characters" (story_id, LOWER(fullname)); -CREATE INDEX "index_characters_on_location_id_and_hostile" ON "characters" ("location_id", "hostile"); -CREATE INDEX "index_characters_on_location_id_and_id" ON "characters" ("location_id", "id"); -CREATE INDEX "index_characters_on_location_id" ON "characters" ("location_id"); -CREATE INDEX "index_characters_on_race_id" ON "characters" ("race_id"); -CREATE INDEX "index_characters_on_story_id_and_is_protagonist" ON "characters" ("story_id", "is_protagonist"); -CREATE INDEX "index_characters_on_story_id" ON "characters" ("story_id"); CREATE INDEX "index_chats_on_character_id" ON "chats" ("character_id"); CREATE INDEX "index_chats_on_player_id" ON "chats" ("player_id"); CREATE INDEX "index_chats_on_conversation_key" ON "chats" ("playthrough_id", "character_id", "purpose"); @@ -641,5 +638,13 @@ CREATE INDEX "index_playthrough_due_endings_on_quest_outcome_id" ON "playthrough CREATE INDEX "index_quest_steps_on_companion_id" ON "quest_steps" ("companion_id"); CREATE UNIQUE INDEX "index_floors_on_location_id" ON "floors" ("location_id"); CREATE INDEX "index_floors_on_down_location_id" ON "floors" ("down_location_id"); +CREATE UNIQUE INDEX "index_characters_on_story_id_and_lower_fullname" ON "characters" (story_id, LOWER(fullname)); +CREATE INDEX "index_characters_on_location_id_and_hostile" ON "characters" ("location_id", "hostile"); +CREATE INDEX "index_characters_on_location_id_and_id" ON "characters" ("location_id", "id"); +CREATE INDEX "index_characters_on_location_id" ON "characters" ("location_id"); +CREATE INDEX "index_characters_on_race_id" ON "characters" ("race_id"); +CREATE INDEX "index_characters_on_story_id_and_is_protagonist" ON "characters" ("story_id", "is_protagonist"); +CREATE INDEX "index_characters_on_story_id" ON "characters" ("story_id"); +CREATE INDEX "index_characters_on_posted_to_id" ON "characters" ("posted_to_id"); PRAGMA writable_schema=OFF; COMMIT; diff --git a/parity/worlds/a-friend-at-the-gate.sql b/parity/worlds/a-friend-at-the-gate.sql index 06be562..7da50b9 100644 --- a/parity/worlds/a-friend-at-the-gate.sql +++ b/parity/worlds/a-friend-at-the-gate.sql @@ -14,18 +14,6 @@ CREATE TABLE IF NOT EXISTS "ruby_llm_usages" ("id" integer PRIMARY KEY AUTOINCRE CREATE TABLE IF NOT EXISTS "ruby_llm_v2_backfills" ("task" varchar NOT NULL, "last_id" integer, "completed" boolean DEFAULT FALSE NOT NULL); CREATE TABLE IF NOT EXISTS "universes" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "physics" text, "technology" text, "weapons" text, "civilizations" text, "geographies" text, "history" text, "economics" text, "politics" text, "religion" text, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, "gravity" varchar, "carrying" varchar); INSERT INTO universes VALUES(1000000001,'Ordinary physics.','Keys, locks and lanterns.','Wooden staves.','A small trading town.','A market, its courtyard and the lane beyond.','Traders built the market together.','Goods traded for coins.','A town council keeps the peace.','The town keeps its own festivals.','2026-10-27 12:13:20.959990','2026-10-27 12:13:20.959990',NULL,NULL); -CREATE TABLE IF NOT EXISTS "characters" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "story_id" integer NOT NULL, "fullname" varchar, "nickname" varchar, "age" integer, "sex" varchar, "personality" text, "appearance" text, "likes" text, "dislikes" text, "fears" text, "backstory" text, "is_companion" boolean, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, "race_id" integer NOT NULL, "is_protagonist" boolean DEFAULT FALSE NOT NULL, "location_id" integer, "deliberately_absent" boolean DEFAULT FALSE NOT NULL, "level" integer, "hit_die" integer, "strength" integer, "dexterity" integer, "will" integer, "hostile" boolean DEFAULT FALSE NOT NULL, "x" integer, "y" integer, "conscious_desire" text, "unconscious_desire" text, "recognized_need" text, "unrecognized_need" text, "desire_pursuit" varchar, "need_pursuit" varchar, CONSTRAINT "fk_rails_56a213cbe2" -FOREIGN KEY ("race_id") - REFERENCES "races" ("id") -, CONSTRAINT "fk_rails_0b4b445641" -FOREIGN KEY ("location_id") - REFERENCES "locations" ("id") -, CONSTRAINT "fk_rails_6608c248e8" -FOREIGN KEY ("story_id") - REFERENCES "stories" ("id") -); -INSERT INTO characters VALUES(1000000001,1000000001,'Cal','Cal',30,'male','Patient and practical.','A travel-worn coat.','A fair bargain.','Needless arguments.','Losing a friend.','A trader returning to the market.',0,'2026-10-27 12:13:21.151410','2026-10-27 12:13:21.151410',1000000001,1,NULL,0,10,8,12,12,12,0,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL); -INSERT INTO characters VALUES(1000000002,1000000001,'Maren','Maren',32,'female','Willing to listen and keep a promise.','A blue coat and a wooden staff.','Clear agreements.','Deception.','Betraying a trust.','A gatekeeper who knows the old way through the courtyard.',0,'2026-10-27 12:13:21.207835','2026-10-27 12:13:21.207835',1000000001,0,1000000001,0,10,8,12,12,12,0,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL); CREATE TABLE IF NOT EXISTS "chats" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "model_id_string" varchar, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, "ruby_llm_model_id" integer, "purpose" varchar, "playthrough_id" integer, "character_id" integer, "cancelled" boolean DEFAULT FALSE NOT NULL, "player_id" integer, CONSTRAINT "fk_rails_415520c982" FOREIGN KEY ("playthrough_id") REFERENCES "playthroughs" ("id") @@ -422,16 +410,32 @@ INSERT INTO schema_migrations VALUES('20250822184649'); INSERT INTO schema_migrations VALUES('20261027120117'); INSERT INTO schema_migrations VALUES('20261030120000'); INSERT INTO schema_migrations VALUES('20261030200000'); +INSERT INTO schema_migrations VALUES('20261030230000'); CREATE TABLE IF NOT EXISTS "ar_internal_metadata" ("key" varchar NOT NULL PRIMARY KEY, "value" varchar, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL); INSERT INTO ar_internal_metadata VALUES('environment','test','2026-10-27 12:10:16.651328','2026-10-27 12:10:16.651334'); INSERT INTO ar_internal_metadata VALUES('schema_sha1','deec051e019741f5fc4e8fe09fff00a4e1d2f04d','2026-10-27 12:10:16.662468','2026-10-27 12:10:16.662473'); -CREATE TABLE IF NOT EXISTS "floors" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "location_id" integer NOT NULL, "rooms" integer NOT NULL, "danger" varchar, "hazard" varchar, "down_location_id" integer, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, CONSTRAINT "fk_rails_ce8b005cb2" +CREATE TABLE IF NOT EXISTS "floors" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "location_id" integer NOT NULL, "rooms" integer NOT NULL, "danger" varchar, "hazard" varchar, "down_location_id" integer, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, "monster_level" integer, CONSTRAINT "fk_rails_ce8b005cb2" FOREIGN KEY ("location_id") REFERENCES "locations" ("id") ON DELETE CASCADE, CONSTRAINT "fk_rails_8a0268155a" FOREIGN KEY ("down_location_id") REFERENCES "locations" ("id") ON DELETE SET NULL); +CREATE TABLE IF NOT EXISTS "characters" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "story_id" integer NOT NULL, "fullname" varchar, "nickname" varchar, "age" integer, "sex" varchar, "personality" text, "appearance" text, "likes" text, "dislikes" text, "fears" text, "backstory" text, "is_companion" boolean, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, "race_id" integer NOT NULL, "is_protagonist" boolean DEFAULT FALSE NOT NULL, "location_id" integer, "deliberately_absent" boolean DEFAULT FALSE NOT NULL, "level" integer, "hit_die" integer, "strength" integer, "dexterity" integer, "will" integer, "hostile" boolean DEFAULT FALSE NOT NULL, "x" integer, "y" integer, "conscious_desire" text, "unconscious_desire" text, "recognized_need" text, "unrecognized_need" text, "desire_pursuit" varchar, "need_pursuit" varchar, "posted_to_id" integer, CONSTRAINT "fk_rails_6608c248e8" +FOREIGN KEY ("story_id") + REFERENCES "stories" ("id") +, CONSTRAINT "fk_rails_0b4b445641" +FOREIGN KEY ("location_id") + REFERENCES "locations" ("id") +, CONSTRAINT "fk_rails_56a213cbe2" +FOREIGN KEY ("race_id") + REFERENCES "races" ("id") +, CONSTRAINT "fk_rails_4c54fc506e" +FOREIGN KEY ("posted_to_id") + REFERENCES "locations" ("id") + ON DELETE SET NULL); +INSERT INTO characters VALUES(1000000001,1000000001,'Cal','Cal',30,'male','Patient and practical.','A travel-worn coat.','A fair bargain.','Needless arguments.','Losing a friend.','A trader returning to the market.',0,'2026-10-27 12:13:21.151410','2026-10-27 12:13:21.151410',1000000001,1,NULL,0,10,8,12,12,12,0,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL); +INSERT INTO characters VALUES(1000000002,1000000001,'Maren','Maren',32,'female','Willing to listen and keep a promise.','A blue coat and a wooden staff.','Clear agreements.','Deception.','Betraying a trust.','A gatekeeper who knows the old way through the courtyard.',0,'2026-10-27 12:13:21.207835','2026-10-27 12:13:21.207835',1000000001,0,1000000001,0,10,8,12,12,12,0,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL); PRAGMA writable_schema=ON; CREATE TABLE IF NOT EXISTS sqlite_sequence(name,seq); DELETE FROM sqlite_sequence; @@ -444,7 +448,6 @@ INSERT INTO sqlite_sequence VALUES('ruby_llm_models',1000000000); INSERT INTO sqlite_sequence VALUES('ruby_llm_tool_calls',1000000000); INSERT INTO sqlite_sequence VALUES('ruby_llm_usages',1000000000); INSERT INTO sqlite_sequence VALUES('universes',1000000001); -INSERT INTO sqlite_sequence VALUES('characters',1000000002); INSERT INTO sqlite_sequence VALUES('chats',1000000000); INSERT INTO sqlite_sequence VALUES('interactions',1000000000); INSERT INTO sqlite_sequence VALUES('items',1000000000); @@ -480,6 +483,7 @@ INSERT INTO sqlite_sequence VALUES('stories',1000000001); INSERT INTO sqlite_sequence VALUES('system_one_receipts',1000000000); INSERT INTO sqlite_sequence VALUES('world_events',1000000000); INSERT INTO sqlite_sequence VALUES('world_mechanics',1000000000); +INSERT INTO sqlite_sequence VALUES('characters',1000000002); CREATE INDEX "index_characters_scenes_on_character_id_and_scene_id" ON "characters_scenes" ("character_id", "scene_id"); CREATE INDEX "index_characters_scenes_on_scene_id_and_character_id" ON "characters_scenes" ("scene_id", "character_id"); CREATE UNIQUE INDEX "index_players_on_name" ON "players" ("name"); @@ -500,13 +504,6 @@ CREATE INDEX "index_ruby_llm_usages_on_chat_type_and_chat_id" ON "ruby_llm_usage CREATE INDEX "index_ruby_llm_usages_on_message_type_and_message_id" ON "ruby_llm_usages" ("message_type", "message_id"); CREATE INDEX "index_ruby_llm_usages_on_status" ON "ruby_llm_usages" ("status"); CREATE UNIQUE INDEX "index_ruby_llm_v2_backfills_on_task" ON "ruby_llm_v2_backfills" ("task"); -CREATE UNIQUE INDEX "index_characters_on_story_id_and_lower_fullname" ON "characters" (story_id, LOWER(fullname)); -CREATE INDEX "index_characters_on_location_id_and_hostile" ON "characters" ("location_id", "hostile"); -CREATE INDEX "index_characters_on_location_id_and_id" ON "characters" ("location_id", "id"); -CREATE INDEX "index_characters_on_location_id" ON "characters" ("location_id"); -CREATE INDEX "index_characters_on_race_id" ON "characters" ("race_id"); -CREATE INDEX "index_characters_on_story_id_and_is_protagonist" ON "characters" ("story_id", "is_protagonist"); -CREATE INDEX "index_characters_on_story_id" ON "characters" ("story_id"); CREATE INDEX "index_chats_on_character_id" ON "chats" ("character_id"); CREATE INDEX "index_chats_on_player_id" ON "chats" ("player_id"); CREATE INDEX "index_chats_on_conversation_key" ON "chats" ("playthrough_id", "character_id", "purpose"); @@ -642,5 +639,13 @@ CREATE INDEX "index_world_mechanics_on_story_id" ON "world_mechanics" ("story_id CREATE INDEX "index_quest_steps_on_companion_id" ON "quest_steps" ("companion_id"); CREATE UNIQUE INDEX "index_floors_on_location_id" ON "floors" ("location_id"); CREATE INDEX "index_floors_on_down_location_id" ON "floors" ("down_location_id"); +CREATE UNIQUE INDEX "index_characters_on_story_id_and_lower_fullname" ON "characters" (story_id, LOWER(fullname)); +CREATE INDEX "index_characters_on_location_id_and_hostile" ON "characters" ("location_id", "hostile"); +CREATE INDEX "index_characters_on_location_id_and_id" ON "characters" ("location_id", "id"); +CREATE INDEX "index_characters_on_location_id" ON "characters" ("location_id"); +CREATE INDEX "index_characters_on_race_id" ON "characters" ("race_id"); +CREATE INDEX "index_characters_on_story_id_and_is_protagonist" ON "characters" ("story_id", "is_protagonist"); +CREATE INDEX "index_characters_on_story_id" ON "characters" ("story_id"); +CREATE INDEX "index_characters_on_posted_to_id" ON "characters" ("posted_to_id"); PRAGMA writable_schema=OFF; COMMIT; diff --git a/parity/worlds/a-guarded-stair.sql b/parity/worlds/a-guarded-stair.sql new file mode 100644 index 0000000..a9da3ae --- /dev/null +++ b/parity/worlds/a-guarded-stair.sql @@ -0,0 +1,654 @@ +PRAGMA foreign_keys=OFF; +BEGIN TRANSACTION; +CREATE TABLE IF NOT EXISTS "characters_scenes" ("character_id" integer NOT NULL, "scene_id" integer NOT NULL); +INSERT INTO characters_scenes VALUES(1000000001,1000000001); +CREATE TABLE IF NOT EXISTS "lab_exits_vantages" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "world" varchar NOT NULL, "name" varchar NOT NULL, "teaser" text NOT NULL, "reached_from" varchar, "danger" varchar, "absent" text, "expects_inside_quantifier" varchar, "expects_population" text, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL); +CREATE TABLE IF NOT EXISTS "lab_realization_kinds" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "world" varchar NOT NULL, "name" varchar NOT NULL, "teaser" text NOT NULL, "reached_from" varchar, "inside" varchar, "danger" varchar, "population" varchar, "expects_inside" text, "expects_population" text, "expects_storeys_above" text, "expects_storeys_below" text, "expects_danger" text, "expects_gradient" text, "expects_hazard" text, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL); +CREATE TABLE IF NOT EXISTS "players" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "name" varchar NOT NULL, "token_digest" varchar NOT NULL, "monthly_limit_usd" decimal(10,4) DEFAULT 1.0 NOT NULL, "revoked_at" datetime(6), "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL); +CREATE TABLE IF NOT EXISTS "playthrough_visits" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "playthrough_id" integer NOT NULL, "location_id" integer NOT NULL, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL); +CREATE TABLE IF NOT EXISTS "refused_lines" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "typed" text NOT NULL, "state" json NOT NULL, "answers" json, "reading" json NOT NULL, "act_confidence" float, "thing_confidence" float, "refusal" json NOT NULL, "place" varchar, "weights" varchar, "playthrough_id" integer, "location_id" integer, "status" varchar DEFAULT 'new' NOT NULL, "label" json, "labelled_at" datetime(6), "note" text, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL); +CREATE TABLE IF NOT EXISTS "ruby_llm_batches" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "provider_batch_id" varchar NOT NULL, "provider" varchar NOT NULL, "status" varchar NOT NULL, "raw_status" varchar, "completed" boolean DEFAULT FALSE NOT NULL, "chat_type" varchar, "batch_protocol" varchar, "chat_ids" json DEFAULT '[]', "request_counts" json, "reported_cost" json, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL); +CREATE TABLE IF NOT EXISTS "ruby_llm_models" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "model_id" varchar NOT NULL, "name" varchar NOT NULL, "provider" varchar NOT NULL, "family" varchar, "model_created_at" datetime(6), "context_window" integer, "max_output_tokens" integer, "knowledge_cutoff" date, "modalities" json DEFAULT '{}', "capabilities" json DEFAULT '[]', "pricing" json DEFAULT '{}', "metadata" json DEFAULT '{}', "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, "unlisted_at" datetime(6)); +CREATE TABLE IF NOT EXISTS "ruby_llm_tool_calls" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "message_id" integer NOT NULL, "tool_call_id" varchar NOT NULL, "name" varchar NOT NULL, "arguments" json DEFAULT '{}', "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, "message_type" varchar NOT NULL, "result_type" varchar, "result_id" integer, "thought_signature" text, "approval" varchar, "remote" boolean DEFAULT FALSE NOT NULL); +CREATE TABLE IF NOT EXISTS "ruby_llm_usages" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "chat_type" varchar NOT NULL, "chat_id" integer NOT NULL, "message_type" varchar, "message_id" integer, "operation" varchar NOT NULL, "provider" varchar NOT NULL, "model" varchar NOT NULL, "status" varchar NOT NULL, "input_tokens" integer, "output_tokens" integer, "cache_read_tokens" integer, "cache_write_tokens" integer, "thinking_tokens" integer, "input_cost" decimal(16,10), "output_cost" decimal(16,10), "cache_read_cost" decimal(16,10), "cache_write_cost" decimal(16,10), "thinking_cost" decimal(16,10), "total_cost" decimal(16,10), "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, "latency_ms" integer, CONSTRAINT chk_rails_71abd85d6e CHECK (operation IN ('chat', 'embedding', 'moderation', 'image', 'speech', 'transcription', 'ocr', 'rerank')), CONSTRAINT chk_rails_f50895962a CHECK (status IN ('pending', 'succeeded', 'failed', 'cancelled'))); +CREATE TABLE IF NOT EXISTS "ruby_llm_v2_backfills" ("task" varchar NOT NULL, "last_id" integer, "completed" boolean DEFAULT FALSE NOT NULL); +CREATE TABLE IF NOT EXISTS "universes" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "physics" text, "technology" text, "weapons" text, "civilizations" text, "geographies" text, "history" text, "economics" text, "politics" text, "religion" text, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, "gravity" varchar, "carrying" varchar); +INSERT INTO universes VALUES(1000000001,'Ordinary physics.','Lanterns, iron keys and hand tools.','Short blades and cudgels.','Delvers who sell what they bring up, and the things that keep the stairs.','A dungeon cut down into black stone under a lantern-lit landing.','The dungeon was cut long ago by people nobody remembers.','Coin for what comes up from below.','Nobody governs the dungeon.','Delvers leave a coin on the landing for luck.','2026-10-30 13:44:45.000666','2026-10-30 13:44:45.000666',NULL,NULL); +CREATE TABLE IF NOT EXISTS "characters" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "story_id" integer NOT NULL, "fullname" varchar, "nickname" varchar, "age" integer, "sex" varchar, "personality" text, "appearance" text, "likes" text, "dislikes" text, "fears" text, "backstory" text, "is_companion" boolean, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, "race_id" integer NOT NULL, "is_protagonist" boolean DEFAULT FALSE NOT NULL, "location_id" integer, "deliberately_absent" boolean DEFAULT FALSE NOT NULL, "level" integer, "hit_die" integer, "strength" integer, "dexterity" integer, "will" integer, "hostile" boolean DEFAULT FALSE NOT NULL, "x" integer, "y" integer, "conscious_desire" text, "unconscious_desire" text, "recognized_need" text, "unrecognized_need" text, "desire_pursuit" varchar, "need_pursuit" varchar, "posted_to_id" integer, CONSTRAINT "fk_rails_56a213cbe2" +FOREIGN KEY ("race_id") + REFERENCES "races" ("id") +, CONSTRAINT "fk_rails_0b4b445641" +FOREIGN KEY ("location_id") + REFERENCES "locations" ("id") +, CONSTRAINT "fk_rails_4c54fc506e" +FOREIGN KEY ("posted_to_id") + REFERENCES "locations" ("id") + ON DELETE SET NULL, CONSTRAINT "fk_rails_6608c248e8" +FOREIGN KEY ("story_id") + REFERENCES "stories" ("id") +); +INSERT INTO characters VALUES(1000000001,1000000001,'Odile','Odile',29,'female','Careful and stubborn.','A lantern on a pole and a short blade.','A clear way back.','Doors she cannot open.','The dark below the first floor.','A delver who has never been below the first floor.',0,'2026-10-30 13:44:45.234206','2026-10-30 13:44:45.234206',1000000001,1,NULL,0,3,8,14,12,12,0,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL); +INSERT INTO characters VALUES(1000000002,1000000001,'Brask the Stairwarden','Brask',90,'male','Silent and patient.','A grey, stooped thing with a long iron key on a cord.','Stillness.','Footsteps on the stair.','Nothing.','He has kept the stair down for longer than the delvers have come.',0,'2026-10-30 13:44:45.268270','2026-10-30 13:44:45.268270',1000000002,0,NULL,1,1,6,8,8,8,1,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,1000000002); +CREATE TABLE IF NOT EXISTS "chats" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "model_id_string" varchar, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, "ruby_llm_model_id" integer, "purpose" varchar, "playthrough_id" integer, "character_id" integer, "cancelled" boolean DEFAULT FALSE NOT NULL, "player_id" integer, CONSTRAINT "fk_rails_415520c982" +FOREIGN KEY ("playthrough_id") + REFERENCES "playthroughs" ("id") +, CONSTRAINT "fk_rails_5c87bd0780" +FOREIGN KEY ("character_id") + REFERENCES "characters" ("id") +, CONSTRAINT "fk_rails_ea73c47d5b" +FOREIGN KEY ("player_id") + REFERENCES "players" ("id") +, CONSTRAINT "fk_rails_d8a12df187" +FOREIGN KEY ("ruby_llm_model_id") + REFERENCES "ruby_llm_models" ("id") +); +CREATE TABLE IF NOT EXISTS "floors" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "location_id" integer NOT NULL, "rooms" integer NOT NULL, "danger" varchar, "hazard" varchar, "down_location_id" integer, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, "monster_level" integer, CONSTRAINT "fk_rails_8a0268155a" +FOREIGN KEY ("down_location_id") + REFERENCES "locations" ("id") + ON DELETE SET NULL, CONSTRAINT "fk_rails_ce8b005cb2" +FOREIGN KEY ("location_id") + REFERENCES "locations" ("id") + ON DELETE CASCADE); +INSERT INTO floors VALUES(1000000001,1000000002,3,'uneasy','none',1000000003,'2026-10-30 13:44:45.087077','2026-10-30 13:44:45.144689',NULL); +INSERT INTO floors VALUES(1000000002,1000000003,2,'dangerous','unlit',NULL,'2026-10-30 13:44:45.103852','2026-10-30 13:44:45.149215',NULL); +CREATE TABLE IF NOT EXISTS "interactions" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "character_id" integer NOT NULL, "scene_id" integer, "location_id" integer, "pre_thought" text, "pre_feeling" text, "action" text, "post_feeling" text, "post_thought" text, "inner_resolution" text, "summary" text, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, "user_input" text, "engine_action" varchar, "action_status" varchar, "action_fact" text, CONSTRAINT "fk_rails_294d4ae9f8" +FOREIGN KEY ("location_id") + REFERENCES "locations" ("id") +, CONSTRAINT "fk_rails_6b73a2cd17" +FOREIGN KEY ("character_id") + REFERENCES "characters" ("id") +, CONSTRAINT "fk_rails_b73583a008" +FOREIGN KEY ("scene_id") + REFERENCES "scenes" ("id") +); +CREATE TABLE IF NOT EXISTS "items" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "character_id" integer, "name" varchar, "description" text, "properties" text, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, "location_id" integer, "playthrough_id" integer, "readable" boolean DEFAULT FALSE NOT NULL, "inscription" text, "template_id" integer, "bulk" varchar DEFAULT 'handy' NOT NULL, "x" integer, "y" integer, "use_kind" varchar DEFAULT 'ordinary' NOT NULL, "combustible" boolean DEFAULT FALSE NOT NULL, "disposition" varchar DEFAULT 'intact' NOT NULL, "fragility" varchar DEFAULT 'sturdy' NOT NULL, "tier" varchar DEFAULT 'portable' NOT NULL, "holds" varchar, "within_id" integer, "how" varchar, "kit_key" varchar, "noticed_at" datetime(6), "searched_at" datetime(6), "hold" varchar, "material" varchar, "wear" varchar, "worn" boolean DEFAULT FALSE NOT NULL, CONSTRAINT "fk_rails_e8ed83a2e6" +FOREIGN KEY ("location_id") + REFERENCES "locations" ("id") +, CONSTRAINT "fk_rails_35423c7ef8" +FOREIGN KEY ("character_id") + REFERENCES "characters" ("id") +, CONSTRAINT "fk_rails_5248e92099" +FOREIGN KEY ("playthrough_id") + REFERENCES "playthroughs" ("id") +); +INSERT INTO items VALUES(1000000001,1000000002,'stair key','A long iron key on a cord, cold to hold.','{}','2026-10-30 13:44:45.303373','2026-10-30 13:44:45.303373',NULL,NULL,0,NULL,NULL,'handy',NULL,NULL,'key',0,'intact','sturdy','portable',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,0); +CREATE TABLE IF NOT EXISTS "lab_exits_judgements" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "vantage_id" integer NOT NULL, "name" varchar NOT NULL, "name_key" varchar NOT NULL, "expects_inside" text, "expects_population" text, "verdict" varchar, "aspects" text, "note" text, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, CONSTRAINT "fk_rails_cbf2b734ae" +FOREIGN KEY ("vantage_id") + REFERENCES "lab_exits_vantages" ("id") +); +CREATE TABLE IF NOT EXISTS "lab_exits_samples" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "vantage_id" integer NOT NULL, "row" json DEFAULT '{}' NOT NULL, "verdict" varchar, "aspects" text, "note" text, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, CONSTRAINT "fk_rails_3cc36a20a9" +FOREIGN KEY ("vantage_id") + REFERENCES "lab_exits_vantages" ("id") +); +CREATE TABLE IF NOT EXISTS "lab_realization_samples" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "kind_id" integer NOT NULL, "row" json DEFAULT '{}' NOT NULL, "verdict" varchar, "aspects" text, "note" text, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, CONSTRAINT "fk_rails_7bb6f06ff4" +FOREIGN KEY ("kind_id") + REFERENCES "lab_realization_kinds" ("id") +); +CREATE TABLE IF NOT EXISTS "location_connections" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "location_id" integer NOT NULL, "connected_location_id" integer NOT NULL, "distance" text, "time_to_travel" text, "travel_method" text, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, "hazard" varchar, "hazard_die" integer, "barrier" varchar DEFAULT 'open' NOT NULL, "key_template_id" integer, CONSTRAINT "fk_rails_99792c7943" +FOREIGN KEY ("location_id") + REFERENCES "locations" ("id") +, CONSTRAINT "fk_rails_34dc49debe" +FOREIGN KEY ("key_template_id") + REFERENCES "items" ("id") + ON DELETE SET NULL, CONSTRAINT "fk_rails_37a61eef17" +FOREIGN KEY ("connected_location_id") + REFERENCES "locations" ("id") +); +INSERT INTO location_connections VALUES(1000000001,1000000001,1000000002,'adjacent','about a minute','taking stairs','2026-10-30 13:44:45.333552','2026-10-30 13:44:45.333552',NULL,NULL,'open',NULL); +INSERT INTO location_connections VALUES(1000000002,1000000002,1000000001,'adjacent','about a minute','taking stairs','2026-10-30 13:44:45.336069','2026-10-30 13:44:45.336069',NULL,NULL,'open',NULL); +INSERT INTO location_connections VALUES(1000000003,1000000002,1000000003,'adjacent','about a minute','taking stairs','2026-10-30 13:44:45.344493','2026-10-30 13:44:45.344493',NULL,NULL,'keyed',1000000001); +INSERT INTO location_connections VALUES(1000000004,1000000003,1000000002,'adjacent','about a minute','taking stairs','2026-10-30 13:44:45.348197','2026-10-30 13:44:45.348197',NULL,NULL,'keyed',1000000001); +CREATE TABLE IF NOT EXISTS "locations" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "story_id" integer NOT NULL, "name" varchar, "description" text, "lore" text, "last_protagonist_visit" datetime(6), "parent_location_id" integer, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, "detail_level" varchar DEFAULT 'stub' NOT NULL, "teaser" text, "mobile" boolean DEFAULT FALSE NOT NULL, "danger" varchar DEFAULT 'safe' NOT NULL, "hazard" varchar, "hazard_die" integer, "x" integer, "y" integer, "z" integer, "width" integer, "depth" integer, "population" varchar, "generation_checkpoint" json, "surface" varchar, "kind" varchar, "density" varchar, CONSTRAINT "fk_rails_5bc98acf09" +FOREIGN KEY ("parent_location_id") + REFERENCES "locations" ("id") +, CONSTRAINT "fk_rails_fedd9b21a0" +FOREIGN KEY ("story_id") + REFERENCES "stories" ("id") +); +INSERT INTO locations VALUES(1000000001,1000000001,'The Landing','A square landing of black stone lit by one lantern, a stair going down from it.','Delvers gather here before they go down.',NULL,NULL,'2026-10-30 13:44:45.058257','2026-10-30 13:44:45.058257','realized','A lantern-lit landing.',0,'safe',NULL,NULL,NULL,NULL,NULL,NULL,NULL,'nobody',NULL,NULL,NULL,NULL); +INSERT INTO locations VALUES(1000000002,1000000001,'The Upper Floor',NULL,NULL,NULL,NULL,'2026-10-30 13:44:45.078386','2026-10-30 13:44:45.078386','stub','The first level of the dungeon, with a stair somewhere that goes further down.',0,'safe',NULL,NULL,NULL,NULL,NULL,12,12,NULL,NULL,NULL,'warren',NULL); +INSERT INTO locations VALUES(1000000003,1000000001,'The Lower Floor',NULL,NULL,NULL,NULL,'2026-10-30 13:44:45.096638','2026-10-30 13:44:45.096638','stub','The second level of the dungeon, colder and quieter than the first.',0,'safe',NULL,NULL,NULL,NULL,NULL,12,6,NULL,NULL,NULL,'warren',NULL); +CREATE TABLE IF NOT EXISTS "locations_world_events" ("location_id" integer NOT NULL, "world_event_id" integer NOT NULL, CONSTRAINT "fk_rails_861146f35c" +FOREIGN KEY ("location_id") + REFERENCES "locations" ("id") +, CONSTRAINT "fk_rails_16317bfdf9" +FOREIGN KEY ("world_event_id") + REFERENCES "world_events" ("id") +); +CREATE TABLE IF NOT EXISTS "messages" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "chat_id" integer NOT NULL, "role" varchar, "content" text, "model_id_string" varchar, "input_tokens" integer, "output_tokens" integer, "tool_call_id" integer, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, "model_id" integer, "scene_id" integer, "content_raw" json, "cache_until_here" boolean DEFAULT FALSE NOT NULL, "finish_reason" varchar, "citations" json, "server_tool_calls" json, "raw_content" json, "raw_reasoning" json, CONSTRAINT "fk_rails_c02b47ad97" +FOREIGN KEY ("model_id") + REFERENCES "ruby_llm_models" ("id") +, CONSTRAINT "fk_rails_0f670de7ba" +FOREIGN KEY ("chat_id") + REFERENCES "chats" ("id") +, CONSTRAINT "fk_rails_5c4d747b35" +FOREIGN KEY ("scene_id") + REFERENCES "scenes" ("id") +); +CREATE TABLE IF NOT EXISTS "playthrough_beats" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "playthrough_id" integer NOT NULL, "quest_step_id" integer NOT NULL, "reached_at" datetime(6) NOT NULL, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, CONSTRAINT "fk_rails_d479d5ffac" +FOREIGN KEY ("playthrough_id") + REFERENCES "playthroughs" ("id") +, CONSTRAINT "fk_rails_c536cc5a9f" +FOREIGN KEY ("quest_step_id") + REFERENCES "quest_steps" ("id") +); +CREATE TABLE IF NOT EXISTS "playthrough_blows" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "playthrough_id" integer NOT NULL, "attacker_id" integer NOT NULL, "target_id" integer NOT NULL, "location_id" integer NOT NULL, "damage" integer NOT NULL, "hp_after" integer NOT NULL, "round" integer NOT NULL, "sequence" integer NOT NULL, "story_timestamp" datetime(6) NOT NULL, "scene_id" integer, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, CONSTRAINT "fk_rails_f0e8a189c5" +FOREIGN KEY ("playthrough_id") + REFERENCES "playthroughs" ("id") +, CONSTRAINT "fk_rails_9a2b0679db" +FOREIGN KEY ("target_id") + REFERENCES "characters" ("id") +, CONSTRAINT "fk_rails_d02f38aa6d" +FOREIGN KEY ("attacker_id") + REFERENCES "characters" ("id") +, CONSTRAINT "fk_rails_104e085e3d" +FOREIGN KEY ("location_id") + REFERENCES "locations" ("id") +, CONSTRAINT "fk_rails_83e55c02a1" +FOREIGN KEY ("scene_id") + REFERENCES "scenes" ("id") +); +CREATE TABLE IF NOT EXISTS "playthrough_commands" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "playthrough_id" integer NOT NULL, "request_token" varchar NOT NULL, "command" text NOT NULL, "status" varchar DEFAULT 'pending' NOT NULL, "result_scene_id" integer, "refusal" json DEFAULT '{}' NOT NULL, "error_kind" varchar, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, "journal" json DEFAULT '{}' NOT NULL, CONSTRAINT "fk_rails_53f23eb49f" +FOREIGN KEY ("playthrough_id") + REFERENCES "playthroughs" ("id") +, CONSTRAINT "fk_rails_69c7d16a1a" +FOREIGN KEY ("result_scene_id") + REFERENCES "scenes" ("id") +); +CREATE TABLE IF NOT EXISTS "playthrough_drifts" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "playthrough_id" integer NOT NULL, "scene_id" integer, "location_id" integer, "action" varchar NOT NULL, "command" text NOT NULL, "offered" text, "story_timestamp" datetime(6), "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, CONSTRAINT "fk_rails_018f29e0ac" +FOREIGN KEY ("playthrough_id") + REFERENCES "playthroughs" ("id") +, CONSTRAINT "fk_rails_094a605342" +FOREIGN KEY ("location_id") + REFERENCES "locations" ("id") +, CONSTRAINT "fk_rails_45044856a9" +FOREIGN KEY ("scene_id") + REFERENCES "scenes" ("id") +); +CREATE TABLE IF NOT EXISTS "playthrough_due_endings" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "playthrough_id" integer NOT NULL, "quest_outcome_id" integer NOT NULL, "due_at" datetime(6) NOT NULL, "declined_at" datetime(6), "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, CONSTRAINT "fk_rails_c471335e2f" +FOREIGN KEY ("playthrough_id") + REFERENCES "playthroughs" ("id") +, CONSTRAINT "fk_rails_b8e95530cf" +FOREIGN KEY ("quest_outcome_id") + REFERENCES "quest_outcomes" ("id") +); +CREATE TABLE IF NOT EXISTS "playthrough_endings" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "playthrough_id" integer NOT NULL, "quest_outcome_id" integer NOT NULL, "reached_at" datetime(6) NOT NULL, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, CONSTRAINT "fk_rails_762b78023d" +FOREIGN KEY ("playthrough_id") + REFERENCES "playthroughs" ("id") +, CONSTRAINT "fk_rails_2220d324b0" +FOREIGN KEY ("quest_outcome_id") + REFERENCES "quest_outcomes" ("id") +); +CREATE TABLE IF NOT EXISTS "playthrough_feedbacks" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "playthrough_id" integer NOT NULL, "scene_id" integer NOT NULL, "verdict" varchar NOT NULL, "note" text, "prose_model" varchar, "prose_purpose" varchar, "prose_models" text, "answering_models" text, "input_tokens" integer, "output_tokens" integer, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, "prose_prompt_digest" varchar, CONSTRAINT "fk_rails_99ee3b49ea" +FOREIGN KEY ("playthrough_id") + REFERENCES "playthroughs" ("id") +, CONSTRAINT "fk_rails_247709935a" +FOREIGN KEY ("scene_id") + REFERENCES "scenes" ("id") +); +CREATE TABLE IF NOT EXISTS "playthrough_npc_states" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "playthrough_id" integer NOT NULL, "character_id" integer NOT NULL, "location_id" integer, "following" boolean DEFAULT FALSE NOT NULL, "ceasefire" boolean DEFAULT FALSE NOT NULL, "peace_after_blow_id" integer DEFAULT 0 NOT NULL, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, "noticed_at" datetime(6), CONSTRAINT "fk_rails_ef100d7841" +FOREIGN KEY ("location_id") + REFERENCES "locations" ("id") +, CONSTRAINT "fk_rails_71a135780d" +FOREIGN KEY ("character_id") + REFERENCES "characters" ("id") +, CONSTRAINT "fk_rails_984efad0e9" +FOREIGN KEY ("playthrough_id") + REFERENCES "playthroughs" ("id") +); +CREATE TABLE IF NOT EXISTS "playthrough_overreaches" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "playthrough_id" integer NOT NULL, "scene_id" integer, "location_id" integer, "action" varchar NOT NULL, "command" text NOT NULL, "acted" text NOT NULL, "unacted" text NOT NULL, "story_timestamp" datetime(6), "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, CONSTRAINT "fk_rails_b903d1bc16" +FOREIGN KEY ("playthrough_id") + REFERENCES "playthroughs" ("id") +, CONSTRAINT "fk_rails_9aac75595a" +FOREIGN KEY ("location_id") + REFERENCES "locations" ("id") +, CONSTRAINT "fk_rails_879cc0f78c" +FOREIGN KEY ("scene_id") + REFERENCES "scenes" ("id") +); +CREATE TABLE IF NOT EXISTS "playthrough_paragraphs" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "playthrough_id" integer NOT NULL, "scene_id" integer NOT NULL, "author" varchar DEFAULT 'player' NOT NULL, "text" text NOT NULL, "audit" json, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, "consent" varchar DEFAULT 'none' NOT NULL, "requests" json, "prompt_digest" varchar, CONSTRAINT "fk_rails_db3983d32c" +FOREIGN KEY ("playthrough_id") + REFERENCES "playthroughs" ("id") +, CONSTRAINT "fk_rails_52ef8100a0" +FOREIGN KEY ("scene_id") + REFERENCES "scenes" ("id") +); +CREATE TABLE IF NOT EXISTS "playthrough_passages" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "playthrough_id" integer NOT NULL, "location_connection_id" integer NOT NULL, "opened_by_item_id" integer, "means" varchar NOT NULL, "opened_at" datetime(6) NOT NULL, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, CONSTRAINT "fk_rails_7bdf6a743a" +FOREIGN KEY ("location_connection_id") + REFERENCES "location_connections" ("id") + ON DELETE CASCADE, CONSTRAINT "fk_rails_75c2355411" +FOREIGN KEY ("opened_by_item_id") + REFERENCES "items" ("id") + ON DELETE SET NULL, CONSTRAINT "fk_rails_995c25e2fd" +FOREIGN KEY ("playthrough_id") + REFERENCES "playthroughs" ("id") + ON DELETE CASCADE); +CREATE TABLE IF NOT EXISTS "playthrough_tolls" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "playthrough_id" integer NOT NULL, "character_id" integer NOT NULL, "location_id" integer NOT NULL, "location_connection_id" integer, "hazard" varchar NOT NULL, "saved" boolean DEFAULT FALSE NOT NULL, "damage" integer NOT NULL, "hp_after" integer NOT NULL, "sequence" integer NOT NULL, "story_timestamp" datetime(6) NOT NULL, "scene_id" integer, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, CONSTRAINT "fk_rails_83dfd307ab" +FOREIGN KEY ("playthrough_id") + REFERENCES "playthroughs" ("id") +, CONSTRAINT "fk_rails_443984de3a" +FOREIGN KEY ("location_connection_id") + REFERENCES "location_connections" ("id") +, CONSTRAINT "fk_rails_8a2212da5c" +FOREIGN KEY ("character_id") + REFERENCES "characters" ("id") +, CONSTRAINT "fk_rails_6ec4904a86" +FOREIGN KEY ("location_id") + REFERENCES "locations" ("id") +, CONSTRAINT "fk_rails_af4dd03665" +FOREIGN KEY ("scene_id") + REFERENCES "scenes" ("id") +); +CREATE TABLE IF NOT EXISTS "playthrough_turn_events" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "playthrough_command_id" integer NOT NULL, "sequence" integer NOT NULL, "kind" varchar NOT NULL, "data" json DEFAULT '{}' NOT NULL, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, CONSTRAINT "fk_rails_c7a6770f24" +FOREIGN KEY ("playthrough_command_id") + REFERENCES "playthrough_commands" ("id") +); +CREATE TABLE IF NOT EXISTS "playthrough_vitals" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "playthrough_id" integer NOT NULL, "character_id" integer NOT NULL, "hp_current" integer NOT NULL, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, "provoked_at" datetime(6), CONSTRAINT "fk_rails_03cf4b1b8e" +FOREIGN KEY ("character_id") + REFERENCES "characters" ("id") +, CONSTRAINT "fk_rails_9956f79aa5" +FOREIGN KEY ("playthrough_id") + REFERENCES "playthroughs" ("id") +); +CREATE TABLE IF NOT EXISTS "playthrough_volitions" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "playthrough_id" integer NOT NULL, "character_id" integer NOT NULL, "location_id" integer NOT NULL, "scene_id" integer, "chosen" varchar NOT NULL, "status" varchar NOT NULL, "fact" text NOT NULL, "serves" varchar NOT NULL, "round" integer NOT NULL, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, "decided_by" varchar, "system_one_error" varchar, CONSTRAINT "fk_rails_55e6f9e865" +FOREIGN KEY ("playthrough_id") + REFERENCES "playthroughs" ("id") +, CONSTRAINT "fk_rails_0c408b6253" +FOREIGN KEY ("character_id") + REFERENCES "characters" ("id") +, CONSTRAINT "fk_rails_1824dea9f6" +FOREIGN KEY ("location_id") + REFERENCES "locations" ("id") +, CONSTRAINT "fk_rails_a054701564" +FOREIGN KEY ("scene_id") + REFERENCES "scenes" ("id") +); +CREATE TABLE IF NOT EXISTS "playthroughs" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "story_id" integer NOT NULL, "character_id" integer, "current_location_id" integer, "current_scene_id" integer, "token" varchar NOT NULL, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, "ended_at" datetime(6), "player_id" integer, "mode" varchar DEFAULT 'narrated' NOT NULL, CONSTRAINT "fk_rails_9b48509224" +FOREIGN KEY ("current_scene_id") + REFERENCES "scenes" ("id") +, CONSTRAINT "fk_rails_3a7e48fa36" +FOREIGN KEY ("current_location_id") + REFERENCES "locations" ("id") +, CONSTRAINT "fk_rails_5b54078c4b" +FOREIGN KEY ("character_id") + REFERENCES "characters" ("id") +, CONSTRAINT "fk_rails_ab75586f4b" +FOREIGN KEY ("player_id") + REFERENCES "players" ("id") +, CONSTRAINT "fk_rails_3d6d3a9c5a" +FOREIGN KEY ("story_id") + REFERENCES "stories" ("id") +); +CREATE TABLE IF NOT EXISTS "quest_outcomes" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "quest_id" integer NOT NULL, "name" varchar NOT NULL, "summary" text NOT NULL, "is_default" boolean DEFAULT FALSE NOT NULL, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, "condition" varchar, "minutes" integer, "ramification_summary" text, "ramification_minutes" integer, "step_position" integer, "character_id" integer, CONSTRAINT "fk_rails_acf8ece7c5" +FOREIGN KEY ("quest_id") + REFERENCES "quests" ("id") +); +CREATE TABLE IF NOT EXISTS "quest_steps" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "quest_id" integer NOT NULL, "position" integer NOT NULL, "summary" text NOT NULL, "trigger_kind" varchar NOT NULL, "target_name" varchar, "teaser" text, "target_type" varchar, "target_id" integer, "minutes" integer, "bound_at" datetime(6), "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, "companion_id" integer, CONSTRAINT "fk_rails_ba1603d17b" +FOREIGN KEY ("quest_id") + REFERENCES "quests" ("id") +); +CREATE TABLE IF NOT EXISTS "quests" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "story_id" integer NOT NULL, "parent_quest_id" integer, "title" varchar NOT NULL, "premise" text NOT NULL, "status" varchar DEFAULT 'open' NOT NULL, "contributes" boolean DEFAULT TRUE NOT NULL, "origin" varchar DEFAULT 'seeded' NOT NULL, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, CONSTRAINT "fk_rails_a64954ae79" +FOREIGN KEY ("parent_quest_id") + REFERENCES "quests" ("id") +, CONSTRAINT "fk_rails_fa77fc496b" +FOREIGN KEY ("story_id") + REFERENCES "stories" ("id") +); +CREATE TABLE IF NOT EXISTS "races" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "universe_id" integer NOT NULL, "name" varchar NOT NULL, "description" text NOT NULL, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, "monstrous" boolean DEFAULT FALSE NOT NULL, CONSTRAINT "fk_rails_c25ac61605" +FOREIGN KEY ("universe_id") + REFERENCES "universes" ("id") +); +INSERT INTO races VALUES(1000000001,1000000001,'Delvers','People who go down into the dungeon for a living.','2026-10-30 13:44:45.004137','2026-10-30 13:44:45.004137',0); +INSERT INTO races VALUES(1000000002,1000000001,'Stairwardens','Grey, silent things that keep the dungeon''s stairs.','2026-10-30 13:44:45.007065','2026-10-30 13:44:45.007065',1); +CREATE TABLE IF NOT EXISTS "relay_receipts" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "player_id" integer NOT NULL, "route" varchar NOT NULL, "model" varchar NOT NULL, "stream" boolean DEFAULT FALSE NOT NULL, "status" varchar DEFAULT 'open' NOT NULL, "reserved_usd" decimal(12,6) NOT NULL, "cost_usd" decimal(12,6), "cost_source" varchar, "input_tokens" integer, "output_tokens" integer, "upstream_status" integer, "finished_at" datetime(6), "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, CONSTRAINT "fk_rails_deae27bd71" +FOREIGN KEY ("player_id") + REFERENCES "players" ("id") +); +CREATE TABLE IF NOT EXISTS "scenes" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "story_id" integer NOT NULL, "location_id" integer NOT NULL, "previous_scene_id" integer, "description" text, "story_timestamp" datetime(6), "summary" text, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, "is_opening" boolean DEFAULT FALSE NOT NULL, "typed" text, "resolved_action" varchar, "acted_on_type" varchar, "acted_on_id" integer, "resolved_by" varchar, "engine_fallback" boolean DEFAULT FALSE NOT NULL, "engine_fact" text, CONSTRAINT "fk_rails_5cc24f985a" +FOREIGN KEY ("previous_scene_id") + REFERENCES "scenes" ("id") +, CONSTRAINT "fk_rails_abfe1c0369" +FOREIGN KEY ("location_id") + REFERENCES "locations" ("id") +, CONSTRAINT "fk_rails_38ea512481" +FOREIGN KEY ("story_id") + REFERENCES "stories" ("id") +); +INSERT INTO scenes VALUES(1000000001,1000000001,1000000001,NULL,'You stand on the lantern-lit landing above the first floor.','2026-10-30 08:00:00','Odile reaches the landing.','2026-10-30 13:44:45.429759','2026-10-30 13:44:45.429759',1,NULL,NULL,NULL,NULL,NULL,0,NULL); +CREATE TABLE IF NOT EXISTS "stories" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "title" varchar, "genre" varchar, "preface" text, "summary" text, "start_time" datetime(6), "universe_id" integer NOT NULL, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, "generation_snapshot" text, "people_limit" integer, "things_limit" integer, CONSTRAINT "fk_rails_2a912ea846" +FOREIGN KEY ("universe_id") + REFERENCES "universes" ("id") +); +INSERT INTO stories VALUES(1000000001,'A Guarded Stair (engine sweep)','dungeon crawl','Every stair down in the dungeon has something standing at the top of it.','Odile goes down past the thing that keeps the stair.','2026-10-30 08:00:00',1000000001,'2026-10-30 13:44:45.029578','2026-10-30 13:44:45.029578',NULL,NULL,NULL); +CREATE TABLE IF NOT EXISTS "system_one_receipts" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "player_id" integer, "playthrough_id" integer, "purpose" varchar, "transport" varchar, "cost_usd" decimal(12,6) NOT NULL, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, "cost_source" varchar DEFAULT 'estimated' NOT NULL, "latency_ms" integer, CONSTRAINT "fk_rails_4ae941c854" +FOREIGN KEY ("player_id") + REFERENCES "players" ("id") +, CONSTRAINT "fk_rails_75847e1556" +FOREIGN KEY ("playthrough_id") + REFERENCES "playthroughs" ("id") +); +CREATE TABLE IF NOT EXISTS "world_events" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "world_mechanic_id" integer, "story_id" integer NOT NULL, "occurred_at" datetime(6) NOT NULL, "summary" text NOT NULL, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, "source" varchar NOT NULL, "playthrough_id" integer, "scheduled_for" datetime(6), "fired_at" datetime(6), CONSTRAINT "fk_rails_cafc0959e8" +FOREIGN KEY ("story_id") + REFERENCES "stories" ("id") +, CONSTRAINT "fk_rails_efc6cdec27" +FOREIGN KEY ("world_mechanic_id") + REFERENCES "world_mechanics" ("id") +); +CREATE TABLE IF NOT EXISTS "world_mechanics" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "story_id" integer NOT NULL, "name" varchar NOT NULL, "kind" varchar NOT NULL, "cadence" varchar NOT NULL, "description" text, "last_run_at" datetime(6), "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, CONSTRAINT "fk_rails_d7328bbbe5" +FOREIGN KEY ("story_id") + REFERENCES "stories" ("id") +); +CREATE TABLE IF NOT EXISTS "schema_migrations" ("version" varchar NOT NULL PRIMARY KEY); +INSERT INTO schema_migrations VALUES('20261030230000'); +INSERT INTO schema_migrations VALUES('20261030200000'); +INSERT INTO schema_migrations VALUES('20261030120000'); +INSERT INTO schema_migrations VALUES('20261029154235'); +INSERT INTO schema_migrations VALUES('20261027120117'); +INSERT INTO schema_migrations VALUES('20261025230000'); +INSERT INTO schema_migrations VALUES('20261025200000'); +INSERT INTO schema_migrations VALUES('20261025120000'); +INSERT INTO schema_migrations VALUES('20261024120000'); +INSERT INTO schema_migrations VALUES('20261015213000'); +INSERT INTO schema_migrations VALUES('20261015200000'); +INSERT INTO schema_migrations VALUES('20261015180000'); +INSERT INTO schema_migrations VALUES('20261015120000'); +INSERT INTO schema_migrations VALUES('20261010220000'); +INSERT INTO schema_migrations VALUES('20261010180000'); +INSERT INTO schema_migrations VALUES('20261010120000'); +INSERT INTO schema_migrations VALUES('20260929023507'); +INSERT INTO schema_migrations VALUES('20260928124453'); +INSERT INTO schema_migrations VALUES('20260928102823'); +INSERT INTO schema_migrations VALUES('20260928035434'); +INSERT INTO schema_migrations VALUES('20260928035429'); +INSERT INTO schema_migrations VALUES('20260928031026'); +INSERT INTO schema_migrations VALUES('20260928025950'); +INSERT INTO schema_migrations VALUES('20260928020741'); +INSERT INTO schema_migrations VALUES('20260927152056'); +INSERT INTO schema_migrations VALUES('20260927031841'); +INSERT INTO schema_migrations VALUES('20260927031839'); +INSERT INTO schema_migrations VALUES('20260927031837'); +INSERT INTO schema_migrations VALUES('20260927031835'); +INSERT INTO schema_migrations VALUES('20260927031834'); +INSERT INTO schema_migrations VALUES('20260926195041'); +INSERT INTO schema_migrations VALUES('20260922141619'); +INSERT INTO schema_migrations VALUES('20260922141618'); +INSERT INTO schema_migrations VALUES('20260922141617'); +INSERT INTO schema_migrations VALUES('20260919202606'); +INSERT INTO schema_migrations VALUES('20260919202605'); +INSERT INTO schema_migrations VALUES('20260917133823'); +INSERT INTO schema_migrations VALUES('20260910041334'); +INSERT INTO schema_migrations VALUES('20260910025040'); +INSERT INTO schema_migrations VALUES('20260909030346'); +INSERT INTO schema_migrations VALUES('20260909023028'); +INSERT INTO schema_migrations VALUES('20260909023014'); +INSERT INTO schema_migrations VALUES('20260908203558'); +INSERT INTO schema_migrations VALUES('20260908155546'); +INSERT INTO schema_migrations VALUES('20260908120000'); +INSERT INTO schema_migrations VALUES('20260907150000'); +INSERT INTO schema_migrations VALUES('20260907140000'); +INSERT INTO schema_migrations VALUES('20260907120000'); +INSERT INTO schema_migrations VALUES('20260906130000'); +INSERT INTO schema_migrations VALUES('20260906120000'); +INSERT INTO schema_migrations VALUES('20260905150000'); +INSERT INTO schema_migrations VALUES('20260905140000'); +INSERT INTO schema_migrations VALUES('20260905130000'); +INSERT INTO schema_migrations VALUES('20260905120000'); +INSERT INTO schema_migrations VALUES('20260905110000'); +INSERT INTO schema_migrations VALUES('20260905100001'); +INSERT INTO schema_migrations VALUES('20260905100000'); +INSERT INTO schema_migrations VALUES('20260905090100'); +INSERT INTO schema_migrations VALUES('20260905090000'); +INSERT INTO schema_migrations VALUES('20260904190000'); +INSERT INTO schema_migrations VALUES('20260904180000'); +INSERT INTO schema_migrations VALUES('20260904170000'); +INSERT INTO schema_migrations VALUES('20260904160000'); +INSERT INTO schema_migrations VALUES('20260904120000'); +INSERT INTO schema_migrations VALUES('20260903170000'); +INSERT INTO schema_migrations VALUES('20260903120000'); +INSERT INTO schema_migrations VALUES('20260902120000'); +INSERT INTO schema_migrations VALUES('20260901170000'); +INSERT INTO schema_migrations VALUES('20260901160000'); +INSERT INTO schema_migrations VALUES('20260901120000'); +INSERT INTO schema_migrations VALUES('20260831140000'); +INSERT INTO schema_migrations VALUES('20260831130000'); +INSERT INTO schema_migrations VALUES('20260831120000'); +INSERT INTO schema_migrations VALUES('20260831000000'); +INSERT INTO schema_migrations VALUES('20260830220946'); +INSERT INTO schema_migrations VALUES('20260830220945'); +INSERT INTO schema_migrations VALUES('20260830210000'); +INSERT INTO schema_migrations VALUES('20260830200001'); +INSERT INTO schema_migrations VALUES('20260830200000'); +INSERT INTO schema_migrations VALUES('20260830192215'); +INSERT INTO schema_migrations VALUES('20260830192214'); +INSERT INTO schema_migrations VALUES('20250824193519'); +INSERT INTO schema_migrations VALUES('20250823024100'); +INSERT INTO schema_migrations VALUES('20250823024050'); +INSERT INTO schema_migrations VALUES('20250823024037'); +INSERT INTO schema_migrations VALUES('20250823024029'); +INSERT INTO schema_migrations VALUES('20250823024021'); +INSERT INTO schema_migrations VALUES('20250823024013'); +INSERT INTO schema_migrations VALUES('20250823023950'); +INSERT INTO schema_migrations VALUES('20250823023942'); +INSERT INTO schema_migrations VALUES('20250823023925'); +INSERT INTO schema_migrations VALUES('20250822184651'); +INSERT INTO schema_migrations VALUES('20250822184650'); +INSERT INTO schema_migrations VALUES('20250822184649'); +CREATE TABLE IF NOT EXISTS "ar_internal_metadata" ("key" varchar NOT NULL PRIMARY KEY, "value" varchar, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL); +INSERT INTO ar_internal_metadata VALUES('environment','test','2026-10-30 13:44:43.769467','2026-10-30 13:44:43.769470'); +INSERT INTO ar_internal_metadata VALUES('schema_sha1','997d2947cb01ed87e8df6e9bddf8aab22ba21a27','2026-10-30 13:44:43.773358','2026-10-30 13:44:43.773359'); +PRAGMA writable_schema=ON; +CREATE TABLE IF NOT EXISTS sqlite_sequence(name,seq); +DELETE FROM sqlite_sequence; +INSERT INTO sqlite_sequence VALUES('lab_exits_vantages',1000000000); +INSERT INTO sqlite_sequence VALUES('lab_realization_kinds',1000000000); +INSERT INTO sqlite_sequence VALUES('players',1000000000); +INSERT INTO sqlite_sequence VALUES('playthrough_visits',1000000000); +INSERT INTO sqlite_sequence VALUES('refused_lines',1000000000); +INSERT INTO sqlite_sequence VALUES('ruby_llm_batches',1000000000); +INSERT INTO sqlite_sequence VALUES('ruby_llm_models',1000000000); +INSERT INTO sqlite_sequence VALUES('ruby_llm_tool_calls',1000000000); +INSERT INTO sqlite_sequence VALUES('ruby_llm_usages',1000000000); +INSERT INTO sqlite_sequence VALUES('universes',1000000001); +INSERT INTO sqlite_sequence VALUES('characters',1000000002); +INSERT INTO sqlite_sequence VALUES('chats',1000000000); +INSERT INTO sqlite_sequence VALUES('floors',1000000002); +INSERT INTO sqlite_sequence VALUES('interactions',1000000000); +INSERT INTO sqlite_sequence VALUES('items',1000000001); +INSERT INTO sqlite_sequence VALUES('lab_exits_judgements',1000000000); +INSERT INTO sqlite_sequence VALUES('lab_exits_samples',1000000000); +INSERT INTO sqlite_sequence VALUES('lab_realization_samples',1000000000); +INSERT INTO sqlite_sequence VALUES('location_connections',1000000004); +INSERT INTO sqlite_sequence VALUES('locations',1000000003); +INSERT INTO sqlite_sequence VALUES('messages',1000000000); +INSERT INTO sqlite_sequence VALUES('playthrough_beats',1000000000); +INSERT INTO sqlite_sequence VALUES('playthrough_blows',1000000000); +INSERT INTO sqlite_sequence VALUES('playthrough_commands',1000000000); +INSERT INTO sqlite_sequence VALUES('playthrough_drifts',1000000000); +INSERT INTO sqlite_sequence VALUES('playthrough_due_endings',1000000000); +INSERT INTO sqlite_sequence VALUES('playthrough_endings',1000000000); +INSERT INTO sqlite_sequence VALUES('playthrough_feedbacks',1000000000); +INSERT INTO sqlite_sequence VALUES('playthrough_npc_states',1000000000); +INSERT INTO sqlite_sequence VALUES('playthrough_overreaches',1000000000); +INSERT INTO sqlite_sequence VALUES('playthrough_paragraphs',1000000000); +INSERT INTO sqlite_sequence VALUES('playthrough_passages',1000000000); +INSERT INTO sqlite_sequence VALUES('playthrough_tolls',1000000000); +INSERT INTO sqlite_sequence VALUES('playthrough_turn_events',1000000000); +INSERT INTO sqlite_sequence VALUES('playthrough_vitals',1000000000); +INSERT INTO sqlite_sequence VALUES('playthrough_volitions',1000000000); +INSERT INTO sqlite_sequence VALUES('playthroughs',1000000000); +INSERT INTO sqlite_sequence VALUES('quest_outcomes',1000000000); +INSERT INTO sqlite_sequence VALUES('quest_steps',1000000000); +INSERT INTO sqlite_sequence VALUES('quests',1000000000); +INSERT INTO sqlite_sequence VALUES('races',1000000002); +INSERT INTO sqlite_sequence VALUES('relay_receipts',1000000000); +INSERT INTO sqlite_sequence VALUES('scenes',1000000001); +INSERT INTO sqlite_sequence VALUES('stories',1000000001); +INSERT INTO sqlite_sequence VALUES('system_one_receipts',1000000000); +INSERT INTO sqlite_sequence VALUES('world_events',1000000000); +INSERT INTO sqlite_sequence VALUES('world_mechanics',1000000000); +CREATE INDEX "index_characters_scenes_on_character_id_and_scene_id" ON "characters_scenes" ("character_id", "scene_id"); +CREATE INDEX "index_characters_scenes_on_scene_id_and_character_id" ON "characters_scenes" ("scene_id", "character_id"); +CREATE UNIQUE INDEX "index_players_on_name" ON "players" ("name"); +CREATE UNIQUE INDEX "index_players_on_token_digest" ON "players" ("token_digest"); +CREATE INDEX "index_playthrough_visits_on_location_id" ON "playthrough_visits" ("location_id"); +CREATE UNIQUE INDEX "index_playthrough_visits_on_playthrough_and_location" ON "playthrough_visits" ("playthrough_id", "location_id"); +CREATE INDEX "index_playthrough_visits_on_playthrough_id" ON "playthrough_visits" ("playthrough_id"); +CREATE INDEX "index_refused_lines_on_status" ON "refused_lines" ("status"); +CREATE UNIQUE INDEX "index_ruby_llm_batches_on_provider_and_provider_batch_id" ON "ruby_llm_batches" ("provider", "provider_batch_id"); +CREATE INDEX "index_ruby_llm_batches_on_status" ON "ruby_llm_batches" ("status"); +CREATE INDEX "index_ruby_llm_models_on_family" ON "ruby_llm_models" ("family"); +CREATE UNIQUE INDEX "index_ruby_llm_models_on_provider_and_model_id" ON "ruby_llm_models" ("provider", "model_id"); +CREATE INDEX "index_ruby_llm_models_on_provider" ON "ruby_llm_models" ("provider"); +CREATE INDEX "index_ruby_llm_tool_calls_on_message_type_and_message_id" ON "ruby_llm_tool_calls" ("message_type", "message_id"); +CREATE INDEX "index_ruby_llm_tool_calls_on_name" ON "ruby_llm_tool_calls" ("name"); +CREATE INDEX "index_ruby_llm_tool_calls_on_result_type_and_result_id" ON "ruby_llm_tool_calls" ("result_type", "result_id"); +CREATE UNIQUE INDEX "index_ruby_llm_tool_calls_on_tool_call_id" ON "ruby_llm_tool_calls" ("tool_call_id"); +CREATE INDEX "index_ruby_llm_usages_on_chat_type_and_chat_id" ON "ruby_llm_usages" ("chat_type", "chat_id"); +CREATE INDEX "index_ruby_llm_usages_on_message_type_and_message_id" ON "ruby_llm_usages" ("message_type", "message_id"); +CREATE INDEX "index_ruby_llm_usages_on_status" ON "ruby_llm_usages" ("status"); +CREATE UNIQUE INDEX "index_ruby_llm_v2_backfills_on_task" ON "ruby_llm_v2_backfills" ("task"); +CREATE UNIQUE INDEX "index_characters_on_story_id_and_lower_fullname" ON "characters" (story_id, LOWER(fullname)); +CREATE INDEX "index_characters_on_location_id_and_hostile" ON "characters" ("location_id", "hostile"); +CREATE INDEX "index_characters_on_location_id_and_id" ON "characters" ("location_id", "id"); +CREATE INDEX "index_characters_on_location_id" ON "characters" ("location_id"); +CREATE INDEX "index_characters_on_posted_to_id" ON "characters" ("posted_to_id"); +CREATE INDEX "index_characters_on_race_id" ON "characters" ("race_id"); +CREATE INDEX "index_characters_on_story_id_and_is_protagonist" ON "characters" ("story_id", "is_protagonist"); +CREATE INDEX "index_characters_on_story_id" ON "characters" ("story_id"); +CREATE INDEX "index_chats_on_character_id" ON "chats" ("character_id"); +CREATE INDEX "index_chats_on_player_id" ON "chats" ("player_id"); +CREATE INDEX "index_chats_on_conversation_key" ON "chats" ("playthrough_id", "character_id", "purpose"); +CREATE INDEX "index_chats_on_playthrough_id" ON "chats" ("playthrough_id"); +CREATE INDEX "index_chats_on_ruby_llm_model_id" ON "chats" ("ruby_llm_model_id"); +CREATE INDEX "index_floors_on_down_location_id" ON "floors" ("down_location_id"); +CREATE UNIQUE INDEX "index_floors_on_location_id" ON "floors" ("location_id"); +CREATE INDEX "index_interactions_on_character_id" ON "interactions" ("character_id"); +CREATE INDEX "index_interactions_on_location_id" ON "interactions" ("location_id"); +CREATE INDEX "index_interactions_on_scene_id" ON "interactions" ("scene_id"); +CREATE INDEX "index_items_on_character_id" ON "items" ("character_id"); +CREATE INDEX "index_items_on_location_id_and_character_id" ON "items" ("location_id", "character_id"); +CREATE INDEX "index_items_on_location_id" ON "items" ("location_id"); +CREATE INDEX "index_items_on_playthrough_id_and_id" ON "items" ("playthrough_id", "id"); +CREATE INDEX "index_items_on_playthrough_id_and_template_id" ON "items" ("playthrough_id", "template_id"); +CREATE INDEX "index_items_on_playthrough_id" ON "items" ("playthrough_id"); +CREATE INDEX "index_items_on_template_id" ON "items" ("template_id"); +CREATE INDEX "index_items_on_within_id" ON "items" ("within_id"); +CREATE UNIQUE INDEX "index_lab_exits_judgements_on_place" ON "lab_exits_judgements" ("vantage_id", "name_key"); +CREATE INDEX "index_lab_exits_judgements_on_vantage_id" ON "lab_exits_judgements" ("vantage_id"); +CREATE INDEX "index_lab_exits_samples_on_vantage_id" ON "lab_exits_samples" ("vantage_id"); +CREATE INDEX "index_lab_realization_samples_on_kind_id" ON "lab_realization_samples" ("kind_id"); +CREATE INDEX "index_location_connections_on_connected_location_id" ON "location_connections" ("connected_location_id"); +CREATE INDEX "index_location_connections_on_key_template_id" ON "location_connections" ("key_template_id"); +CREATE UNIQUE INDEX "idx_on_location_id_connected_location_id_a0efda2bf6" ON "location_connections" ("location_id", "connected_location_id"); +CREATE INDEX "index_location_connections_on_location_id" ON "location_connections" ("location_id"); +CREATE UNIQUE INDEX "index_locations_on_story_id_and_lower_name" ON "locations" (story_id, lower(name)); +CREATE INDEX "index_locations_on_parent_location_id" ON "locations" ("parent_location_id"); +CREATE INDEX "index_locations_on_story_id_and_detail_level" ON "locations" ("story_id", "detail_level"); +CREATE INDEX "index_locations_on_story_id" ON "locations" ("story_id"); +CREATE INDEX "index_locations_world_events_on_location_id" ON "locations_world_events" ("location_id"); +CREATE UNIQUE INDEX "index_locations_world_events_on_world_event_id_and_location_id" ON "locations_world_events" ("world_event_id", "location_id"); +CREATE INDEX "index_locations_world_events_on_world_event_id" ON "locations_world_events" ("world_event_id"); +CREATE INDEX "index_messages_on_chat_id" ON "messages" ("chat_id"); +CREATE INDEX "index_messages_on_model_id" ON "messages" ("model_id"); +CREATE INDEX "index_messages_on_scene_id" ON "messages" ("scene_id"); +CREATE INDEX "index_messages_on_tool_call_id" ON "messages" ("tool_call_id"); +CREATE UNIQUE INDEX "index_playthrough_beats_on_playthrough_id_and_quest_step_id" ON "playthrough_beats" ("playthrough_id", "quest_step_id"); +CREATE INDEX "index_playthrough_beats_on_playthrough_id" ON "playthrough_beats" ("playthrough_id"); +CREATE INDEX "index_playthrough_beats_on_quest_step_id" ON "playthrough_beats" ("quest_step_id"); +CREATE INDEX "index_playthrough_blows_on_attacker_id" ON "playthrough_blows" ("attacker_id"); +CREATE INDEX "index_playthrough_blows_on_location_id" ON "playthrough_blows" ("location_id"); +CREATE INDEX "index_playthrough_blows_on_playthrough_and_scene" ON "playthrough_blows" ("playthrough_id", "scene_id", "id"); +CREATE INDEX "index_playthrough_blows_on_playthrough_id" ON "playthrough_blows" ("playthrough_id"); +CREATE INDEX "index_playthrough_blows_on_scene_id" ON "playthrough_blows" ("scene_id"); +CREATE INDEX "index_playthrough_blows_on_target_id" ON "playthrough_blows" ("target_id"); +CREATE UNIQUE INDEX "index_playthrough_commands_on_submission" ON "playthrough_commands" ("playthrough_id", "request_token", "command"); +CREATE INDEX "index_playthrough_commands_on_playthrough_id" ON "playthrough_commands" ("playthrough_id"); +CREATE INDEX "index_playthrough_commands_on_result_scene_id" ON "playthrough_commands" ("result_scene_id"); +CREATE INDEX "index_playthrough_drifts_on_action" ON "playthrough_drifts" ("action"); +CREATE INDEX "index_playthrough_drifts_on_location_id" ON "playthrough_drifts" ("location_id"); +CREATE INDEX "index_playthrough_drifts_on_playthrough_id_and_story_timestamp" ON "playthrough_drifts" ("playthrough_id", "story_timestamp"); +CREATE INDEX "index_playthrough_drifts_on_playthrough_id" ON "playthrough_drifts" ("playthrough_id"); +CREATE INDEX "index_playthrough_drifts_on_scene_id" ON "playthrough_drifts" ("scene_id"); +CREATE UNIQUE INDEX "index_playthrough_due_endings_on_playthrough_id" ON "playthrough_due_endings" ("playthrough_id"); +CREATE INDEX "index_playthrough_due_endings_on_quest_outcome_id" ON "playthrough_due_endings" ("quest_outcome_id"); +CREATE UNIQUE INDEX "idx_on_playthrough_id_quest_outcome_id_7ea31b4171" ON "playthrough_endings" ("playthrough_id", "quest_outcome_id"); +CREATE INDEX "index_playthrough_endings_on_playthrough_id" ON "playthrough_endings" ("playthrough_id"); +CREATE INDEX "index_playthrough_endings_on_quest_outcome_id" ON "playthrough_endings" ("quest_outcome_id"); +CREATE UNIQUE INDEX "index_playthrough_feedbacks_on_playthrough_id_and_scene_id" ON "playthrough_feedbacks" ("playthrough_id", "scene_id"); +CREATE INDEX "index_playthrough_feedbacks_on_playthrough_id" ON "playthrough_feedbacks" ("playthrough_id"); +CREATE INDEX "index_playthrough_feedbacks_on_prose_model" ON "playthrough_feedbacks" ("prose_model"); +CREATE INDEX "index_playthrough_feedbacks_on_prose_prompt_digest" ON "playthrough_feedbacks" ("prose_prompt_digest"); +CREATE INDEX "index_playthrough_feedbacks_on_scene_id" ON "playthrough_feedbacks" ("scene_id"); +CREATE INDEX "index_playthrough_feedbacks_on_verdict" ON "playthrough_feedbacks" ("verdict"); +CREATE INDEX "index_playthrough_npc_states_on_character_id" ON "playthrough_npc_states" ("character_id"); +CREATE INDEX "index_playthrough_npc_states_on_location_id" ON "playthrough_npc_states" ("location_id"); +CREATE UNIQUE INDEX "idx_on_playthrough_id_character_id_008ae03862" ON "playthrough_npc_states" ("playthrough_id", "character_id"); +CREATE INDEX "index_playthrough_npc_states_on_playthrough_id" ON "playthrough_npc_states" ("playthrough_id"); +CREATE INDEX "index_playthrough_overreaches_on_action" ON "playthrough_overreaches" ("action"); +CREATE INDEX "index_playthrough_overreaches_on_location_id" ON "playthrough_overreaches" ("location_id"); +CREATE INDEX "idx_on_playthrough_id_story_timestamp_b54cd36315" ON "playthrough_overreaches" ("playthrough_id", "story_timestamp"); +CREATE INDEX "index_playthrough_overreaches_on_playthrough_id" ON "playthrough_overreaches" ("playthrough_id"); +CREATE INDEX "index_playthrough_overreaches_on_scene_id" ON "playthrough_overreaches" ("scene_id"); +CREATE INDEX "index_playthrough_paragraphs_on_playthrough_id" ON "playthrough_paragraphs" ("playthrough_id"); +CREATE UNIQUE INDEX "index_playthrough_paragraphs_on_scene_id_and_author" ON "playthrough_paragraphs" ("scene_id", "author"); +CREATE INDEX "index_playthrough_passages_on_location_connection_id" ON "playthrough_passages" ("location_connection_id"); +CREATE INDEX "index_playthrough_passages_on_opened_by_item_id" ON "playthrough_passages" ("opened_by_item_id"); +CREATE UNIQUE INDEX "idx_on_playthrough_id_location_connection_id_6d3a29d910" ON "playthrough_passages" ("playthrough_id", "location_connection_id"); +CREATE INDEX "index_playthrough_passages_on_playthrough_id" ON "playthrough_passages" ("playthrough_id"); +CREATE INDEX "index_playthrough_tolls_on_character_id" ON "playthrough_tolls" ("character_id"); +CREATE INDEX "index_playthrough_tolls_on_location_connection_id" ON "playthrough_tolls" ("location_connection_id"); +CREATE INDEX "index_playthrough_tolls_on_location_id" ON "playthrough_tolls" ("location_id"); +CREATE INDEX "index_playthrough_tolls_on_playthrough_and_scene" ON "playthrough_tolls" ("playthrough_id", "scene_id", "id"); +CREATE INDEX "index_playthrough_tolls_on_playthrough_id" ON "playthrough_tolls" ("playthrough_id"); +CREATE INDEX "index_playthrough_tolls_on_scene_id" ON "playthrough_tolls" ("scene_id"); +CREATE UNIQUE INDEX "index_playthrough_turn_events_on_command_and_sequence" ON "playthrough_turn_events" ("playthrough_command_id", "sequence"); +CREATE INDEX "index_playthrough_turn_events_on_playthrough_command_id" ON "playthrough_turn_events" ("playthrough_command_id"); +CREATE INDEX "index_playthrough_vitals_on_character_id" ON "playthrough_vitals" ("character_id"); +CREATE UNIQUE INDEX "index_playthrough_vitals_on_playthrough_and_character" ON "playthrough_vitals" ("playthrough_id", "character_id"); +CREATE INDEX "index_playthrough_vitals_on_playthrough_id" ON "playthrough_vitals" ("playthrough_id"); +CREATE INDEX "index_playthrough_volitions_on_character_id" ON "playthrough_volitions" ("character_id"); +CREATE INDEX "index_playthrough_volitions_on_location_id" ON "playthrough_volitions" ("location_id"); +CREATE INDEX "index_playthrough_volitions_on_playthrough_and_scene" ON "playthrough_volitions" ("playthrough_id", "scene_id", "id"); +CREATE INDEX "index_playthrough_volitions_on_playthrough_id" ON "playthrough_volitions" ("playthrough_id"); +CREATE INDEX "index_playthrough_volitions_on_scene_id" ON "playthrough_volitions" ("scene_id"); +CREATE INDEX "index_playthroughs_on_character_id" ON "playthroughs" ("character_id"); +CREATE INDEX "index_playthroughs_on_current_location_id" ON "playthroughs" ("current_location_id"); +CREATE INDEX "index_playthroughs_on_current_scene_id" ON "playthroughs" ("current_scene_id"); +CREATE INDEX "index_playthroughs_on_player_id" ON "playthroughs" ("player_id"); +CREATE INDEX "index_playthroughs_on_story_id" ON "playthroughs" ("story_id"); +CREATE UNIQUE INDEX "index_playthroughs_on_token" ON "playthroughs" ("token"); +CREATE INDEX "index_quest_outcomes_on_character_id" ON "quest_outcomes" ("character_id"); +CREATE UNIQUE INDEX "index_quest_outcomes_on_quest_id_and_name" ON "quest_outcomes" ("quest_id", "name"); +CREATE INDEX "index_quest_outcomes_on_quest_id" ON "quest_outcomes" ("quest_id"); +CREATE INDEX "index_quest_steps_on_companion_id" ON "quest_steps" ("companion_id"); +CREATE UNIQUE INDEX "index_quest_steps_on_quest_id_and_position" ON "quest_steps" ("quest_id", "position"); +CREATE INDEX "index_quest_steps_on_quest_id" ON "quest_steps" ("quest_id"); +CREATE INDEX "index_quest_steps_on_target" ON "quest_steps" ("target_type", "target_id"); +CREATE INDEX "index_quests_on_parent_quest_id" ON "quests" ("parent_quest_id"); +CREATE UNIQUE INDEX "index_quests_on_story_id_and_title" ON "quests" ("story_id", "title"); +CREATE INDEX "index_quests_on_story_id" ON "quests" ("story_id"); +CREATE UNIQUE INDEX "index_races_on_universe_id_and_name" ON "races" ("universe_id", "name"); +CREATE INDEX "index_races_on_universe_id" ON "races" ("universe_id"); +CREATE INDEX "index_relay_receipts_on_player_id_and_created_at" ON "relay_receipts" ("player_id", "created_at"); +CREATE INDEX "index_relay_receipts_on_player_id_and_status" ON "relay_receipts" ("player_id", "status"); +CREATE INDEX "index_relay_receipts_on_player_id" ON "relay_receipts" ("player_id"); +CREATE INDEX "index_scenes_on_acted_on" ON "scenes" ("acted_on_type", "acted_on_id"); +CREATE INDEX "index_scenes_on_location_id" ON "scenes" ("location_id"); +CREATE INDEX "index_scenes_on_previous_scene_id" ON "scenes" ("previous_scene_id"); +CREATE INDEX "index_scenes_on_story_id_and_is_opening" ON "scenes" ("story_id", "is_opening"); +CREATE INDEX "index_scenes_on_story_id_and_resolved_action" ON "scenes" ("story_id", "resolved_action"); +CREATE INDEX "index_scenes_on_story_id_and_story_timestamp" ON "scenes" ("story_id", "story_timestamp"); +CREATE INDEX "index_scenes_on_story_id" ON "scenes" ("story_id"); +CREATE INDEX "index_stories_on_universe_id" ON "stories" ("universe_id"); +CREATE INDEX "index_system_one_receipts_on_player_id_and_created_at" ON "system_one_receipts" ("player_id", "created_at"); +CREATE INDEX "index_system_one_receipts_on_player_id" ON "system_one_receipts" ("player_id"); +CREATE INDEX "index_system_one_receipts_on_playthrough_id" ON "system_one_receipts" ("playthrough_id"); +CREATE INDEX "index_world_events_on_playthrough_id" ON "world_events" ("playthrough_id"); +CREATE INDEX "index_world_events_on_story_id_and_occurred_at" ON "world_events" ("story_id", "occurred_at"); +CREATE INDEX "index_world_events_on_story_id_and_scheduled_for" ON "world_events" ("story_id", "scheduled_for"); +CREATE INDEX "index_world_events_on_story_id" ON "world_events" ("story_id"); +CREATE INDEX "index_world_events_on_world_mechanic_id" ON "world_events" ("world_mechanic_id"); +CREATE UNIQUE INDEX "index_world_mechanics_on_story_id_and_name" ON "world_mechanics" ("story_id", "name"); +CREATE INDEX "index_world_mechanics_on_story_id" ON "world_mechanics" ("story_id"); +PRAGMA writable_schema=OFF; +COMMIT; diff --git a/parity/worlds/a-key-for-the-other-door.sql b/parity/worlds/a-key-for-the-other-door.sql index 9c0bddb..d9e0d47 100644 --- a/parity/worlds/a-key-for-the-other-door.sql +++ b/parity/worlds/a-key-for-the-other-door.sql @@ -13,17 +13,6 @@ CREATE TABLE IF NOT EXISTS "ruby_llm_usages" ("id" integer PRIMARY KEY AUTOINCRE CREATE TABLE IF NOT EXISTS "ruby_llm_v2_backfills" ("completed" boolean DEFAULT FALSE NOT NULL, "last_id" integer, "task" varchar NOT NULL); CREATE TABLE IF NOT EXISTS "universes" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "civilizations" text, "created_at" datetime(6) NOT NULL, "economics" text, "geographies" text, "history" text, "physics" text, "politics" text, "religion" text, "technology" text, "updated_at" datetime(6) NOT NULL, "weapons" text, "gravity" varchar, "carrying" varchar); INSERT INTO universes VALUES(1000000001,'A small trading town.','2026-10-02 13:25:54.189413','Goods traded for coins.','A workshop, a cellar and a storeroom.','Traders built the workshop together.','Ordinary physics.','A town council keeps the peace.','The town keeps its own festivals.','Keys, locks and hand tools.','2026-10-02 13:25:54.189413','Wooden staves.',NULL,NULL); -CREATE TABLE IF NOT EXISTS "characters" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "age" integer, "appearance" text, "backstory" text, "conscious_desire" text, "created_at" datetime(6) NOT NULL, "deliberately_absent" boolean DEFAULT FALSE NOT NULL, "desire_pursuit" varchar, "dexterity" integer, "dislikes" text, "fears" text, "fullname" varchar, "hit_die" integer, "hostile" boolean DEFAULT FALSE NOT NULL, "is_companion" boolean, "is_protagonist" boolean DEFAULT FALSE NOT NULL, "level" integer, "likes" text, "location_id" integer, "need_pursuit" varchar, "nickname" varchar, "personality" text, "race_id" integer NOT NULL, "recognized_need" text, "sex" varchar, "story_id" integer NOT NULL, "strength" integer, "unconscious_desire" text, "unrecognized_need" text, "updated_at" datetime(6) NOT NULL, "will" integer, "x" integer, "y" integer, CONSTRAINT "fk_rails_56a213cbe2" -FOREIGN KEY ("race_id") - REFERENCES "races" ("id") -, CONSTRAINT "fk_rails_0b4b445641" -FOREIGN KEY ("location_id") - REFERENCES "locations" ("id") -, CONSTRAINT "fk_rails_6608c248e8" -FOREIGN KEY ("story_id") - REFERENCES "stories" ("id") -); -INSERT INTO characters VALUES(1000000001,30,'A travel-worn coat.','A trader looking for stores.',NULL,'2026-10-02 13:25:54.417562',0,NULL,12,'Waste.','Being shut in.','Mar',8,0,0,1,3,'Well-kept tools.',NULL,NULL,'Mar','Patient and practical.',1000000001,NULL,'female',1000000001,10,NULL,NULL,'2026-10-02 13:25:54.417562',12,NULL,NULL); CREATE TABLE IF NOT EXISTS "chats" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "cancelled" boolean DEFAULT FALSE NOT NULL, "character_id" integer, "created_at" datetime(6) NOT NULL, "model_id_string" varchar, "player_id" integer, "playthrough_id" integer, "purpose" varchar, "ruby_llm_model_id" integer, "updated_at" datetime(6) NOT NULL, CONSTRAINT "fk_rails_415520c982" FOREIGN KEY ("playthrough_id") REFERENCES "playthroughs" ("id") @@ -414,6 +403,7 @@ INSERT INTO schema_migrations VALUES('20261025230000'); INSERT INTO schema_migrations VALUES('20261027120117'); INSERT INTO schema_migrations VALUES('20261030120000'); INSERT INTO schema_migrations VALUES('20261030200000'); +INSERT INTO schema_migrations VALUES('20261030230000'); CREATE TABLE IF NOT EXISTS "ar_internal_metadata" ("key" varchar NOT NULL PRIMARY KEY, "value" varchar, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL); INSERT INTO ar_internal_metadata VALUES('environment','test','2026-10-02 13:25:33.143313','2026-10-02 13:25:33.143316'); INSERT INTO ar_internal_metadata VALUES('schema_sha1','072abc2bfb80d111a1808df270e9210911f5e81d','2026-10-02 13:25:33.148224','2026-10-02 13:25:33.148228'); @@ -424,13 +414,27 @@ FOREIGN KEY ("playthrough_id") FOREIGN KEY ("quest_outcome_id") REFERENCES "quest_outcomes" ("id") ); -CREATE TABLE IF NOT EXISTS "floors" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "location_id" integer NOT NULL, "rooms" integer NOT NULL, "danger" varchar, "hazard" varchar, "down_location_id" integer, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, CONSTRAINT "fk_rails_ce8b005cb2" +CREATE TABLE IF NOT EXISTS "floors" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "location_id" integer NOT NULL, "rooms" integer NOT NULL, "danger" varchar, "hazard" varchar, "down_location_id" integer, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, "monster_level" integer, CONSTRAINT "fk_rails_ce8b005cb2" FOREIGN KEY ("location_id") REFERENCES "locations" ("id") ON DELETE CASCADE, CONSTRAINT "fk_rails_8a0268155a" FOREIGN KEY ("down_location_id") REFERENCES "locations" ("id") ON DELETE SET NULL); +CREATE TABLE IF NOT EXISTS "characters" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "age" integer, "appearance" text, "backstory" text, "conscious_desire" text, "created_at" datetime(6) NOT NULL, "deliberately_absent" boolean DEFAULT FALSE NOT NULL, "desire_pursuit" varchar, "dexterity" integer, "dislikes" text, "fears" text, "fullname" varchar, "hit_die" integer, "hostile" boolean DEFAULT FALSE NOT NULL, "is_companion" boolean, "is_protagonist" boolean DEFAULT FALSE NOT NULL, "level" integer, "likes" text, "location_id" integer, "need_pursuit" varchar, "nickname" varchar, "personality" text, "race_id" integer NOT NULL, "recognized_need" text, "sex" varchar, "story_id" integer NOT NULL, "strength" integer, "unconscious_desire" text, "unrecognized_need" text, "updated_at" datetime(6) NOT NULL, "will" integer, "x" integer, "y" integer, "posted_to_id" integer, CONSTRAINT "fk_rails_6608c248e8" +FOREIGN KEY ("story_id") + REFERENCES "stories" ("id") +, CONSTRAINT "fk_rails_0b4b445641" +FOREIGN KEY ("location_id") + REFERENCES "locations" ("id") +, CONSTRAINT "fk_rails_56a213cbe2" +FOREIGN KEY ("race_id") + REFERENCES "races" ("id") +, CONSTRAINT "fk_rails_4c54fc506e" +FOREIGN KEY ("posted_to_id") + REFERENCES "locations" ("id") + ON DELETE SET NULL); +INSERT INTO characters VALUES(1000000001,30,'A travel-worn coat.','A trader looking for stores.',NULL,'2026-10-02 13:25:54.417562',0,NULL,12,'Waste.','Being shut in.','Mar',8,0,0,1,3,'Well-kept tools.',NULL,NULL,'Mar','Patient and practical.',1000000001,NULL,'female',1000000001,10,NULL,NULL,'2026-10-02 13:25:54.417562',12,NULL,NULL,NULL); PRAGMA writable_schema=ON; CREATE TABLE IF NOT EXISTS sqlite_sequence(name,seq); DELETE FROM sqlite_sequence; @@ -443,7 +447,6 @@ INSERT INTO sqlite_sequence VALUES('ruby_llm_models',1000000000); INSERT INTO sqlite_sequence VALUES('ruby_llm_tool_calls',1000000000); INSERT INTO sqlite_sequence VALUES('ruby_llm_usages',1000000000); INSERT INTO sqlite_sequence VALUES('universes',1000000001); -INSERT INTO sqlite_sequence VALUES('characters',1000000001); INSERT INTO sqlite_sequence VALUES('chats',1000000000); INSERT INTO sqlite_sequence VALUES('interactions',1000000000); INSERT INTO sqlite_sequence VALUES('items',1000000003); @@ -478,6 +481,7 @@ INSERT INTO sqlite_sequence VALUES('stories',1000000001); INSERT INTO sqlite_sequence VALUES('system_one_receipts',1000000000); INSERT INTO sqlite_sequence VALUES('world_events',1000000000); INSERT INTO sqlite_sequence VALUES('world_mechanics',1000000000); +INSERT INTO sqlite_sequence VALUES('characters',1000000001); CREATE INDEX "index_characters_scenes_on_character_id_and_scene_id" ON "characters_scenes" ("character_id", "scene_id"); CREATE INDEX "index_characters_scenes_on_scene_id_and_character_id" ON "characters_scenes" ("scene_id", "character_id"); CREATE UNIQUE INDEX "index_players_on_name" ON "players" ("name"); @@ -498,13 +502,6 @@ CREATE INDEX "index_ruby_llm_usages_on_chat_type_and_chat_id" ON "ruby_llm_usage CREATE INDEX "index_ruby_llm_usages_on_message_type_and_message_id" ON "ruby_llm_usages" ("message_type", "message_id"); CREATE INDEX "index_ruby_llm_usages_on_status" ON "ruby_llm_usages" ("status"); CREATE UNIQUE INDEX "index_ruby_llm_v2_backfills_on_task" ON "ruby_llm_v2_backfills" ("task"); -CREATE UNIQUE INDEX "index_characters_on_story_id_and_lower_fullname" ON "characters" (story_id, LOWER(fullname)); -CREATE INDEX "index_characters_on_location_id_and_hostile" ON "characters" ("location_id", "hostile"); -CREATE INDEX "index_characters_on_location_id_and_id" ON "characters" ("location_id", "id"); -CREATE INDEX "index_characters_on_location_id" ON "characters" ("location_id"); -CREATE INDEX "index_characters_on_race_id" ON "characters" ("race_id"); -CREATE INDEX "index_characters_on_story_id_and_is_protagonist" ON "characters" ("story_id", "is_protagonist"); -CREATE INDEX "index_characters_on_story_id" ON "characters" ("story_id"); CREATE INDEX "index_chats_on_character_id" ON "chats" ("character_id"); CREATE INDEX "index_chats_on_player_id" ON "chats" ("player_id"); CREATE INDEX "index_chats_on_conversation_key" ON "chats" ("playthrough_id", "character_id", "purpose"); @@ -640,5 +637,13 @@ CREATE INDEX "index_playthrough_due_endings_on_quest_outcome_id" ON "playthrough CREATE INDEX "index_quest_steps_on_companion_id" ON "quest_steps" ("companion_id"); CREATE UNIQUE INDEX "index_floors_on_location_id" ON "floors" ("location_id"); CREATE INDEX "index_floors_on_down_location_id" ON "floors" ("down_location_id"); +CREATE UNIQUE INDEX "index_characters_on_story_id_and_lower_fullname" ON "characters" (story_id, LOWER(fullname)); +CREATE INDEX "index_characters_on_location_id_and_hostile" ON "characters" ("location_id", "hostile"); +CREATE INDEX "index_characters_on_location_id_and_id" ON "characters" ("location_id", "id"); +CREATE INDEX "index_characters_on_location_id" ON "characters" ("location_id"); +CREATE INDEX "index_characters_on_race_id" ON "characters" ("race_id"); +CREATE INDEX "index_characters_on_story_id_and_is_protagonist" ON "characters" ("story_id", "is_protagonist"); +CREATE INDEX "index_characters_on_story_id" ON "characters" ("story_id"); +CREATE INDEX "index_characters_on_posted_to_id" ON "characters" ("posted_to_id"); PRAGMA writable_schema=OFF; COMMIT; diff --git a/parity/worlds/a-lock-in-the-moving-city.sql b/parity/worlds/a-lock-in-the-moving-city.sql index ef98f32..cabaafb 100644 --- a/parity/worlds/a-lock-in-the-moving-city.sql +++ b/parity/worlds/a-lock-in-the-moving-city.sql @@ -13,17 +13,6 @@ CREATE TABLE IF NOT EXISTS "ruby_llm_usages" ("id" integer PRIMARY KEY AUTOINCRE CREATE TABLE IF NOT EXISTS "ruby_llm_v2_backfills" ("completed" boolean DEFAULT FALSE NOT NULL, "last_id" integer, "task" varchar NOT NULL); CREATE TABLE IF NOT EXISTS "universes" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "civilizations" text, "created_at" datetime(6) NOT NULL, "economics" text, "geographies" text, "history" text, "physics" text, "politics" text, "religion" text, "technology" text, "updated_at" datetime(6) NOT NULL, "weapons" text, "gravity" varchar, "carrying" varchar); INSERT INTO universes VALUES(1000000001,'A small trading town.','2026-10-02 13:25:59.630090','Goods traded for coins.','Two moving workshops between two fixed yards.','Traders learned to keep their keys through every rearrangement.','Ordinary physics except that the city''s streets move every hour.','A town council keeps the peace.','The town keeps its own festivals.','Keys, locks and hand tools.','2026-10-02 13:25:59.630090','Wooden staves.',NULL,NULL); -CREATE TABLE IF NOT EXISTS "characters" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "age" integer, "appearance" text, "backstory" text, "conscious_desire" text, "created_at" datetime(6) NOT NULL, "deliberately_absent" boolean DEFAULT FALSE NOT NULL, "desire_pursuit" varchar, "dexterity" integer, "dislikes" text, "fears" text, "fullname" varchar, "hit_die" integer, "hostile" boolean DEFAULT FALSE NOT NULL, "is_companion" boolean, "is_protagonist" boolean DEFAULT FALSE NOT NULL, "level" integer, "likes" text, "location_id" integer, "need_pursuit" varchar, "nickname" varchar, "personality" text, "race_id" integer NOT NULL, "recognized_need" text, "sex" varchar, "story_id" integer NOT NULL, "strength" integer, "unconscious_desire" text, "unrecognized_need" text, "updated_at" datetime(6) NOT NULL, "will" integer, "x" integer, "y" integer, CONSTRAINT "fk_rails_56a213cbe2" -FOREIGN KEY ("race_id") - REFERENCES "races" ("id") -, CONSTRAINT "fk_rails_0b4b445641" -FOREIGN KEY ("location_id") - REFERENCES "locations" ("id") -, CONSTRAINT "fk_rails_6608c248e8" -FOREIGN KEY ("story_id") - REFERENCES "stories" ("id") -); -INSERT INTO characters VALUES(1000000001,30,'A travel-worn coat.','A trader returning for supplies.',NULL,'2026-10-02 13:25:59.775380',0,NULL,12,'Waste.','Losing needed supplies.','Cal',8,0,0,1,3,'Well-kept tools.',NULL,NULL,'Cal','Patient and practical.',1000000001,NULL,'male',1000000001,12,NULL,NULL,'2026-10-02 13:25:59.775380',12,NULL,NULL); CREATE TABLE IF NOT EXISTS "chats" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "cancelled" boolean DEFAULT FALSE NOT NULL, "character_id" integer, "created_at" datetime(6) NOT NULL, "model_id_string" varchar, "player_id" integer, "playthrough_id" integer, "purpose" varchar, "ruby_llm_model_id" integer, "updated_at" datetime(6) NOT NULL, CONSTRAINT "fk_rails_415520c982" FOREIGN KEY ("playthrough_id") REFERENCES "playthroughs" ("id") @@ -413,6 +402,7 @@ INSERT INTO schema_migrations VALUES('20261025230000'); INSERT INTO schema_migrations VALUES('20261027120117'); INSERT INTO schema_migrations VALUES('20261030120000'); INSERT INTO schema_migrations VALUES('20261030200000'); +INSERT INTO schema_migrations VALUES('20261030230000'); CREATE TABLE IF NOT EXISTS "ar_internal_metadata" ("key" varchar NOT NULL PRIMARY KEY, "value" varchar, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL); INSERT INTO ar_internal_metadata VALUES('environment','test','2026-10-02 13:25:33.143313','2026-10-02 13:25:33.143316'); INSERT INTO ar_internal_metadata VALUES('schema_sha1','072abc2bfb80d111a1808df270e9210911f5e81d','2026-10-02 13:25:33.148224','2026-10-02 13:25:33.148228'); @@ -423,13 +413,27 @@ FOREIGN KEY ("playthrough_id") FOREIGN KEY ("quest_outcome_id") REFERENCES "quest_outcomes" ("id") ); -CREATE TABLE IF NOT EXISTS "floors" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "location_id" integer NOT NULL, "rooms" integer NOT NULL, "danger" varchar, "hazard" varchar, "down_location_id" integer, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, CONSTRAINT "fk_rails_ce8b005cb2" +CREATE TABLE IF NOT EXISTS "floors" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "location_id" integer NOT NULL, "rooms" integer NOT NULL, "danger" varchar, "hazard" varchar, "down_location_id" integer, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, "monster_level" integer, CONSTRAINT "fk_rails_ce8b005cb2" FOREIGN KEY ("location_id") REFERENCES "locations" ("id") ON DELETE CASCADE, CONSTRAINT "fk_rails_8a0268155a" FOREIGN KEY ("down_location_id") REFERENCES "locations" ("id") ON DELETE SET NULL); +CREATE TABLE IF NOT EXISTS "characters" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "age" integer, "appearance" text, "backstory" text, "conscious_desire" text, "created_at" datetime(6) NOT NULL, "deliberately_absent" boolean DEFAULT FALSE NOT NULL, "desire_pursuit" varchar, "dexterity" integer, "dislikes" text, "fears" text, "fullname" varchar, "hit_die" integer, "hostile" boolean DEFAULT FALSE NOT NULL, "is_companion" boolean, "is_protagonist" boolean DEFAULT FALSE NOT NULL, "level" integer, "likes" text, "location_id" integer, "need_pursuit" varchar, "nickname" varchar, "personality" text, "race_id" integer NOT NULL, "recognized_need" text, "sex" varchar, "story_id" integer NOT NULL, "strength" integer, "unconscious_desire" text, "unrecognized_need" text, "updated_at" datetime(6) NOT NULL, "will" integer, "x" integer, "y" integer, "posted_to_id" integer, CONSTRAINT "fk_rails_6608c248e8" +FOREIGN KEY ("story_id") + REFERENCES "stories" ("id") +, CONSTRAINT "fk_rails_0b4b445641" +FOREIGN KEY ("location_id") + REFERENCES "locations" ("id") +, CONSTRAINT "fk_rails_56a213cbe2" +FOREIGN KEY ("race_id") + REFERENCES "races" ("id") +, CONSTRAINT "fk_rails_4c54fc506e" +FOREIGN KEY ("posted_to_id") + REFERENCES "locations" ("id") + ON DELETE SET NULL); +INSERT INTO characters VALUES(1000000001,30,'A travel-worn coat.','A trader returning for supplies.',NULL,'2026-10-02 13:25:59.775380',0,NULL,12,'Waste.','Losing needed supplies.','Cal',8,0,0,1,3,'Well-kept tools.',NULL,NULL,'Cal','Patient and practical.',1000000001,NULL,'male',1000000001,12,NULL,NULL,'2026-10-02 13:25:59.775380',12,NULL,NULL,NULL); PRAGMA writable_schema=ON; CREATE TABLE IF NOT EXISTS sqlite_sequence(name,seq); DELETE FROM sqlite_sequence; @@ -442,7 +446,6 @@ INSERT INTO sqlite_sequence VALUES('ruby_llm_models',1000000000); INSERT INTO sqlite_sequence VALUES('ruby_llm_tool_calls',1000000000); INSERT INTO sqlite_sequence VALUES('ruby_llm_usages',1000000000); INSERT INTO sqlite_sequence VALUES('universes',1000000001); -INSERT INTO sqlite_sequence VALUES('characters',1000000001); INSERT INTO sqlite_sequence VALUES('chats',1000000000); INSERT INTO sqlite_sequence VALUES('interactions',1000000000); INSERT INTO sqlite_sequence VALUES('items',1000000001); @@ -477,6 +480,7 @@ INSERT INTO sqlite_sequence VALUES('stories',1000000001); INSERT INTO sqlite_sequence VALUES('system_one_receipts',1000000000); INSERT INTO sqlite_sequence VALUES('world_events',1000000000); INSERT INTO sqlite_sequence VALUES('world_mechanics',1000000001); +INSERT INTO sqlite_sequence VALUES('characters',1000000001); CREATE INDEX "index_characters_scenes_on_character_id_and_scene_id" ON "characters_scenes" ("character_id", "scene_id"); CREATE INDEX "index_characters_scenes_on_scene_id_and_character_id" ON "characters_scenes" ("scene_id", "character_id"); CREATE UNIQUE INDEX "index_players_on_name" ON "players" ("name"); @@ -497,13 +501,6 @@ CREATE INDEX "index_ruby_llm_usages_on_chat_type_and_chat_id" ON "ruby_llm_usage CREATE INDEX "index_ruby_llm_usages_on_message_type_and_message_id" ON "ruby_llm_usages" ("message_type", "message_id"); CREATE INDEX "index_ruby_llm_usages_on_status" ON "ruby_llm_usages" ("status"); CREATE UNIQUE INDEX "index_ruby_llm_v2_backfills_on_task" ON "ruby_llm_v2_backfills" ("task"); -CREATE UNIQUE INDEX "index_characters_on_story_id_and_lower_fullname" ON "characters" (story_id, LOWER(fullname)); -CREATE INDEX "index_characters_on_location_id_and_hostile" ON "characters" ("location_id", "hostile"); -CREATE INDEX "index_characters_on_location_id_and_id" ON "characters" ("location_id", "id"); -CREATE INDEX "index_characters_on_location_id" ON "characters" ("location_id"); -CREATE INDEX "index_characters_on_race_id" ON "characters" ("race_id"); -CREATE INDEX "index_characters_on_story_id_and_is_protagonist" ON "characters" ("story_id", "is_protagonist"); -CREATE INDEX "index_characters_on_story_id" ON "characters" ("story_id"); CREATE INDEX "index_chats_on_character_id" ON "chats" ("character_id"); CREATE INDEX "index_chats_on_player_id" ON "chats" ("player_id"); CREATE INDEX "index_chats_on_conversation_key" ON "chats" ("playthrough_id", "character_id", "purpose"); @@ -639,5 +636,13 @@ CREATE INDEX "index_playthrough_due_endings_on_quest_outcome_id" ON "playthrough CREATE INDEX "index_quest_steps_on_companion_id" ON "quest_steps" ("companion_id"); CREATE UNIQUE INDEX "index_floors_on_location_id" ON "floors" ("location_id"); CREATE INDEX "index_floors_on_down_location_id" ON "floors" ("down_location_id"); +CREATE UNIQUE INDEX "index_characters_on_story_id_and_lower_fullname" ON "characters" (story_id, LOWER(fullname)); +CREATE INDEX "index_characters_on_location_id_and_hostile" ON "characters" ("location_id", "hostile"); +CREATE INDEX "index_characters_on_location_id_and_id" ON "characters" ("location_id", "id"); +CREATE INDEX "index_characters_on_location_id" ON "characters" ("location_id"); +CREATE INDEX "index_characters_on_race_id" ON "characters" ("race_id"); +CREATE INDEX "index_characters_on_story_id_and_is_protagonist" ON "characters" ("story_id", "is_protagonist"); +CREATE INDEX "index_characters_on_story_id" ON "characters" ("story_id"); +CREATE INDEX "index_characters_on_posted_to_id" ON "characters" ("posted_to_id"); PRAGMA writable_schema=OFF; COMMIT; diff --git a/parity/worlds/a-lodge-inside-the-gate.sql b/parity/worlds/a-lodge-inside-the-gate.sql index 94b05a6..be95816 100644 --- a/parity/worlds/a-lodge-inside-the-gate.sql +++ b/parity/worlds/a-lodge-inside-the-gate.sql @@ -13,20 +13,6 @@ CREATE TABLE IF NOT EXISTS "ruby_llm_usages" ("id" integer PRIMARY KEY AUTOINCRE CREATE TABLE IF NOT EXISTS "ruby_llm_v2_backfills" ("completed" boolean DEFAULT FALSE NOT NULL, "last_id" integer, "task" varchar NOT NULL); CREATE TABLE IF NOT EXISTS "universes" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "civilizations" text, "created_at" datetime(6) NOT NULL, "economics" text, "geographies" text, "history" text, "physics" text, "politics" text, "religion" text, "technology" text, "updated_at" datetime(6) NOT NULL, "weapons" text, "gravity" varchar, "carrying" varchar); INSERT INTO universes VALUES(1000000001,'A town that charges for its gate.','2026-10-02 13:25:50.669794','Tokens, tolls and porterage.','A walled town''s gate.','The gate has been tolled since the wall was built.','Ordinary physics.','The gatewarden answers to the town council.','The town keeps a quiet calendar.','Lanterns, ledgers and a toll bar on a chain.','2026-10-02 13:25:50.669794','A porter''s cudgel and a keeper''s staff.',NULL,NULL); -CREATE TABLE IF NOT EXISTS "characters" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "age" integer, "appearance" text, "backstory" text, "conscious_desire" text, "created_at" datetime(6) NOT NULL, "deliberately_absent" boolean DEFAULT FALSE NOT NULL, "desire_pursuit" varchar, "dexterity" integer, "dislikes" text, "fears" text, "fullname" varchar, "hit_die" integer, "hostile" boolean DEFAULT FALSE NOT NULL, "is_companion" boolean, "is_protagonist" boolean DEFAULT FALSE NOT NULL, "level" integer, "likes" text, "location_id" integer, "need_pursuit" varchar, "nickname" varchar, "personality" text, "race_id" integer NOT NULL, "recognized_need" text, "sex" varchar, "story_id" integer NOT NULL, "strength" integer, "unconscious_desire" text, "unrecognized_need" text, "updated_at" datetime(6) NOT NULL, "will" integer, "x" integer, "y" integer, CONSTRAINT "fk_rails_56a213cbe2" -FOREIGN KEY ("race_id") - REFERENCES "races" ("id") -, CONSTRAINT "fk_rails_0b4b445641" -FOREIGN KEY ("location_id") - REFERENCES "locations" ("id") -, CONSTRAINT "fk_rails_6608c248e8" -FOREIGN KEY ("story_id") - REFERENCES "stories" ("id") -); -INSERT INTO characters VALUES(1000000001,31,'A drover''s cloak and mud to the knee.','A drover who sold her flock outside the wall and has come in to pay her way through.','Wren wants to get through the gate and into the town before the market bell.','2026-10-02 13:25:50.870515',0,'reach',12,'Paying twice for one gate.','Being turned back at the wall.','Wren Adley',8,0,0,1,10,'A clear road.',NULL,'withhold','Wren','Patient and watchful.',1000000001,'Wren needs to keep the token until somebody entitled to it asks.','female',1000000001,12,'Wren wants somebody in town to know her name.','Wren needs to ask for help once.','2026-10-02 13:25:50.870515',12,NULL,NULL); -INSERT INTO characters VALUES(1000000002,44,'A porter''s sash and a ring of lodge keys.','The head porter, who has met every traveller through this gate for twenty years.','Mabry wants to see every traveller in and settled before they reach the market.','2026-10-02 13:25:50.949246',0,'attend',12,'A cold lodge.','Letting the wrong person through.','Mabry Quill',8,0,0,0,10,'A traveller with news.',1000000002,'keep','Mabry','Warm and nosy.',1000000001,'Mabry needs to keep the lodge book.','female',1000000001,12,'Mabry wants to be thanked.','Mabry needs to let someone pass without questions.','2026-10-02 13:25:50.949246',12,NULL,NULL); -INSERT INTO characters VALUES(1000000003,23,'A sash too big for him and ink on one thumb.','The under-porter, who took the post to be left alone and has not been.','Osric wants to be anywhere but the lodge when a traveller comes in.','2026-10-02 13:25:50.954473',0,'avoid',12,'Being looked at.','Being asked a question he cannot answer.','Osric Venn',8,0,0,0,10,'The back stair when nobody is on it.',1000000002,'withhold','Osric','Shy and restless.',1000000001,'Osric needs to answer the door.','male',1000000001,12,'Osric wants to be good at the post.','Osric needs to stay in the room.','2026-10-02 13:25:50.954473',12,NULL,NULL); -INSERT INTO characters VALUES(1000000004,57,'A keeper''s coat and an iron-shod staff.','The toll keeper, who has decided every traveller is trying to cheat the bar.','Grist wants the booth to himself and every token in his box.','2026-10-02 13:25:50.969712',0,'keep',12,'Travellers who come into the booth.','Being short at the count.','Grist Harrow',8,1,0,0,10,'A full toll box.',1000000003,'withhold','Grist','Suspicious and quick with a staff.',1000000001,'Grist needs to keep the count true.','male',1000000001,12,'Grist wants to be trusted with the gate.','Grist needs to let one traveller through for nothing.','2026-10-02 13:25:50.969712',12,NULL,NULL); CREATE TABLE IF NOT EXISTS "chats" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "cancelled" boolean DEFAULT FALSE NOT NULL, "character_id" integer, "created_at" datetime(6) NOT NULL, "model_id_string" varchar, "player_id" integer, "playthrough_id" integer, "purpose" varchar, "ruby_llm_model_id" integer, "updated_at" datetime(6) NOT NULL, CONSTRAINT "fk_rails_415520c982" FOREIGN KEY ("playthrough_id") REFERENCES "playthroughs" ("id") @@ -416,6 +402,7 @@ INSERT INTO schema_migrations VALUES('20261025230000'); INSERT INTO schema_migrations VALUES('20261027120117'); INSERT INTO schema_migrations VALUES('20261030120000'); INSERT INTO schema_migrations VALUES('20261030200000'); +INSERT INTO schema_migrations VALUES('20261030230000'); CREATE TABLE IF NOT EXISTS "ar_internal_metadata" ("key" varchar NOT NULL PRIMARY KEY, "value" varchar, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL); INSERT INTO ar_internal_metadata VALUES('environment','test','2026-10-02 13:25:33.143313','2026-10-02 13:25:33.143316'); INSERT INTO ar_internal_metadata VALUES('schema_sha1','072abc2bfb80d111a1808df270e9210911f5e81d','2026-10-02 13:25:33.148224','2026-10-02 13:25:33.148228'); @@ -426,13 +413,30 @@ FOREIGN KEY ("playthrough_id") FOREIGN KEY ("quest_outcome_id") REFERENCES "quest_outcomes" ("id") ); -CREATE TABLE IF NOT EXISTS "floors" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "location_id" integer NOT NULL, "rooms" integer NOT NULL, "danger" varchar, "hazard" varchar, "down_location_id" integer, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, CONSTRAINT "fk_rails_ce8b005cb2" +CREATE TABLE IF NOT EXISTS "floors" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "location_id" integer NOT NULL, "rooms" integer NOT NULL, "danger" varchar, "hazard" varchar, "down_location_id" integer, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, "monster_level" integer, CONSTRAINT "fk_rails_ce8b005cb2" FOREIGN KEY ("location_id") REFERENCES "locations" ("id") ON DELETE CASCADE, CONSTRAINT "fk_rails_8a0268155a" FOREIGN KEY ("down_location_id") REFERENCES "locations" ("id") ON DELETE SET NULL); +CREATE TABLE IF NOT EXISTS "characters" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "age" integer, "appearance" text, "backstory" text, "conscious_desire" text, "created_at" datetime(6) NOT NULL, "deliberately_absent" boolean DEFAULT FALSE NOT NULL, "desire_pursuit" varchar, "dexterity" integer, "dislikes" text, "fears" text, "fullname" varchar, "hit_die" integer, "hostile" boolean DEFAULT FALSE NOT NULL, "is_companion" boolean, "is_protagonist" boolean DEFAULT FALSE NOT NULL, "level" integer, "likes" text, "location_id" integer, "need_pursuit" varchar, "nickname" varchar, "personality" text, "race_id" integer NOT NULL, "recognized_need" text, "sex" varchar, "story_id" integer NOT NULL, "strength" integer, "unconscious_desire" text, "unrecognized_need" text, "updated_at" datetime(6) NOT NULL, "will" integer, "x" integer, "y" integer, "posted_to_id" integer, CONSTRAINT "fk_rails_6608c248e8" +FOREIGN KEY ("story_id") + REFERENCES "stories" ("id") +, CONSTRAINT "fk_rails_0b4b445641" +FOREIGN KEY ("location_id") + REFERENCES "locations" ("id") +, CONSTRAINT "fk_rails_56a213cbe2" +FOREIGN KEY ("race_id") + REFERENCES "races" ("id") +, CONSTRAINT "fk_rails_4c54fc506e" +FOREIGN KEY ("posted_to_id") + REFERENCES "locations" ("id") + ON DELETE SET NULL); +INSERT INTO characters VALUES(1000000001,31,'A drover''s cloak and mud to the knee.','A drover who sold her flock outside the wall and has come in to pay her way through.','Wren wants to get through the gate and into the town before the market bell.','2026-10-02 13:25:50.870515',0,'reach',12,'Paying twice for one gate.','Being turned back at the wall.','Wren Adley',8,0,0,1,10,'A clear road.',NULL,'withhold','Wren','Patient and watchful.',1000000001,'Wren needs to keep the token until somebody entitled to it asks.','female',1000000001,12,'Wren wants somebody in town to know her name.','Wren needs to ask for help once.','2026-10-02 13:25:50.870515',12,NULL,NULL,NULL); +INSERT INTO characters VALUES(1000000002,44,'A porter''s sash and a ring of lodge keys.','The head porter, who has met every traveller through this gate for twenty years.','Mabry wants to see every traveller in and settled before they reach the market.','2026-10-02 13:25:50.949246',0,'attend',12,'A cold lodge.','Letting the wrong person through.','Mabry Quill',8,0,0,0,10,'A traveller with news.',1000000002,'keep','Mabry','Warm and nosy.',1000000001,'Mabry needs to keep the lodge book.','female',1000000001,12,'Mabry wants to be thanked.','Mabry needs to let someone pass without questions.','2026-10-02 13:25:50.949246',12,NULL,NULL,NULL); +INSERT INTO characters VALUES(1000000003,23,'A sash too big for him and ink on one thumb.','The under-porter, who took the post to be left alone and has not been.','Osric wants to be anywhere but the lodge when a traveller comes in.','2026-10-02 13:25:50.954473',0,'avoid',12,'Being looked at.','Being asked a question he cannot answer.','Osric Venn',8,0,0,0,10,'The back stair when nobody is on it.',1000000002,'withhold','Osric','Shy and restless.',1000000001,'Osric needs to answer the door.','male',1000000001,12,'Osric wants to be good at the post.','Osric needs to stay in the room.','2026-10-02 13:25:50.954473',12,NULL,NULL,NULL); +INSERT INTO characters VALUES(1000000004,57,'A keeper''s coat and an iron-shod staff.','The toll keeper, who has decided every traveller is trying to cheat the bar.','Grist wants the booth to himself and every token in his box.','2026-10-02 13:25:50.969712',0,'keep',12,'Travellers who come into the booth.','Being short at the count.','Grist Harrow',8,1,0,0,10,'A full toll box.',1000000003,'withhold','Grist','Suspicious and quick with a staff.',1000000001,'Grist needs to keep the count true.','male',1000000001,12,'Grist wants to be trusted with the gate.','Grist needs to let one traveller through for nothing.','2026-10-02 13:25:50.969712',12,NULL,NULL,NULL); PRAGMA writable_schema=ON; CREATE TABLE IF NOT EXISTS sqlite_sequence(name,seq); DELETE FROM sqlite_sequence; @@ -445,7 +449,6 @@ INSERT INTO sqlite_sequence VALUES('ruby_llm_models',1000000000); INSERT INTO sqlite_sequence VALUES('ruby_llm_tool_calls',1000000000); INSERT INTO sqlite_sequence VALUES('ruby_llm_usages',1000000000); INSERT INTO sqlite_sequence VALUES('universes',1000000001); -INSERT INTO sqlite_sequence VALUES('characters',1000000004); INSERT INTO sqlite_sequence VALUES('chats',1000000000); INSERT INTO sqlite_sequence VALUES('interactions',1000000000); INSERT INTO sqlite_sequence VALUES('items',1000000002); @@ -480,6 +483,7 @@ INSERT INTO sqlite_sequence VALUES('stories',1000000001); INSERT INTO sqlite_sequence VALUES('system_one_receipts',1000000000); INSERT INTO sqlite_sequence VALUES('world_events',1000000000); INSERT INTO sqlite_sequence VALUES('world_mechanics',1000000000); +INSERT INTO sqlite_sequence VALUES('characters',1000000004); CREATE INDEX "index_characters_scenes_on_character_id_and_scene_id" ON "characters_scenes" ("character_id", "scene_id"); CREATE INDEX "index_characters_scenes_on_scene_id_and_character_id" ON "characters_scenes" ("scene_id", "character_id"); CREATE UNIQUE INDEX "index_players_on_name" ON "players" ("name"); @@ -500,13 +504,6 @@ CREATE INDEX "index_ruby_llm_usages_on_chat_type_and_chat_id" ON "ruby_llm_usage CREATE INDEX "index_ruby_llm_usages_on_message_type_and_message_id" ON "ruby_llm_usages" ("message_type", "message_id"); CREATE INDEX "index_ruby_llm_usages_on_status" ON "ruby_llm_usages" ("status"); CREATE UNIQUE INDEX "index_ruby_llm_v2_backfills_on_task" ON "ruby_llm_v2_backfills" ("task"); -CREATE UNIQUE INDEX "index_characters_on_story_id_and_lower_fullname" ON "characters" (story_id, LOWER(fullname)); -CREATE INDEX "index_characters_on_location_id_and_hostile" ON "characters" ("location_id", "hostile"); -CREATE INDEX "index_characters_on_location_id_and_id" ON "characters" ("location_id", "id"); -CREATE INDEX "index_characters_on_location_id" ON "characters" ("location_id"); -CREATE INDEX "index_characters_on_race_id" ON "characters" ("race_id"); -CREATE INDEX "index_characters_on_story_id_and_is_protagonist" ON "characters" ("story_id", "is_protagonist"); -CREATE INDEX "index_characters_on_story_id" ON "characters" ("story_id"); CREATE INDEX "index_chats_on_character_id" ON "chats" ("character_id"); CREATE INDEX "index_chats_on_player_id" ON "chats" ("player_id"); CREATE INDEX "index_chats_on_conversation_key" ON "chats" ("playthrough_id", "character_id", "purpose"); @@ -642,5 +639,13 @@ CREATE INDEX "index_playthrough_due_endings_on_quest_outcome_id" ON "playthrough CREATE INDEX "index_quest_steps_on_companion_id" ON "quest_steps" ("companion_id"); CREATE UNIQUE INDEX "index_floors_on_location_id" ON "floors" ("location_id"); CREATE INDEX "index_floors_on_down_location_id" ON "floors" ("down_location_id"); +CREATE UNIQUE INDEX "index_characters_on_story_id_and_lower_fullname" ON "characters" (story_id, LOWER(fullname)); +CREATE INDEX "index_characters_on_location_id_and_hostile" ON "characters" ("location_id", "hostile"); +CREATE INDEX "index_characters_on_location_id_and_id" ON "characters" ("location_id", "id"); +CREATE INDEX "index_characters_on_location_id" ON "characters" ("location_id"); +CREATE INDEX "index_characters_on_race_id" ON "characters" ("race_id"); +CREATE INDEX "index_characters_on_story_id_and_is_protagonist" ON "characters" ("story_id", "is_protagonist"); +CREATE INDEX "index_characters_on_story_id" ON "characters" ("story_id"); +CREATE INDEX "index_characters_on_posted_to_id" ON "characters" ("posted_to_id"); PRAGMA writable_schema=OFF; COMMIT; diff --git a/parity/worlds/a-turn-at-the-gate.sql b/parity/worlds/a-turn-at-the-gate.sql index 4683b29..203ee3f 100644 --- a/parity/worlds/a-turn-at-the-gate.sql +++ b/parity/worlds/a-turn-at-the-gate.sql @@ -14,18 +14,6 @@ CREATE TABLE IF NOT EXISTS "ruby_llm_usages" ("id" integer PRIMARY KEY AUTOINCRE CREATE TABLE IF NOT EXISTS "ruby_llm_v2_backfills" ("completed" boolean DEFAULT FALSE NOT NULL, "last_id" integer, "task" varchar NOT NULL); CREATE TABLE IF NOT EXISTS "universes" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "civilizations" text, "created_at" datetime(6) NOT NULL, "economics" text, "geographies" text, "history" text, "physics" text, "politics" text, "religion" text, "technology" text, "updated_at" datetime(6) NOT NULL, "weapons" text, "gravity" varchar, "carrying" varchar); INSERT INTO universes VALUES(1000000001,'A small trading town.','2026-10-02 13:26:07.913454','Goods traded for coins.','A market and its courtyard.','Traders built the market together.','Ordinary physics.','A town council keeps the peace.','The town keeps its own festivals.','Keys, locks and lanterns.','2026-10-02 13:26:07.913454','Wooden staves.',NULL,NULL); -CREATE TABLE IF NOT EXISTS "characters" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "age" integer, "appearance" text, "backstory" text, "conscious_desire" text, "created_at" datetime(6) NOT NULL, "deliberately_absent" boolean DEFAULT FALSE NOT NULL, "desire_pursuit" varchar, "dexterity" integer, "dislikes" text, "fears" text, "fullname" varchar, "hit_die" integer, "hostile" boolean DEFAULT FALSE NOT NULL, "is_companion" boolean, "is_protagonist" boolean DEFAULT FALSE NOT NULL, "level" integer, "likes" text, "location_id" integer, "need_pursuit" varchar, "nickname" varchar, "personality" text, "race_id" integer NOT NULL, "recognized_need" text, "sex" varchar, "story_id" integer NOT NULL, "strength" integer, "unconscious_desire" text, "unrecognized_need" text, "updated_at" datetime(6) NOT NULL, "will" integer, "x" integer, "y" integer, CONSTRAINT "fk_rails_56a213cbe2" -FOREIGN KEY ("race_id") - REFERENCES "races" ("id") -, CONSTRAINT "fk_rails_0b4b445641" -FOREIGN KEY ("location_id") - REFERENCES "locations" ("id") -, CONSTRAINT "fk_rails_6608c248e8" -FOREIGN KEY ("story_id") - REFERENCES "stories" ("id") -); -INSERT INTO characters VALUES(1000000001,30,'A travel-worn coat.','A trader returning to the market.',NULL,'2026-10-02 13:26:08.098372',0,NULL,12,'Needless arguments.','Losing a friend.','Cal',8,0,0,1,10,'A fair bargain.',NULL,NULL,'Cal','Patient and practical.',1000000001,NULL,'male',1000000001,12,NULL,NULL,'2026-10-02 13:26:08.098372',12,NULL,NULL); -INSERT INTO characters VALUES(1000000002,32,'A blue coat and a wooden staff.','A gatekeeper who misunderstood Cal''s arrival.',NULL,'2026-10-02 13:26:08.139887',0,NULL,12,'Deception.','Betraying a trust.','Maren',8,1,0,0,10,'Clear agreements.',1000000001,NULL,'Maren','Willing to listen and keep a promise.',1000000001,NULL,'female',1000000001,12,NULL,NULL,'2026-10-02 13:26:08.139887',12,NULL,NULL); CREATE TABLE IF NOT EXISTS "chats" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "cancelled" boolean DEFAULT FALSE NOT NULL, "character_id" integer, "created_at" datetime(6) NOT NULL, "model_id_string" varchar, "player_id" integer, "playthrough_id" integer, "purpose" varchar, "ruby_llm_model_id" integer, "updated_at" datetime(6) NOT NULL, CONSTRAINT "fk_rails_415520c982" FOREIGN KEY ("playthrough_id") REFERENCES "playthroughs" ("id") @@ -412,6 +400,7 @@ INSERT INTO schema_migrations VALUES('20261025230000'); INSERT INTO schema_migrations VALUES('20261027120117'); INSERT INTO schema_migrations VALUES('20261030120000'); INSERT INTO schema_migrations VALUES('20261030200000'); +INSERT INTO schema_migrations VALUES('20261030230000'); CREATE TABLE IF NOT EXISTS "ar_internal_metadata" ("key" varchar NOT NULL PRIMARY KEY, "value" varchar, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL); INSERT INTO ar_internal_metadata VALUES('environment','test','2026-10-02 13:25:33.143313','2026-10-02 13:25:33.143316'); INSERT INTO ar_internal_metadata VALUES('schema_sha1','072abc2bfb80d111a1808df270e9210911f5e81d','2026-10-02 13:25:33.148224','2026-10-02 13:25:33.148228'); @@ -422,13 +411,28 @@ FOREIGN KEY ("playthrough_id") FOREIGN KEY ("quest_outcome_id") REFERENCES "quest_outcomes" ("id") ); -CREATE TABLE IF NOT EXISTS "floors" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "location_id" integer NOT NULL, "rooms" integer NOT NULL, "danger" varchar, "hazard" varchar, "down_location_id" integer, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, CONSTRAINT "fk_rails_ce8b005cb2" +CREATE TABLE IF NOT EXISTS "floors" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "location_id" integer NOT NULL, "rooms" integer NOT NULL, "danger" varchar, "hazard" varchar, "down_location_id" integer, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, "monster_level" integer, CONSTRAINT "fk_rails_ce8b005cb2" FOREIGN KEY ("location_id") REFERENCES "locations" ("id") ON DELETE CASCADE, CONSTRAINT "fk_rails_8a0268155a" FOREIGN KEY ("down_location_id") REFERENCES "locations" ("id") ON DELETE SET NULL); +CREATE TABLE IF NOT EXISTS "characters" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "age" integer, "appearance" text, "backstory" text, "conscious_desire" text, "created_at" datetime(6) NOT NULL, "deliberately_absent" boolean DEFAULT FALSE NOT NULL, "desire_pursuit" varchar, "dexterity" integer, "dislikes" text, "fears" text, "fullname" varchar, "hit_die" integer, "hostile" boolean DEFAULT FALSE NOT NULL, "is_companion" boolean, "is_protagonist" boolean DEFAULT FALSE NOT NULL, "level" integer, "likes" text, "location_id" integer, "need_pursuit" varchar, "nickname" varchar, "personality" text, "race_id" integer NOT NULL, "recognized_need" text, "sex" varchar, "story_id" integer NOT NULL, "strength" integer, "unconscious_desire" text, "unrecognized_need" text, "updated_at" datetime(6) NOT NULL, "will" integer, "x" integer, "y" integer, "posted_to_id" integer, CONSTRAINT "fk_rails_6608c248e8" +FOREIGN KEY ("story_id") + REFERENCES "stories" ("id") +, CONSTRAINT "fk_rails_0b4b445641" +FOREIGN KEY ("location_id") + REFERENCES "locations" ("id") +, CONSTRAINT "fk_rails_56a213cbe2" +FOREIGN KEY ("race_id") + REFERENCES "races" ("id") +, CONSTRAINT "fk_rails_4c54fc506e" +FOREIGN KEY ("posted_to_id") + REFERENCES "locations" ("id") + ON DELETE SET NULL); +INSERT INTO characters VALUES(1000000001,30,'A travel-worn coat.','A trader returning to the market.',NULL,'2026-10-02 13:26:08.098372',0,NULL,12,'Needless arguments.','Losing a friend.','Cal',8,0,0,1,10,'A fair bargain.',NULL,NULL,'Cal','Patient and practical.',1000000001,NULL,'male',1000000001,12,NULL,NULL,'2026-10-02 13:26:08.098372',12,NULL,NULL,NULL); +INSERT INTO characters VALUES(1000000002,32,'A blue coat and a wooden staff.','A gatekeeper who misunderstood Cal''s arrival.',NULL,'2026-10-02 13:26:08.139887',0,NULL,12,'Deception.','Betraying a trust.','Maren',8,1,0,0,10,'Clear agreements.',1000000001,NULL,'Maren','Willing to listen and keep a promise.',1000000001,NULL,'female',1000000001,12,NULL,NULL,'2026-10-02 13:26:08.139887',12,NULL,NULL,NULL); PRAGMA writable_schema=ON; CREATE TABLE IF NOT EXISTS sqlite_sequence(name,seq); DELETE FROM sqlite_sequence; @@ -441,7 +445,6 @@ INSERT INTO sqlite_sequence VALUES('ruby_llm_models',1000000000); INSERT INTO sqlite_sequence VALUES('ruby_llm_tool_calls',1000000000); INSERT INTO sqlite_sequence VALUES('ruby_llm_usages',1000000000); INSERT INTO sqlite_sequence VALUES('universes',1000000001); -INSERT INTO sqlite_sequence VALUES('characters',1000000002); INSERT INTO sqlite_sequence VALUES('chats',1000000000); INSERT INTO sqlite_sequence VALUES('interactions',1000000000); INSERT INTO sqlite_sequence VALUES('items',1000000002); @@ -476,6 +479,7 @@ INSERT INTO sqlite_sequence VALUES('stories',1000000001); INSERT INTO sqlite_sequence VALUES('system_one_receipts',1000000000); INSERT INTO sqlite_sequence VALUES('world_events',1000000000); INSERT INTO sqlite_sequence VALUES('world_mechanics',1000000000); +INSERT INTO sqlite_sequence VALUES('characters',1000000002); CREATE INDEX "index_characters_scenes_on_character_id_and_scene_id" ON "characters_scenes" ("character_id", "scene_id"); CREATE INDEX "index_characters_scenes_on_scene_id_and_character_id" ON "characters_scenes" ("scene_id", "character_id"); CREATE UNIQUE INDEX "index_players_on_name" ON "players" ("name"); @@ -496,13 +500,6 @@ CREATE INDEX "index_ruby_llm_usages_on_chat_type_and_chat_id" ON "ruby_llm_usage CREATE INDEX "index_ruby_llm_usages_on_message_type_and_message_id" ON "ruby_llm_usages" ("message_type", "message_id"); CREATE INDEX "index_ruby_llm_usages_on_status" ON "ruby_llm_usages" ("status"); CREATE UNIQUE INDEX "index_ruby_llm_v2_backfills_on_task" ON "ruby_llm_v2_backfills" ("task"); -CREATE UNIQUE INDEX "index_characters_on_story_id_and_lower_fullname" ON "characters" (story_id, LOWER(fullname)); -CREATE INDEX "index_characters_on_location_id_and_hostile" ON "characters" ("location_id", "hostile"); -CREATE INDEX "index_characters_on_location_id_and_id" ON "characters" ("location_id", "id"); -CREATE INDEX "index_characters_on_location_id" ON "characters" ("location_id"); -CREATE INDEX "index_characters_on_race_id" ON "characters" ("race_id"); -CREATE INDEX "index_characters_on_story_id_and_is_protagonist" ON "characters" ("story_id", "is_protagonist"); -CREATE INDEX "index_characters_on_story_id" ON "characters" ("story_id"); CREATE INDEX "index_chats_on_character_id" ON "chats" ("character_id"); CREATE INDEX "index_chats_on_player_id" ON "chats" ("player_id"); CREATE INDEX "index_chats_on_conversation_key" ON "chats" ("playthrough_id", "character_id", "purpose"); @@ -638,5 +635,13 @@ CREATE INDEX "index_playthrough_due_endings_on_quest_outcome_id" ON "playthrough CREATE INDEX "index_quest_steps_on_companion_id" ON "quest_steps" ("companion_id"); CREATE UNIQUE INDEX "index_floors_on_location_id" ON "floors" ("location_id"); CREATE INDEX "index_floors_on_down_location_id" ON "floors" ("down_location_id"); +CREATE UNIQUE INDEX "index_characters_on_story_id_and_lower_fullname" ON "characters" (story_id, LOWER(fullname)); +CREATE INDEX "index_characters_on_location_id_and_hostile" ON "characters" ("location_id", "hostile"); +CREATE INDEX "index_characters_on_location_id_and_id" ON "characters" ("location_id", "id"); +CREATE INDEX "index_characters_on_location_id" ON "characters" ("location_id"); +CREATE INDEX "index_characters_on_race_id" ON "characters" ("race_id"); +CREATE INDEX "index_characters_on_story_id_and_is_protagonist" ON "characters" ("story_id", "is_protagonist"); +CREATE INDEX "index_characters_on_story_id" ON "characters" ("story_id"); +CREATE INDEX "index_characters_on_posted_to_id" ON "characters" ("posted_to_id"); PRAGMA writable_schema=OFF; COMMIT; diff --git a/parity/worlds/a-window-over-the-yard.sql b/parity/worlds/a-window-over-the-yard.sql index 1e09203..8bb737a 100644 --- a/parity/worlds/a-window-over-the-yard.sql +++ b/parity/worlds/a-window-over-the-yard.sql @@ -13,17 +13,6 @@ CREATE TABLE IF NOT EXISTS "ruby_llm_usages" ("id" integer PRIMARY KEY AUTOINCRE CREATE TABLE IF NOT EXISTS "ruby_llm_v2_backfills" ("completed" boolean DEFAULT FALSE NOT NULL, "last_id" integer, "task" varchar NOT NULL); CREATE TABLE IF NOT EXISTS "universes" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "civilizations" text, "created_at" datetime(6) NOT NULL, "economics" text, "geographies" text, "history" text, "physics" text, "politics" text, "religion" text, "technology" text, "updated_at" datetime(6) NOT NULL, "weapons" text, "gravity" varchar, "carrying" varchar); INSERT INTO universes VALUES(1000000001,'A farming parish that keeps to itself.','2026-10-02 13:25:52.508735','Paid at market in coin, and in kind the rest of the year.','One farm at the end of a lane, and the fields around it.','Four generations of Halloways have worked the same ground.','Ordinary physics. What falls, falls as hard as it would anywhere.','The parish council, which is mostly Halloways.','A harvest service, and nothing much in between.','Hand tools, a well and a lamp.','2026-10-02 13:25:52.508735','A pitchfork, and whatever is nearest the door.','ordinary',NULL); -CREATE TABLE IF NOT EXISTS "characters" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "age" integer, "appearance" text, "backstory" text, "conscious_desire" text, "created_at" datetime(6) NOT NULL, "deliberately_absent" boolean DEFAULT FALSE NOT NULL, "desire_pursuit" varchar, "dexterity" integer, "dislikes" text, "fears" text, "fullname" varchar, "hit_die" integer, "hostile" boolean DEFAULT FALSE NOT NULL, "is_companion" boolean, "is_protagonist" boolean DEFAULT FALSE NOT NULL, "level" integer, "likes" text, "location_id" integer, "need_pursuit" varchar, "nickname" varchar, "personality" text, "race_id" integer NOT NULL, "recognized_need" text, "sex" varchar, "story_id" integer NOT NULL, "strength" integer, "unconscious_desire" text, "unrecognized_need" text, "updated_at" datetime(6) NOT NULL, "will" integer, "x" integer, "y" integer, CONSTRAINT "fk_rails_56a213cbe2" -FOREIGN KEY ("race_id") - REFERENCES "races" ("id") -, CONSTRAINT "fk_rails_0b4b445641" -FOREIGN KEY ("location_id") - REFERENCES "locations" ("id") -, CONSTRAINT "fk_rails_6608c248e8" -FOREIGN KEY ("story_id") - REFERENCES "stories" ("id") -); -INSERT INTO characters VALUES(1000000001,29,'A work coat two sizes too big.','Runs the farm since her father''s knees gave out, and keeps the accounts in her head.',NULL,'2026-10-02 13:25:52.726724',0,NULL,11,'A door bolted from the wrong side','The farm going to her cousin','Wenna Halloway',8,0,0,1,3,'A job finished before dark',1000000001,NULL,'Wenna','Practical, and slow to be frightened.',1000000001,NULL,'female',1000000001,11,NULL,NULL,'2026-10-02 13:25:52.726724',12,NULL,NULL); CREATE TABLE IF NOT EXISTS "chats" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "cancelled" boolean DEFAULT FALSE NOT NULL, "character_id" integer, "created_at" datetime(6) NOT NULL, "model_id_string" varchar, "player_id" integer, "playthrough_id" integer, "purpose" varchar, "ruby_llm_model_id" integer, "updated_at" datetime(6) NOT NULL, CONSTRAINT "fk_rails_415520c982" FOREIGN KEY ("playthrough_id") REFERENCES "playthroughs" ("id") @@ -414,6 +403,7 @@ INSERT INTO schema_migrations VALUES('20261025230000'); INSERT INTO schema_migrations VALUES('20261027120117'); INSERT INTO schema_migrations VALUES('20261030120000'); INSERT INTO schema_migrations VALUES('20261030200000'); +INSERT INTO schema_migrations VALUES('20261030230000'); CREATE TABLE IF NOT EXISTS "ar_internal_metadata" ("key" varchar NOT NULL PRIMARY KEY, "value" varchar, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL); INSERT INTO ar_internal_metadata VALUES('environment','test','2026-10-02 13:25:33.143313','2026-10-02 13:25:33.143316'); INSERT INTO ar_internal_metadata VALUES('schema_sha1','072abc2bfb80d111a1808df270e9210911f5e81d','2026-10-02 13:25:33.148224','2026-10-02 13:25:33.148228'); @@ -424,13 +414,27 @@ FOREIGN KEY ("playthrough_id") FOREIGN KEY ("quest_outcome_id") REFERENCES "quest_outcomes" ("id") ); -CREATE TABLE IF NOT EXISTS "floors" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "location_id" integer NOT NULL, "rooms" integer NOT NULL, "danger" varchar, "hazard" varchar, "down_location_id" integer, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, CONSTRAINT "fk_rails_ce8b005cb2" +CREATE TABLE IF NOT EXISTS "floors" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "location_id" integer NOT NULL, "rooms" integer NOT NULL, "danger" varchar, "hazard" varchar, "down_location_id" integer, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, "monster_level" integer, CONSTRAINT "fk_rails_ce8b005cb2" FOREIGN KEY ("location_id") REFERENCES "locations" ("id") ON DELETE CASCADE, CONSTRAINT "fk_rails_8a0268155a" FOREIGN KEY ("down_location_id") REFERENCES "locations" ("id") ON DELETE SET NULL); +CREATE TABLE IF NOT EXISTS "characters" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "age" integer, "appearance" text, "backstory" text, "conscious_desire" text, "created_at" datetime(6) NOT NULL, "deliberately_absent" boolean DEFAULT FALSE NOT NULL, "desire_pursuit" varchar, "dexterity" integer, "dislikes" text, "fears" text, "fullname" varchar, "hit_die" integer, "hostile" boolean DEFAULT FALSE NOT NULL, "is_companion" boolean, "is_protagonist" boolean DEFAULT FALSE NOT NULL, "level" integer, "likes" text, "location_id" integer, "need_pursuit" varchar, "nickname" varchar, "personality" text, "race_id" integer NOT NULL, "recognized_need" text, "sex" varchar, "story_id" integer NOT NULL, "strength" integer, "unconscious_desire" text, "unrecognized_need" text, "updated_at" datetime(6) NOT NULL, "will" integer, "x" integer, "y" integer, "posted_to_id" integer, CONSTRAINT "fk_rails_6608c248e8" +FOREIGN KEY ("story_id") + REFERENCES "stories" ("id") +, CONSTRAINT "fk_rails_0b4b445641" +FOREIGN KEY ("location_id") + REFERENCES "locations" ("id") +, CONSTRAINT "fk_rails_56a213cbe2" +FOREIGN KEY ("race_id") + REFERENCES "races" ("id") +, CONSTRAINT "fk_rails_4c54fc506e" +FOREIGN KEY ("posted_to_id") + REFERENCES "locations" ("id") + ON DELETE SET NULL); +INSERT INTO characters VALUES(1000000001,29,'A work coat two sizes too big.','Runs the farm since her father''s knees gave out, and keeps the accounts in her head.',NULL,'2026-10-02 13:25:52.726724',0,NULL,11,'A door bolted from the wrong side','The farm going to her cousin','Wenna Halloway',8,0,0,1,3,'A job finished before dark',1000000001,NULL,'Wenna','Practical, and slow to be frightened.',1000000001,NULL,'female',1000000001,11,NULL,NULL,'2026-10-02 13:25:52.726724',12,NULL,NULL,NULL); PRAGMA writable_schema=ON; CREATE TABLE IF NOT EXISTS sqlite_sequence(name,seq); DELETE FROM sqlite_sequence; @@ -443,7 +447,6 @@ INSERT INTO sqlite_sequence VALUES('ruby_llm_models',1000000000); INSERT INTO sqlite_sequence VALUES('ruby_llm_tool_calls',1000000000); INSERT INTO sqlite_sequence VALUES('ruby_llm_usages',1000000000); INSERT INTO sqlite_sequence VALUES('universes',1000000001); -INSERT INTO sqlite_sequence VALUES('characters',1000000001); INSERT INTO sqlite_sequence VALUES('chats',1000000000); INSERT INTO sqlite_sequence VALUES('interactions',1000000000); INSERT INTO sqlite_sequence VALUES('items',1000000000); @@ -478,6 +481,7 @@ INSERT INTO sqlite_sequence VALUES('stories',1000000001); INSERT INTO sqlite_sequence VALUES('system_one_receipts',1000000000); INSERT INTO sqlite_sequence VALUES('world_events',1000000000); INSERT INTO sqlite_sequence VALUES('world_mechanics',1000000000); +INSERT INTO sqlite_sequence VALUES('characters',1000000001); CREATE INDEX "index_characters_scenes_on_character_id_and_scene_id" ON "characters_scenes" ("character_id", "scene_id"); CREATE INDEX "index_characters_scenes_on_scene_id_and_character_id" ON "characters_scenes" ("scene_id", "character_id"); CREATE UNIQUE INDEX "index_players_on_name" ON "players" ("name"); @@ -498,13 +502,6 @@ CREATE INDEX "index_ruby_llm_usages_on_chat_type_and_chat_id" ON "ruby_llm_usage CREATE INDEX "index_ruby_llm_usages_on_message_type_and_message_id" ON "ruby_llm_usages" ("message_type", "message_id"); CREATE INDEX "index_ruby_llm_usages_on_status" ON "ruby_llm_usages" ("status"); CREATE UNIQUE INDEX "index_ruby_llm_v2_backfills_on_task" ON "ruby_llm_v2_backfills" ("task"); -CREATE UNIQUE INDEX "index_characters_on_story_id_and_lower_fullname" ON "characters" (story_id, LOWER(fullname)); -CREATE INDEX "index_characters_on_location_id_and_hostile" ON "characters" ("location_id", "hostile"); -CREATE INDEX "index_characters_on_location_id_and_id" ON "characters" ("location_id", "id"); -CREATE INDEX "index_characters_on_location_id" ON "characters" ("location_id"); -CREATE INDEX "index_characters_on_race_id" ON "characters" ("race_id"); -CREATE INDEX "index_characters_on_story_id_and_is_protagonist" ON "characters" ("story_id", "is_protagonist"); -CREATE INDEX "index_characters_on_story_id" ON "characters" ("story_id"); CREATE INDEX "index_chats_on_character_id" ON "chats" ("character_id"); CREATE INDEX "index_chats_on_player_id" ON "chats" ("player_id"); CREATE INDEX "index_chats_on_conversation_key" ON "chats" ("playthrough_id", "character_id", "purpose"); @@ -640,5 +637,13 @@ CREATE INDEX "index_playthrough_due_endings_on_quest_outcome_id" ON "playthrough CREATE INDEX "index_quest_steps_on_companion_id" ON "quest_steps" ("companion_id"); CREATE UNIQUE INDEX "index_floors_on_location_id" ON "floors" ("location_id"); CREATE INDEX "index_floors_on_down_location_id" ON "floors" ("down_location_id"); +CREATE UNIQUE INDEX "index_characters_on_story_id_and_lower_fullname" ON "characters" (story_id, LOWER(fullname)); +CREATE INDEX "index_characters_on_location_id_and_hostile" ON "characters" ("location_id", "hostile"); +CREATE INDEX "index_characters_on_location_id_and_id" ON "characters" ("location_id", "id"); +CREATE INDEX "index_characters_on_location_id" ON "characters" ("location_id"); +CREATE INDEX "index_characters_on_race_id" ON "characters" ("race_id"); +CREATE INDEX "index_characters_on_story_id_and_is_protagonist" ON "characters" ("story_id", "is_protagonist"); +CREATE INDEX "index_characters_on_story_id" ON "characters" ("story_id"); +CREATE INDEX "index_characters_on_posted_to_id" ON "characters" ("posted_to_id"); PRAGMA writable_schema=OFF; COMMIT; diff --git a/parity/worlds/a-writ-on-the-copying-desk.sql b/parity/worlds/a-writ-on-the-copying-desk.sql index 3a2e566..0fb5953 100644 --- a/parity/worlds/a-writ-on-the-copying-desk.sql +++ b/parity/worlds/a-writ-on-the-copying-desk.sql @@ -13,17 +13,6 @@ CREATE TABLE IF NOT EXISTS "ruby_llm_usages" ("id" integer PRIMARY KEY AUTOINCRE CREATE TABLE IF NOT EXISTS "ruby_llm_v2_backfills" ("completed" boolean DEFAULT FALSE NOT NULL, "last_id" integer, "task" varchar NOT NULL); CREATE TABLE IF NOT EXISTS "universes" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "civilizations" text, "created_at" datetime(6) NOT NULL, "economics" text, "geographies" text, "history" text, "physics" text, "politics" text, "religion" text, "technology" text, "updated_at" datetime(6) NOT NULL, "weapons" text, "gravity" varchar, "carrying" varchar); INSERT INTO universes VALUES(1000000001,'A town that keeps its records by hand.','2026-10-26 02:10:11.352145','A fee a page.','A copying house and its archive.','The copying house has copied every deed in the town.','Ordinary physics.','The registrar signs what the copyists write.','The town keeps a quiet calendar.','Pens, ink, lamps and tinderboxes.','2026-10-26 02:10:11.352145','Nothing anybody carries.',NULL,NULL); -CREATE TABLE IF NOT EXISTS "characters" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "age" integer, "appearance" text, "backstory" text, "conscious_desire" text, "created_at" datetime(6) NOT NULL, "deliberately_absent" boolean DEFAULT FALSE NOT NULL, "desire_pursuit" varchar, "dexterity" integer, "dislikes" text, "fears" text, "fullname" varchar, "hit_die" integer, "hostile" boolean DEFAULT FALSE NOT NULL, "is_companion" boolean, "is_protagonist" boolean DEFAULT FALSE NOT NULL, "level" integer, "likes" text, "location_id" integer, "need_pursuit" varchar, "nickname" varchar, "personality" text, "race_id" integer NOT NULL, "recognized_need" text, "sex" varchar, "story_id" integer NOT NULL, "strength" integer, "unconscious_desire" text, "unrecognized_need" text, "updated_at" datetime(6) NOT NULL, "will" integer, "x" integer, "y" integer, CONSTRAINT "fk_rails_56a213cbe2" -FOREIGN KEY ("race_id") - REFERENCES "races" ("id") -, CONSTRAINT "fk_rails_0b4b445641" -FOREIGN KEY ("location_id") - REFERENCES "locations" ("id") -, CONSTRAINT "fk_rails_6608c248e8" -FOREIGN KEY ("story_id") - REFERENCES "stories" ("id") -); -INSERT INTO characters VALUES(1000000001,29,'Ink on both cuffs.','A copyist who has been asked to run an errand instead of copying.',NULL,'2026-10-26 02:10:11.603319',0,NULL,12,'Errands.','Losing a deed.','Tamsin Rook',8,0,0,1,3,'A clean page.',NULL,NULL,'Tamsin','Careful, and tired of being careful.',1000000001,NULL,'female',1000000001,15,NULL,NULL,'2026-10-26 02:10:11.603319',12,NULL,NULL); CREATE TABLE IF NOT EXISTS "chats" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "cancelled" boolean DEFAULT FALSE NOT NULL, "character_id" integer, "created_at" datetime(6) NOT NULL, "model_id_string" varchar, "player_id" integer, "playthrough_id" integer, "purpose" varchar, "ruby_llm_model_id" integer, "updated_at" datetime(6) NOT NULL, CONSTRAINT "fk_rails_415520c982" FOREIGN KEY ("playthrough_id") REFERENCES "playthroughs" ("id") @@ -423,16 +412,31 @@ INSERT INTO schema_migrations VALUES('20250822184649'); INSERT INTO schema_migrations VALUES('20261027120117'); INSERT INTO schema_migrations VALUES('20261030120000'); INSERT INTO schema_migrations VALUES('20261030200000'); +INSERT INTO schema_migrations VALUES('20261030230000'); CREATE TABLE IF NOT EXISTS "ar_internal_metadata" ("key" varchar NOT NULL PRIMARY KEY, "value" varchar, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL); INSERT INTO ar_internal_metadata VALUES('environment','test','2026-10-26 02:09:45.289336','2026-10-26 02:09:45.289340'); INSERT INTO ar_internal_metadata VALUES('schema_sha1','9fcff787fff0ed60123ee84a65c7f80738f353f3','2026-10-26 02:09:45.295415','2026-10-26 02:09:45.295421'); -CREATE TABLE IF NOT EXISTS "floors" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "location_id" integer NOT NULL, "rooms" integer NOT NULL, "danger" varchar, "hazard" varchar, "down_location_id" integer, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, CONSTRAINT "fk_rails_ce8b005cb2" +CREATE TABLE IF NOT EXISTS "floors" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "location_id" integer NOT NULL, "rooms" integer NOT NULL, "danger" varchar, "hazard" varchar, "down_location_id" integer, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, "monster_level" integer, CONSTRAINT "fk_rails_ce8b005cb2" FOREIGN KEY ("location_id") REFERENCES "locations" ("id") ON DELETE CASCADE, CONSTRAINT "fk_rails_8a0268155a" FOREIGN KEY ("down_location_id") REFERENCES "locations" ("id") ON DELETE SET NULL); +CREATE TABLE IF NOT EXISTS "characters" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "age" integer, "appearance" text, "backstory" text, "conscious_desire" text, "created_at" datetime(6) NOT NULL, "deliberately_absent" boolean DEFAULT FALSE NOT NULL, "desire_pursuit" varchar, "dexterity" integer, "dislikes" text, "fears" text, "fullname" varchar, "hit_die" integer, "hostile" boolean DEFAULT FALSE NOT NULL, "is_companion" boolean, "is_protagonist" boolean DEFAULT FALSE NOT NULL, "level" integer, "likes" text, "location_id" integer, "need_pursuit" varchar, "nickname" varchar, "personality" text, "race_id" integer NOT NULL, "recognized_need" text, "sex" varchar, "story_id" integer NOT NULL, "strength" integer, "unconscious_desire" text, "unrecognized_need" text, "updated_at" datetime(6) NOT NULL, "will" integer, "x" integer, "y" integer, "posted_to_id" integer, CONSTRAINT "fk_rails_6608c248e8" +FOREIGN KEY ("story_id") + REFERENCES "stories" ("id") +, CONSTRAINT "fk_rails_0b4b445641" +FOREIGN KEY ("location_id") + REFERENCES "locations" ("id") +, CONSTRAINT "fk_rails_56a213cbe2" +FOREIGN KEY ("race_id") + REFERENCES "races" ("id") +, CONSTRAINT "fk_rails_4c54fc506e" +FOREIGN KEY ("posted_to_id") + REFERENCES "locations" ("id") + ON DELETE SET NULL); +INSERT INTO characters VALUES(1000000001,29,'Ink on both cuffs.','A copyist who has been asked to run an errand instead of copying.',NULL,'2026-10-26 02:10:11.603319',0,NULL,12,'Errands.','Losing a deed.','Tamsin Rook',8,0,0,1,3,'A clean page.',NULL,NULL,'Tamsin','Careful, and tired of being careful.',1000000001,NULL,'female',1000000001,15,NULL,NULL,'2026-10-26 02:10:11.603319',12,NULL,NULL,NULL); PRAGMA writable_schema=ON; CREATE TABLE IF NOT EXISTS sqlite_sequence(name,seq); DELETE FROM sqlite_sequence; @@ -445,7 +449,6 @@ INSERT INTO sqlite_sequence VALUES('ruby_llm_models',1000000000); INSERT INTO sqlite_sequence VALUES('ruby_llm_tool_calls',1000000000); INSERT INTO sqlite_sequence VALUES('ruby_llm_usages',1000000000); INSERT INTO sqlite_sequence VALUES('universes',1000000001); -INSERT INTO sqlite_sequence VALUES('characters',1000000001); INSERT INTO sqlite_sequence VALUES('chats',1000000000); INSERT INTO sqlite_sequence VALUES('interactions',1000000000); INSERT INTO sqlite_sequence VALUES('items',1000000007); @@ -481,6 +484,7 @@ INSERT INTO sqlite_sequence VALUES('stories',1000000001); INSERT INTO sqlite_sequence VALUES('system_one_receipts',1000000000); INSERT INTO sqlite_sequence VALUES('world_events',1000000000); INSERT INTO sqlite_sequence VALUES('world_mechanics',1000000000); +INSERT INTO sqlite_sequence VALUES('characters',1000000001); CREATE INDEX "index_characters_scenes_on_character_id_and_scene_id" ON "characters_scenes" ("character_id", "scene_id"); CREATE INDEX "index_characters_scenes_on_scene_id_and_character_id" ON "characters_scenes" ("scene_id", "character_id"); CREATE UNIQUE INDEX "index_players_on_name" ON "players" ("name"); @@ -501,13 +505,6 @@ CREATE INDEX "index_ruby_llm_usages_on_chat_type_and_chat_id" ON "ruby_llm_usage CREATE INDEX "index_ruby_llm_usages_on_message_type_and_message_id" ON "ruby_llm_usages" ("message_type", "message_id"); CREATE INDEX "index_ruby_llm_usages_on_status" ON "ruby_llm_usages" ("status"); CREATE UNIQUE INDEX "index_ruby_llm_v2_backfills_on_task" ON "ruby_llm_v2_backfills" ("task"); -CREATE UNIQUE INDEX "index_characters_on_story_id_and_lower_fullname" ON "characters" (story_id, LOWER(fullname)); -CREATE INDEX "index_characters_on_location_id_and_hostile" ON "characters" ("location_id", "hostile"); -CREATE INDEX "index_characters_on_location_id_and_id" ON "characters" ("location_id", "id"); -CREATE INDEX "index_characters_on_location_id" ON "characters" ("location_id"); -CREATE INDEX "index_characters_on_race_id" ON "characters" ("race_id"); -CREATE INDEX "index_characters_on_story_id_and_is_protagonist" ON "characters" ("story_id", "is_protagonist"); -CREATE INDEX "index_characters_on_story_id" ON "characters" ("story_id"); CREATE INDEX "index_chats_on_character_id" ON "chats" ("character_id"); CREATE INDEX "index_chats_on_player_id" ON "chats" ("player_id"); CREATE INDEX "index_chats_on_conversation_key" ON "chats" ("playthrough_id", "character_id", "purpose"); @@ -643,5 +640,13 @@ CREATE INDEX "index_world_mechanics_on_story_id" ON "world_mechanics" ("story_id CREATE INDEX "index_quest_steps_on_companion_id" ON "quest_steps" ("companion_id"); CREATE UNIQUE INDEX "index_floors_on_location_id" ON "floors" ("location_id"); CREATE INDEX "index_floors_on_down_location_id" ON "floors" ("down_location_id"); +CREATE UNIQUE INDEX "index_characters_on_story_id_and_lower_fullname" ON "characters" (story_id, LOWER(fullname)); +CREATE INDEX "index_characters_on_location_id_and_hostile" ON "characters" ("location_id", "hostile"); +CREATE INDEX "index_characters_on_location_id_and_id" ON "characters" ("location_id", "id"); +CREATE INDEX "index_characters_on_location_id" ON "characters" ("location_id"); +CREATE INDEX "index_characters_on_race_id" ON "characters" ("race_id"); +CREATE INDEX "index_characters_on_story_id_and_is_protagonist" ON "characters" ("story_id", "is_protagonist"); +CREATE INDEX "index_characters_on_story_id" ON "characters" ("story_id"); +CREATE INDEX "index_characters_on_posted_to_id" ON "characters" ("posted_to_id"); PRAGMA writable_schema=OFF; COMMIT; diff --git a/parity/worlds/a-written-mill-beside-a-stub.sql b/parity/worlds/a-written-mill-beside-a-stub.sql index 634be80..e176ebc 100644 --- a/parity/worlds/a-written-mill-beside-a-stub.sql +++ b/parity/worlds/a-written-mill-beside-a-stub.sql @@ -13,17 +13,6 @@ CREATE TABLE IF NOT EXISTS "ruby_llm_usages" ("id" integer PRIMARY KEY AUTOINCRE CREATE TABLE IF NOT EXISTS "ruby_llm_v2_backfills" ("completed" boolean DEFAULT FALSE NOT NULL, "last_id" integer, "task" varchar NOT NULL); CREATE TABLE IF NOT EXISTS "universes" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "civilizations" text, "created_at" datetime(6) NOT NULL, "economics" text, "geographies" text, "history" text, "physics" text, "politics" text, "religion" text, "technology" text, "updated_at" datetime(6) NOT NULL, "weapons" text, "gravity" varchar, "carrying" varchar); INSERT INTO universes VALUES(1000000001,'A small trading town.','2026-10-02 13:26:04.438010','Goods traded for coins.','A market, its workshop and an old mill.','Traders built the market together.','Ordinary physics.','A town council keeps the peace.','The town keeps its own festivals.','Keys, locks and lanterns.','2026-10-02 13:26:04.438010','Wooden staves.',NULL,NULL); -CREATE TABLE IF NOT EXISTS "characters" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "age" integer, "appearance" text, "backstory" text, "conscious_desire" text, "created_at" datetime(6) NOT NULL, "deliberately_absent" boolean DEFAULT FALSE NOT NULL, "desire_pursuit" varchar, "dexterity" integer, "dislikes" text, "fears" text, "fullname" varchar, "hit_die" integer, "hostile" boolean DEFAULT FALSE NOT NULL, "is_companion" boolean, "is_protagonist" boolean DEFAULT FALSE NOT NULL, "level" integer, "likes" text, "location_id" integer, "need_pursuit" varchar, "nickname" varchar, "personality" text, "race_id" integer NOT NULL, "recognized_need" text, "sex" varchar, "story_id" integer NOT NULL, "strength" integer, "unconscious_desire" text, "unrecognized_need" text, "updated_at" datetime(6) NOT NULL, "will" integer, "x" integer, "y" integer, CONSTRAINT "fk_rails_56a213cbe2" -FOREIGN KEY ("race_id") - REFERENCES "races" ("id") -, CONSTRAINT "fk_rails_0b4b445641" -FOREIGN KEY ("location_id") - REFERENCES "locations" ("id") -, CONSTRAINT "fk_rails_6608c248e8" -FOREIGN KEY ("story_id") - REFERENCES "stories" ("id") -); -INSERT INTO characters VALUES(1000000001,30,'A travel-worn coat.','A trader returning to the market.',NULL,'2026-10-02 13:26:04.567073',0,NULL,12,'Needless arguments.','Losing a friend.','Cal',8,0,0,1,10,'A fair bargain.',NULL,NULL,'Cal','Patient and practical.',1000000001,NULL,'male',1000000001,12,NULL,NULL,'2026-10-02 13:26:04.567073',12,NULL,NULL); CREATE TABLE IF NOT EXISTS "chats" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "cancelled" boolean DEFAULT FALSE NOT NULL, "character_id" integer, "created_at" datetime(6) NOT NULL, "model_id_string" varchar, "player_id" integer, "playthrough_id" integer, "purpose" varchar, "ruby_llm_model_id" integer, "updated_at" datetime(6) NOT NULL, CONSTRAINT "fk_rails_415520c982" FOREIGN KEY ("playthrough_id") REFERENCES "playthroughs" ("id") @@ -408,6 +397,7 @@ INSERT INTO schema_migrations VALUES('20261025230000'); INSERT INTO schema_migrations VALUES('20261027120117'); INSERT INTO schema_migrations VALUES('20261030120000'); INSERT INTO schema_migrations VALUES('20261030200000'); +INSERT INTO schema_migrations VALUES('20261030230000'); CREATE TABLE IF NOT EXISTS "ar_internal_metadata" ("key" varchar NOT NULL PRIMARY KEY, "value" varchar, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL); INSERT INTO ar_internal_metadata VALUES('environment','test','2026-10-02 13:25:33.143313','2026-10-02 13:25:33.143316'); INSERT INTO ar_internal_metadata VALUES('schema_sha1','072abc2bfb80d111a1808df270e9210911f5e81d','2026-10-02 13:25:33.148224','2026-10-02 13:25:33.148228'); @@ -418,13 +408,27 @@ FOREIGN KEY ("playthrough_id") FOREIGN KEY ("quest_outcome_id") REFERENCES "quest_outcomes" ("id") ); -CREATE TABLE IF NOT EXISTS "floors" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "location_id" integer NOT NULL, "rooms" integer NOT NULL, "danger" varchar, "hazard" varchar, "down_location_id" integer, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, CONSTRAINT "fk_rails_ce8b005cb2" +CREATE TABLE IF NOT EXISTS "floors" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "location_id" integer NOT NULL, "rooms" integer NOT NULL, "danger" varchar, "hazard" varchar, "down_location_id" integer, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, "monster_level" integer, CONSTRAINT "fk_rails_ce8b005cb2" FOREIGN KEY ("location_id") REFERENCES "locations" ("id") ON DELETE CASCADE, CONSTRAINT "fk_rails_8a0268155a" FOREIGN KEY ("down_location_id") REFERENCES "locations" ("id") ON DELETE SET NULL); +CREATE TABLE IF NOT EXISTS "characters" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "age" integer, "appearance" text, "backstory" text, "conscious_desire" text, "created_at" datetime(6) NOT NULL, "deliberately_absent" boolean DEFAULT FALSE NOT NULL, "desire_pursuit" varchar, "dexterity" integer, "dislikes" text, "fears" text, "fullname" varchar, "hit_die" integer, "hostile" boolean DEFAULT FALSE NOT NULL, "is_companion" boolean, "is_protagonist" boolean DEFAULT FALSE NOT NULL, "level" integer, "likes" text, "location_id" integer, "need_pursuit" varchar, "nickname" varchar, "personality" text, "race_id" integer NOT NULL, "recognized_need" text, "sex" varchar, "story_id" integer NOT NULL, "strength" integer, "unconscious_desire" text, "unrecognized_need" text, "updated_at" datetime(6) NOT NULL, "will" integer, "x" integer, "y" integer, "posted_to_id" integer, CONSTRAINT "fk_rails_6608c248e8" +FOREIGN KEY ("story_id") + REFERENCES "stories" ("id") +, CONSTRAINT "fk_rails_0b4b445641" +FOREIGN KEY ("location_id") + REFERENCES "locations" ("id") +, CONSTRAINT "fk_rails_56a213cbe2" +FOREIGN KEY ("race_id") + REFERENCES "races" ("id") +, CONSTRAINT "fk_rails_4c54fc506e" +FOREIGN KEY ("posted_to_id") + REFERENCES "locations" ("id") + ON DELETE SET NULL); +INSERT INTO characters VALUES(1000000001,30,'A travel-worn coat.','A trader returning to the market.',NULL,'2026-10-02 13:26:04.567073',0,NULL,12,'Needless arguments.','Losing a friend.','Cal',8,0,0,1,10,'A fair bargain.',NULL,NULL,'Cal','Patient and practical.',1000000001,NULL,'male',1000000001,12,NULL,NULL,'2026-10-02 13:26:04.567073',12,NULL,NULL,NULL); PRAGMA writable_schema=ON; CREATE TABLE IF NOT EXISTS sqlite_sequence(name,seq); DELETE FROM sqlite_sequence; @@ -437,7 +441,6 @@ INSERT INTO sqlite_sequence VALUES('ruby_llm_models',1000000000); INSERT INTO sqlite_sequence VALUES('ruby_llm_tool_calls',1000000000); INSERT INTO sqlite_sequence VALUES('ruby_llm_usages',1000000000); INSERT INTO sqlite_sequence VALUES('universes',1000000001); -INSERT INTO sqlite_sequence VALUES('characters',1000000001); INSERT INTO sqlite_sequence VALUES('chats',1000000000); INSERT INTO sqlite_sequence VALUES('interactions',1000000000); INSERT INTO sqlite_sequence VALUES('items',1000000000); @@ -472,6 +475,7 @@ INSERT INTO sqlite_sequence VALUES('stories',1000000001); INSERT INTO sqlite_sequence VALUES('system_one_receipts',1000000000); INSERT INTO sqlite_sequence VALUES('world_events',1000000000); INSERT INTO sqlite_sequence VALUES('world_mechanics',1000000000); +INSERT INTO sqlite_sequence VALUES('characters',1000000001); CREATE INDEX "index_characters_scenes_on_character_id_and_scene_id" ON "characters_scenes" ("character_id", "scene_id"); CREATE INDEX "index_characters_scenes_on_scene_id_and_character_id" ON "characters_scenes" ("scene_id", "character_id"); CREATE UNIQUE INDEX "index_players_on_name" ON "players" ("name"); @@ -492,13 +496,6 @@ CREATE INDEX "index_ruby_llm_usages_on_chat_type_and_chat_id" ON "ruby_llm_usage CREATE INDEX "index_ruby_llm_usages_on_message_type_and_message_id" ON "ruby_llm_usages" ("message_type", "message_id"); CREATE INDEX "index_ruby_llm_usages_on_status" ON "ruby_llm_usages" ("status"); CREATE UNIQUE INDEX "index_ruby_llm_v2_backfills_on_task" ON "ruby_llm_v2_backfills" ("task"); -CREATE UNIQUE INDEX "index_characters_on_story_id_and_lower_fullname" ON "characters" (story_id, LOWER(fullname)); -CREATE INDEX "index_characters_on_location_id_and_hostile" ON "characters" ("location_id", "hostile"); -CREATE INDEX "index_characters_on_location_id_and_id" ON "characters" ("location_id", "id"); -CREATE INDEX "index_characters_on_location_id" ON "characters" ("location_id"); -CREATE INDEX "index_characters_on_race_id" ON "characters" ("race_id"); -CREATE INDEX "index_characters_on_story_id_and_is_protagonist" ON "characters" ("story_id", "is_protagonist"); -CREATE INDEX "index_characters_on_story_id" ON "characters" ("story_id"); CREATE INDEX "index_chats_on_character_id" ON "chats" ("character_id"); CREATE INDEX "index_chats_on_player_id" ON "chats" ("player_id"); CREATE INDEX "index_chats_on_conversation_key" ON "chats" ("playthrough_id", "character_id", "purpose"); @@ -634,5 +631,13 @@ CREATE INDEX "index_playthrough_due_endings_on_quest_outcome_id" ON "playthrough CREATE INDEX "index_quest_steps_on_companion_id" ON "quest_steps" ("companion_id"); CREATE UNIQUE INDEX "index_floors_on_location_id" ON "floors" ("location_id"); CREATE INDEX "index_floors_on_down_location_id" ON "floors" ("down_location_id"); +CREATE UNIQUE INDEX "index_characters_on_story_id_and_lower_fullname" ON "characters" (story_id, LOWER(fullname)); +CREATE INDEX "index_characters_on_location_id_and_hostile" ON "characters" ("location_id", "hostile"); +CREATE INDEX "index_characters_on_location_id_and_id" ON "characters" ("location_id", "id"); +CREATE INDEX "index_characters_on_location_id" ON "characters" ("location_id"); +CREATE INDEX "index_characters_on_race_id" ON "characters" ("race_id"); +CREATE INDEX "index_characters_on_story_id_and_is_protagonist" ON "characters" ("story_id", "is_protagonist"); +CREATE INDEX "index_characters_on_story_id" ON "characters" ("story_id"); +CREATE INDEX "index_characters_on_posted_to_id" ON "characters" ("posted_to_id"); PRAGMA writable_schema=OFF; COMMIT; diff --git a/parity/worlds/a-yard-before-the-winter.sql b/parity/worlds/a-yard-before-the-winter.sql index 30301f3..50b669b 100644 --- a/parity/worlds/a-yard-before-the-winter.sql +++ b/parity/worlds/a-yard-before-the-winter.sql @@ -13,17 +13,6 @@ CREATE TABLE IF NOT EXISTS "ruby_llm_usages" ("id" integer PRIMARY KEY AUTOINCRE CREATE TABLE IF NOT EXISTS "ruby_llm_v2_backfills" ("completed" boolean DEFAULT FALSE NOT NULL, "last_id" integer, "task" varchar NOT NULL); CREATE TABLE IF NOT EXISTS "universes" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "civilizations" text, "created_at" datetime(6) NOT NULL, "economics" text, "geographies" text, "history" text, "physics" text, "politics" text, "religion" text, "technology" text, "updated_at" datetime(6) NOT NULL, "weapons" text, "gravity" varchar, "carrying" varchar); INSERT INTO universes VALUES(1000000001,'Scattered farms under a warden''s charter.','2026-10-02 13:25:41.333552','Salt is traded for grain.','A farmstead in a salt-marsh valley.','The salt road closed three winters ago.','Ordinary physics.','The warden''s word is law on the marsh.','The farms keep a harvest feast.','Carts, lanterns and iron tools.','2026-10-02 13:25:41.333552','Cudgels.',NULL,NULL); -CREATE TABLE IF NOT EXISTS "characters" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "age" integer, "appearance" text, "backstory" text, "conscious_desire" text, "created_at" datetime(6) NOT NULL, "deliberately_absent" boolean DEFAULT FALSE NOT NULL, "desire_pursuit" varchar, "dexterity" integer, "dislikes" text, "fears" text, "fullname" varchar, "hit_die" integer, "hostile" boolean DEFAULT FALSE NOT NULL, "is_companion" boolean, "is_protagonist" boolean DEFAULT FALSE NOT NULL, "level" integer, "likes" text, "location_id" integer, "need_pursuit" varchar, "nickname" varchar, "personality" text, "race_id" integer NOT NULL, "recognized_need" text, "sex" varchar, "story_id" integer NOT NULL, "strength" integer, "unconscious_desire" text, "unrecognized_need" text, "updated_at" datetime(6) NOT NULL, "will" integer, "x" integer, "y" integer, CONSTRAINT "fk_rails_56a213cbe2" -FOREIGN KEY ("race_id") - REFERENCES "races" ("id") -, CONSTRAINT "fk_rails_0b4b445641" -FOREIGN KEY ("location_id") - REFERENCES "locations" ("id") -, CONSTRAINT "fk_rails_6608c248e8" -FOREIGN KEY ("story_id") - REFERENCES "stories" ("id") -); -INSERT INTO characters VALUES(1000000001,34,'A salt-stained coat.','A salt-carter with no road to cart on.',NULL,'2026-10-02 13:25:41.524363',0,NULL,11,'Waiting.','A hungry winter.','Tamsin',8,0,0,1,4,'An open road.',NULL,NULL,'Tamsin','Blunt and tireless.',1000000001,NULL,'female',1000000001,13,NULL,NULL,'2026-10-02 13:25:41.524363',10,NULL,NULL); CREATE TABLE IF NOT EXISTS "chats" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "cancelled" boolean DEFAULT FALSE NOT NULL, "character_id" integer, "created_at" datetime(6) NOT NULL, "model_id_string" varchar, "player_id" integer, "playthrough_id" integer, "purpose" varchar, "ruby_llm_model_id" integer, "updated_at" datetime(6) NOT NULL, CONSTRAINT "fk_rails_415520c982" FOREIGN KEY ("playthrough_id") REFERENCES "playthroughs" ("id") @@ -422,6 +411,7 @@ INSERT INTO schema_migrations VALUES('20261025230000'); INSERT INTO schema_migrations VALUES('20261027120117'); INSERT INTO schema_migrations VALUES('20261030120000'); INSERT INTO schema_migrations VALUES('20261030200000'); +INSERT INTO schema_migrations VALUES('20261030230000'); CREATE TABLE IF NOT EXISTS "ar_internal_metadata" ("key" varchar NOT NULL PRIMARY KEY, "value" varchar, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL); INSERT INTO ar_internal_metadata VALUES('environment','test','2026-10-02 13:25:33.143313','2026-10-02 13:25:33.143316'); INSERT INTO ar_internal_metadata VALUES('schema_sha1','072abc2bfb80d111a1808df270e9210911f5e81d','2026-10-02 13:25:33.148224','2026-10-02 13:25:33.148228'); @@ -432,13 +422,27 @@ FOREIGN KEY ("playthrough_id") FOREIGN KEY ("quest_outcome_id") REFERENCES "quest_outcomes" ("id") ); -CREATE TABLE IF NOT EXISTS "floors" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "location_id" integer NOT NULL, "rooms" integer NOT NULL, "danger" varchar, "hazard" varchar, "down_location_id" integer, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, CONSTRAINT "fk_rails_ce8b005cb2" +CREATE TABLE IF NOT EXISTS "floors" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "location_id" integer NOT NULL, "rooms" integer NOT NULL, "danger" varchar, "hazard" varchar, "down_location_id" integer, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, "monster_level" integer, CONSTRAINT "fk_rails_ce8b005cb2" FOREIGN KEY ("location_id") REFERENCES "locations" ("id") ON DELETE CASCADE, CONSTRAINT "fk_rails_8a0268155a" FOREIGN KEY ("down_location_id") REFERENCES "locations" ("id") ON DELETE SET NULL); +CREATE TABLE IF NOT EXISTS "characters" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "age" integer, "appearance" text, "backstory" text, "conscious_desire" text, "created_at" datetime(6) NOT NULL, "deliberately_absent" boolean DEFAULT FALSE NOT NULL, "desire_pursuit" varchar, "dexterity" integer, "dislikes" text, "fears" text, "fullname" varchar, "hit_die" integer, "hostile" boolean DEFAULT FALSE NOT NULL, "is_companion" boolean, "is_protagonist" boolean DEFAULT FALSE NOT NULL, "level" integer, "likes" text, "location_id" integer, "need_pursuit" varchar, "nickname" varchar, "personality" text, "race_id" integer NOT NULL, "recognized_need" text, "sex" varchar, "story_id" integer NOT NULL, "strength" integer, "unconscious_desire" text, "unrecognized_need" text, "updated_at" datetime(6) NOT NULL, "will" integer, "x" integer, "y" integer, "posted_to_id" integer, CONSTRAINT "fk_rails_6608c248e8" +FOREIGN KEY ("story_id") + REFERENCES "stories" ("id") +, CONSTRAINT "fk_rails_0b4b445641" +FOREIGN KEY ("location_id") + REFERENCES "locations" ("id") +, CONSTRAINT "fk_rails_56a213cbe2" +FOREIGN KEY ("race_id") + REFERENCES "races" ("id") +, CONSTRAINT "fk_rails_4c54fc506e" +FOREIGN KEY ("posted_to_id") + REFERENCES "locations" ("id") + ON DELETE SET NULL); +INSERT INTO characters VALUES(1000000001,34,'A salt-stained coat.','A salt-carter with no road to cart on.',NULL,'2026-10-02 13:25:41.524363',0,NULL,11,'Waiting.','A hungry winter.','Tamsin',8,0,0,1,4,'An open road.',NULL,NULL,'Tamsin','Blunt and tireless.',1000000001,NULL,'female',1000000001,13,NULL,NULL,'2026-10-02 13:25:41.524363',10,NULL,NULL,NULL); PRAGMA writable_schema=ON; CREATE TABLE IF NOT EXISTS sqlite_sequence(name,seq); DELETE FROM sqlite_sequence; @@ -451,7 +455,6 @@ INSERT INTO sqlite_sequence VALUES('ruby_llm_models',1000000000); INSERT INTO sqlite_sequence VALUES('ruby_llm_tool_calls',1000000000); INSERT INTO sqlite_sequence VALUES('ruby_llm_usages',1000000000); INSERT INTO sqlite_sequence VALUES('universes',1000000001); -INSERT INTO sqlite_sequence VALUES('characters',1000000001); INSERT INTO sqlite_sequence VALUES('chats',1000000000); INSERT INTO sqlite_sequence VALUES('interactions',1000000000); INSERT INTO sqlite_sequence VALUES('items',1000000000); @@ -486,6 +489,7 @@ INSERT INTO sqlite_sequence VALUES('stories',1000000001); INSERT INTO sqlite_sequence VALUES('system_one_receipts',1000000000); INSERT INTO sqlite_sequence VALUES('world_events',1000000000); INSERT INTO sqlite_sequence VALUES('world_mechanics',1000000000); +INSERT INTO sqlite_sequence VALUES('characters',1000000001); CREATE INDEX "index_characters_scenes_on_character_id_and_scene_id" ON "characters_scenes" ("character_id", "scene_id"); CREATE INDEX "index_characters_scenes_on_scene_id_and_character_id" ON "characters_scenes" ("scene_id", "character_id"); CREATE UNIQUE INDEX "index_players_on_name" ON "players" ("name"); @@ -506,13 +510,6 @@ CREATE INDEX "index_ruby_llm_usages_on_chat_type_and_chat_id" ON "ruby_llm_usage CREATE INDEX "index_ruby_llm_usages_on_message_type_and_message_id" ON "ruby_llm_usages" ("message_type", "message_id"); CREATE INDEX "index_ruby_llm_usages_on_status" ON "ruby_llm_usages" ("status"); CREATE UNIQUE INDEX "index_ruby_llm_v2_backfills_on_task" ON "ruby_llm_v2_backfills" ("task"); -CREATE UNIQUE INDEX "index_characters_on_story_id_and_lower_fullname" ON "characters" (story_id, LOWER(fullname)); -CREATE INDEX "index_characters_on_location_id_and_hostile" ON "characters" ("location_id", "hostile"); -CREATE INDEX "index_characters_on_location_id_and_id" ON "characters" ("location_id", "id"); -CREATE INDEX "index_characters_on_location_id" ON "characters" ("location_id"); -CREATE INDEX "index_characters_on_race_id" ON "characters" ("race_id"); -CREATE INDEX "index_characters_on_story_id_and_is_protagonist" ON "characters" ("story_id", "is_protagonist"); -CREATE INDEX "index_characters_on_story_id" ON "characters" ("story_id"); CREATE INDEX "index_chats_on_character_id" ON "chats" ("character_id"); CREATE INDEX "index_chats_on_player_id" ON "chats" ("player_id"); CREATE INDEX "index_chats_on_conversation_key" ON "chats" ("playthrough_id", "character_id", "purpose"); @@ -648,5 +645,13 @@ CREATE INDEX "index_playthrough_due_endings_on_quest_outcome_id" ON "playthrough CREATE INDEX "index_quest_steps_on_companion_id" ON "quest_steps" ("companion_id"); CREATE UNIQUE INDEX "index_floors_on_location_id" ON "floors" ("location_id"); CREATE INDEX "index_floors_on_down_location_id" ON "floors" ("down_location_id"); +CREATE UNIQUE INDEX "index_characters_on_story_id_and_lower_fullname" ON "characters" (story_id, LOWER(fullname)); +CREATE INDEX "index_characters_on_location_id_and_hostile" ON "characters" ("location_id", "hostile"); +CREATE INDEX "index_characters_on_location_id_and_id" ON "characters" ("location_id", "id"); +CREATE INDEX "index_characters_on_location_id" ON "characters" ("location_id"); +CREATE INDEX "index_characters_on_race_id" ON "characters" ("race_id"); +CREATE INDEX "index_characters_on_story_id_and_is_protagonist" ON "characters" ("story_id", "is_protagonist"); +CREATE INDEX "index_characters_on_story_id" ON "characters" ("story_id"); +CREATE INDEX "index_characters_on_posted_to_id" ON "characters" ("posted_to_id"); PRAGMA writable_schema=OFF; COMMIT; diff --git a/parity/worlds/the-cloakroom-at-the-ferry.sql b/parity/worlds/the-cloakroom-at-the-ferry.sql index 5b82324..823c640 100644 --- a/parity/worlds/the-cloakroom-at-the-ferry.sql +++ b/parity/worlds/the-cloakroom-at-the-ferry.sql @@ -14,18 +14,6 @@ CREATE TABLE IF NOT EXISTS "ruby_llm_usages" ("id" integer PRIMARY KEY AUTOINCRE CREATE TABLE IF NOT EXISTS "ruby_llm_v2_backfills" ("completed" boolean DEFAULT FALSE NOT NULL, "last_id" integer, "task" varchar NOT NULL); CREATE TABLE IF NOT EXISTS "universes" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "civilizations" text, "created_at" datetime(6) NOT NULL, "economics" text, "geographies" text, "history" text, "physics" text, "politics" text, "religion" text, "technology" text, "updated_at" datetime(6) NOT NULL, "weapons" text, "gravity" varchar, "carrying" varchar); INSERT INTO universes VALUES(1000000001,'A ferry town that lives by the crossing.','2026-10-25 22:48:32.785826','Coins and brass ferry tokens.','A river crossing on a cold estuary.','The chain ferry has crossed the estuary since the old bridge fell.','Ordinary physics.','The ferryman answers to the river board.','The town lights a lamp for the drowned each winter.','Oil lamps, ropes and a chain ferry.','2026-10-25 22:48:32.785826','Boathooks and walking sticks.',NULL,NULL); -CREATE TABLE IF NOT EXISTS "characters" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "age" integer, "appearance" text, "backstory" text, "conscious_desire" text, "created_at" datetime(6) NOT NULL, "deliberately_absent" boolean DEFAULT FALSE NOT NULL, "desire_pursuit" varchar, "dexterity" integer, "dislikes" text, "fears" text, "fullname" varchar, "hit_die" integer, "hostile" boolean DEFAULT FALSE NOT NULL, "is_companion" boolean, "is_protagonist" boolean DEFAULT FALSE NOT NULL, "level" integer, "likes" text, "location_id" integer, "need_pursuit" varchar, "nickname" varchar, "personality" text, "race_id" integer NOT NULL, "recognized_need" text, "sex" varchar, "story_id" integer NOT NULL, "strength" integer, "unconscious_desire" text, "unrecognized_need" text, "updated_at" datetime(6) NOT NULL, "will" integer, "x" integer, "y" integer, CONSTRAINT "fk_rails_56a213cbe2" -FOREIGN KEY ("race_id") - REFERENCES "races" ("id") -, CONSTRAINT "fk_rails_0b4b445641" -FOREIGN KEY ("location_id") - REFERENCES "locations" ("id") -, CONSTRAINT "fk_rails_6608c248e8" -FOREIGN KEY ("story_id") - REFERENCES "stories" ("id") -); -INSERT INTO characters VALUES(1000000001,31,'Wind-reddened cheeks and ink on her fingers.','A surveyor waiting on the morning crossing.',NULL,'2026-10-25 22:48:32.982956',0,NULL,12,'Waiting.','A missed crossing.','Wren Calloway',8,0,0,1,3,'A dry bench.',NULL,NULL,'Wren','Patient and cold.',1000000001,NULL,'female',1000000001,12,NULL,NULL,'2026-10-25 22:48:32.982956',12,NULL,NULL); -INSERT INTO characters VALUES(1000000002,62,'A white beard and a tarred cap.','The ferryman, who has worked the chain for forty years.',NULL,'2026-10-25 22:48:33.060368',0,NULL,8,'Passengers who talk.','The chain parting.','Abel Moss',6,0,0,0,1,'A slack tide.',1000000001,NULL,'Abel','Gruff and slow.',1000000001,NULL,'male',1000000001,8,NULL,NULL,'2026-10-25 22:48:33.060368',10,NULL,NULL); CREATE TABLE IF NOT EXISTS "chats" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "cancelled" boolean DEFAULT FALSE NOT NULL, "character_id" integer, "created_at" datetime(6) NOT NULL, "model_id_string" varchar, "player_id" integer, "playthrough_id" integer, "purpose" varchar, "ruby_llm_model_id" integer, "updated_at" datetime(6) NOT NULL, CONSTRAINT "fk_rails_415520c982" FOREIGN KEY ("playthrough_id") REFERENCES "playthroughs" ("id") @@ -422,16 +410,32 @@ INSERT INTO schema_migrations VALUES('20261025230000'); INSERT INTO schema_migrations VALUES('20261027120117'); INSERT INTO schema_migrations VALUES('20261030120000'); INSERT INTO schema_migrations VALUES('20261030200000'); +INSERT INTO schema_migrations VALUES('20261030230000'); CREATE TABLE IF NOT EXISTS "ar_internal_metadata" ("key" varchar NOT NULL PRIMARY KEY, "value" varchar, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL); INSERT INTO ar_internal_metadata VALUES('environment','test','2026-10-25 22:48:20.183741','2026-10-25 22:48:20.183743'); INSERT INTO ar_internal_metadata VALUES('schema_sha1','1ce6b3b7eea61111c69aac54baf282539c772990','2026-10-25 22:48:20.187552','2026-10-25 22:48:20.187553'); -CREATE TABLE IF NOT EXISTS "floors" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "location_id" integer NOT NULL, "rooms" integer NOT NULL, "danger" varchar, "hazard" varchar, "down_location_id" integer, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, CONSTRAINT "fk_rails_ce8b005cb2" +CREATE TABLE IF NOT EXISTS "floors" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "location_id" integer NOT NULL, "rooms" integer NOT NULL, "danger" varchar, "hazard" varchar, "down_location_id" integer, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, "monster_level" integer, CONSTRAINT "fk_rails_ce8b005cb2" FOREIGN KEY ("location_id") REFERENCES "locations" ("id") ON DELETE CASCADE, CONSTRAINT "fk_rails_8a0268155a" FOREIGN KEY ("down_location_id") REFERENCES "locations" ("id") ON DELETE SET NULL); +CREATE TABLE IF NOT EXISTS "characters" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "age" integer, "appearance" text, "backstory" text, "conscious_desire" text, "created_at" datetime(6) NOT NULL, "deliberately_absent" boolean DEFAULT FALSE NOT NULL, "desire_pursuit" varchar, "dexterity" integer, "dislikes" text, "fears" text, "fullname" varchar, "hit_die" integer, "hostile" boolean DEFAULT FALSE NOT NULL, "is_companion" boolean, "is_protagonist" boolean DEFAULT FALSE NOT NULL, "level" integer, "likes" text, "location_id" integer, "need_pursuit" varchar, "nickname" varchar, "personality" text, "race_id" integer NOT NULL, "recognized_need" text, "sex" varchar, "story_id" integer NOT NULL, "strength" integer, "unconscious_desire" text, "unrecognized_need" text, "updated_at" datetime(6) NOT NULL, "will" integer, "x" integer, "y" integer, "posted_to_id" integer, CONSTRAINT "fk_rails_6608c248e8" +FOREIGN KEY ("story_id") + REFERENCES "stories" ("id") +, CONSTRAINT "fk_rails_0b4b445641" +FOREIGN KEY ("location_id") + REFERENCES "locations" ("id") +, CONSTRAINT "fk_rails_56a213cbe2" +FOREIGN KEY ("race_id") + REFERENCES "races" ("id") +, CONSTRAINT "fk_rails_4c54fc506e" +FOREIGN KEY ("posted_to_id") + REFERENCES "locations" ("id") + ON DELETE SET NULL); +INSERT INTO characters VALUES(1000000001,31,'Wind-reddened cheeks and ink on her fingers.','A surveyor waiting on the morning crossing.',NULL,'2026-10-25 22:48:32.982956',0,NULL,12,'Waiting.','A missed crossing.','Wren Calloway',8,0,0,1,3,'A dry bench.',NULL,NULL,'Wren','Patient and cold.',1000000001,NULL,'female',1000000001,12,NULL,NULL,'2026-10-25 22:48:32.982956',12,NULL,NULL,NULL); +INSERT INTO characters VALUES(1000000002,62,'A white beard and a tarred cap.','The ferryman, who has worked the chain for forty years.',NULL,'2026-10-25 22:48:33.060368',0,NULL,8,'Passengers who talk.','The chain parting.','Abel Moss',6,0,0,0,1,'A slack tide.',1000000001,NULL,'Abel','Gruff and slow.',1000000001,NULL,'male',1000000001,8,NULL,NULL,'2026-10-25 22:48:33.060368',10,NULL,NULL,NULL); PRAGMA writable_schema=ON; CREATE TABLE IF NOT EXISTS sqlite_sequence(name,seq); DELETE FROM sqlite_sequence; @@ -444,7 +448,6 @@ INSERT INTO sqlite_sequence VALUES('ruby_llm_models',1000000000); INSERT INTO sqlite_sequence VALUES('ruby_llm_tool_calls',1000000000); INSERT INTO sqlite_sequence VALUES('ruby_llm_usages',1000000000); INSERT INTO sqlite_sequence VALUES('universes',1000000001); -INSERT INTO sqlite_sequence VALUES('characters',1000000002); INSERT INTO sqlite_sequence VALUES('chats',1000000000); INSERT INTO sqlite_sequence VALUES('interactions',1000000000); INSERT INTO sqlite_sequence VALUES('items',1000000008); @@ -480,6 +483,7 @@ INSERT INTO sqlite_sequence VALUES('stories',1000000001); INSERT INTO sqlite_sequence VALUES('system_one_receipts',1000000000); INSERT INTO sqlite_sequence VALUES('world_events',1000000000); INSERT INTO sqlite_sequence VALUES('world_mechanics',1000000000); +INSERT INTO sqlite_sequence VALUES('characters',1000000002); CREATE INDEX "index_characters_scenes_on_character_id_and_scene_id" ON "characters_scenes" ("character_id", "scene_id"); CREATE INDEX "index_characters_scenes_on_scene_id_and_character_id" ON "characters_scenes" ("scene_id", "character_id"); CREATE UNIQUE INDEX "index_players_on_name" ON "players" ("name"); @@ -500,13 +504,6 @@ CREATE INDEX "index_ruby_llm_usages_on_chat_type_and_chat_id" ON "ruby_llm_usage CREATE INDEX "index_ruby_llm_usages_on_message_type_and_message_id" ON "ruby_llm_usages" ("message_type", "message_id"); CREATE INDEX "index_ruby_llm_usages_on_status" ON "ruby_llm_usages" ("status"); CREATE UNIQUE INDEX "index_ruby_llm_v2_backfills_on_task" ON "ruby_llm_v2_backfills" ("task"); -CREATE UNIQUE INDEX "index_characters_on_story_id_and_lower_fullname" ON "characters" (story_id, LOWER(fullname)); -CREATE INDEX "index_characters_on_location_id_and_hostile" ON "characters" ("location_id", "hostile"); -CREATE INDEX "index_characters_on_location_id_and_id" ON "characters" ("location_id", "id"); -CREATE INDEX "index_characters_on_location_id" ON "characters" ("location_id"); -CREATE INDEX "index_characters_on_race_id" ON "characters" ("race_id"); -CREATE INDEX "index_characters_on_story_id_and_is_protagonist" ON "characters" ("story_id", "is_protagonist"); -CREATE INDEX "index_characters_on_story_id" ON "characters" ("story_id"); CREATE INDEX "index_chats_on_character_id" ON "chats" ("character_id"); CREATE INDEX "index_chats_on_player_id" ON "chats" ("player_id"); CREATE INDEX "index_chats_on_conversation_key" ON "chats" ("playthrough_id", "character_id", "purpose"); @@ -642,5 +639,13 @@ CREATE INDEX "index_world_mechanics_on_story_id" ON "world_mechanics" ("story_id CREATE INDEX "index_quest_steps_on_companion_id" ON "quest_steps" ("companion_id"); CREATE UNIQUE INDEX "index_floors_on_location_id" ON "floors" ("location_id"); CREATE INDEX "index_floors_on_down_location_id" ON "floors" ("down_location_id"); +CREATE UNIQUE INDEX "index_characters_on_story_id_and_lower_fullname" ON "characters" (story_id, LOWER(fullname)); +CREATE INDEX "index_characters_on_location_id_and_hostile" ON "characters" ("location_id", "hostile"); +CREATE INDEX "index_characters_on_location_id_and_id" ON "characters" ("location_id", "id"); +CREATE INDEX "index_characters_on_location_id" ON "characters" ("location_id"); +CREATE INDEX "index_characters_on_race_id" ON "characters" ("race_id"); +CREATE INDEX "index_characters_on_story_id_and_is_protagonist" ON "characters" ("story_id", "is_protagonist"); +CREATE INDEX "index_characters_on_story_id" ON "characters" ("story_id"); +CREATE INDEX "index_characters_on_posted_to_id" ON "characters" ("posted_to_id"); PRAGMA writable_schema=OFF; COMMIT; diff --git a/parity/worlds/the-furnished-rooms.sql b/parity/worlds/the-furnished-rooms.sql index 30bce70..016546d 100644 --- a/parity/worlds/the-furnished-rooms.sql +++ b/parity/worlds/the-furnished-rooms.sql @@ -13,17 +13,6 @@ CREATE TABLE IF NOT EXISTS "ruby_llm_usages" ("id" integer PRIMARY KEY AUTOINCRE CREATE TABLE IF NOT EXISTS "ruby_llm_v2_backfills" ("completed" boolean DEFAULT FALSE NOT NULL, "last_id" integer, "task" varchar NOT NULL); CREATE TABLE IF NOT EXISTS "universes" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "civilizations" text, "created_at" datetime(6) NOT NULL, "economics" text, "geographies" text, "history" text, "physics" text, "politics" text, "religion" text, "technology" text, "updated_at" datetime(6) NOT NULL, "weapons" text, "gravity" varchar, "carrying" varchar); INSERT INTO universes VALUES(1000000001,'A walled town that keeps its records.','2026-10-02 13:25:45.035816','Coins and stamped receipts.','A ward office, a wood behind it and a market street.','The ward office has kept the town''s ledgers for a century.','Ordinary physics.','The ward keeps the peace.','The town keeps its own festivals.','Ink, lamps and hand carts.','2026-10-02 13:25:45.035816','Walking sticks.',NULL,NULL); -CREATE TABLE IF NOT EXISTS "characters" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "age" integer, "appearance" text, "backstory" text, "conscious_desire" text, "created_at" datetime(6) NOT NULL, "deliberately_absent" boolean DEFAULT FALSE NOT NULL, "desire_pursuit" varchar, "dexterity" integer, "dislikes" text, "fears" text, "fullname" varchar, "hit_die" integer, "hostile" boolean DEFAULT FALSE NOT NULL, "is_companion" boolean, "is_protagonist" boolean DEFAULT FALSE NOT NULL, "level" integer, "likes" text, "location_id" integer, "need_pursuit" varchar, "nickname" varchar, "personality" text, "race_id" integer NOT NULL, "recognized_need" text, "sex" varchar, "story_id" integer NOT NULL, "strength" integer, "unconscious_desire" text, "unrecognized_need" text, "updated_at" datetime(6) NOT NULL, "will" integer, "x" integer, "y" integer, CONSTRAINT "fk_rails_56a213cbe2" -FOREIGN KEY ("race_id") - REFERENCES "races" ("id") -, CONSTRAINT "fk_rails_0b4b445641" -FOREIGN KEY ("location_id") - REFERENCES "locations" ("id") -, CONSTRAINT "fk_rails_6608c248e8" -FOREIGN KEY ("story_id") - REFERENCES "stories" ("id") -); -INSERT INTO characters VALUES(1000000001,34,'An ink-stained coat.','A ward auditor sent to look over the clerk''s rooms.',NULL,'2026-10-02 13:25:45.301225',0,NULL,12,'Loose paper.','A lost record.','Mira Vell',8,0,0,1,3,'A tidy ledger.',NULL,NULL,'Mira','Careful and dry.',1000000001,NULL,'female',1000000001,10,NULL,NULL,'2026-10-02 13:25:45.301225',12,NULL,NULL); CREATE TABLE IF NOT EXISTS "chats" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "cancelled" boolean DEFAULT FALSE NOT NULL, "character_id" integer, "created_at" datetime(6) NOT NULL, "model_id_string" varchar, "player_id" integer, "playthrough_id" integer, "purpose" varchar, "ruby_llm_model_id" integer, "updated_at" datetime(6) NOT NULL, CONSTRAINT "fk_rails_415520c982" FOREIGN KEY ("playthrough_id") REFERENCES "playthroughs" ("id") @@ -423,6 +412,7 @@ INSERT INTO schema_migrations VALUES('20261025230000'); INSERT INTO schema_migrations VALUES('20261027120117'); INSERT INTO schema_migrations VALUES('20261030120000'); INSERT INTO schema_migrations VALUES('20261030200000'); +INSERT INTO schema_migrations VALUES('20261030230000'); CREATE TABLE IF NOT EXISTS "ar_internal_metadata" ("key" varchar NOT NULL PRIMARY KEY, "value" varchar, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL); INSERT INTO ar_internal_metadata VALUES('environment','test','2026-10-02 13:25:33.143313','2026-10-02 13:25:33.143316'); INSERT INTO ar_internal_metadata VALUES('schema_sha1','072abc2bfb80d111a1808df270e9210911f5e81d','2026-10-02 13:25:33.148224','2026-10-02 13:25:33.148228'); @@ -433,13 +423,27 @@ FOREIGN KEY ("playthrough_id") FOREIGN KEY ("quest_outcome_id") REFERENCES "quest_outcomes" ("id") ); -CREATE TABLE IF NOT EXISTS "floors" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "location_id" integer NOT NULL, "rooms" integer NOT NULL, "danger" varchar, "hazard" varchar, "down_location_id" integer, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, CONSTRAINT "fk_rails_ce8b005cb2" +CREATE TABLE IF NOT EXISTS "floors" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "location_id" integer NOT NULL, "rooms" integer NOT NULL, "danger" varchar, "hazard" varchar, "down_location_id" integer, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, "monster_level" integer, CONSTRAINT "fk_rails_ce8b005cb2" FOREIGN KEY ("location_id") REFERENCES "locations" ("id") ON DELETE CASCADE, CONSTRAINT "fk_rails_8a0268155a" FOREIGN KEY ("down_location_id") REFERENCES "locations" ("id") ON DELETE SET NULL); +CREATE TABLE IF NOT EXISTS "characters" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "age" integer, "appearance" text, "backstory" text, "conscious_desire" text, "created_at" datetime(6) NOT NULL, "deliberately_absent" boolean DEFAULT FALSE NOT NULL, "desire_pursuit" varchar, "dexterity" integer, "dislikes" text, "fears" text, "fullname" varchar, "hit_die" integer, "hostile" boolean DEFAULT FALSE NOT NULL, "is_companion" boolean, "is_protagonist" boolean DEFAULT FALSE NOT NULL, "level" integer, "likes" text, "location_id" integer, "need_pursuit" varchar, "nickname" varchar, "personality" text, "race_id" integer NOT NULL, "recognized_need" text, "sex" varchar, "story_id" integer NOT NULL, "strength" integer, "unconscious_desire" text, "unrecognized_need" text, "updated_at" datetime(6) NOT NULL, "will" integer, "x" integer, "y" integer, "posted_to_id" integer, CONSTRAINT "fk_rails_6608c248e8" +FOREIGN KEY ("story_id") + REFERENCES "stories" ("id") +, CONSTRAINT "fk_rails_0b4b445641" +FOREIGN KEY ("location_id") + REFERENCES "locations" ("id") +, CONSTRAINT "fk_rails_56a213cbe2" +FOREIGN KEY ("race_id") + REFERENCES "races" ("id") +, CONSTRAINT "fk_rails_4c54fc506e" +FOREIGN KEY ("posted_to_id") + REFERENCES "locations" ("id") + ON DELETE SET NULL); +INSERT INTO characters VALUES(1000000001,34,'An ink-stained coat.','A ward auditor sent to look over the clerk''s rooms.',NULL,'2026-10-02 13:25:45.301225',0,NULL,12,'Loose paper.','A lost record.','Mira Vell',8,0,0,1,3,'A tidy ledger.',NULL,NULL,'Mira','Careful and dry.',1000000001,NULL,'female',1000000001,10,NULL,NULL,'2026-10-02 13:25:45.301225',12,NULL,NULL,NULL); PRAGMA writable_schema=ON; CREATE TABLE IF NOT EXISTS sqlite_sequence(name,seq); DELETE FROM sqlite_sequence; @@ -452,7 +456,6 @@ INSERT INTO sqlite_sequence VALUES('ruby_llm_models',1000000000); INSERT INTO sqlite_sequence VALUES('ruby_llm_tool_calls',1000000000); INSERT INTO sqlite_sequence VALUES('ruby_llm_usages',1000000000); INSERT INTO sqlite_sequence VALUES('universes',1000000001); -INSERT INTO sqlite_sequence VALUES('characters',1000000001); INSERT INTO sqlite_sequence VALUES('chats',1000000000); INSERT INTO sqlite_sequence VALUES('interactions',1000000000); INSERT INTO sqlite_sequence VALUES('items',1000000012); @@ -487,6 +490,7 @@ INSERT INTO sqlite_sequence VALUES('stories',1000000001); INSERT INTO sqlite_sequence VALUES('system_one_receipts',1000000000); INSERT INTO sqlite_sequence VALUES('world_events',1000000000); INSERT INTO sqlite_sequence VALUES('world_mechanics',1000000000); +INSERT INTO sqlite_sequence VALUES('characters',1000000001); CREATE INDEX "index_characters_scenes_on_character_id_and_scene_id" ON "characters_scenes" ("character_id", "scene_id"); CREATE INDEX "index_characters_scenes_on_scene_id_and_character_id" ON "characters_scenes" ("scene_id", "character_id"); CREATE UNIQUE INDEX "index_players_on_name" ON "players" ("name"); @@ -507,13 +511,6 @@ CREATE INDEX "index_ruby_llm_usages_on_chat_type_and_chat_id" ON "ruby_llm_usage CREATE INDEX "index_ruby_llm_usages_on_message_type_and_message_id" ON "ruby_llm_usages" ("message_type", "message_id"); CREATE INDEX "index_ruby_llm_usages_on_status" ON "ruby_llm_usages" ("status"); CREATE UNIQUE INDEX "index_ruby_llm_v2_backfills_on_task" ON "ruby_llm_v2_backfills" ("task"); -CREATE UNIQUE INDEX "index_characters_on_story_id_and_lower_fullname" ON "characters" (story_id, LOWER(fullname)); -CREATE INDEX "index_characters_on_location_id_and_hostile" ON "characters" ("location_id", "hostile"); -CREATE INDEX "index_characters_on_location_id_and_id" ON "characters" ("location_id", "id"); -CREATE INDEX "index_characters_on_location_id" ON "characters" ("location_id"); -CREATE INDEX "index_characters_on_race_id" ON "characters" ("race_id"); -CREATE INDEX "index_characters_on_story_id_and_is_protagonist" ON "characters" ("story_id", "is_protagonist"); -CREATE INDEX "index_characters_on_story_id" ON "characters" ("story_id"); CREATE INDEX "index_chats_on_character_id" ON "chats" ("character_id"); CREATE INDEX "index_chats_on_player_id" ON "chats" ("player_id"); CREATE INDEX "index_chats_on_conversation_key" ON "chats" ("playthrough_id", "character_id", "purpose"); @@ -649,5 +646,13 @@ CREATE INDEX "index_playthrough_due_endings_on_quest_outcome_id" ON "playthrough CREATE INDEX "index_quest_steps_on_companion_id" ON "quest_steps" ("companion_id"); CREATE UNIQUE INDEX "index_floors_on_location_id" ON "floors" ("location_id"); CREATE INDEX "index_floors_on_down_location_id" ON "floors" ("down_location_id"); +CREATE UNIQUE INDEX "index_characters_on_story_id_and_lower_fullname" ON "characters" (story_id, LOWER(fullname)); +CREATE INDEX "index_characters_on_location_id_and_hostile" ON "characters" ("location_id", "hostile"); +CREATE INDEX "index_characters_on_location_id_and_id" ON "characters" ("location_id", "id"); +CREATE INDEX "index_characters_on_location_id" ON "characters" ("location_id"); +CREATE INDEX "index_characters_on_race_id" ON "characters" ("race_id"); +CREATE INDEX "index_characters_on_story_id_and_is_protagonist" ON "characters" ("story_id", "is_protagonist"); +CREATE INDEX "index_characters_on_story_id" ON "characters" ("story_id"); +CREATE INDEX "index_characters_on_posted_to_id" ON "characters" ("posted_to_id"); PRAGMA writable_schema=OFF; COMMIT; diff --git a/parity/worlds/the-green-at-saltmarsh.sql b/parity/worlds/the-green-at-saltmarsh.sql index aadbcf7..459427d 100644 --- a/parity/worlds/the-green-at-saltmarsh.sql +++ b/parity/worlds/the-green-at-saltmarsh.sql @@ -13,19 +13,6 @@ CREATE TABLE IF NOT EXISTS "ruby_llm_usages" ("id" integer PRIMARY KEY AUTOINCRE CREATE TABLE IF NOT EXISTS "ruby_llm_v2_backfills" ("completed" boolean DEFAULT FALSE NOT NULL, "last_id" integer, "task" varchar NOT NULL); CREATE TABLE IF NOT EXISTS "universes" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "civilizations" text, "created_at" datetime(6) NOT NULL, "economics" text, "geographies" text, "history" text, "physics" text, "politics" text, "religion" text, "technology" text, "updated_at" datetime(6) NOT NULL, "weapons" text, "gravity" varchar, "carrying" varchar); INSERT INTO universes VALUES(1000000001,'A fishing village that makes its own rope and sells the rest.','2026-10-02 13:26:02.866161','Rope by the fathom to the boats, and nets by the dozen to the market.','A salt marsh on a tidal river, with the village on the only dry ground.','The Coyles have laid the village''s rope since the harbour was dug.','Ordinary physics. What is thrown comes down, and the heavier it is the sooner.','The harbour master, who buys the rope and sets its price.','A blessing of the boats at midsummer.','Hemp, tar, a spinning wheel and a long shed to lay the rope in.','2026-10-02 13:26:02.866161','A marlinspike, at a pinch.','ordinary',NULL); -CREATE TABLE IF NOT EXISTS "characters" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "age" integer, "appearance" text, "backstory" text, "conscious_desire" text, "created_at" datetime(6) NOT NULL, "deliberately_absent" boolean DEFAULT FALSE NOT NULL, "desire_pursuit" varchar, "dexterity" integer, "dislikes" text, "fears" text, "fullname" varchar, "hit_die" integer, "hostile" boolean DEFAULT FALSE NOT NULL, "is_companion" boolean, "is_protagonist" boolean DEFAULT FALSE NOT NULL, "level" integer, "likes" text, "location_id" integer, "need_pursuit" varchar, "nickname" varchar, "personality" text, "race_id" integer NOT NULL, "recognized_need" text, "sex" varchar, "story_id" integer NOT NULL, "strength" integer, "unconscious_desire" text, "unrecognized_need" text, "updated_at" datetime(6) NOT NULL, "will" integer, "x" integer, "y" integer, CONSTRAINT "fk_rails_56a213cbe2" -FOREIGN KEY ("race_id") - REFERENCES "races" ("id") -, CONSTRAINT "fk_rails_0b4b445641" -FOREIGN KEY ("location_id") - REFERENCES "locations" ("id") -, CONSTRAINT "fk_rails_6608c248e8" -FOREIGN KEY ("story_id") - REFERENCES "stories" ("id") -); -INSERT INTO characters VALUES(1000000001,27,'Tar to the wrists, and a rope burn across one palm.','Walks the ropewalk twice a day, and can tell a bad strand by the feel of it.',NULL,'2026-10-02 13:26:03.006411',0,NULL,11,'Being hurried on a long lay','The walk closing for good','Jory Pask',8,0,0,1,2,'A cable laid without a kink',NULL,NULL,'Jory','Steady, and slow to say what he has noticed.',1000000001,NULL,'male',1000000001,18,NULL,NULL,'2026-10-02 13:26:03.006411',10,NULL,NULL); -INSERT INTO characters VALUES(1000000002,61,'Bent at the shoulders, with a net across his knees.','Laid rope on the walk for forty years, and mends nets on the green now.',NULL,'2026-10-02 13:26:03.102878',0,NULL,12,'New rope','Being asked to walk the ropewalk again','Wat Coyle',6,0,0,0,1,'A fine day for mending',1000000001,NULL,'Wat','Talkative, and certain the old ways were better.',1000000001,NULL,'male',1000000001,11,NULL,NULL,'2026-10-02 13:26:03.102878',10,10,27); -INSERT INTO characters VALUES(1000000003,38,'Ink on one cuff, tar on the other.','Keeps the ropewalk''s book, and signs for every fathom that leaves it.',NULL,'2026-10-02 13:26:03.111748',0,NULL,13,'Rope sold off the book','The parted cable being traced to her signature','Hester Vane',6,0,0,0,1,'A book that balances',1000000002,NULL,'Hester','Exact, and short with anybody who is not.',1000000001,NULL,'female',1000000001,10,NULL,NULL,'2026-10-02 13:26:03.111748',12,18,1); CREATE TABLE IF NOT EXISTS "chats" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "cancelled" boolean DEFAULT FALSE NOT NULL, "character_id" integer, "created_at" datetime(6) NOT NULL, "model_id_string" varchar, "player_id" integer, "playthrough_id" integer, "purpose" varchar, "ruby_llm_model_id" integer, "updated_at" datetime(6) NOT NULL, CONSTRAINT "fk_rails_415520c982" FOREIGN KEY ("playthrough_id") REFERENCES "playthroughs" ("id") @@ -415,6 +402,7 @@ INSERT INTO schema_migrations VALUES('20261025230000'); INSERT INTO schema_migrations VALUES('20261027120117'); INSERT INTO schema_migrations VALUES('20261030120000'); INSERT INTO schema_migrations VALUES('20261030200000'); +INSERT INTO schema_migrations VALUES('20261030230000'); CREATE TABLE IF NOT EXISTS "ar_internal_metadata" ("key" varchar NOT NULL PRIMARY KEY, "value" varchar, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL); INSERT INTO ar_internal_metadata VALUES('environment','test','2026-10-02 13:25:33.143313','2026-10-02 13:25:33.143316'); INSERT INTO ar_internal_metadata VALUES('schema_sha1','072abc2bfb80d111a1808df270e9210911f5e81d','2026-10-02 13:25:33.148224','2026-10-02 13:25:33.148228'); @@ -425,13 +413,29 @@ FOREIGN KEY ("playthrough_id") FOREIGN KEY ("quest_outcome_id") REFERENCES "quest_outcomes" ("id") ); -CREATE TABLE IF NOT EXISTS "floors" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "location_id" integer NOT NULL, "rooms" integer NOT NULL, "danger" varchar, "hazard" varchar, "down_location_id" integer, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, CONSTRAINT "fk_rails_ce8b005cb2" +CREATE TABLE IF NOT EXISTS "floors" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "location_id" integer NOT NULL, "rooms" integer NOT NULL, "danger" varchar, "hazard" varchar, "down_location_id" integer, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, "monster_level" integer, CONSTRAINT "fk_rails_ce8b005cb2" FOREIGN KEY ("location_id") REFERENCES "locations" ("id") ON DELETE CASCADE, CONSTRAINT "fk_rails_8a0268155a" FOREIGN KEY ("down_location_id") REFERENCES "locations" ("id") ON DELETE SET NULL); +CREATE TABLE IF NOT EXISTS "characters" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "age" integer, "appearance" text, "backstory" text, "conscious_desire" text, "created_at" datetime(6) NOT NULL, "deliberately_absent" boolean DEFAULT FALSE NOT NULL, "desire_pursuit" varchar, "dexterity" integer, "dislikes" text, "fears" text, "fullname" varchar, "hit_die" integer, "hostile" boolean DEFAULT FALSE NOT NULL, "is_companion" boolean, "is_protagonist" boolean DEFAULT FALSE NOT NULL, "level" integer, "likes" text, "location_id" integer, "need_pursuit" varchar, "nickname" varchar, "personality" text, "race_id" integer NOT NULL, "recognized_need" text, "sex" varchar, "story_id" integer NOT NULL, "strength" integer, "unconscious_desire" text, "unrecognized_need" text, "updated_at" datetime(6) NOT NULL, "will" integer, "x" integer, "y" integer, "posted_to_id" integer, CONSTRAINT "fk_rails_6608c248e8" +FOREIGN KEY ("story_id") + REFERENCES "stories" ("id") +, CONSTRAINT "fk_rails_0b4b445641" +FOREIGN KEY ("location_id") + REFERENCES "locations" ("id") +, CONSTRAINT "fk_rails_56a213cbe2" +FOREIGN KEY ("race_id") + REFERENCES "races" ("id") +, CONSTRAINT "fk_rails_4c54fc506e" +FOREIGN KEY ("posted_to_id") + REFERENCES "locations" ("id") + ON DELETE SET NULL); +INSERT INTO characters VALUES(1000000001,27,'Tar to the wrists, and a rope burn across one palm.','Walks the ropewalk twice a day, and can tell a bad strand by the feel of it.',NULL,'2026-10-02 13:26:03.006411',0,NULL,11,'Being hurried on a long lay','The walk closing for good','Jory Pask',8,0,0,1,2,'A cable laid without a kink',NULL,NULL,'Jory','Steady, and slow to say what he has noticed.',1000000001,NULL,'male',1000000001,18,NULL,NULL,'2026-10-02 13:26:03.006411',10,NULL,NULL,NULL); +INSERT INTO characters VALUES(1000000002,61,'Bent at the shoulders, with a net across his knees.','Laid rope on the walk for forty years, and mends nets on the green now.',NULL,'2026-10-02 13:26:03.102878',0,NULL,12,'New rope','Being asked to walk the ropewalk again','Wat Coyle',6,0,0,0,1,'A fine day for mending',1000000001,NULL,'Wat','Talkative, and certain the old ways were better.',1000000001,NULL,'male',1000000001,11,NULL,NULL,'2026-10-02 13:26:03.102878',10,10,27,NULL); +INSERT INTO characters VALUES(1000000003,38,'Ink on one cuff, tar on the other.','Keeps the ropewalk''s book, and signs for every fathom that leaves it.',NULL,'2026-10-02 13:26:03.111748',0,NULL,13,'Rope sold off the book','The parted cable being traced to her signature','Hester Vane',6,0,0,0,1,'A book that balances',1000000002,NULL,'Hester','Exact, and short with anybody who is not.',1000000001,NULL,'female',1000000001,10,NULL,NULL,'2026-10-02 13:26:03.111748',12,18,1,NULL); PRAGMA writable_schema=ON; CREATE TABLE IF NOT EXISTS sqlite_sequence(name,seq); DELETE FROM sqlite_sequence; @@ -444,7 +448,6 @@ INSERT INTO sqlite_sequence VALUES('ruby_llm_models',1000000000); INSERT INTO sqlite_sequence VALUES('ruby_llm_tool_calls',1000000000); INSERT INTO sqlite_sequence VALUES('ruby_llm_usages',1000000000); INSERT INTO sqlite_sequence VALUES('universes',1000000001); -INSERT INTO sqlite_sequence VALUES('characters',1000000003); INSERT INTO sqlite_sequence VALUES('chats',1000000000); INSERT INTO sqlite_sequence VALUES('interactions',1000000000); INSERT INTO sqlite_sequence VALUES('items',1000000004); @@ -479,6 +482,7 @@ INSERT INTO sqlite_sequence VALUES('stories',1000000001); INSERT INTO sqlite_sequence VALUES('system_one_receipts',1000000000); INSERT INTO sqlite_sequence VALUES('world_events',1000000000); INSERT INTO sqlite_sequence VALUES('world_mechanics',1000000000); +INSERT INTO sqlite_sequence VALUES('characters',1000000003); CREATE INDEX "index_characters_scenes_on_character_id_and_scene_id" ON "characters_scenes" ("character_id", "scene_id"); CREATE INDEX "index_characters_scenes_on_scene_id_and_character_id" ON "characters_scenes" ("scene_id", "character_id"); CREATE UNIQUE INDEX "index_players_on_name" ON "players" ("name"); @@ -499,13 +503,6 @@ CREATE INDEX "index_ruby_llm_usages_on_chat_type_and_chat_id" ON "ruby_llm_usage CREATE INDEX "index_ruby_llm_usages_on_message_type_and_message_id" ON "ruby_llm_usages" ("message_type", "message_id"); CREATE INDEX "index_ruby_llm_usages_on_status" ON "ruby_llm_usages" ("status"); CREATE UNIQUE INDEX "index_ruby_llm_v2_backfills_on_task" ON "ruby_llm_v2_backfills" ("task"); -CREATE UNIQUE INDEX "index_characters_on_story_id_and_lower_fullname" ON "characters" (story_id, LOWER(fullname)); -CREATE INDEX "index_characters_on_location_id_and_hostile" ON "characters" ("location_id", "hostile"); -CREATE INDEX "index_characters_on_location_id_and_id" ON "characters" ("location_id", "id"); -CREATE INDEX "index_characters_on_location_id" ON "characters" ("location_id"); -CREATE INDEX "index_characters_on_race_id" ON "characters" ("race_id"); -CREATE INDEX "index_characters_on_story_id_and_is_protagonist" ON "characters" ("story_id", "is_protagonist"); -CREATE INDEX "index_characters_on_story_id" ON "characters" ("story_id"); CREATE INDEX "index_chats_on_character_id" ON "chats" ("character_id"); CREATE INDEX "index_chats_on_player_id" ON "chats" ("player_id"); CREATE INDEX "index_chats_on_conversation_key" ON "chats" ("playthrough_id", "character_id", "purpose"); @@ -641,5 +638,13 @@ CREATE INDEX "index_playthrough_due_endings_on_quest_outcome_id" ON "playthrough CREATE INDEX "index_quest_steps_on_companion_id" ON "quest_steps" ("companion_id"); CREATE UNIQUE INDEX "index_floors_on_location_id" ON "floors" ("location_id"); CREATE INDEX "index_floors_on_down_location_id" ON "floors" ("down_location_id"); +CREATE UNIQUE INDEX "index_characters_on_story_id_and_lower_fullname" ON "characters" (story_id, LOWER(fullname)); +CREATE INDEX "index_characters_on_location_id_and_hostile" ON "characters" ("location_id", "hostile"); +CREATE INDEX "index_characters_on_location_id_and_id" ON "characters" ("location_id", "id"); +CREATE INDEX "index_characters_on_location_id" ON "characters" ("location_id"); +CREATE INDEX "index_characters_on_race_id" ON "characters" ("race_id"); +CREATE INDEX "index_characters_on_story_id_and_is_protagonist" ON "characters" ("story_id", "is_protagonist"); +CREATE INDEX "index_characters_on_story_id" ON "characters" ("story_id"); +CREATE INDEX "index_characters_on_posted_to_id" ON "characters" ("posted_to_id"); PRAGMA writable_schema=OFF; COMMIT; diff --git a/parity/worlds/the-iron-gate-descends.sql b/parity/worlds/the-iron-gate-descends.sql index 21e3504..4bb853e 100644 --- a/parity/worlds/the-iron-gate-descends.sql +++ b/parity/worlds/the-iron-gate-descends.sql @@ -12,20 +12,6 @@ CREATE TABLE IF NOT EXISTS "ruby_llm_usages" ("id" integer PRIMARY KEY AUTOINCRE CREATE TABLE IF NOT EXISTS "ruby_llm_v2_backfills" ("completed" boolean DEFAULT FALSE NOT NULL, "last_id" integer, "task" varchar NOT NULL); CREATE TABLE IF NOT EXISTS "universes" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "civilizations" text, "created_at" datetime(6) NOT NULL, "economics" text, "geographies" text, "history" text, "physics" text, "politics" text, "religion" text, "technology" text, "updated_at" datetime(6) NOT NULL, "weapons" text, "gravity" varchar, "carrying" varchar); INSERT INTO universes VALUES(1000000001,'The Kingdom of Durnhold is ruled by the Iron Council, a cabal of knights and priests who enforce the Church’s decrees from the Obsidian Throne in the capital, Spire’s Rest. The goblins are divided into warring clans, the largest being the Blackfang, who control the dungeon where the prince is held. The Trollkin have no settlements, only nomadic bands, while the Veythi operate from floating barges on the river, their only permanent structure being the Rust Market, a black-market hub hidden in the river’s mist. The uneasy peace is maintained by the Iron Council’s threat of annihilation and the goblins’ fear of open war, but raids and executions are common.','2026-10-02 13:26:05.985617','Durnhold’s wealth comes from iron and coal, mined by convicts in the Spire’s shadow. The Church taxes all trade, taking a third of all goods in the name of the Eternal Pyre. Goblins trade in stolen Durnish steel and rare fungi, which they sell to the Veythi for clockwork parts. The Veythi deal in information and forbidden tech, but their currency is blood—each transaction requires a drop from the buyer, stored in vials as proof of the exchange. Trollkin have no economy, surviving by hunting and scavenging.','The kingdom of Durnhold is a land of jagged iron cliffs and black pine forests, its heart dominated by the Ashen Spire, a dead volcano where the Church burns heretics. The goblin caves lie beneath the Spire’s roots, a labyrinth of obsidian tunnels lit by bioluminescent fungi that dim every 12 hours. The nearest town, Hollow’s End, is a day’s ride north, its walls built from the bones of ancient trolls. The river Veyth, wide and slow, marks the border between Durnhold and the goblin territories, its waters undrinkable due to high iron content.','A century ago, the Durnish waged the War of the Spire, driving the goblins underground and sealing the caves with iron gates. The Church then burned the last great library of the Veythi, accusing them of heresy, which forced the river-dwellers into secrecy. Twenty years past, the Iron Council executed the last Trollkin king, scattering his people. Now, the prince’s capture by the Blackfang clan has shattered the fragile truce, as the Council debates whether to negotiate or burn the caves to the ground.','Gravity is consistent at 9.8 m/s², but the world is veined with ley lines—thin, invisible rivers of latent magic that pulse at a frequency of 0.3 Hz, detectable only by iron-forged compasses. These lines can be tapped by blood rituals, but each use drains the caster’s lifespan by exactly one day per minute of magic. The ley lines are finite; overuse in a region causes them to fade for a century. No magic can resurrect the dead or heal mortal wounds, only delay them.','The Iron Council holds power through fear, their knights enforcing the Church’s will with fire and steel. The Blackfang goblins are the only clan bold enough to defy them, using the prince as leverage to demand the return of their sacred obsidian, stolen during the War of the Spire. The Veythi play both sides, selling secrets to the Council while smuggling weapons to the goblins. The Trollkin are caught in the middle, hunted by the Church but too few in number to resist. The prince’s rescue could tip the balance—if he lives, the Council may strike; if he dies, the goblins will have nothing to lose.','The Church of the Eternal Pyre preaches that the world is a forge, and all life must be tempered by suffering to be made pure. They believe the ley lines are the veins of a sleeping god, and that magic is theft from the divine. The goblins worship the Silent Maw, a god who demands blood and silence, believing that all noise is a lie. The Veythi have no gods, only the River, which they see as a force of change and decay. The Trollkin revere the First Maw, a primordial troll said to have birthed their race, but their rituals are secret, performed only in the dark.','The world is locked in a late medieval stasis, with iron and steel as the primary metals. Clockwork mechanisms exist but are rare, limited to devices no larger than a loaf of bread due to the scarcity of precision gears. Gunpowder is known but unstable, often exploding prematurely in 1 out of 5 attempts. Most advancements are suppressed by the Church of the Eternal Pyre, which burns any invention deemed heretical.','2026-10-02 13:26:05.985617','Swords, axes, and maces dominate, with the knightly class wielding blades of cold-forged Veldarian steel, which never rusts but shatters if struck by goblin-wrought obsidian. Goblins favor curved daggers coated in paralyzing venom, effective only if the wound draws blood. Crossbows are used by mercenaries but require 30 seconds to reload, making them impractical in close quarters. No ranged weapon can pierce the hide of a cave troll, which guards the dungeon’s depths.',NULL,NULL); -CREATE TABLE IF NOT EXISTS "characters" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "age" integer, "appearance" text, "backstory" text, "conscious_desire" text, "created_at" datetime(6) NOT NULL, "deliberately_absent" boolean DEFAULT FALSE NOT NULL, "desire_pursuit" varchar, "dexterity" integer, "dislikes" text, "fears" text, "fullname" varchar, "hit_die" integer, "hostile" boolean DEFAULT FALSE NOT NULL, "is_companion" boolean, "is_protagonist" boolean DEFAULT FALSE NOT NULL, "level" integer, "likes" text, "location_id" integer, "need_pursuit" varchar, "nickname" varchar, "personality" text, "race_id" integer NOT NULL, "recognized_need" text, "sex" varchar, "story_id" integer NOT NULL, "strength" integer, "unconscious_desire" text, "unrecognized_need" text, "updated_at" datetime(6) NOT NULL, "will" integer, "x" integer, "y" integer, CONSTRAINT "fk_rails_56a213cbe2" -FOREIGN KEY ("race_id") - REFERENCES "races" ("id") -, CONSTRAINT "fk_rails_0b4b445641" -FOREIGN KEY ("location_id") - REFERENCES "locations" ("id") -, CONSTRAINT "fk_rails_6608c248e8" -FOREIGN KEY ("story_id") - REFERENCES "stories" ("id") -); -INSERT INTO characters VALUES(1000000001,107,'Towering over most, Veythra’s gray fur is streaked with silver, a sign of her age, and her broad frame is draped in a tattered cloak woven from river reeds. Her tusks are filed to points, and her amber eyes glow faintly in the dark, a trait of her lineage. Scars crisscross her arms, each a story she refuses to tell.','Veythra was born on a Veythi barge, the only Trollkin child among river traders who saw her as an omen. Raised by an exiled Durnish knight turned mentor, she learned the ways of the wild and the weight of secrets. The Church’s hunts forced her into hiding, but she never forgot the debt she owed to the river for sparing her life. Now, she wanders the banks of the Veyth, tracking the movements of the Blackfang goblins, driven by a quiet vow to protect the last of her kind. The prince’s capture has drawn her attention, as the goblins’ boldness threatens the fragile balance she has spent a lifetime maintaining. She seeks not glory, but survival—for herself and the scattered remnants of the Trollkin.',NULL,'2026-10-02 13:26:06.326309',0,NULL,7,'fire, the Church’s hymns, the taste of cooked meat, being watched, iron chains','being trapped underground, the Church’s pyres, losing her sense of smell, the Silent Maw’s hunger','Veythra the Silent',8,0,0,1,1,'raw fish, the sound of flowing water, the scent of rain on stone, the weight of a well-balanced axe, solitude',NULL,NULL,'The River’s Shadow','Veythra is reserved and deliberate, speaking only when necessary and often in riddles. She carries a quiet intensity, observing the world with a hunter’s patience, though she treats others with a cautious respect born of survival. Her loyalty is rare but absolute once given.',1000000003,NULL,'female',1000000001,13,NULL,NULL,'2026-10-02 13:26:06.326309',10,NULL,NULL); -INSERT INTO characters VALUES(1000000002,54,'A tall Trollkin woman with weathered, grayish skin and deep-set, amber eyes. She wears a patched cloak of stitched hides and carries a notched axe at her belt. Her hair, streaked with white, is tied back in a loose braid.','Gorva was once part of a nomadic band, but age and injury left her behind. She now drifts between towns like Hollow’s End, trading odd jobs for coin or shelter. She knows the paths through the black pines better than most, and she has a grudging respect for the Iron Council’s reach.',NULL,'2026-10-02 13:26:06.347579',0,NULL,14,'fools, the Church, wasted time','fire, deep water','Gorva the Wanderer',6,0,0,0,1,'quiet, strong drink, sharp blades',1000000007,NULL,'Gorva','Gorva speaks little but watches everything, her voice a low rumble when she does. She treats strangers with cautious curiosity, though her patience wears thin quickly.',1000000003,NULL,'female',1000000001,10,NULL,NULL,'2026-10-02 13:26:06.347579',6,NULL,NULL); -INSERT INTO characters VALUES(1000000003,41,'A Veythi woman with dark, braided hair streaked with silver, her sharp features framed by a high-collared coat of river-worn leather. Her hands are calloused from years of hauling nets and ropes, and her eyes flicker with the restlessness of someone used to the water.','Lyssa was born on the barges of the Veythi but left the river life after a falling-out with her kin. Now she trades in Hollow’s End, smuggling goods between the town and the Rust Market hidden in the river’s mist. She knows the Iron Council’s patrols and the goblin raids better than anyone.',NULL,'2026-10-02 13:26:06.351914',0,NULL,7,'the Church, betrayal, cold nights','being trapped, the Spire’s shadow','Lyssa of the Mist',10,0,0,0,1,'bargaining, river stories, fine cloth',1000000007,NULL,'Lyssa','Lyssa is quick with a joke and quicker with a deal, her voice smooth as the river’s current. She sizes up strangers with a practiced eye, always weighing what they might be worth.',1000000004,NULL,'trans woman',1000000001,10,NULL,NULL,'2026-10-02 13:26:06.351914',9,NULL,NULL); -INSERT INTO characters VALUES(1000000004,19,'A tall Durnish youth gone gaunt, his ashen skin greyer still for a season without sun, and his dark hair cropped to the scalp where the goblins took it for their own uses. What is left of a doublet of Council blue hangs off him, rotted at the seams and stiff with old blood at the collar. His wrists carry two bright rings of scar from the chain, and the third finger of his left hand is scarred around the base where the signet was pulled over the knuckle. His eyes have adjusted to the fungi’s glow and water in any brighter light.','Aurel Durn is the last living son of the Iron Council’s ruling line, and until the spring he had never been further from the Obsidian Throne than the smelters at the Spire’s foot. He was taken on the north road to Hollow’s End with eleven men-at-arms around him, none of whom outlived the ambush; the Blackfang came out of the black pines without a sound, which is how the clan hunts, and cut the horses first. Somewhere outside the iron gate they wrenched the signet from his left hand, and he understood, watching it go into the mud, that they meant to be believed rather than paid. He has been in the dark since. The clan keeps him whole and keeps him fed on fungus-mash because a corpse buys nothing, and they have told him plainly what he is worth: the sacred obsidian the Durnish took in the War of the Spire, weighed out and returned. He counts the dimming of the fungi to keep the days, and he has counted enough of them to know the arithmetic the Church will do. Rescue is not what he expects. Being burned with the caves is.',NULL,'2026-10-02 13:26:06.357747',0,NULL,10,'fungus-mash, the Church’s arithmetic, being spoken to as a price, pity, torchlight held close to his face','the Council burning the caves with him inside them, the Silent Maw’s hunger, losing his count of the hours, hearing his own name used as a sum','Prince Aurel Durn',6,0,0,0,1,'the twelve-hour dimming of the fungi, which is his only clock, clean water, the sound of iron being worked, his mother’s salt-and-iron psalm, being told the truth about numbers',1000000014,NULL,'Aurel','Aurel is courteous in a way that has stopped costing him anything, and he has learned the goblin habit of silence well enough to make Durnish visitors uneasy. He listens first, at length, before he says a word, and what he says is usually a price or a warning. He does not beg and he does not thank; he bargains, even from the floor of a cell, because bargaining is the only office left to him and he was raised to hold one.',1000000001,NULL,'male',1000000001,8,NULL,NULL,'2026-10-02 13:26:06.357747',16,10,7); CREATE TABLE IF NOT EXISTS "chats" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "cancelled" boolean DEFAULT FALSE NOT NULL, "character_id" integer, "created_at" datetime(6) NOT NULL, "model_id_string" varchar, "player_id" integer, "playthrough_id" integer, "purpose" varchar, "ruby_llm_model_id" integer, "updated_at" datetime(6) NOT NULL, CONSTRAINT "fk_rails_415520c982" FOREIGN KEY ("playthrough_id") REFERENCES "playthroughs" ("id") @@ -475,6 +461,7 @@ INSERT INTO schema_migrations VALUES('20261025230000'); INSERT INTO schema_migrations VALUES('20261027120117'); INSERT INTO schema_migrations VALUES('20261030120000'); INSERT INTO schema_migrations VALUES('20261030200000'); +INSERT INTO schema_migrations VALUES('20261030230000'); CREATE TABLE IF NOT EXISTS "ar_internal_metadata" ("key" varchar NOT NULL PRIMARY KEY, "value" varchar, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL); INSERT INTO ar_internal_metadata VALUES('environment','test','2026-10-02 13:25:33.143313','2026-10-02 13:25:33.143316'); INSERT INTO ar_internal_metadata VALUES('schema_sha1','072abc2bfb80d111a1808df270e9210911f5e81d','2026-10-02 13:25:33.148224','2026-10-02 13:25:33.148228'); @@ -485,13 +472,30 @@ FOREIGN KEY ("playthrough_id") FOREIGN KEY ("quest_outcome_id") REFERENCES "quest_outcomes" ("id") ); -CREATE TABLE IF NOT EXISTS "floors" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "location_id" integer NOT NULL, "rooms" integer NOT NULL, "danger" varchar, "hazard" varchar, "down_location_id" integer, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, CONSTRAINT "fk_rails_ce8b005cb2" +CREATE TABLE IF NOT EXISTS "floors" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "location_id" integer NOT NULL, "rooms" integer NOT NULL, "danger" varchar, "hazard" varchar, "down_location_id" integer, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, "monster_level" integer, CONSTRAINT "fk_rails_ce8b005cb2" FOREIGN KEY ("location_id") REFERENCES "locations" ("id") ON DELETE CASCADE, CONSTRAINT "fk_rails_8a0268155a" FOREIGN KEY ("down_location_id") REFERENCES "locations" ("id") ON DELETE SET NULL); +CREATE TABLE IF NOT EXISTS "characters" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "age" integer, "appearance" text, "backstory" text, "conscious_desire" text, "created_at" datetime(6) NOT NULL, "deliberately_absent" boolean DEFAULT FALSE NOT NULL, "desire_pursuit" varchar, "dexterity" integer, "dislikes" text, "fears" text, "fullname" varchar, "hit_die" integer, "hostile" boolean DEFAULT FALSE NOT NULL, "is_companion" boolean, "is_protagonist" boolean DEFAULT FALSE NOT NULL, "level" integer, "likes" text, "location_id" integer, "need_pursuit" varchar, "nickname" varchar, "personality" text, "race_id" integer NOT NULL, "recognized_need" text, "sex" varchar, "story_id" integer NOT NULL, "strength" integer, "unconscious_desire" text, "unrecognized_need" text, "updated_at" datetime(6) NOT NULL, "will" integer, "x" integer, "y" integer, "posted_to_id" integer, CONSTRAINT "fk_rails_6608c248e8" +FOREIGN KEY ("story_id") + REFERENCES "stories" ("id") +, CONSTRAINT "fk_rails_0b4b445641" +FOREIGN KEY ("location_id") + REFERENCES "locations" ("id") +, CONSTRAINT "fk_rails_56a213cbe2" +FOREIGN KEY ("race_id") + REFERENCES "races" ("id") +, CONSTRAINT "fk_rails_4c54fc506e" +FOREIGN KEY ("posted_to_id") + REFERENCES "locations" ("id") + ON DELETE SET NULL); +INSERT INTO characters VALUES(1000000001,107,'Towering over most, Veythra’s gray fur is streaked with silver, a sign of her age, and her broad frame is draped in a tattered cloak woven from river reeds. Her tusks are filed to points, and her amber eyes glow faintly in the dark, a trait of her lineage. Scars crisscross her arms, each a story she refuses to tell.','Veythra was born on a Veythi barge, the only Trollkin child among river traders who saw her as an omen. Raised by an exiled Durnish knight turned mentor, she learned the ways of the wild and the weight of secrets. The Church’s hunts forced her into hiding, but she never forgot the debt she owed to the river for sparing her life. Now, she wanders the banks of the Veyth, tracking the movements of the Blackfang goblins, driven by a quiet vow to protect the last of her kind. The prince’s capture has drawn her attention, as the goblins’ boldness threatens the fragile balance she has spent a lifetime maintaining. She seeks not glory, but survival—for herself and the scattered remnants of the Trollkin.',NULL,'2026-10-02 13:26:06.326309',0,NULL,7,'fire, the Church’s hymns, the taste of cooked meat, being watched, iron chains','being trapped underground, the Church’s pyres, losing her sense of smell, the Silent Maw’s hunger','Veythra the Silent',8,0,0,1,1,'raw fish, the sound of flowing water, the scent of rain on stone, the weight of a well-balanced axe, solitude',NULL,NULL,'The River’s Shadow','Veythra is reserved and deliberate, speaking only when necessary and often in riddles. She carries a quiet intensity, observing the world with a hunter’s patience, though she treats others with a cautious respect born of survival. Her loyalty is rare but absolute once given.',1000000003,NULL,'female',1000000001,13,NULL,NULL,'2026-10-02 13:26:06.326309',10,NULL,NULL,NULL); +INSERT INTO characters VALUES(1000000002,54,'A tall Trollkin woman with weathered, grayish skin and deep-set, amber eyes. She wears a patched cloak of stitched hides and carries a notched axe at her belt. Her hair, streaked with white, is tied back in a loose braid.','Gorva was once part of a nomadic band, but age and injury left her behind. She now drifts between towns like Hollow’s End, trading odd jobs for coin or shelter. She knows the paths through the black pines better than most, and she has a grudging respect for the Iron Council’s reach.',NULL,'2026-10-02 13:26:06.347579',0,NULL,14,'fools, the Church, wasted time','fire, deep water','Gorva the Wanderer',6,0,0,0,1,'quiet, strong drink, sharp blades',1000000007,NULL,'Gorva','Gorva speaks little but watches everything, her voice a low rumble when she does. She treats strangers with cautious curiosity, though her patience wears thin quickly.',1000000003,NULL,'female',1000000001,10,NULL,NULL,'2026-10-02 13:26:06.347579',6,NULL,NULL,NULL); +INSERT INTO characters VALUES(1000000003,41,'A Veythi woman with dark, braided hair streaked with silver, her sharp features framed by a high-collared coat of river-worn leather. Her hands are calloused from years of hauling nets and ropes, and her eyes flicker with the restlessness of someone used to the water.','Lyssa was born on the barges of the Veythi but left the river life after a falling-out with her kin. Now she trades in Hollow’s End, smuggling goods between the town and the Rust Market hidden in the river’s mist. She knows the Iron Council’s patrols and the goblin raids better than anyone.',NULL,'2026-10-02 13:26:06.351914',0,NULL,7,'the Church, betrayal, cold nights','being trapped, the Spire’s shadow','Lyssa of the Mist',10,0,0,0,1,'bargaining, river stories, fine cloth',1000000007,NULL,'Lyssa','Lyssa is quick with a joke and quicker with a deal, her voice smooth as the river’s current. She sizes up strangers with a practiced eye, always weighing what they might be worth.',1000000004,NULL,'trans woman',1000000001,10,NULL,NULL,'2026-10-02 13:26:06.351914',9,NULL,NULL,NULL); +INSERT INTO characters VALUES(1000000004,19,'A tall Durnish youth gone gaunt, his ashen skin greyer still for a season without sun, and his dark hair cropped to the scalp where the goblins took it for their own uses. What is left of a doublet of Council blue hangs off him, rotted at the seams and stiff with old blood at the collar. His wrists carry two bright rings of scar from the chain, and the third finger of his left hand is scarred around the base where the signet was pulled over the knuckle. His eyes have adjusted to the fungi’s glow and water in any brighter light.','Aurel Durn is the last living son of the Iron Council’s ruling line, and until the spring he had never been further from the Obsidian Throne than the smelters at the Spire’s foot. He was taken on the north road to Hollow’s End with eleven men-at-arms around him, none of whom outlived the ambush; the Blackfang came out of the black pines without a sound, which is how the clan hunts, and cut the horses first. Somewhere outside the iron gate they wrenched the signet from his left hand, and he understood, watching it go into the mud, that they meant to be believed rather than paid. He has been in the dark since. The clan keeps him whole and keeps him fed on fungus-mash because a corpse buys nothing, and they have told him plainly what he is worth: the sacred obsidian the Durnish took in the War of the Spire, weighed out and returned. He counts the dimming of the fungi to keep the days, and he has counted enough of them to know the arithmetic the Church will do. Rescue is not what he expects. Being burned with the caves is.',NULL,'2026-10-02 13:26:06.357747',0,NULL,10,'fungus-mash, the Church’s arithmetic, being spoken to as a price, pity, torchlight held close to his face','the Council burning the caves with him inside them, the Silent Maw’s hunger, losing his count of the hours, hearing his own name used as a sum','Prince Aurel Durn',6,0,0,0,1,'the twelve-hour dimming of the fungi, which is his only clock, clean water, the sound of iron being worked, his mother’s salt-and-iron psalm, being told the truth about numbers',1000000014,NULL,'Aurel','Aurel is courteous in a way that has stopped costing him anything, and he has learned the goblin habit of silence well enough to make Durnish visitors uneasy. He listens first, at length, before he says a word, and what he says is usually a price or a warning. He does not beg and he does not thank; he bargains, even from the floor of a cell, because bargaining is the only office left to him and he was raised to hold one.',1000000001,NULL,'male',1000000001,8,NULL,NULL,'2026-10-02 13:26:06.357747',16,10,7,NULL); PRAGMA writable_schema=ON; CREATE TABLE IF NOT EXISTS sqlite_sequence(name,seq); DELETE FROM sqlite_sequence; @@ -504,7 +508,6 @@ INSERT INTO sqlite_sequence VALUES('ruby_llm_models',1000000000); INSERT INTO sqlite_sequence VALUES('ruby_llm_tool_calls',1000000000); INSERT INTO sqlite_sequence VALUES('ruby_llm_usages',1000000000); INSERT INTO sqlite_sequence VALUES('universes',1000000001); -INSERT INTO sqlite_sequence VALUES('characters',1000000004); INSERT INTO sqlite_sequence VALUES('chats',1000000000); INSERT INTO sqlite_sequence VALUES('interactions',1000000000); INSERT INTO sqlite_sequence VALUES('items',1000000012); @@ -539,6 +542,7 @@ INSERT INTO sqlite_sequence VALUES('stories',1000000001); INSERT INTO sqlite_sequence VALUES('system_one_receipts',1000000000); INSERT INTO sqlite_sequence VALUES('world_events',1000000000); INSERT INTO sqlite_sequence VALUES('world_mechanics',1000000000); +INSERT INTO sqlite_sequence VALUES('characters',1000000004); CREATE INDEX "index_characters_scenes_on_character_id_and_scene_id" ON "characters_scenes" ("character_id", "scene_id"); CREATE INDEX "index_characters_scenes_on_scene_id_and_character_id" ON "characters_scenes" ("scene_id", "character_id"); CREATE UNIQUE INDEX "index_players_on_name" ON "players" ("name"); @@ -559,13 +563,6 @@ CREATE INDEX "index_ruby_llm_usages_on_chat_type_and_chat_id" ON "ruby_llm_usage CREATE INDEX "index_ruby_llm_usages_on_message_type_and_message_id" ON "ruby_llm_usages" ("message_type", "message_id"); CREATE INDEX "index_ruby_llm_usages_on_status" ON "ruby_llm_usages" ("status"); CREATE UNIQUE INDEX "index_ruby_llm_v2_backfills_on_task" ON "ruby_llm_v2_backfills" ("task"); -CREATE UNIQUE INDEX "index_characters_on_story_id_and_lower_fullname" ON "characters" (story_id, LOWER(fullname)); -CREATE INDEX "index_characters_on_location_id_and_hostile" ON "characters" ("location_id", "hostile"); -CREATE INDEX "index_characters_on_location_id_and_id" ON "characters" ("location_id", "id"); -CREATE INDEX "index_characters_on_location_id" ON "characters" ("location_id"); -CREATE INDEX "index_characters_on_race_id" ON "characters" ("race_id"); -CREATE INDEX "index_characters_on_story_id_and_is_protagonist" ON "characters" ("story_id", "is_protagonist"); -CREATE INDEX "index_characters_on_story_id" ON "characters" ("story_id"); CREATE INDEX "index_chats_on_character_id" ON "chats" ("character_id"); CREATE INDEX "index_chats_on_player_id" ON "chats" ("player_id"); CREATE INDEX "index_chats_on_conversation_key" ON "chats" ("playthrough_id", "character_id", "purpose"); @@ -701,5 +698,13 @@ CREATE INDEX "index_playthrough_due_endings_on_quest_outcome_id" ON "playthrough CREATE INDEX "index_quest_steps_on_companion_id" ON "quest_steps" ("companion_id"); CREATE UNIQUE INDEX "index_floors_on_location_id" ON "floors" ("location_id"); CREATE INDEX "index_floors_on_down_location_id" ON "floors" ("down_location_id"); +CREATE UNIQUE INDEX "index_characters_on_story_id_and_lower_fullname" ON "characters" (story_id, LOWER(fullname)); +CREATE INDEX "index_characters_on_location_id_and_hostile" ON "characters" ("location_id", "hostile"); +CREATE INDEX "index_characters_on_location_id_and_id" ON "characters" ("location_id", "id"); +CREATE INDEX "index_characters_on_location_id" ON "characters" ("location_id"); +CREATE INDEX "index_characters_on_race_id" ON "characters" ("race_id"); +CREATE INDEX "index_characters_on_story_id_and_is_protagonist" ON "characters" ("story_id", "is_protagonist"); +CREATE INDEX "index_characters_on_story_id" ON "characters" ("story_id"); +CREATE INDEX "index_characters_on_posted_to_id" ON "characters" ("posted_to_id"); PRAGMA writable_schema=OFF; COMMIT; diff --git a/parity/worlds/the-landing-stair.sql b/parity/worlds/the-landing-stair.sql index cb86110..26b7741 100644 --- a/parity/worlds/the-landing-stair.sql +++ b/parity/worlds/the-landing-stair.sql @@ -13,17 +13,6 @@ CREATE TABLE IF NOT EXISTS "ruby_llm_usages" ("id" integer PRIMARY KEY AUTOINCRE CREATE TABLE IF NOT EXISTS "ruby_llm_v2_backfills" ("completed" boolean DEFAULT FALSE NOT NULL, "last_id" integer, "task" varchar NOT NULL); CREATE TABLE IF NOT EXISTS "universes" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "civilizations" text, "created_at" datetime(6) NOT NULL, "economics" text, "geographies" text, "history" text, "physics" text, "politics" text, "religion" text, "technology" text, "updated_at" datetime(6) NOT NULL, "weapons" text, "gravity" varchar, "carrying" varchar); INSERT INTO universes VALUES(1000000001,'A port that has never been anything else.','2026-10-02 13:26:13.008098','Paid in salt weight, settled in coin when the assessor is watching.','One quay, one stair house standing on it, and the water at the end of both.','Three hundred years of the same tide.','Ordinary physics, and a tide that keeps to its table.','The harbourmaster, and whoever is owed money by the harbourmaster.','The tide table, read aloud.','Rope, tar, lamp oil and a ledger.','2026-10-02 13:26:13.008098','A gaff hook, and whatever is nearest the door.',NULL,NULL); -CREATE TABLE IF NOT EXISTS "characters" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "age" integer, "appearance" text, "backstory" text, "conscious_desire" text, "created_at" datetime(6) NOT NULL, "deliberately_absent" boolean DEFAULT FALSE NOT NULL, "desire_pursuit" varchar, "dexterity" integer, "dislikes" text, "fears" text, "fullname" varchar, "hit_die" integer, "hostile" boolean DEFAULT FALSE NOT NULL, "is_companion" boolean, "is_protagonist" boolean DEFAULT FALSE NOT NULL, "level" integer, "likes" text, "location_id" integer, "need_pursuit" varchar, "nickname" varchar, "personality" text, "race_id" integer NOT NULL, "recognized_need" text, "sex" varchar, "story_id" integer NOT NULL, "strength" integer, "unconscious_desire" text, "unrecognized_need" text, "updated_at" datetime(6) NOT NULL, "will" integer, "x" integer, "y" integer, CONSTRAINT "fk_rails_56a213cbe2" -FOREIGN KEY ("race_id") - REFERENCES "races" ("id") -, CONSTRAINT "fk_rails_0b4b445641" -FOREIGN KEY ("location_id") - REFERENCES "locations" ("id") -, CONSTRAINT "fk_rails_6608c248e8" -FOREIGN KEY ("story_id") - REFERENCES "stories" ("id") -); -INSERT INTO characters VALUES(1000000001,38,'Salt at the cuff, whatever she is wearing.','Kept the quay''s books for eleven years without once being asked to.',NULL,'2026-10-02 13:26:13.280276',0,NULL,13,'A door found open','Being the last one to know','Nell Cawsand',8,0,0,1,3,'A tide that comes in on the table',1000000001,NULL,'Nell','Unhurried, and counts things twice.',1000000001,NULL,'female',1000000001,11,NULL,NULL,'2026-10-02 13:26:13.280276',12,NULL,NULL); CREATE TABLE IF NOT EXISTS "chats" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "cancelled" boolean DEFAULT FALSE NOT NULL, "character_id" integer, "created_at" datetime(6) NOT NULL, "model_id_string" varchar, "player_id" integer, "playthrough_id" integer, "purpose" varchar, "ruby_llm_model_id" integer, "updated_at" datetime(6) NOT NULL, CONSTRAINT "fk_rails_415520c982" FOREIGN KEY ("playthrough_id") REFERENCES "playthroughs" ("id") @@ -418,6 +407,7 @@ INSERT INTO schema_migrations VALUES('20261025230000'); INSERT INTO schema_migrations VALUES('20261027120117'); INSERT INTO schema_migrations VALUES('20261030120000'); INSERT INTO schema_migrations VALUES('20261030200000'); +INSERT INTO schema_migrations VALUES('20261030230000'); CREATE TABLE IF NOT EXISTS "ar_internal_metadata" ("key" varchar NOT NULL PRIMARY KEY, "value" varchar, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL); INSERT INTO ar_internal_metadata VALUES('environment','test','2026-10-02 13:25:33.143313','2026-10-02 13:25:33.143316'); INSERT INTO ar_internal_metadata VALUES('schema_sha1','072abc2bfb80d111a1808df270e9210911f5e81d','2026-10-02 13:25:33.148224','2026-10-02 13:25:33.148228'); @@ -428,13 +418,27 @@ FOREIGN KEY ("playthrough_id") FOREIGN KEY ("quest_outcome_id") REFERENCES "quest_outcomes" ("id") ); -CREATE TABLE IF NOT EXISTS "floors" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "location_id" integer NOT NULL, "rooms" integer NOT NULL, "danger" varchar, "hazard" varchar, "down_location_id" integer, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, CONSTRAINT "fk_rails_ce8b005cb2" +CREATE TABLE IF NOT EXISTS "floors" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "location_id" integer NOT NULL, "rooms" integer NOT NULL, "danger" varchar, "hazard" varchar, "down_location_id" integer, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, "monster_level" integer, CONSTRAINT "fk_rails_ce8b005cb2" FOREIGN KEY ("location_id") REFERENCES "locations" ("id") ON DELETE CASCADE, CONSTRAINT "fk_rails_8a0268155a" FOREIGN KEY ("down_location_id") REFERENCES "locations" ("id") ON DELETE SET NULL); +CREATE TABLE IF NOT EXISTS "characters" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "age" integer, "appearance" text, "backstory" text, "conscious_desire" text, "created_at" datetime(6) NOT NULL, "deliberately_absent" boolean DEFAULT FALSE NOT NULL, "desire_pursuit" varchar, "dexterity" integer, "dislikes" text, "fears" text, "fullname" varchar, "hit_die" integer, "hostile" boolean DEFAULT FALSE NOT NULL, "is_companion" boolean, "is_protagonist" boolean DEFAULT FALSE NOT NULL, "level" integer, "likes" text, "location_id" integer, "need_pursuit" varchar, "nickname" varchar, "personality" text, "race_id" integer NOT NULL, "recognized_need" text, "sex" varchar, "story_id" integer NOT NULL, "strength" integer, "unconscious_desire" text, "unrecognized_need" text, "updated_at" datetime(6) NOT NULL, "will" integer, "x" integer, "y" integer, "posted_to_id" integer, CONSTRAINT "fk_rails_6608c248e8" +FOREIGN KEY ("story_id") + REFERENCES "stories" ("id") +, CONSTRAINT "fk_rails_0b4b445641" +FOREIGN KEY ("location_id") + REFERENCES "locations" ("id") +, CONSTRAINT "fk_rails_56a213cbe2" +FOREIGN KEY ("race_id") + REFERENCES "races" ("id") +, CONSTRAINT "fk_rails_4c54fc506e" +FOREIGN KEY ("posted_to_id") + REFERENCES "locations" ("id") + ON DELETE SET NULL); +INSERT INTO characters VALUES(1000000001,38,'Salt at the cuff, whatever she is wearing.','Kept the quay''s books for eleven years without once being asked to.',NULL,'2026-10-02 13:26:13.280276',0,NULL,13,'A door found open','Being the last one to know','Nell Cawsand',8,0,0,1,3,'A tide that comes in on the table',1000000001,NULL,'Nell','Unhurried, and counts things twice.',1000000001,NULL,'female',1000000001,11,NULL,NULL,'2026-10-02 13:26:13.280276',12,NULL,NULL,NULL); PRAGMA writable_schema=ON; CREATE TABLE IF NOT EXISTS sqlite_sequence(name,seq); DELETE FROM sqlite_sequence; @@ -447,7 +451,6 @@ INSERT INTO sqlite_sequence VALUES('ruby_llm_models',1000000000); INSERT INTO sqlite_sequence VALUES('ruby_llm_tool_calls',1000000000); INSERT INTO sqlite_sequence VALUES('ruby_llm_usages',1000000000); INSERT INTO sqlite_sequence VALUES('universes',1000000001); -INSERT INTO sqlite_sequence VALUES('characters',1000000001); INSERT INTO sqlite_sequence VALUES('chats',1000000000); INSERT INTO sqlite_sequence VALUES('interactions',1000000000); INSERT INTO sqlite_sequence VALUES('items',1000000000); @@ -482,6 +485,7 @@ INSERT INTO sqlite_sequence VALUES('stories',1000000001); INSERT INTO sqlite_sequence VALUES('system_one_receipts',1000000000); INSERT INTO sqlite_sequence VALUES('world_events',1000000000); INSERT INTO sqlite_sequence VALUES('world_mechanics',1000000000); +INSERT INTO sqlite_sequence VALUES('characters',1000000001); CREATE INDEX "index_characters_scenes_on_character_id_and_scene_id" ON "characters_scenes" ("character_id", "scene_id"); CREATE INDEX "index_characters_scenes_on_scene_id_and_character_id" ON "characters_scenes" ("scene_id", "character_id"); CREATE UNIQUE INDEX "index_players_on_name" ON "players" ("name"); @@ -502,13 +506,6 @@ CREATE INDEX "index_ruby_llm_usages_on_chat_type_and_chat_id" ON "ruby_llm_usage CREATE INDEX "index_ruby_llm_usages_on_message_type_and_message_id" ON "ruby_llm_usages" ("message_type", "message_id"); CREATE INDEX "index_ruby_llm_usages_on_status" ON "ruby_llm_usages" ("status"); CREATE UNIQUE INDEX "index_ruby_llm_v2_backfills_on_task" ON "ruby_llm_v2_backfills" ("task"); -CREATE UNIQUE INDEX "index_characters_on_story_id_and_lower_fullname" ON "characters" (story_id, LOWER(fullname)); -CREATE INDEX "index_characters_on_location_id_and_hostile" ON "characters" ("location_id", "hostile"); -CREATE INDEX "index_characters_on_location_id_and_id" ON "characters" ("location_id", "id"); -CREATE INDEX "index_characters_on_location_id" ON "characters" ("location_id"); -CREATE INDEX "index_characters_on_race_id" ON "characters" ("race_id"); -CREATE INDEX "index_characters_on_story_id_and_is_protagonist" ON "characters" ("story_id", "is_protagonist"); -CREATE INDEX "index_characters_on_story_id" ON "characters" ("story_id"); CREATE INDEX "index_chats_on_character_id" ON "chats" ("character_id"); CREATE INDEX "index_chats_on_player_id" ON "chats" ("player_id"); CREATE INDEX "index_chats_on_conversation_key" ON "chats" ("playthrough_id", "character_id", "purpose"); @@ -644,5 +641,13 @@ CREATE INDEX "index_playthrough_due_endings_on_quest_outcome_id" ON "playthrough CREATE INDEX "index_quest_steps_on_companion_id" ON "quest_steps" ("companion_id"); CREATE UNIQUE INDEX "index_floors_on_location_id" ON "floors" ("location_id"); CREATE INDEX "index_floors_on_down_location_id" ON "floors" ("down_location_id"); +CREATE UNIQUE INDEX "index_characters_on_story_id_and_lower_fullname" ON "characters" (story_id, LOWER(fullname)); +CREATE INDEX "index_characters_on_location_id_and_hostile" ON "characters" ("location_id", "hostile"); +CREATE INDEX "index_characters_on_location_id_and_id" ON "characters" ("location_id", "id"); +CREATE INDEX "index_characters_on_location_id" ON "characters" ("location_id"); +CREATE INDEX "index_characters_on_race_id" ON "characters" ("race_id"); +CREATE INDEX "index_characters_on_story_id_and_is_protagonist" ON "characters" ("story_id", "is_protagonist"); +CREATE INDEX "index_characters_on_story_id" ON "characters" ("story_id"); +CREATE INDEX "index_characters_on_posted_to_id" ON "characters" ("posted_to_id"); PRAGMA writable_schema=OFF; COMMIT; diff --git a/parity/worlds/the-lunar-cartographer.sql b/parity/worlds/the-lunar-cartographer.sql index 81129b1..5c993ef 100644 --- a/parity/worlds/the-lunar-cartographer.sql +++ b/parity/worlds/the-lunar-cartographer.sql @@ -14,19 +14,6 @@ CREATE TABLE IF NOT EXISTS "ruby_llm_usages" ("id" integer PRIMARY KEY AUTOINCRE CREATE TABLE IF NOT EXISTS "ruby_llm_v2_backfills" ("task" varchar NOT NULL, "last_id" integer, "completed" boolean DEFAULT FALSE NOT NULL); CREATE TABLE IF NOT EXISTS "universes" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "physics" text, "technology" text, "weapons" text, "civilizations" text, "geographies" text, "history" text, "economics" text, "politics" text, "religion" text, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, "gravity" varchar, "carrying" varchar); INSERT INTO universes VALUES(1000000001,'This world adheres to standard physical laws, with one exception: the city of Nocturnis, built on the ruins of an ancient war, rearranges itself every night due to the residual magical energy from the Great War. This energy, called Nocturna, is harnessed from the moon and causes buildings, streets, and landmarks to shift and change. The effects of Nocturna are limited to the city boundaries, and its influence wanes during the day, allowing the city to remain static until nightfall. Nocturna does not affect living beings directly, but prolonged exposure can cause disorientation and memory loss. The city''s rearrangement follows certain patterns, often influenced by the phases of the moon, but these patterns are complex and not fully understood.','Technology in this world is a blend of pre-war remnants and post-war innovations. The Great War, which ended 75 years ago, left behind advanced but often broken or dangerous technology. The current society has managed to salvage and repurpose some of this tech, but much of it remains beyond their understanding. Most people rely on steam-powered machinery and simple electronics. The city of Nocturnis has a unique technology called Lunar Compasses, which are devices that can predict the city''s nightly rearrangements with about 60% accuracy. These compasses are highly sought after but require rare moonstones to function.','Weapons in this world are a mix of traditional and advanced. Most common are steam-powered firearms, which require a special type of ammunition infused with a small amount of Nocturna to function. Swords and other melee weapons are also common, often made from salvaged pre-war materials. The most feared weapons are the remnants of the Great War, such as the Arcane Cannons, which can harness Nocturna to devastating effect. However, these weapons are rare and closely guarded by the city''s ruling council, the Lunar Sovereigns. Carrying weapons within the city limits is legal but regulated, and only licensed individuals can carry firearms.','The city of Nocturnis is the center of civilization, ruled by the Lunar Sovereigns who control the supply of moonstones and advanced technology. The Sovereigns live in the opulent Sovereign''s Circle, while the rest of the population resides in the ever-changing districts. The Scorch Nomads roam the wasteland, trading scavenged pre-war technology and resources with the Verdant Folk and Crystal Dwarves. The Verdant Folk live in harmony with nature in the Verdant Expanse, while the Crystal Dwarves mine moonstones in the Crystal Peaks. Tensions exist between the Lunar Sovereigns and the other factions due to the Sovereigns'' control of resources and technology, as well as their secretive and hierarchical nature.','The world is dominated by the ever-changing city of Nocturnis, surrounded by a vast, desolate landscape known as the Scorch. The Scorch is a reminder of the Great War, a wasteland where little life thrives. To the north lies the Crystal Peaks, a mountain range rich in moonstones. To the east, the Verdant Expanse is a vast forest that has begun to reclaim the land. Within Nocturnis, notable landmarks include the Ever-Shifting Bazaar, a marketplace that changes location and layout every night, and the Celestial Spire, a tower that remains static despite the city''s rearrangements. The city is divided into districts, each with its unique characteristics, such as the Industrial Quarter, the Residential Loop, and the Sovereign''s Circle.','The Great War, which ended 75 years ago, was a cataclysmic event that reshaped the world. It was fought between the ancient precursors to the Lunar Sovereigns and an alliance of other races over control of Nocturna and advanced technology. The war resulted in the devastation of the landscape, now known as the Scorch, and the creation of the ever-changing city of Nocturnis. In the aftermath, the Lunar Sovereigns seized control of the city and its resources, leading to the current societal structure and tensions.','The economy of this world revolves around the trade of moonstones, technology, and resources. The Lunar Sovereigns control the supply of moonstones, which are essential for powering advanced technology and Lunar Compasses. The Crystal Dwarves mine these moonstones and trade them for resources with the other factions. The Scorch Nomads scavenge pre-war technology and resources from the Scorch, trading them with the Verdant Folk for food and other necessities. The Verdant Folk, in turn, trade with the other factions for technology and resources they cannot produce themselves.','The Lunar Sovereigns hold the most power, controlling access to moonstones and advanced technology. Their rule is absolute within Nocturnis, but their influence wanes in the outer regions. The Scorch Nomads and Verdant Folk have a tense but mutually beneficial relationship, with the Nomads providing technology and the Verdant Folk providing food and shelter. The Crystal Dwarves maintain a neutral stance, focusing on their mining operations and trade agreements. The primary conflict lies between the Lunar Sovereigns and the other factions, who resent the Sovereigns'' control of resources and technology.','Religion in this world is varied and often tied to the unique aspects of each faction. The Lunar Sovereigns worship the moon and Nocturna, believing themselves to be chosen by its power. The Scorch Nomads revere the ancient pre-war technology, seeing it as a reminder of a lost golden age. The Verdant Folk practice a form of animism, believing in the spirits of nature and the interconnectedness of all living things. The Crystal Dwarves worship the mountains and the earth, seeing themselves as stewards of its resources. Religious tensions exist, particularly between the Lunar Sovereigns and the other factions, who see the Sovereigns'' beliefs as justification for their control and oppression.','2026-10-29 02:09:12.903247','2026-10-29 02:09:12.903247',NULL,NULL); -CREATE TABLE IF NOT EXISTS "characters" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "story_id" integer NOT NULL, "fullname" varchar, "nickname" varchar, "age" integer, "sex" varchar, "personality" text, "appearance" text, "likes" text, "dislikes" text, "fears" text, "backstory" text, "is_companion" boolean, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, "race_id" integer NOT NULL, "is_protagonist" boolean DEFAULT FALSE NOT NULL, "location_id" integer, "deliberately_absent" boolean DEFAULT FALSE NOT NULL, "level" integer, "hit_die" integer, "strength" integer, "dexterity" integer, "will" integer, "hostile" boolean DEFAULT FALSE NOT NULL, "x" integer, "y" integer, "conscious_desire" text, "unconscious_desire" text, "recognized_need" text, "unrecognized_need" text, "desire_pursuit" varchar, "need_pursuit" varchar, CONSTRAINT "fk_rails_56a213cbe2" -FOREIGN KEY ("race_id") - REFERENCES "races" ("id") -, CONSTRAINT "fk_rails_0b4b445641" -FOREIGN KEY ("location_id") - REFERENCES "locations" ("id") -, CONSTRAINT "fk_rails_6608c248e8" -FOREIGN KEY ("story_id") - REFERENCES "stories" ("id") -); -INSERT INTO characters VALUES(1000000001,1000000001,'Isbet Marrow','Iz',34,'female','Methodical to the point of rudeness. She would rather finish a bearing than finish a sentence, and she trusts a measurement she took herself over anything a Sovereign puts in writing. Dry, patient, slow to anger, and incapable of forgetting a debt in either direction.','Weathered dark skin, a nomad''s squint, and charcoal on the side of every finger. Her hair is cropped short and going grey at one temple only.','Fresh bearings, strong tea, the hour before the city moves','Sovereign paperwork, being told a street has always been there','The memory loss. She has started writing down what she did yesterday.','Born in a Scorch caravan that made its living selling bearings and salvaged road-signs to a city which rearranged itself out from under them every night. She walked into Nocturnis at nineteen with a stolen theodolite and a talent nobody in the caravan had a word for: she can look at a street and tell where it has been. The Lunar Sovereigns hired her four years ago to map the nightly rearrangements, pay her badly, and deny in public that the survey exists at all. She has been behind on rent for most of that time.',0,'2026-10-29 02:09:13.143141','2026-10-29 02:09:13.143141',1000000004,1,NULL,0,3,8,11,14,13,0,NULL,NULL,'Isbet Marrow wants to reach a complete map of the city''s nightly rearrangements.','Isbet Marrow wants to keep the one measurement that proves the Sovereigns are lying.','Isbet Marrow needs to keep her survey notes in order across every night.','Isbet Marrow needs to reach the memory of what she has begun to lose.','reach','keep'); -INSERT INTO characters VALUES(1000000002,1000000001,'Grenn Ollivar','Old Grenn',61,'male','Loud, aggrieved, and fundamentally decent under about two inches of grievance. He threatens eviction roughly weekly and has carried one out once in thirty years. He would very much like to be asked about the house.','Heavyset, pale even for a Sovereign, silver hair gone thin on top, and a cough he blames on the plaster dust. He carries a ledger under one arm at all hours, including the hours when nobody is awake to owe him anything.','His own foundations, being right about the plaster, a settled ledger','The Sovereign''s Circle, tenants who talk to the walls','That the house will crack after all, and that his family was right','A Sovereign family''s third son, bought out of his inheritance at thirty for a sum that covered exactly one boarding house in the Larkspur Quarter. He has spent the thirty years since laying his foundation stones loose so the building survives the shifting, and reminding anyone who will hold still that he owns it outright. He does not go up to the third floor after dark.',0,'2026-10-29 02:09:13.198081','2026-10-29 02:09:13.231377',1000000002,0,1000000001,0,1,6,9,7,12,0,0,3,'Grenn Ollivar wants to obtain the overdue rent before the Bell of Saint Aravel rings again.','Grenn Ollivar wants to reach someone who will ask him about the foundations.','Grenn Ollivar needs to keep his boarding-house ledger settled.','Grenn Ollivar needs to reach the third floor after dark and inspect his house.','obtain','reach'); -INSERT INTO characters VALUES(1000000003,1000000001,'Marek Sollen','the Ringer',44,'male','Nothing decides anything up there any more. What is left is habit and a terrible steadiness: he climbs, he waits, he pulls, and anything in the chamber that still answers to a name is something he goes for without hurrying and without stopping.','A big man gone grey all the way through, skin and coat and eyes the same lamp-lit nothing, standing straighter than a living man stands. His hands are still a ringer''s hands. He does not blink and he does not look away.','The hour. The rope. The weight coming back up the line','Anything that says a name out loud in his tower','Nothing at all, and that is the whole of what is wrong with him','He rang the eleven o''clock for twenty-nine years, which meant twenty-nine years of standing in the one tower the city cannot move while the rest of it went past underneath him. Nobody told him what that does. The parish stopped seeing him some while before it stopped hearing him, and by then he had already forgotten his sister''s name, then his own, then what the rope was for -- though he still pulls it, on the hour, more or less.',0,'2026-10-29 02:09:13.201303','2026-10-29 02:09:13.271925',1000000003,0,1000000007,0,1,10,14,8,3,1,3,7,'Marek Sollen wants to keep the bell ringing at the appointed hour.','Marek Sollen wants to avoid every name spoken in the tower.','Marek Sollen needs to attend the rope until the city hears its hour.','Marek Sollen needs to reach the person he was before the bell took his name.','keep','avoid'); CREATE TABLE IF NOT EXISTS "chats" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "model_id_string" varchar, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, "ruby_llm_model_id" integer, "purpose" varchar, "playthrough_id" integer, "character_id" integer, "cancelled" boolean DEFAULT FALSE NOT NULL, "player_id" integer, CONSTRAINT "fk_rails_415520c982" FOREIGN KEY ("playthrough_id") REFERENCES "playthroughs" ("id") @@ -446,16 +433,33 @@ INSERT INTO schema_migrations VALUES('20250822184650'); INSERT INTO schema_migrations VALUES('20250822184649'); INSERT INTO schema_migrations VALUES('20261030120000'); INSERT INTO schema_migrations VALUES('20261030200000'); +INSERT INTO schema_migrations VALUES('20261030230000'); CREATE TABLE IF NOT EXISTS "ar_internal_metadata" ("key" varchar NOT NULL PRIMARY KEY, "value" varchar, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL); INSERT INTO ar_internal_metadata VALUES('environment','test','2026-10-29 02:06:59.131845','2026-10-29 02:06:59.131848'); INSERT INTO ar_internal_metadata VALUES('schema_sha1','9ea7d9a7496841168065535777683ed7c64300e0','2026-10-29 02:06:59.136646','2026-10-29 02:06:59.136649'); -CREATE TABLE IF NOT EXISTS "floors" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "location_id" integer NOT NULL, "rooms" integer NOT NULL, "danger" varchar, "hazard" varchar, "down_location_id" integer, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, CONSTRAINT "fk_rails_ce8b005cb2" +CREATE TABLE IF NOT EXISTS "floors" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "location_id" integer NOT NULL, "rooms" integer NOT NULL, "danger" varchar, "hazard" varchar, "down_location_id" integer, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, "monster_level" integer, CONSTRAINT "fk_rails_ce8b005cb2" FOREIGN KEY ("location_id") REFERENCES "locations" ("id") ON DELETE CASCADE, CONSTRAINT "fk_rails_8a0268155a" FOREIGN KEY ("down_location_id") REFERENCES "locations" ("id") ON DELETE SET NULL); +CREATE TABLE IF NOT EXISTS "characters" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "story_id" integer NOT NULL, "fullname" varchar, "nickname" varchar, "age" integer, "sex" varchar, "personality" text, "appearance" text, "likes" text, "dislikes" text, "fears" text, "backstory" text, "is_companion" boolean, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, "race_id" integer NOT NULL, "is_protagonist" boolean DEFAULT FALSE NOT NULL, "location_id" integer, "deliberately_absent" boolean DEFAULT FALSE NOT NULL, "level" integer, "hit_die" integer, "strength" integer, "dexterity" integer, "will" integer, "hostile" boolean DEFAULT FALSE NOT NULL, "x" integer, "y" integer, "conscious_desire" text, "unconscious_desire" text, "recognized_need" text, "unrecognized_need" text, "desire_pursuit" varchar, "need_pursuit" varchar, "posted_to_id" integer, CONSTRAINT "fk_rails_6608c248e8" +FOREIGN KEY ("story_id") + REFERENCES "stories" ("id") +, CONSTRAINT "fk_rails_0b4b445641" +FOREIGN KEY ("location_id") + REFERENCES "locations" ("id") +, CONSTRAINT "fk_rails_56a213cbe2" +FOREIGN KEY ("race_id") + REFERENCES "races" ("id") +, CONSTRAINT "fk_rails_4c54fc506e" +FOREIGN KEY ("posted_to_id") + REFERENCES "locations" ("id") + ON DELETE SET NULL); +INSERT INTO characters VALUES(1000000001,1000000001,'Isbet Marrow','Iz',34,'female','Methodical to the point of rudeness. She would rather finish a bearing than finish a sentence, and she trusts a measurement she took herself over anything a Sovereign puts in writing. Dry, patient, slow to anger, and incapable of forgetting a debt in either direction.','Weathered dark skin, a nomad''s squint, and charcoal on the side of every finger. Her hair is cropped short and going grey at one temple only.','Fresh bearings, strong tea, the hour before the city moves','Sovereign paperwork, being told a street has always been there','The memory loss. She has started writing down what she did yesterday.','Born in a Scorch caravan that made its living selling bearings and salvaged road-signs to a city which rearranged itself out from under them every night. She walked into Nocturnis at nineteen with a stolen theodolite and a talent nobody in the caravan had a word for: she can look at a street and tell where it has been. The Lunar Sovereigns hired her four years ago to map the nightly rearrangements, pay her badly, and deny in public that the survey exists at all. She has been behind on rent for most of that time.',0,'2026-10-29 02:09:13.143141','2026-10-29 02:09:13.143141',1000000004,1,NULL,0,3,8,11,14,13,0,NULL,NULL,'Isbet Marrow wants to reach a complete map of the city''s nightly rearrangements.','Isbet Marrow wants to keep the one measurement that proves the Sovereigns are lying.','Isbet Marrow needs to keep her survey notes in order across every night.','Isbet Marrow needs to reach the memory of what she has begun to lose.','reach','keep',NULL); +INSERT INTO characters VALUES(1000000002,1000000001,'Grenn Ollivar','Old Grenn',61,'male','Loud, aggrieved, and fundamentally decent under about two inches of grievance. He threatens eviction roughly weekly and has carried one out once in thirty years. He would very much like to be asked about the house.','Heavyset, pale even for a Sovereign, silver hair gone thin on top, and a cough he blames on the plaster dust. He carries a ledger under one arm at all hours, including the hours when nobody is awake to owe him anything.','His own foundations, being right about the plaster, a settled ledger','The Sovereign''s Circle, tenants who talk to the walls','That the house will crack after all, and that his family was right','A Sovereign family''s third son, bought out of his inheritance at thirty for a sum that covered exactly one boarding house in the Larkspur Quarter. He has spent the thirty years since laying his foundation stones loose so the building survives the shifting, and reminding anyone who will hold still that he owns it outright. He does not go up to the third floor after dark.',0,'2026-10-29 02:09:13.198081','2026-10-29 02:09:13.231377',1000000002,0,1000000001,0,1,6,9,7,12,0,0,3,'Grenn Ollivar wants to obtain the overdue rent before the Bell of Saint Aravel rings again.','Grenn Ollivar wants to reach someone who will ask him about the foundations.','Grenn Ollivar needs to keep his boarding-house ledger settled.','Grenn Ollivar needs to reach the third floor after dark and inspect his house.','obtain','reach',NULL); +INSERT INTO characters VALUES(1000000003,1000000001,'Marek Sollen','the Ringer',44,'male','Nothing decides anything up there any more. What is left is habit and a terrible steadiness: he climbs, he waits, he pulls, and anything in the chamber that still answers to a name is something he goes for without hurrying and without stopping.','A big man gone grey all the way through, skin and coat and eyes the same lamp-lit nothing, standing straighter than a living man stands. His hands are still a ringer''s hands. He does not blink and he does not look away.','The hour. The rope. The weight coming back up the line','Anything that says a name out loud in his tower','Nothing at all, and that is the whole of what is wrong with him','He rang the eleven o''clock for twenty-nine years, which meant twenty-nine years of standing in the one tower the city cannot move while the rest of it went past underneath him. Nobody told him what that does. The parish stopped seeing him some while before it stopped hearing him, and by then he had already forgotten his sister''s name, then his own, then what the rope was for -- though he still pulls it, on the hour, more or less.',0,'2026-10-29 02:09:13.201303','2026-10-29 02:09:13.271925',1000000003,0,1000000007,0,1,10,14,8,3,1,3,7,'Marek Sollen wants to keep the bell ringing at the appointed hour.','Marek Sollen wants to avoid every name spoken in the tower.','Marek Sollen needs to attend the rope until the city hears its hour.','Marek Sollen needs to reach the person he was before the bell took his name.','keep','avoid',NULL); PRAGMA writable_schema=ON; CREATE TABLE IF NOT EXISTS sqlite_sequence(name,seq); DELETE FROM sqlite_sequence; @@ -468,7 +472,6 @@ INSERT INTO sqlite_sequence VALUES('ruby_llm_models',1000000000); INSERT INTO sqlite_sequence VALUES('ruby_llm_tool_calls',1000000000); INSERT INTO sqlite_sequence VALUES('ruby_llm_usages',1000000000); INSERT INTO sqlite_sequence VALUES('universes',1000000001); -INSERT INTO sqlite_sequence VALUES('characters',1000000003); INSERT INTO sqlite_sequence VALUES('chats',1000000000); INSERT INTO sqlite_sequence VALUES('interactions',1000000000); INSERT INTO sqlite_sequence VALUES('items',1000000003); @@ -504,6 +507,7 @@ INSERT INTO sqlite_sequence VALUES('stories',1000000001); INSERT INTO sqlite_sequence VALUES('system_one_receipts',1000000000); INSERT INTO sqlite_sequence VALUES('world_events',1000000002); INSERT INTO sqlite_sequence VALUES('world_mechanics',1000000001); +INSERT INTO sqlite_sequence VALUES('characters',1000000003); CREATE INDEX "index_characters_scenes_on_character_id_and_scene_id" ON "characters_scenes" ("character_id", "scene_id"); CREATE INDEX "index_characters_scenes_on_scene_id_and_character_id" ON "characters_scenes" ("scene_id", "character_id"); CREATE UNIQUE INDEX "index_players_on_name" ON "players" ("name"); @@ -524,13 +528,6 @@ CREATE INDEX "index_ruby_llm_usages_on_chat_type_and_chat_id" ON "ruby_llm_usage CREATE INDEX "index_ruby_llm_usages_on_message_type_and_message_id" ON "ruby_llm_usages" ("message_type", "message_id"); CREATE INDEX "index_ruby_llm_usages_on_status" ON "ruby_llm_usages" ("status"); CREATE UNIQUE INDEX "index_ruby_llm_v2_backfills_on_task" ON "ruby_llm_v2_backfills" ("task"); -CREATE UNIQUE INDEX "index_characters_on_story_id_and_lower_fullname" ON "characters" (story_id, LOWER(fullname)); -CREATE INDEX "index_characters_on_location_id_and_hostile" ON "characters" ("location_id", "hostile"); -CREATE INDEX "index_characters_on_location_id_and_id" ON "characters" ("location_id", "id"); -CREATE INDEX "index_characters_on_location_id" ON "characters" ("location_id"); -CREATE INDEX "index_characters_on_race_id" ON "characters" ("race_id"); -CREATE INDEX "index_characters_on_story_id_and_is_protagonist" ON "characters" ("story_id", "is_protagonist"); -CREATE INDEX "index_characters_on_story_id" ON "characters" ("story_id"); CREATE INDEX "index_chats_on_character_id" ON "chats" ("character_id"); CREATE INDEX "index_chats_on_player_id" ON "chats" ("player_id"); CREATE INDEX "index_chats_on_conversation_key" ON "chats" ("playthrough_id", "character_id", "purpose"); @@ -666,5 +663,13 @@ CREATE UNIQUE INDEX "index_world_mechanics_on_story_id_and_name" ON "world_mecha CREATE INDEX "index_world_mechanics_on_story_id" ON "world_mechanics" ("story_id"); CREATE UNIQUE INDEX "index_floors_on_location_id" ON "floors" ("location_id"); CREATE INDEX "index_floors_on_down_location_id" ON "floors" ("down_location_id"); +CREATE UNIQUE INDEX "index_characters_on_story_id_and_lower_fullname" ON "characters" (story_id, LOWER(fullname)); +CREATE INDEX "index_characters_on_location_id_and_hostile" ON "characters" ("location_id", "hostile"); +CREATE INDEX "index_characters_on_location_id_and_id" ON "characters" ("location_id", "id"); +CREATE INDEX "index_characters_on_location_id" ON "characters" ("location_id"); +CREATE INDEX "index_characters_on_race_id" ON "characters" ("race_id"); +CREATE INDEX "index_characters_on_story_id_and_is_protagonist" ON "characters" ("story_id", "is_protagonist"); +CREATE INDEX "index_characters_on_story_id" ON "characters" ("story_id"); +CREATE INDEX "index_characters_on_posted_to_id" ON "characters" ("posted_to_id"); PRAGMA writable_schema=OFF; COMMIT; diff --git a/parity/worlds/the-pottery-on-mill-lane.sql b/parity/worlds/the-pottery-on-mill-lane.sql index 5735e6b..b093b60 100644 --- a/parity/worlds/the-pottery-on-mill-lane.sql +++ b/parity/worlds/the-pottery-on-mill-lane.sql @@ -13,17 +13,6 @@ CREATE TABLE IF NOT EXISTS "ruby_llm_usages" ("id" integer PRIMARY KEY AUTOINCRE CREATE TABLE IF NOT EXISTS "ruby_llm_v2_backfills" ("completed" boolean DEFAULT FALSE NOT NULL, "last_id" integer, "task" varchar NOT NULL); CREATE TABLE IF NOT EXISTS "universes" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "civilizations" text, "created_at" datetime(6) NOT NULL, "economics" text, "geographies" text, "history" text, "physics" text, "politics" text, "religion" text, "technology" text, "updated_at" datetime(6) NOT NULL, "weapons" text, "gravity" varchar, "carrying" varchar); INSERT INTO universes VALUES(1000000001,'A market town that buys its pots from one family.','2026-10-02 13:26:01.210088','Sold by the piece on market day, and by the crate to the inns.','A mill town on a slow river, with a clay pit upstream.','The Hollins have fired the town''s crockery for three generations.','Ordinary physics. What is dropped falls, and what is fired breaks.','The market reeve, who sets the stall rents.','A saint''s day in spring, when nobody works the wheel.','A wheel, a kiln, and a lamp in the loft.','2026-10-02 13:26:01.210088','Nothing much, unless a crock counts.','ordinary',NULL); -CREATE TABLE IF NOT EXISTS "characters" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "age" integer, "appearance" text, "backstory" text, "conscious_desire" text, "created_at" datetime(6) NOT NULL, "deliberately_absent" boolean DEFAULT FALSE NOT NULL, "desire_pursuit" varchar, "dexterity" integer, "dislikes" text, "fears" text, "fullname" varchar, "hit_die" integer, "hostile" boolean DEFAULT FALSE NOT NULL, "is_companion" boolean, "is_protagonist" boolean DEFAULT FALSE NOT NULL, "level" integer, "likes" text, "location_id" integer, "need_pursuit" varchar, "nickname" varchar, "personality" text, "race_id" integer NOT NULL, "recognized_need" text, "sex" varchar, "story_id" integer NOT NULL, "strength" integer, "unconscious_desire" text, "unrecognized_need" text, "updated_at" datetime(6) NOT NULL, "will" integer, "x" integer, "y" integer, CONSTRAINT "fk_rails_56a213cbe2" -FOREIGN KEY ("race_id") - REFERENCES "races" ("id") -, CONSTRAINT "fk_rails_0b4b445641" -FOREIGN KEY ("location_id") - REFERENCES "locations" ("id") -, CONSTRAINT "fk_rails_6608c248e8" -FOREIGN KEY ("story_id") - REFERENCES "stories" ("id") -); -INSERT INTO characters VALUES(1000000001,34,'Clay to the elbows, and an apron gone grey with it.','Throws every pot the pottery sells, and keeps the kiln book in her own hand.',NULL,'2026-10-02 13:26:01.444968',0,NULL,12,'Somebody touching the kiln without asking','The pottery going to her brother','Ada Hollin',8,0,0,1,3,'A clean firing',NULL,NULL,'Ada','Quick, and quicker to lose her temper with a cracked pot.',1000000001,NULL,'female',1000000001,16,NULL,NULL,'2026-10-02 13:26:01.444968',11,NULL,NULL); CREATE TABLE IF NOT EXISTS "chats" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "cancelled" boolean DEFAULT FALSE NOT NULL, "character_id" integer, "created_at" datetime(6) NOT NULL, "model_id_string" varchar, "player_id" integer, "playthrough_id" integer, "purpose" varchar, "ruby_llm_model_id" integer, "updated_at" datetime(6) NOT NULL, CONSTRAINT "fk_rails_415520c982" FOREIGN KEY ("playthrough_id") REFERENCES "playthroughs" ("id") @@ -422,6 +411,7 @@ INSERT INTO schema_migrations VALUES('20261025230000'); INSERT INTO schema_migrations VALUES('20261027120117'); INSERT INTO schema_migrations VALUES('20261030120000'); INSERT INTO schema_migrations VALUES('20261030200000'); +INSERT INTO schema_migrations VALUES('20261030230000'); CREATE TABLE IF NOT EXISTS "ar_internal_metadata" ("key" varchar NOT NULL PRIMARY KEY, "value" varchar, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL); INSERT INTO ar_internal_metadata VALUES('environment','test','2026-10-02 13:25:33.143313','2026-10-02 13:25:33.143316'); INSERT INTO ar_internal_metadata VALUES('schema_sha1','072abc2bfb80d111a1808df270e9210911f5e81d','2026-10-02 13:25:33.148224','2026-10-02 13:25:33.148228'); @@ -432,13 +422,27 @@ FOREIGN KEY ("playthrough_id") FOREIGN KEY ("quest_outcome_id") REFERENCES "quest_outcomes" ("id") ); -CREATE TABLE IF NOT EXISTS "floors" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "location_id" integer NOT NULL, "rooms" integer NOT NULL, "danger" varchar, "hazard" varchar, "down_location_id" integer, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, CONSTRAINT "fk_rails_ce8b005cb2" +CREATE TABLE IF NOT EXISTS "floors" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "location_id" integer NOT NULL, "rooms" integer NOT NULL, "danger" varchar, "hazard" varchar, "down_location_id" integer, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, "monster_level" integer, CONSTRAINT "fk_rails_ce8b005cb2" FOREIGN KEY ("location_id") REFERENCES "locations" ("id") ON DELETE CASCADE, CONSTRAINT "fk_rails_8a0268155a" FOREIGN KEY ("down_location_id") REFERENCES "locations" ("id") ON DELETE SET NULL); +CREATE TABLE IF NOT EXISTS "characters" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "age" integer, "appearance" text, "backstory" text, "conscious_desire" text, "created_at" datetime(6) NOT NULL, "deliberately_absent" boolean DEFAULT FALSE NOT NULL, "desire_pursuit" varchar, "dexterity" integer, "dislikes" text, "fears" text, "fullname" varchar, "hit_die" integer, "hostile" boolean DEFAULT FALSE NOT NULL, "is_companion" boolean, "is_protagonist" boolean DEFAULT FALSE NOT NULL, "level" integer, "likes" text, "location_id" integer, "need_pursuit" varchar, "nickname" varchar, "personality" text, "race_id" integer NOT NULL, "recognized_need" text, "sex" varchar, "story_id" integer NOT NULL, "strength" integer, "unconscious_desire" text, "unrecognized_need" text, "updated_at" datetime(6) NOT NULL, "will" integer, "x" integer, "y" integer, "posted_to_id" integer, CONSTRAINT "fk_rails_6608c248e8" +FOREIGN KEY ("story_id") + REFERENCES "stories" ("id") +, CONSTRAINT "fk_rails_0b4b445641" +FOREIGN KEY ("location_id") + REFERENCES "locations" ("id") +, CONSTRAINT "fk_rails_56a213cbe2" +FOREIGN KEY ("race_id") + REFERENCES "races" ("id") +, CONSTRAINT "fk_rails_4c54fc506e" +FOREIGN KEY ("posted_to_id") + REFERENCES "locations" ("id") + ON DELETE SET NULL); +INSERT INTO characters VALUES(1000000001,34,'Clay to the elbows, and an apron gone grey with it.','Throws every pot the pottery sells, and keeps the kiln book in her own hand.',NULL,'2026-10-02 13:26:01.444968',0,NULL,12,'Somebody touching the kiln without asking','The pottery going to her brother','Ada Hollin',8,0,0,1,3,'A clean firing',NULL,NULL,'Ada','Quick, and quicker to lose her temper with a cracked pot.',1000000001,NULL,'female',1000000001,16,NULL,NULL,'2026-10-02 13:26:01.444968',11,NULL,NULL,NULL); PRAGMA writable_schema=ON; CREATE TABLE IF NOT EXISTS sqlite_sequence(name,seq); DELETE FROM sqlite_sequence; @@ -451,7 +455,6 @@ INSERT INTO sqlite_sequence VALUES('ruby_llm_models',1000000000); INSERT INTO sqlite_sequence VALUES('ruby_llm_tool_calls',1000000000); INSERT INTO sqlite_sequence VALUES('ruby_llm_usages',1000000000); INSERT INTO sqlite_sequence VALUES('universes',1000000001); -INSERT INTO sqlite_sequence VALUES('characters',1000000001); INSERT INTO sqlite_sequence VALUES('chats',1000000000); INSERT INTO sqlite_sequence VALUES('interactions',1000000000); INSERT INTO sqlite_sequence VALUES('items',1000000005); @@ -486,6 +489,7 @@ INSERT INTO sqlite_sequence VALUES('stories',1000000001); INSERT INTO sqlite_sequence VALUES('system_one_receipts',1000000000); INSERT INTO sqlite_sequence VALUES('world_events',1000000000); INSERT INTO sqlite_sequence VALUES('world_mechanics',1000000000); +INSERT INTO sqlite_sequence VALUES('characters',1000000001); CREATE INDEX "index_characters_scenes_on_character_id_and_scene_id" ON "characters_scenes" ("character_id", "scene_id"); CREATE INDEX "index_characters_scenes_on_scene_id_and_character_id" ON "characters_scenes" ("scene_id", "character_id"); CREATE UNIQUE INDEX "index_players_on_name" ON "players" ("name"); @@ -506,13 +510,6 @@ CREATE INDEX "index_ruby_llm_usages_on_chat_type_and_chat_id" ON "ruby_llm_usage CREATE INDEX "index_ruby_llm_usages_on_message_type_and_message_id" ON "ruby_llm_usages" ("message_type", "message_id"); CREATE INDEX "index_ruby_llm_usages_on_status" ON "ruby_llm_usages" ("status"); CREATE UNIQUE INDEX "index_ruby_llm_v2_backfills_on_task" ON "ruby_llm_v2_backfills" ("task"); -CREATE UNIQUE INDEX "index_characters_on_story_id_and_lower_fullname" ON "characters" (story_id, LOWER(fullname)); -CREATE INDEX "index_characters_on_location_id_and_hostile" ON "characters" ("location_id", "hostile"); -CREATE INDEX "index_characters_on_location_id_and_id" ON "characters" ("location_id", "id"); -CREATE INDEX "index_characters_on_location_id" ON "characters" ("location_id"); -CREATE INDEX "index_characters_on_race_id" ON "characters" ("race_id"); -CREATE INDEX "index_characters_on_story_id_and_is_protagonist" ON "characters" ("story_id", "is_protagonist"); -CREATE INDEX "index_characters_on_story_id" ON "characters" ("story_id"); CREATE INDEX "index_chats_on_character_id" ON "chats" ("character_id"); CREATE INDEX "index_chats_on_player_id" ON "chats" ("player_id"); CREATE INDEX "index_chats_on_conversation_key" ON "chats" ("playthrough_id", "character_id", "purpose"); @@ -648,5 +645,13 @@ CREATE INDEX "index_playthrough_due_endings_on_quest_outcome_id" ON "playthrough CREATE INDEX "index_quest_steps_on_companion_id" ON "quest_steps" ("companion_id"); CREATE UNIQUE INDEX "index_floors_on_location_id" ON "floors" ("location_id"); CREATE INDEX "index_floors_on_down_location_id" ON "floors" ("down_location_id"); +CREATE UNIQUE INDEX "index_characters_on_story_id_and_lower_fullname" ON "characters" (story_id, LOWER(fullname)); +CREATE INDEX "index_characters_on_location_id_and_hostile" ON "characters" ("location_id", "hostile"); +CREATE INDEX "index_characters_on_location_id_and_id" ON "characters" ("location_id", "id"); +CREATE INDEX "index_characters_on_location_id" ON "characters" ("location_id"); +CREATE INDEX "index_characters_on_race_id" ON "characters" ("race_id"); +CREATE INDEX "index_characters_on_story_id_and_is_protagonist" ON "characters" ("story_id", "is_protagonist"); +CREATE INDEX "index_characters_on_story_id" ON "characters" ("story_id"); +CREATE INDEX "index_characters_on_posted_to_id" ON "characters" ("posted_to_id"); PRAGMA writable_schema=OFF; COMMIT; diff --git a/parity/worlds/the-quay-house.sql b/parity/worlds/the-quay-house.sql index 859eb2d..e5f6d6b 100644 --- a/parity/worlds/the-quay-house.sql +++ b/parity/worlds/the-quay-house.sql @@ -13,17 +13,6 @@ CREATE TABLE IF NOT EXISTS "ruby_llm_usages" ("id" integer PRIMARY KEY AUTOINCRE CREATE TABLE IF NOT EXISTS "ruby_llm_v2_backfills" ("completed" boolean DEFAULT FALSE NOT NULL, "last_id" integer, "task" varchar NOT NULL); CREATE TABLE IF NOT EXISTS "universes" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "civilizations" text, "created_at" datetime(6) NOT NULL, "economics" text, "geographies" text, "history" text, "physics" text, "politics" text, "religion" text, "technology" text, "updated_at" datetime(6) NOT NULL, "weapons" text, "gravity" varchar, "carrying" varchar); INSERT INTO universes VALUES(1000000001,'A port that has never been anything else.','2026-10-02 13:25:36.094650','Paid in salt weight, settled in coin when the assessor is watching.','One quay, one custom house standing on it, and the water at the end of both.','Three hundred years of the same tide.','Ordinary physics, and a tide that keeps to its table.','The harbourmaster, and whoever is owed money by the harbourmaster.','The tide table, read aloud.','Rope, tar, lamp oil and a ledger.','2026-10-02 13:25:36.094650','A gaff hook, and whatever is nearest the door.',NULL,NULL); -CREATE TABLE IF NOT EXISTS "characters" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "age" integer, "appearance" text, "backstory" text, "conscious_desire" text, "created_at" datetime(6) NOT NULL, "deliberately_absent" boolean DEFAULT FALSE NOT NULL, "desire_pursuit" varchar, "dexterity" integer, "dislikes" text, "fears" text, "fullname" varchar, "hit_die" integer, "hostile" boolean DEFAULT FALSE NOT NULL, "is_companion" boolean, "is_protagonist" boolean DEFAULT FALSE NOT NULL, "level" integer, "likes" text, "location_id" integer, "need_pursuit" varchar, "nickname" varchar, "personality" text, "race_id" integer NOT NULL, "recognized_need" text, "sex" varchar, "story_id" integer NOT NULL, "strength" integer, "unconscious_desire" text, "unrecognized_need" text, "updated_at" datetime(6) NOT NULL, "will" integer, "x" integer, "y" integer, CONSTRAINT "fk_rails_56a213cbe2" -FOREIGN KEY ("race_id") - REFERENCES "races" ("id") -, CONSTRAINT "fk_rails_0b4b445641" -FOREIGN KEY ("location_id") - REFERENCES "locations" ("id") -, CONSTRAINT "fk_rails_6608c248e8" -FOREIGN KEY ("story_id") - REFERENCES "stories" ("id") -); -INSERT INTO characters VALUES(1000000001,38,'Salt at the cuff, whatever she is wearing.','Kept the quay''s books for eleven years without once being asked to.',NULL,'2026-10-02 13:25:36.472221',0,NULL,13,'A door found open','Being the last one to know','Nell Cawsand',8,0,0,1,3,'A tide that comes in on the table',1000000001,NULL,'Nell','Unhurried, and counts things twice.',1000000001,NULL,'female',1000000001,11,NULL,NULL,'2026-10-02 13:25:36.472221',12,NULL,NULL); CREATE TABLE IF NOT EXISTS "chats" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "cancelled" boolean DEFAULT FALSE NOT NULL, "character_id" integer, "created_at" datetime(6) NOT NULL, "model_id_string" varchar, "player_id" integer, "playthrough_id" integer, "purpose" varchar, "ruby_llm_model_id" integer, "updated_at" datetime(6) NOT NULL, CONSTRAINT "fk_rails_415520c982" FOREIGN KEY ("playthrough_id") REFERENCES "playthroughs" ("id") @@ -457,6 +446,7 @@ INSERT INTO schema_migrations VALUES('20261025230000'); INSERT INTO schema_migrations VALUES('20261027120117'); INSERT INTO schema_migrations VALUES('20261030120000'); INSERT INTO schema_migrations VALUES('20261030200000'); +INSERT INTO schema_migrations VALUES('20261030230000'); CREATE TABLE IF NOT EXISTS "ar_internal_metadata" ("key" varchar NOT NULL PRIMARY KEY, "value" varchar, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL); INSERT INTO ar_internal_metadata VALUES('environment','test','2026-10-02 13:25:33.143313','2026-10-02 13:25:33.143316'); INSERT INTO ar_internal_metadata VALUES('schema_sha1','072abc2bfb80d111a1808df270e9210911f5e81d','2026-10-02 13:25:33.148224','2026-10-02 13:25:33.148228'); @@ -467,13 +457,27 @@ FOREIGN KEY ("playthrough_id") FOREIGN KEY ("quest_outcome_id") REFERENCES "quest_outcomes" ("id") ); -CREATE TABLE IF NOT EXISTS "floors" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "location_id" integer NOT NULL, "rooms" integer NOT NULL, "danger" varchar, "hazard" varchar, "down_location_id" integer, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, CONSTRAINT "fk_rails_ce8b005cb2" +CREATE TABLE IF NOT EXISTS "floors" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "location_id" integer NOT NULL, "rooms" integer NOT NULL, "danger" varchar, "hazard" varchar, "down_location_id" integer, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, "monster_level" integer, CONSTRAINT "fk_rails_ce8b005cb2" FOREIGN KEY ("location_id") REFERENCES "locations" ("id") ON DELETE CASCADE, CONSTRAINT "fk_rails_8a0268155a" FOREIGN KEY ("down_location_id") REFERENCES "locations" ("id") ON DELETE SET NULL); +CREATE TABLE IF NOT EXISTS "characters" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "age" integer, "appearance" text, "backstory" text, "conscious_desire" text, "created_at" datetime(6) NOT NULL, "deliberately_absent" boolean DEFAULT FALSE NOT NULL, "desire_pursuit" varchar, "dexterity" integer, "dislikes" text, "fears" text, "fullname" varchar, "hit_die" integer, "hostile" boolean DEFAULT FALSE NOT NULL, "is_companion" boolean, "is_protagonist" boolean DEFAULT FALSE NOT NULL, "level" integer, "likes" text, "location_id" integer, "need_pursuit" varchar, "nickname" varchar, "personality" text, "race_id" integer NOT NULL, "recognized_need" text, "sex" varchar, "story_id" integer NOT NULL, "strength" integer, "unconscious_desire" text, "unrecognized_need" text, "updated_at" datetime(6) NOT NULL, "will" integer, "x" integer, "y" integer, "posted_to_id" integer, CONSTRAINT "fk_rails_6608c248e8" +FOREIGN KEY ("story_id") + REFERENCES "stories" ("id") +, CONSTRAINT "fk_rails_0b4b445641" +FOREIGN KEY ("location_id") + REFERENCES "locations" ("id") +, CONSTRAINT "fk_rails_56a213cbe2" +FOREIGN KEY ("race_id") + REFERENCES "races" ("id") +, CONSTRAINT "fk_rails_4c54fc506e" +FOREIGN KEY ("posted_to_id") + REFERENCES "locations" ("id") + ON DELETE SET NULL); +INSERT INTO characters VALUES(1000000001,38,'Salt at the cuff, whatever she is wearing.','Kept the quay''s books for eleven years without once being asked to.',NULL,'2026-10-02 13:25:36.472221',0,NULL,13,'A door found open','Being the last one to know','Nell Cawsand',8,0,0,1,3,'A tide that comes in on the table',1000000001,NULL,'Nell','Unhurried, and counts things twice.',1000000001,NULL,'female',1000000001,11,NULL,NULL,'2026-10-02 13:25:36.472221',12,NULL,NULL,NULL); PRAGMA writable_schema=ON; CREATE TABLE IF NOT EXISTS sqlite_sequence(name,seq); DELETE FROM sqlite_sequence; @@ -486,7 +490,6 @@ INSERT INTO sqlite_sequence VALUES('ruby_llm_models',1000000000); INSERT INTO sqlite_sequence VALUES('ruby_llm_tool_calls',1000000000); INSERT INTO sqlite_sequence VALUES('ruby_llm_usages',1000000000); INSERT INTO sqlite_sequence VALUES('universes',1000000001); -INSERT INTO sqlite_sequence VALUES('characters',1000000001); INSERT INTO sqlite_sequence VALUES('chats',1000000000); INSERT INTO sqlite_sequence VALUES('interactions',1000000000); INSERT INTO sqlite_sequence VALUES('items',1000000001); @@ -521,6 +524,7 @@ INSERT INTO sqlite_sequence VALUES('stories',1000000001); INSERT INTO sqlite_sequence VALUES('system_one_receipts',1000000000); INSERT INTO sqlite_sequence VALUES('world_events',1000000000); INSERT INTO sqlite_sequence VALUES('world_mechanics',1000000000); +INSERT INTO sqlite_sequence VALUES('characters',1000000001); CREATE INDEX "index_characters_scenes_on_character_id_and_scene_id" ON "characters_scenes" ("character_id", "scene_id"); CREATE INDEX "index_characters_scenes_on_scene_id_and_character_id" ON "characters_scenes" ("scene_id", "character_id"); CREATE UNIQUE INDEX "index_players_on_name" ON "players" ("name"); @@ -541,13 +545,6 @@ CREATE INDEX "index_ruby_llm_usages_on_chat_type_and_chat_id" ON "ruby_llm_usage CREATE INDEX "index_ruby_llm_usages_on_message_type_and_message_id" ON "ruby_llm_usages" ("message_type", "message_id"); CREATE INDEX "index_ruby_llm_usages_on_status" ON "ruby_llm_usages" ("status"); CREATE UNIQUE INDEX "index_ruby_llm_v2_backfills_on_task" ON "ruby_llm_v2_backfills" ("task"); -CREATE UNIQUE INDEX "index_characters_on_story_id_and_lower_fullname" ON "characters" (story_id, LOWER(fullname)); -CREATE INDEX "index_characters_on_location_id_and_hostile" ON "characters" ("location_id", "hostile"); -CREATE INDEX "index_characters_on_location_id_and_id" ON "characters" ("location_id", "id"); -CREATE INDEX "index_characters_on_location_id" ON "characters" ("location_id"); -CREATE INDEX "index_characters_on_race_id" ON "characters" ("race_id"); -CREATE INDEX "index_characters_on_story_id_and_is_protagonist" ON "characters" ("story_id", "is_protagonist"); -CREATE INDEX "index_characters_on_story_id" ON "characters" ("story_id"); CREATE INDEX "index_chats_on_character_id" ON "chats" ("character_id"); CREATE INDEX "index_chats_on_player_id" ON "chats" ("player_id"); CREATE INDEX "index_chats_on_conversation_key" ON "chats" ("playthrough_id", "character_id", "purpose"); @@ -683,5 +680,13 @@ CREATE INDEX "index_playthrough_due_endings_on_quest_outcome_id" ON "playthrough CREATE INDEX "index_quest_steps_on_companion_id" ON "quest_steps" ("companion_id"); CREATE UNIQUE INDEX "index_floors_on_location_id" ON "floors" ("location_id"); CREATE INDEX "index_floors_on_down_location_id" ON "floors" ("down_location_id"); +CREATE UNIQUE INDEX "index_characters_on_story_id_and_lower_fullname" ON "characters" (story_id, LOWER(fullname)); +CREATE INDEX "index_characters_on_location_id_and_hostile" ON "characters" ("location_id", "hostile"); +CREATE INDEX "index_characters_on_location_id_and_id" ON "characters" ("location_id", "id"); +CREATE INDEX "index_characters_on_location_id" ON "characters" ("location_id"); +CREATE INDEX "index_characters_on_race_id" ON "characters" ("race_id"); +CREATE INDEX "index_characters_on_story_id_and_is_protagonist" ON "characters" ("story_id", "is_protagonist"); +CREATE INDEX "index_characters_on_story_id" ON "characters" ("story_id"); +CREATE INDEX "index_characters_on_posted_to_id" ON "characters" ("posted_to_id"); PRAGMA writable_schema=OFF; COMMIT; diff --git a/parity/worlds/the-rook-wood-gate.sql b/parity/worlds/the-rook-wood-gate.sql index bfeb0ba..b09080e 100644 --- a/parity/worlds/the-rook-wood-gate.sql +++ b/parity/worlds/the-rook-wood-gate.sql @@ -13,17 +13,6 @@ CREATE TABLE IF NOT EXISTS "ruby_llm_usages" ("id" integer PRIMARY KEY AUTOINCRE CREATE TABLE IF NOT EXISTS "ruby_llm_v2_backfills" ("completed" boolean DEFAULT FALSE NOT NULL, "last_id" integer, "task" varchar NOT NULL); CREATE TABLE IF NOT EXISTS "universes" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "civilizations" text, "created_at" datetime(6) NOT NULL, "economics" text, "geographies" text, "history" text, "physics" text, "politics" text, "religion" text, "technology" text, "updated_at" datetime(6) NOT NULL, "weapons" text, "gravity" varchar, "carrying" varchar); INSERT INTO universes VALUES(1000000001,'A walled town that keeps its records.','2026-10-15 21:45:55.663919','Coins and stamped receipts.','A walled ward and the wood behind its gate.','The wood was the ward''s before the wall was.','Ordinary physics.','The ward keeps the peace.','The town keeps its own festivals.','Lamps, hand carts and hatchets.','2026-10-15 21:45:55.663919','Walking sticks.',NULL,NULL); -CREATE TABLE IF NOT EXISTS "characters" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "age" integer, "appearance" text, "backstory" text, "conscious_desire" text, "created_at" datetime(6) NOT NULL, "deliberately_absent" boolean DEFAULT FALSE NOT NULL, "desire_pursuit" varchar, "dexterity" integer, "dislikes" text, "fears" text, "fullname" varchar, "hit_die" integer, "hostile" boolean DEFAULT FALSE NOT NULL, "is_companion" boolean, "is_protagonist" boolean DEFAULT FALSE NOT NULL, "level" integer, "likes" text, "location_id" integer, "need_pursuit" varchar, "nickname" varchar, "personality" text, "race_id" integer NOT NULL, "recognized_need" text, "sex" varchar, "story_id" integer NOT NULL, "strength" integer, "unconscious_desire" text, "unrecognized_need" text, "updated_at" datetime(6) NOT NULL, "will" integer, "x" integer, "y" integer, CONSTRAINT "fk_rails_56a213cbe2" -FOREIGN KEY ("race_id") - REFERENCES "races" ("id") -, CONSTRAINT "fk_rails_0b4b445641" -FOREIGN KEY ("location_id") - REFERENCES "locations" ("id") -, CONSTRAINT "fk_rails_6608c248e8" -FOREIGN KEY ("story_id") - REFERENCES "stories" ("id") -); -INSERT INTO characters VALUES(1000000001,29,'A canvas apron.','A ward porter sent out for kindling.',NULL,'2026-10-15 21:45:55.786953',0,NULL,11,'Wet boots.','The wood after dark.','Tamsin Rook',8,0,0,1,2,'A dry log.',NULL,NULL,'Tamsin','Patient and plain.',1000000001,NULL,'female',1000000001,10,NULL,NULL,'2026-10-15 21:45:55.786953',10,NULL,NULL); CREATE TABLE IF NOT EXISTS "chats" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "cancelled" boolean DEFAULT FALSE NOT NULL, "character_id" integer, "created_at" datetime(6) NOT NULL, "model_id_string" varchar, "player_id" integer, "playthrough_id" integer, "purpose" varchar, "ruby_llm_model_id" integer, "updated_at" datetime(6) NOT NULL, CONSTRAINT "fk_rails_415520c982" FOREIGN KEY ("playthrough_id") REFERENCES "playthroughs" ("id") @@ -405,6 +394,7 @@ INSERT INTO schema_migrations VALUES('20261025230000'); INSERT INTO schema_migrations VALUES('20261027120117'); INSERT INTO schema_migrations VALUES('20261030120000'); INSERT INTO schema_migrations VALUES('20261030200000'); +INSERT INTO schema_migrations VALUES('20261030230000'); CREATE TABLE IF NOT EXISTS "ar_internal_metadata" ("key" varchar NOT NULL PRIMARY KEY, "value" varchar, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL); INSERT INTO ar_internal_metadata VALUES('environment','test','2026-10-15 21:43:40.193939','2026-10-15 21:43:40.193941'); INSERT INTO ar_internal_metadata VALUES('schema_sha1','62d31b9fa38811bdb1f557cc2a6f6d523066e2aa','2026-10-15 21:43:40.198165','2026-10-15 21:43:40.198167'); @@ -415,13 +405,27 @@ FOREIGN KEY ("playthrough_id") FOREIGN KEY ("quest_outcome_id") REFERENCES "quest_outcomes" ("id") ); -CREATE TABLE IF NOT EXISTS "floors" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "location_id" integer NOT NULL, "rooms" integer NOT NULL, "danger" varchar, "hazard" varchar, "down_location_id" integer, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, CONSTRAINT "fk_rails_ce8b005cb2" +CREATE TABLE IF NOT EXISTS "floors" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "location_id" integer NOT NULL, "rooms" integer NOT NULL, "danger" varchar, "hazard" varchar, "down_location_id" integer, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, "monster_level" integer, CONSTRAINT "fk_rails_ce8b005cb2" FOREIGN KEY ("location_id") REFERENCES "locations" ("id") ON DELETE CASCADE, CONSTRAINT "fk_rails_8a0268155a" FOREIGN KEY ("down_location_id") REFERENCES "locations" ("id") ON DELETE SET NULL); +CREATE TABLE IF NOT EXISTS "characters" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "age" integer, "appearance" text, "backstory" text, "conscious_desire" text, "created_at" datetime(6) NOT NULL, "deliberately_absent" boolean DEFAULT FALSE NOT NULL, "desire_pursuit" varchar, "dexterity" integer, "dislikes" text, "fears" text, "fullname" varchar, "hit_die" integer, "hostile" boolean DEFAULT FALSE NOT NULL, "is_companion" boolean, "is_protagonist" boolean DEFAULT FALSE NOT NULL, "level" integer, "likes" text, "location_id" integer, "need_pursuit" varchar, "nickname" varchar, "personality" text, "race_id" integer NOT NULL, "recognized_need" text, "sex" varchar, "story_id" integer NOT NULL, "strength" integer, "unconscious_desire" text, "unrecognized_need" text, "updated_at" datetime(6) NOT NULL, "will" integer, "x" integer, "y" integer, "posted_to_id" integer, CONSTRAINT "fk_rails_6608c248e8" +FOREIGN KEY ("story_id") + REFERENCES "stories" ("id") +, CONSTRAINT "fk_rails_0b4b445641" +FOREIGN KEY ("location_id") + REFERENCES "locations" ("id") +, CONSTRAINT "fk_rails_56a213cbe2" +FOREIGN KEY ("race_id") + REFERENCES "races" ("id") +, CONSTRAINT "fk_rails_4c54fc506e" +FOREIGN KEY ("posted_to_id") + REFERENCES "locations" ("id") + ON DELETE SET NULL); +INSERT INTO characters VALUES(1000000001,29,'A canvas apron.','A ward porter sent out for kindling.',NULL,'2026-10-15 21:45:55.786953',0,NULL,11,'Wet boots.','The wood after dark.','Tamsin Rook',8,0,0,1,2,'A dry log.',NULL,NULL,'Tamsin','Patient and plain.',1000000001,NULL,'female',1000000001,10,NULL,NULL,'2026-10-15 21:45:55.786953',10,NULL,NULL,NULL); PRAGMA writable_schema=ON; CREATE TABLE IF NOT EXISTS sqlite_sequence(name,seq); DELETE FROM sqlite_sequence; @@ -434,7 +438,6 @@ INSERT INTO sqlite_sequence VALUES('ruby_llm_models',1000000000); INSERT INTO sqlite_sequence VALUES('ruby_llm_tool_calls',1000000000); INSERT INTO sqlite_sequence VALUES('ruby_llm_usages',1000000000); INSERT INTO sqlite_sequence VALUES('universes',1000000001); -INSERT INTO sqlite_sequence VALUES('characters',1000000001); INSERT INTO sqlite_sequence VALUES('chats',1000000000); INSERT INTO sqlite_sequence VALUES('interactions',1000000000); INSERT INTO sqlite_sequence VALUES('items',1000000000); @@ -469,6 +472,7 @@ INSERT INTO sqlite_sequence VALUES('stories',1000000001); INSERT INTO sqlite_sequence VALUES('system_one_receipts',1000000000); INSERT INTO sqlite_sequence VALUES('world_events',1000000000); INSERT INTO sqlite_sequence VALUES('world_mechanics',1000000000); +INSERT INTO sqlite_sequence VALUES('characters',1000000001); CREATE INDEX "index_characters_scenes_on_character_id_and_scene_id" ON "characters_scenes" ("character_id", "scene_id"); CREATE INDEX "index_characters_scenes_on_scene_id_and_character_id" ON "characters_scenes" ("scene_id", "character_id"); CREATE UNIQUE INDEX "index_players_on_name" ON "players" ("name"); @@ -489,13 +493,6 @@ CREATE INDEX "index_ruby_llm_usages_on_chat_type_and_chat_id" ON "ruby_llm_usage CREATE INDEX "index_ruby_llm_usages_on_message_type_and_message_id" ON "ruby_llm_usages" ("message_type", "message_id"); CREATE INDEX "index_ruby_llm_usages_on_status" ON "ruby_llm_usages" ("status"); CREATE UNIQUE INDEX "index_ruby_llm_v2_backfills_on_task" ON "ruby_llm_v2_backfills" ("task"); -CREATE UNIQUE INDEX "index_characters_on_story_id_and_lower_fullname" ON "characters" (story_id, LOWER(fullname)); -CREATE INDEX "index_characters_on_location_id_and_hostile" ON "characters" ("location_id", "hostile"); -CREATE INDEX "index_characters_on_location_id_and_id" ON "characters" ("location_id", "id"); -CREATE INDEX "index_characters_on_location_id" ON "characters" ("location_id"); -CREATE INDEX "index_characters_on_race_id" ON "characters" ("race_id"); -CREATE INDEX "index_characters_on_story_id_and_is_protagonist" ON "characters" ("story_id", "is_protagonist"); -CREATE INDEX "index_characters_on_story_id" ON "characters" ("story_id"); CREATE INDEX "index_chats_on_character_id" ON "chats" ("character_id"); CREATE INDEX "index_chats_on_player_id" ON "chats" ("player_id"); CREATE INDEX "index_chats_on_conversation_key" ON "chats" ("playthrough_id", "character_id", "purpose"); @@ -631,5 +628,13 @@ CREATE INDEX "index_playthrough_due_endings_on_quest_outcome_id" ON "playthrough CREATE INDEX "index_quest_steps_on_companion_id" ON "quest_steps" ("companion_id"); CREATE UNIQUE INDEX "index_floors_on_location_id" ON "floors" ("location_id"); CREATE INDEX "index_floors_on_down_location_id" ON "floors" ("down_location_id"); +CREATE UNIQUE INDEX "index_characters_on_story_id_and_lower_fullname" ON "characters" (story_id, LOWER(fullname)); +CREATE INDEX "index_characters_on_location_id_and_hostile" ON "characters" ("location_id", "hostile"); +CREATE INDEX "index_characters_on_location_id_and_id" ON "characters" ("location_id", "id"); +CREATE INDEX "index_characters_on_location_id" ON "characters" ("location_id"); +CREATE INDEX "index_characters_on_race_id" ON "characters" ("race_id"); +CREATE INDEX "index_characters_on_story_id_and_is_protagonist" ON "characters" ("story_id", "is_protagonist"); +CREATE INDEX "index_characters_on_story_id" ON "characters" ("story_id"); +CREATE INDEX "index_characters_on_posted_to_id" ON "characters" ("posted_to_id"); PRAGMA writable_schema=OFF; COMMIT; diff --git a/parity/worlds/the-ropewalk-at-saltmarsh.sql b/parity/worlds/the-ropewalk-at-saltmarsh.sql index a072338..9738e7f 100644 --- a/parity/worlds/the-ropewalk-at-saltmarsh.sql +++ b/parity/worlds/the-ropewalk-at-saltmarsh.sql @@ -13,19 +13,6 @@ CREATE TABLE IF NOT EXISTS "ruby_llm_usages" ("id" integer PRIMARY KEY AUTOINCRE CREATE TABLE IF NOT EXISTS "ruby_llm_v2_backfills" ("completed" boolean DEFAULT FALSE NOT NULL, "last_id" integer, "task" varchar NOT NULL); CREATE TABLE IF NOT EXISTS "universes" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "civilizations" text, "created_at" datetime(6) NOT NULL, "economics" text, "geographies" text, "history" text, "physics" text, "politics" text, "religion" text, "technology" text, "updated_at" datetime(6) NOT NULL, "weapons" text, "gravity" varchar, "carrying" varchar); INSERT INTO universes VALUES(1000000001,'A fishing village that makes its own rope and sells the rest.','2026-10-02 13:26:02.866161','Rope by the fathom to the boats, and nets by the dozen to the market.','A salt marsh on a tidal river, with the village on the only dry ground.','The Coyles have laid the village''s rope since the harbour was dug.','Ordinary physics. What is thrown comes down, and the heavier it is the sooner.','The harbour master, who buys the rope and sets its price.','A blessing of the boats at midsummer.','Hemp, tar, a spinning wheel and a long shed to lay the rope in.','2026-10-02 13:26:02.866161','A marlinspike, at a pinch.','ordinary',NULL); -CREATE TABLE IF NOT EXISTS "characters" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "age" integer, "appearance" text, "backstory" text, "conscious_desire" text, "created_at" datetime(6) NOT NULL, "deliberately_absent" boolean DEFAULT FALSE NOT NULL, "desire_pursuit" varchar, "dexterity" integer, "dislikes" text, "fears" text, "fullname" varchar, "hit_die" integer, "hostile" boolean DEFAULT FALSE NOT NULL, "is_companion" boolean, "is_protagonist" boolean DEFAULT FALSE NOT NULL, "level" integer, "likes" text, "location_id" integer, "need_pursuit" varchar, "nickname" varchar, "personality" text, "race_id" integer NOT NULL, "recognized_need" text, "sex" varchar, "story_id" integer NOT NULL, "strength" integer, "unconscious_desire" text, "unrecognized_need" text, "updated_at" datetime(6) NOT NULL, "will" integer, "x" integer, "y" integer, CONSTRAINT "fk_rails_56a213cbe2" -FOREIGN KEY ("race_id") - REFERENCES "races" ("id") -, CONSTRAINT "fk_rails_0b4b445641" -FOREIGN KEY ("location_id") - REFERENCES "locations" ("id") -, CONSTRAINT "fk_rails_6608c248e8" -FOREIGN KEY ("story_id") - REFERENCES "stories" ("id") -); -INSERT INTO characters VALUES(1000000001,27,'Tar to the wrists, and a rope burn across one palm.','Walks the ropewalk twice a day, and can tell a bad strand by the feel of it.',NULL,'2026-10-02 13:26:03.006411',0,NULL,11,'Being hurried on a long lay','The walk closing for good','Jory Pask',8,0,0,1,2,'A cable laid without a kink',NULL,NULL,'Jory','Steady, and slow to say what he has noticed.',1000000001,NULL,'male',1000000001,18,NULL,NULL,'2026-10-02 13:26:03.006411',10,NULL,NULL); -INSERT INTO characters VALUES(1000000002,61,'Bent at the shoulders, with a net across his knees.','Laid rope on the walk for forty years, and mends nets on the green now.',NULL,'2026-10-02 13:26:03.102878',0,NULL,12,'New rope','Being asked to walk the ropewalk again','Wat Coyle',6,0,0,0,1,'A fine day for mending',1000000001,NULL,'Wat','Talkative, and certain the old ways were better.',1000000001,NULL,'male',1000000001,11,NULL,NULL,'2026-10-02 13:26:03.102878',10,NULL,NULL); -INSERT INTO characters VALUES(1000000003,38,'Ink on one cuff, tar on the other.','Keeps the ropewalk''s book, and signs for every fathom that leaves it.',NULL,'2026-10-02 13:26:03.111748',0,NULL,13,'Rope sold off the book','The parted cable being traced to her signature','Hester Vane',6,0,0,0,1,'A book that balances',1000000002,NULL,'Hester','Exact, and short with anybody who is not.',1000000001,NULL,'female',1000000001,10,NULL,NULL,'2026-10-02 13:26:03.111748',12,18,1); CREATE TABLE IF NOT EXISTS "chats" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "cancelled" boolean DEFAULT FALSE NOT NULL, "character_id" integer, "created_at" datetime(6) NOT NULL, "model_id_string" varchar, "player_id" integer, "playthrough_id" integer, "purpose" varchar, "ruby_llm_model_id" integer, "updated_at" datetime(6) NOT NULL, CONSTRAINT "fk_rails_415520c982" FOREIGN KEY ("playthrough_id") REFERENCES "playthroughs" ("id") @@ -415,6 +402,7 @@ INSERT INTO schema_migrations VALUES('20261025230000'); INSERT INTO schema_migrations VALUES('20261027120117'); INSERT INTO schema_migrations VALUES('20261030120000'); INSERT INTO schema_migrations VALUES('20261030200000'); +INSERT INTO schema_migrations VALUES('20261030230000'); CREATE TABLE IF NOT EXISTS "ar_internal_metadata" ("key" varchar NOT NULL PRIMARY KEY, "value" varchar, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL); INSERT INTO ar_internal_metadata VALUES('environment','test','2026-10-02 13:25:33.143313','2026-10-02 13:25:33.143316'); INSERT INTO ar_internal_metadata VALUES('schema_sha1','072abc2bfb80d111a1808df270e9210911f5e81d','2026-10-02 13:25:33.148224','2026-10-02 13:25:33.148228'); @@ -425,13 +413,29 @@ FOREIGN KEY ("playthrough_id") FOREIGN KEY ("quest_outcome_id") REFERENCES "quest_outcomes" ("id") ); -CREATE TABLE IF NOT EXISTS "floors" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "location_id" integer NOT NULL, "rooms" integer NOT NULL, "danger" varchar, "hazard" varchar, "down_location_id" integer, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, CONSTRAINT "fk_rails_ce8b005cb2" +CREATE TABLE IF NOT EXISTS "floors" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "location_id" integer NOT NULL, "rooms" integer NOT NULL, "danger" varchar, "hazard" varchar, "down_location_id" integer, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, "monster_level" integer, CONSTRAINT "fk_rails_ce8b005cb2" FOREIGN KEY ("location_id") REFERENCES "locations" ("id") ON DELETE CASCADE, CONSTRAINT "fk_rails_8a0268155a" FOREIGN KEY ("down_location_id") REFERENCES "locations" ("id") ON DELETE SET NULL); +CREATE TABLE IF NOT EXISTS "characters" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "age" integer, "appearance" text, "backstory" text, "conscious_desire" text, "created_at" datetime(6) NOT NULL, "deliberately_absent" boolean DEFAULT FALSE NOT NULL, "desire_pursuit" varchar, "dexterity" integer, "dislikes" text, "fears" text, "fullname" varchar, "hit_die" integer, "hostile" boolean DEFAULT FALSE NOT NULL, "is_companion" boolean, "is_protagonist" boolean DEFAULT FALSE NOT NULL, "level" integer, "likes" text, "location_id" integer, "need_pursuit" varchar, "nickname" varchar, "personality" text, "race_id" integer NOT NULL, "recognized_need" text, "sex" varchar, "story_id" integer NOT NULL, "strength" integer, "unconscious_desire" text, "unrecognized_need" text, "updated_at" datetime(6) NOT NULL, "will" integer, "x" integer, "y" integer, "posted_to_id" integer, CONSTRAINT "fk_rails_6608c248e8" +FOREIGN KEY ("story_id") + REFERENCES "stories" ("id") +, CONSTRAINT "fk_rails_0b4b445641" +FOREIGN KEY ("location_id") + REFERENCES "locations" ("id") +, CONSTRAINT "fk_rails_56a213cbe2" +FOREIGN KEY ("race_id") + REFERENCES "races" ("id") +, CONSTRAINT "fk_rails_4c54fc506e" +FOREIGN KEY ("posted_to_id") + REFERENCES "locations" ("id") + ON DELETE SET NULL); +INSERT INTO characters VALUES(1000000001,27,'Tar to the wrists, and a rope burn across one palm.','Walks the ropewalk twice a day, and can tell a bad strand by the feel of it.',NULL,'2026-10-02 13:26:03.006411',0,NULL,11,'Being hurried on a long lay','The walk closing for good','Jory Pask',8,0,0,1,2,'A cable laid without a kink',NULL,NULL,'Jory','Steady, and slow to say what he has noticed.',1000000001,NULL,'male',1000000001,18,NULL,NULL,'2026-10-02 13:26:03.006411',10,NULL,NULL,NULL); +INSERT INTO characters VALUES(1000000002,61,'Bent at the shoulders, with a net across his knees.','Laid rope on the walk for forty years, and mends nets on the green now.',NULL,'2026-10-02 13:26:03.102878',0,NULL,12,'New rope','Being asked to walk the ropewalk again','Wat Coyle',6,0,0,0,1,'A fine day for mending',1000000001,NULL,'Wat','Talkative, and certain the old ways were better.',1000000001,NULL,'male',1000000001,11,NULL,NULL,'2026-10-02 13:26:03.102878',10,NULL,NULL,NULL); +INSERT INTO characters VALUES(1000000003,38,'Ink on one cuff, tar on the other.','Keeps the ropewalk''s book, and signs for every fathom that leaves it.',NULL,'2026-10-02 13:26:03.111748',0,NULL,13,'Rope sold off the book','The parted cable being traced to her signature','Hester Vane',6,0,0,0,1,'A book that balances',1000000002,NULL,'Hester','Exact, and short with anybody who is not.',1000000001,NULL,'female',1000000001,10,NULL,NULL,'2026-10-02 13:26:03.111748',12,18,1,NULL); PRAGMA writable_schema=ON; CREATE TABLE IF NOT EXISTS sqlite_sequence(name,seq); DELETE FROM sqlite_sequence; @@ -444,7 +448,6 @@ INSERT INTO sqlite_sequence VALUES('ruby_llm_models',1000000000); INSERT INTO sqlite_sequence VALUES('ruby_llm_tool_calls',1000000000); INSERT INTO sqlite_sequence VALUES('ruby_llm_usages',1000000000); INSERT INTO sqlite_sequence VALUES('universes',1000000001); -INSERT INTO sqlite_sequence VALUES('characters',1000000003); INSERT INTO sqlite_sequence VALUES('chats',1000000000); INSERT INTO sqlite_sequence VALUES('interactions',1000000000); INSERT INTO sqlite_sequence VALUES('items',1000000004); @@ -479,6 +482,7 @@ INSERT INTO sqlite_sequence VALUES('stories',1000000001); INSERT INTO sqlite_sequence VALUES('system_one_receipts',1000000000); INSERT INTO sqlite_sequence VALUES('world_events',1000000000); INSERT INTO sqlite_sequence VALUES('world_mechanics',1000000000); +INSERT INTO sqlite_sequence VALUES('characters',1000000003); CREATE INDEX "index_characters_scenes_on_character_id_and_scene_id" ON "characters_scenes" ("character_id", "scene_id"); CREATE INDEX "index_characters_scenes_on_scene_id_and_character_id" ON "characters_scenes" ("scene_id", "character_id"); CREATE UNIQUE INDEX "index_players_on_name" ON "players" ("name"); @@ -499,13 +503,6 @@ CREATE INDEX "index_ruby_llm_usages_on_chat_type_and_chat_id" ON "ruby_llm_usage CREATE INDEX "index_ruby_llm_usages_on_message_type_and_message_id" ON "ruby_llm_usages" ("message_type", "message_id"); CREATE INDEX "index_ruby_llm_usages_on_status" ON "ruby_llm_usages" ("status"); CREATE UNIQUE INDEX "index_ruby_llm_v2_backfills_on_task" ON "ruby_llm_v2_backfills" ("task"); -CREATE UNIQUE INDEX "index_characters_on_story_id_and_lower_fullname" ON "characters" (story_id, LOWER(fullname)); -CREATE INDEX "index_characters_on_location_id_and_hostile" ON "characters" ("location_id", "hostile"); -CREATE INDEX "index_characters_on_location_id_and_id" ON "characters" ("location_id", "id"); -CREATE INDEX "index_characters_on_location_id" ON "characters" ("location_id"); -CREATE INDEX "index_characters_on_race_id" ON "characters" ("race_id"); -CREATE INDEX "index_characters_on_story_id_and_is_protagonist" ON "characters" ("story_id", "is_protagonist"); -CREATE INDEX "index_characters_on_story_id" ON "characters" ("story_id"); CREATE INDEX "index_chats_on_character_id" ON "chats" ("character_id"); CREATE INDEX "index_chats_on_player_id" ON "chats" ("player_id"); CREATE INDEX "index_chats_on_conversation_key" ON "chats" ("playthrough_id", "character_id", "purpose"); @@ -641,5 +638,13 @@ CREATE INDEX "index_playthrough_due_endings_on_quest_outcome_id" ON "playthrough CREATE INDEX "index_quest_steps_on_companion_id" ON "quest_steps" ("companion_id"); CREATE UNIQUE INDEX "index_floors_on_location_id" ON "floors" ("location_id"); CREATE INDEX "index_floors_on_down_location_id" ON "floors" ("down_location_id"); +CREATE UNIQUE INDEX "index_characters_on_story_id_and_lower_fullname" ON "characters" (story_id, LOWER(fullname)); +CREATE INDEX "index_characters_on_location_id_and_hostile" ON "characters" ("location_id", "hostile"); +CREATE INDEX "index_characters_on_location_id_and_id" ON "characters" ("location_id", "id"); +CREATE INDEX "index_characters_on_location_id" ON "characters" ("location_id"); +CREATE INDEX "index_characters_on_race_id" ON "characters" ("race_id"); +CREATE INDEX "index_characters_on_story_id_and_is_protagonist" ON "characters" ("story_id", "is_protagonist"); +CREATE INDEX "index_characters_on_story_id" ON "characters" ("story_id"); +CREATE INDEX "index_characters_on_posted_to_id" ON "characters" ("posted_to_id"); PRAGMA writable_schema=OFF; COMMIT; diff --git a/parity/worlds/the-salt-assizes.sql b/parity/worlds/the-salt-assizes.sql index acb751c..0cd77b4 100644 --- a/parity/worlds/the-salt-assizes.sql +++ b/parity/worlds/the-salt-assizes.sql @@ -14,19 +14,6 @@ CREATE TABLE IF NOT EXISTS "ruby_llm_usages" ("id" integer PRIMARY KEY AUTOINCRE CREATE TABLE IF NOT EXISTS "ruby_llm_v2_backfills" ("task" varchar NOT NULL, "last_id" integer, "completed" boolean DEFAULT FALSE NOT NULL); CREATE TABLE IF NOT EXISTS "universes" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "physics" text, "technology" text, "weapons" text, "civilizations" text, "geographies" text, "history" text, "economics" text, "politics" text, "religion" text, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, "gravity" varchar, "carrying" varchar); INSERT INTO universes VALUES(1000000001,'Standard physical laws, with one local exception the courts are built around: on this coast an oath spoken over standing salt water binds, and an oath spoken over dry ground does not. Nobody has explained it and everybody has arranged their life around it. A sworn statement made on the causeway at low water holds a man in place -- he cannot walk away from it, cannot contradict it, and will find his own mouth refusing the lie. The same words spoken inland are only words. The binding lasts exactly as long as the water that heard it stays where it is, which on this coast is about six hours.','Sail, tar, oil lamp and iron. Roughly eighteenth century and skewed entirely toward the sea: block and tackle, chain, dredging gear, tide tables computed by hand and reprinted every spring. There is no telegraph and no engine on the coast. The most advanced instrument in the county is the Assize bell, cast to a pitch that carries over surf, and the second is the tide-slate, a ruled board of black slate on which the day''s high water is chalked and which no one is permitted to erase but the Bell-Keeper.','Boat hooks, gutting knives, and the chain. Violence on this coast is mostly the sea''s, and what people do to each other they do with the law and the tide -- a man left chained at the post through one turn of the water is a man the county has killed without touching him. Firearms exist and are ruined by salt within a season, so nobody bothers. The Assizes carry no guard beyond the Bell-Keeper, because the causeway itself is the guard: at high water there is nowhere to run to.','One county, one court, and a hard division between the people of the shore and the people of the marsh. The shore lives by salvage, oysters and the wrecking trade and considers the law a weather event. The marsh lives by grazing and considers itself the county''s conscience. Above both sit the Justicars, appointed for life and travelling in circuit, and beside them the Bell-Keepers -- a guild of one family per coast, who read the water and are the only people whose word can rise a court. Everyone here can tell you how long they have until the water turns.','A drowned coast of mud flats, salt marsh and islands that are only islands twice a day. The Assize causeway runs a mile out from the shore to Gallows Rock and is walkable for about five hours either side of low water. On the rock stand the court, the tide post and the beached hulk of the *Vestry*, a revenue cutter that came ashore ninety years ago and was never floated off. Inland the marsh runs to grazing, and beyond that the county town, which the Assizes visit once and never twice.','The Assizes were founded four hundred years ago after a wrecking case collapsed twice on perjury, when somebody noticed that the men who had sworn on the flats had not been able to lie. The court moved onto the causeway the following spring and has sat there since. Twice the Crown has tried to move it inland and twice the verdicts stopped holding. Ninety years ago the revenue cutter *Vestry* came ashore across the court''s own steps during a hearing; the court adjourned, the hearing resumed inside the wreck, and the wreck has been the vestry ever since.','Oysters, salt, salvage and fees. Everything on this coast is paid at low water and settled before the turn, which makes credit almost unknown and grudges almost permanent. The Assizes are funded by the fees of the cases they hear, which means a court with nothing to try does not eat -- a fact everybody notices and nobody says. The wrecking trade is illegal, universal, and the single largest source of coin between the marsh and the sea.','The Justicars answer to the Crown and are ignored by it. Real power on the coast is the ability to say what time the water turns, which is the Bell-Keeper''s, and the ability to say what was sworn, which is the court''s. The two have been quietly at odds for a century: a Justicar who wants a verdict wants more water than the Keeper will give, and a Keeper who rings the court up early has ended a hearing that a life depended on. Neither can overrule the other and both know it.','The coast keeps the Turning: that the world is water and the only sin is being where you said you would not be when it comes in. Its clergy are tide-readers, its sacrament is the count of the last hour before high water, and its heresy -- held quietly by most of the marsh -- is that the sea does not care and never did. Oaths are religious business here as much as legal, which is why a Bell-Keeper is sworn and a Justicar is merely appointed.','2026-10-29 02:09:14.997573','2026-10-29 02:09:14.997573',NULL,NULL); -CREATE TABLE IF NOT EXISTS "characters" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "story_id" integer NOT NULL, "fullname" varchar, "nickname" varchar, "age" integer, "sex" varchar, "personality" text, "appearance" text, "likes" text, "dislikes" text, "fears" text, "backstory" text, "is_companion" boolean, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, "race_id" integer NOT NULL, "is_protagonist" boolean DEFAULT FALSE NOT NULL, "location_id" integer, "deliberately_absent" boolean DEFAULT FALSE NOT NULL, "level" integer, "hit_die" integer, "strength" integer, "dexterity" integer, "will" integer, "hostile" boolean DEFAULT FALSE NOT NULL, "x" integer, "y" integer, "conscious_desire" text, "unconscious_desire" text, "recognized_need" text, "unrecognized_need" text, "desire_pursuit" varchar, "need_pursuit" varchar, CONSTRAINT "fk_rails_56a213cbe2" -FOREIGN KEY ("race_id") - REFERENCES "races" ("id") -, CONSTRAINT "fk_rails_0b4b445641" -FOREIGN KEY ("location_id") - REFERENCES "locations" ("id") -, CONSTRAINT "fk_rails_6608c248e8" -FOREIGN KEY ("story_id") - REFERENCES "stories" ("id") -); -INSERT INTO characters VALUES(1000000001,1000000001,'Coraith Vell','Vell',44,'female','Exact, literal and slow to speak, with the Keeper''s inability to give a figure she is not sure of. She does not argue; she states the water and lets people arrange themselves around it. Under pressure she gets quieter and more precise, which people mistake for calm and is closer to fury.','Tall, salt-weathered a decade early, with cropped grey hair and a permanent squint to seaward. Deaf in the left ear, which faces the bell. Chalk is worn into the creases of her right hand so deeply that it does not wash out.','A slate that agrees with the water, the ten minutes before a turn, the sound of a court rising on time','A figure given without a source, being asked to round, anyone standing between her and the flats','Ringing a court up and finding afterwards that the water had not turned','Sworn Bell-Keeper at seven, as her mother and her mother''s mother were, and keeper of this causeway alone since she was twenty-five. She has rung nineteen years of Assizes up and down and has ended two hearings early, one of which cost a man his acquittal and is the only thing she has ever apologised for. She writes the slate herself every night, in a still room, on a dry board, and has never had cause to check it in the morning.',0,'2026-10-29 02:09:15.196954','2026-10-29 02:09:15.196954',1000000001,1,NULL,0,3,8,13,10,14,0,NULL,NULL,'Coraith Vell wants to keep the tide-slate true through the day''s high water.','Coraith Vell wants to reach the source of the hand that altered her figure.','Coraith Vell needs to attend the bell and ring the court on time.','Coraith Vell needs to reach the flats and measure the water herself.','keep','reach'); -INSERT INTO characters VALUES(1000000002,1000000001,'Ammon Brace','Justicar Brace',58,'male','Courteous, unhurried, genuinely fair-minded within the law and entirely unable to see outside it. He will hear anybody out. He wants the hearing to happen because a hearing not held is a man held without one, and he will not notice that the fastest way to have it is to trust a number nobody sourced.','Neat, dry and out of place: gown, bands, town shoes ruined by the causeway, and a face that has been polite in bad weather for thirty years. He keeps his hands folded because they are cold.','A court that sits when it says it will, a witness who answers the question, being given a figure','Adjournments, the smell of the flats, being asked to wait without a reason','Signing off on a delay that turns out to have been a sentence','Appointed at thirty-one and moved every season since, through forty parishes he could recite and not one he could find a friend in. He has heard eleven wrecking cases on this coast and returned nine convictions, and he is fairly sure that is the correct proportion. He has never in his life known when the water would turn, and he has spent thirty years being told.',0,'2026-10-29 02:09:15.273680','2026-10-29 02:09:15.330754',1000000002,0,1000000001,0,1,6,8,9,16,0,0,3,'Ammon Brace wants to reach the hearing before the tide closes the causeway.','Ammon Brace wants to obtain certainty that the law has not failed the accused.','Ammon Brace needs to attend every witness until the hearing is complete.','Ammon Brace needs to reach a judgment that accounts for the water beyond the law.','reach','attend'); -INSERT INTO characters VALUES(1000000003,1000000001,'Neb Halloran','Neb',33,'male','Loud, funny and shrewd, and getting quieter the higher the water gets. He bargains out of habit even when he has nothing, and he tells the truth in fragments, testing what each one buys before he parts with the next.','Broad, sunburnt over salt-burn, with a wrecker''s hands and a rope-scar around the left wrist that predates the chain by ten years. Shirt, no coat; they took the coat.','A tide that comes in slow, being called by his name, an argument he can win','Being counted with the marsh, questions about his cousins, the sound of the bell','Drowning at the post while the court is still deciding whether to hear him','Third-generation salvager out of the shore villages, chained at the tide post since the ebb for the wrecking of the *Marianne Dowe* -- a job everybody on this coast agrees took six men and which only he has been charged with. He knows all six names. He has said none of them, because five of them are his cousins and the sixth pays for his mother''s roof.',0,'2026-10-29 02:09:15.295749','2026-10-29 02:09:15.351873',1000000004,0,1000000002,0,1,10,15,13,9,0,3,3,'Neb Halloran wants to obtain a hearing before the water reaches the tide post.','Neb Halloran wants to avoid naming the five cousins who paid for his mother''s roof.','Neb Halloran needs to keep his mother''s roof secure through the trial.','Neb Halloran needs to reach the truth without leaving his cousins to drown in his place.','obtain','avoid'); CREATE TABLE IF NOT EXISTS "chats" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "model_id_string" varchar, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, "ruby_llm_model_id" integer, "purpose" varchar, "playthrough_id" integer, "character_id" integer, "cancelled" boolean DEFAULT FALSE NOT NULL, "player_id" integer, CONSTRAINT "fk_rails_415520c982" FOREIGN KEY ("playthrough_id") REFERENCES "playthroughs" ("id") @@ -436,16 +423,33 @@ INSERT INTO schema_migrations VALUES('20250822184650'); INSERT INTO schema_migrations VALUES('20250822184649'); INSERT INTO schema_migrations VALUES('20261030120000'); INSERT INTO schema_migrations VALUES('20261030200000'); +INSERT INTO schema_migrations VALUES('20261030230000'); CREATE TABLE IF NOT EXISTS "ar_internal_metadata" ("key" varchar NOT NULL PRIMARY KEY, "value" varchar, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL); INSERT INTO ar_internal_metadata VALUES('environment','test','2026-10-29 02:06:59.131845','2026-10-29 02:06:59.131848'); INSERT INTO ar_internal_metadata VALUES('schema_sha1','9ea7d9a7496841168065535777683ed7c64300e0','2026-10-29 02:06:59.136646','2026-10-29 02:06:59.136649'); -CREATE TABLE IF NOT EXISTS "floors" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "location_id" integer NOT NULL, "rooms" integer NOT NULL, "danger" varchar, "hazard" varchar, "down_location_id" integer, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, CONSTRAINT "fk_rails_ce8b005cb2" +CREATE TABLE IF NOT EXISTS "floors" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "location_id" integer NOT NULL, "rooms" integer NOT NULL, "danger" varchar, "hazard" varchar, "down_location_id" integer, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, "monster_level" integer, CONSTRAINT "fk_rails_ce8b005cb2" FOREIGN KEY ("location_id") REFERENCES "locations" ("id") ON DELETE CASCADE, CONSTRAINT "fk_rails_8a0268155a" FOREIGN KEY ("down_location_id") REFERENCES "locations" ("id") ON DELETE SET NULL); +CREATE TABLE IF NOT EXISTS "characters" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "story_id" integer NOT NULL, "fullname" varchar, "nickname" varchar, "age" integer, "sex" varchar, "personality" text, "appearance" text, "likes" text, "dislikes" text, "fears" text, "backstory" text, "is_companion" boolean, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, "race_id" integer NOT NULL, "is_protagonist" boolean DEFAULT FALSE NOT NULL, "location_id" integer, "deliberately_absent" boolean DEFAULT FALSE NOT NULL, "level" integer, "hit_die" integer, "strength" integer, "dexterity" integer, "will" integer, "hostile" boolean DEFAULT FALSE NOT NULL, "x" integer, "y" integer, "conscious_desire" text, "unconscious_desire" text, "recognized_need" text, "unrecognized_need" text, "desire_pursuit" varchar, "need_pursuit" varchar, "posted_to_id" integer, CONSTRAINT "fk_rails_6608c248e8" +FOREIGN KEY ("story_id") + REFERENCES "stories" ("id") +, CONSTRAINT "fk_rails_0b4b445641" +FOREIGN KEY ("location_id") + REFERENCES "locations" ("id") +, CONSTRAINT "fk_rails_56a213cbe2" +FOREIGN KEY ("race_id") + REFERENCES "races" ("id") +, CONSTRAINT "fk_rails_4c54fc506e" +FOREIGN KEY ("posted_to_id") + REFERENCES "locations" ("id") + ON DELETE SET NULL); +INSERT INTO characters VALUES(1000000001,1000000001,'Coraith Vell','Vell',44,'female','Exact, literal and slow to speak, with the Keeper''s inability to give a figure she is not sure of. She does not argue; she states the water and lets people arrange themselves around it. Under pressure she gets quieter and more precise, which people mistake for calm and is closer to fury.','Tall, salt-weathered a decade early, with cropped grey hair and a permanent squint to seaward. Deaf in the left ear, which faces the bell. Chalk is worn into the creases of her right hand so deeply that it does not wash out.','A slate that agrees with the water, the ten minutes before a turn, the sound of a court rising on time','A figure given without a source, being asked to round, anyone standing between her and the flats','Ringing a court up and finding afterwards that the water had not turned','Sworn Bell-Keeper at seven, as her mother and her mother''s mother were, and keeper of this causeway alone since she was twenty-five. She has rung nineteen years of Assizes up and down and has ended two hearings early, one of which cost a man his acquittal and is the only thing she has ever apologised for. She writes the slate herself every night, in a still room, on a dry board, and has never had cause to check it in the morning.',0,'2026-10-29 02:09:15.196954','2026-10-29 02:09:15.196954',1000000001,1,NULL,0,3,8,13,10,14,0,NULL,NULL,'Coraith Vell wants to keep the tide-slate true through the day''s high water.','Coraith Vell wants to reach the source of the hand that altered her figure.','Coraith Vell needs to attend the bell and ring the court on time.','Coraith Vell needs to reach the flats and measure the water herself.','keep','reach',NULL); +INSERT INTO characters VALUES(1000000002,1000000001,'Ammon Brace','Justicar Brace',58,'male','Courteous, unhurried, genuinely fair-minded within the law and entirely unable to see outside it. He will hear anybody out. He wants the hearing to happen because a hearing not held is a man held without one, and he will not notice that the fastest way to have it is to trust a number nobody sourced.','Neat, dry and out of place: gown, bands, town shoes ruined by the causeway, and a face that has been polite in bad weather for thirty years. He keeps his hands folded because they are cold.','A court that sits when it says it will, a witness who answers the question, being given a figure','Adjournments, the smell of the flats, being asked to wait without a reason','Signing off on a delay that turns out to have been a sentence','Appointed at thirty-one and moved every season since, through forty parishes he could recite and not one he could find a friend in. He has heard eleven wrecking cases on this coast and returned nine convictions, and he is fairly sure that is the correct proportion. He has never in his life known when the water would turn, and he has spent thirty years being told.',0,'2026-10-29 02:09:15.273680','2026-10-29 02:09:15.330754',1000000002,0,1000000001,0,1,6,8,9,16,0,0,3,'Ammon Brace wants to reach the hearing before the tide closes the causeway.','Ammon Brace wants to obtain certainty that the law has not failed the accused.','Ammon Brace needs to attend every witness until the hearing is complete.','Ammon Brace needs to reach a judgment that accounts for the water beyond the law.','reach','attend',NULL); +INSERT INTO characters VALUES(1000000003,1000000001,'Neb Halloran','Neb',33,'male','Loud, funny and shrewd, and getting quieter the higher the water gets. He bargains out of habit even when he has nothing, and he tells the truth in fragments, testing what each one buys before he parts with the next.','Broad, sunburnt over salt-burn, with a wrecker''s hands and a rope-scar around the left wrist that predates the chain by ten years. Shirt, no coat; they took the coat.','A tide that comes in slow, being called by his name, an argument he can win','Being counted with the marsh, questions about his cousins, the sound of the bell','Drowning at the post while the court is still deciding whether to hear him','Third-generation salvager out of the shore villages, chained at the tide post since the ebb for the wrecking of the *Marianne Dowe* -- a job everybody on this coast agrees took six men and which only he has been charged with. He knows all six names. He has said none of them, because five of them are his cousins and the sixth pays for his mother''s roof.',0,'2026-10-29 02:09:15.295749','2026-10-29 02:09:15.351873',1000000004,0,1000000002,0,1,10,15,13,9,0,3,3,'Neb Halloran wants to obtain a hearing before the water reaches the tide post.','Neb Halloran wants to avoid naming the five cousins who paid for his mother''s roof.','Neb Halloran needs to keep his mother''s roof secure through the trial.','Neb Halloran needs to reach the truth without leaving his cousins to drown in his place.','obtain','avoid',NULL); PRAGMA writable_schema=ON; CREATE TABLE IF NOT EXISTS sqlite_sequence(name,seq); DELETE FROM sqlite_sequence; @@ -458,7 +462,6 @@ INSERT INTO sqlite_sequence VALUES('ruby_llm_models',1000000000); INSERT INTO sqlite_sequence VALUES('ruby_llm_tool_calls',1000000000); INSERT INTO sqlite_sequence VALUES('ruby_llm_usages',1000000000); INSERT INTO sqlite_sequence VALUES('universes',1000000001); -INSERT INTO sqlite_sequence VALUES('characters',1000000003); INSERT INTO sqlite_sequence VALUES('chats',1000000000); INSERT INTO sqlite_sequence VALUES('interactions',1000000000); INSERT INTO sqlite_sequence VALUES('items',1000000009); @@ -494,6 +497,7 @@ INSERT INTO sqlite_sequence VALUES('stories',1000000001); INSERT INTO sqlite_sequence VALUES('system_one_receipts',1000000000); INSERT INTO sqlite_sequence VALUES('world_events',1000000000); INSERT INTO sqlite_sequence VALUES('world_mechanics',1000000000); +INSERT INTO sqlite_sequence VALUES('characters',1000000003); CREATE INDEX "index_characters_scenes_on_character_id_and_scene_id" ON "characters_scenes" ("character_id", "scene_id"); CREATE INDEX "index_characters_scenes_on_scene_id_and_character_id" ON "characters_scenes" ("scene_id", "character_id"); CREATE UNIQUE INDEX "index_players_on_name" ON "players" ("name"); @@ -514,13 +518,6 @@ CREATE INDEX "index_ruby_llm_usages_on_chat_type_and_chat_id" ON "ruby_llm_usage CREATE INDEX "index_ruby_llm_usages_on_message_type_and_message_id" ON "ruby_llm_usages" ("message_type", "message_id"); CREATE INDEX "index_ruby_llm_usages_on_status" ON "ruby_llm_usages" ("status"); CREATE UNIQUE INDEX "index_ruby_llm_v2_backfills_on_task" ON "ruby_llm_v2_backfills" ("task"); -CREATE UNIQUE INDEX "index_characters_on_story_id_and_lower_fullname" ON "characters" (story_id, LOWER(fullname)); -CREATE INDEX "index_characters_on_location_id_and_hostile" ON "characters" ("location_id", "hostile"); -CREATE INDEX "index_characters_on_location_id_and_id" ON "characters" ("location_id", "id"); -CREATE INDEX "index_characters_on_location_id" ON "characters" ("location_id"); -CREATE INDEX "index_characters_on_race_id" ON "characters" ("race_id"); -CREATE INDEX "index_characters_on_story_id_and_is_protagonist" ON "characters" ("story_id", "is_protagonist"); -CREATE INDEX "index_characters_on_story_id" ON "characters" ("story_id"); CREATE INDEX "index_chats_on_character_id" ON "chats" ("character_id"); CREATE INDEX "index_chats_on_player_id" ON "chats" ("player_id"); CREATE INDEX "index_chats_on_conversation_key" ON "chats" ("playthrough_id", "character_id", "purpose"); @@ -656,5 +653,13 @@ CREATE UNIQUE INDEX "index_world_mechanics_on_story_id_and_name" ON "world_mecha CREATE INDEX "index_world_mechanics_on_story_id" ON "world_mechanics" ("story_id"); CREATE UNIQUE INDEX "index_floors_on_location_id" ON "floors" ("location_id"); CREATE INDEX "index_floors_on_down_location_id" ON "floors" ("down_location_id"); +CREATE UNIQUE INDEX "index_characters_on_story_id_and_lower_fullname" ON "characters" (story_id, LOWER(fullname)); +CREATE INDEX "index_characters_on_location_id_and_hostile" ON "characters" ("location_id", "hostile"); +CREATE INDEX "index_characters_on_location_id_and_id" ON "characters" ("location_id", "id"); +CREATE INDEX "index_characters_on_location_id" ON "characters" ("location_id"); +CREATE INDEX "index_characters_on_race_id" ON "characters" ("race_id"); +CREATE INDEX "index_characters_on_story_id_and_is_protagonist" ON "characters" ("story_id", "is_protagonist"); +CREATE INDEX "index_characters_on_story_id" ON "characters" ("story_id"); +CREATE INDEX "index_characters_on_posted_to_id" ON "characters" ("posted_to_id"); PRAGMA writable_schema=OFF; COMMIT; diff --git a/parity/worlds/the-unfinished-workshop.sql b/parity/worlds/the-unfinished-workshop.sql index 6e82473..414cc22 100644 --- a/parity/worlds/the-unfinished-workshop.sql +++ b/parity/worlds/the-unfinished-workshop.sql @@ -13,17 +13,6 @@ CREATE TABLE IF NOT EXISTS "ruby_llm_usages" ("id" integer PRIMARY KEY AUTOINCRE CREATE TABLE IF NOT EXISTS "ruby_llm_v2_backfills" ("completed" boolean DEFAULT FALSE NOT NULL, "last_id" integer, "task" varchar NOT NULL); CREATE TABLE IF NOT EXISTS "universes" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "civilizations" text, "created_at" datetime(6) NOT NULL, "economics" text, "geographies" text, "history" text, "physics" text, "politics" text, "religion" text, "technology" text, "updated_at" datetime(6) NOT NULL, "weapons" text, "gravity" varchar, "carrying" varchar); INSERT INTO universes VALUES(1000000001,'A small trading town.','2026-10-02 13:25:34.495331','Goods traded for coins.','A market and its workshop.','Traders built the market together.','Ordinary physics.','A town council keeps the peace.','The town keeps its own festivals.','Keys, locks and lanterns.','2026-10-02 13:25:34.495331','Wooden staves.',NULL,NULL); -CREATE TABLE IF NOT EXISTS "characters" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "age" integer, "appearance" text, "backstory" text, "conscious_desire" text, "created_at" datetime(6) NOT NULL, "deliberately_absent" boolean DEFAULT FALSE NOT NULL, "desire_pursuit" varchar, "dexterity" integer, "dislikes" text, "fears" text, "fullname" varchar, "hit_die" integer, "hostile" boolean DEFAULT FALSE NOT NULL, "is_companion" boolean, "is_protagonist" boolean DEFAULT FALSE NOT NULL, "level" integer, "likes" text, "location_id" integer, "need_pursuit" varchar, "nickname" varchar, "personality" text, "race_id" integer NOT NULL, "recognized_need" text, "sex" varchar, "story_id" integer NOT NULL, "strength" integer, "unconscious_desire" text, "unrecognized_need" text, "updated_at" datetime(6) NOT NULL, "will" integer, "x" integer, "y" integer, CONSTRAINT "fk_rails_56a213cbe2" -FOREIGN KEY ("race_id") - REFERENCES "races" ("id") -, CONSTRAINT "fk_rails_0b4b445641" -FOREIGN KEY ("location_id") - REFERENCES "locations" ("id") -, CONSTRAINT "fk_rails_6608c248e8" -FOREIGN KEY ("story_id") - REFERENCES "stories" ("id") -); -INSERT INTO characters VALUES(1000000001,30,'A travel-worn coat.','A trader returning to the market.',NULL,'2026-10-02 13:25:34.627193',0,NULL,12,'Needless arguments.','Losing a friend.','Cal',8,0,0,1,10,'A fair bargain.',NULL,NULL,'Cal','Patient and practical.',1000000001,NULL,'male',1000000001,12,NULL,NULL,'2026-10-02 13:25:34.627193',12,NULL,NULL); CREATE TABLE IF NOT EXISTS "chats" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "cancelled" boolean DEFAULT FALSE NOT NULL, "character_id" integer, "created_at" datetime(6) NOT NULL, "model_id_string" varchar, "player_id" integer, "playthrough_id" integer, "purpose" varchar, "ruby_llm_model_id" integer, "updated_at" datetime(6) NOT NULL, CONSTRAINT "fk_rails_415520c982" FOREIGN KEY ("playthrough_id") REFERENCES "playthroughs" ("id") @@ -405,6 +394,7 @@ INSERT INTO schema_migrations VALUES('20261025230000'); INSERT INTO schema_migrations VALUES('20261027120117'); INSERT INTO schema_migrations VALUES('20261030120000'); INSERT INTO schema_migrations VALUES('20261030200000'); +INSERT INTO schema_migrations VALUES('20261030230000'); CREATE TABLE IF NOT EXISTS "ar_internal_metadata" ("key" varchar NOT NULL PRIMARY KEY, "value" varchar, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL); INSERT INTO ar_internal_metadata VALUES('environment','test','2026-10-02 13:25:33.143313','2026-10-02 13:25:33.143316'); INSERT INTO ar_internal_metadata VALUES('schema_sha1','072abc2bfb80d111a1808df270e9210911f5e81d','2026-10-02 13:25:33.148224','2026-10-02 13:25:33.148228'); @@ -415,13 +405,27 @@ FOREIGN KEY ("playthrough_id") FOREIGN KEY ("quest_outcome_id") REFERENCES "quest_outcomes" ("id") ); -CREATE TABLE IF NOT EXISTS "floors" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "location_id" integer NOT NULL, "rooms" integer NOT NULL, "danger" varchar, "hazard" varchar, "down_location_id" integer, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, CONSTRAINT "fk_rails_ce8b005cb2" +CREATE TABLE IF NOT EXISTS "floors" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "location_id" integer NOT NULL, "rooms" integer NOT NULL, "danger" varchar, "hazard" varchar, "down_location_id" integer, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, "monster_level" integer, CONSTRAINT "fk_rails_ce8b005cb2" FOREIGN KEY ("location_id") REFERENCES "locations" ("id") ON DELETE CASCADE, CONSTRAINT "fk_rails_8a0268155a" FOREIGN KEY ("down_location_id") REFERENCES "locations" ("id") ON DELETE SET NULL); +CREATE TABLE IF NOT EXISTS "characters" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "age" integer, "appearance" text, "backstory" text, "conscious_desire" text, "created_at" datetime(6) NOT NULL, "deliberately_absent" boolean DEFAULT FALSE NOT NULL, "desire_pursuit" varchar, "dexterity" integer, "dislikes" text, "fears" text, "fullname" varchar, "hit_die" integer, "hostile" boolean DEFAULT FALSE NOT NULL, "is_companion" boolean, "is_protagonist" boolean DEFAULT FALSE NOT NULL, "level" integer, "likes" text, "location_id" integer, "need_pursuit" varchar, "nickname" varchar, "personality" text, "race_id" integer NOT NULL, "recognized_need" text, "sex" varchar, "story_id" integer NOT NULL, "strength" integer, "unconscious_desire" text, "unrecognized_need" text, "updated_at" datetime(6) NOT NULL, "will" integer, "x" integer, "y" integer, "posted_to_id" integer, CONSTRAINT "fk_rails_6608c248e8" +FOREIGN KEY ("story_id") + REFERENCES "stories" ("id") +, CONSTRAINT "fk_rails_0b4b445641" +FOREIGN KEY ("location_id") + REFERENCES "locations" ("id") +, CONSTRAINT "fk_rails_56a213cbe2" +FOREIGN KEY ("race_id") + REFERENCES "races" ("id") +, CONSTRAINT "fk_rails_4c54fc506e" +FOREIGN KEY ("posted_to_id") + REFERENCES "locations" ("id") + ON DELETE SET NULL); +INSERT INTO characters VALUES(1000000001,30,'A travel-worn coat.','A trader returning to the market.',NULL,'2026-10-02 13:25:34.627193',0,NULL,12,'Needless arguments.','Losing a friend.','Cal',8,0,0,1,10,'A fair bargain.',NULL,NULL,'Cal','Patient and practical.',1000000001,NULL,'male',1000000001,12,NULL,NULL,'2026-10-02 13:25:34.627193',12,NULL,NULL,NULL); PRAGMA writable_schema=ON; CREATE TABLE IF NOT EXISTS sqlite_sequence(name,seq); DELETE FROM sqlite_sequence; @@ -434,7 +438,6 @@ INSERT INTO sqlite_sequence VALUES('ruby_llm_models',1000000000); INSERT INTO sqlite_sequence VALUES('ruby_llm_tool_calls',1000000000); INSERT INTO sqlite_sequence VALUES('ruby_llm_usages',1000000000); INSERT INTO sqlite_sequence VALUES('universes',1000000001); -INSERT INTO sqlite_sequence VALUES('characters',1000000001); INSERT INTO sqlite_sequence VALUES('chats',1000000000); INSERT INTO sqlite_sequence VALUES('interactions',1000000000); INSERT INTO sqlite_sequence VALUES('items',1000000000); @@ -469,6 +472,7 @@ INSERT INTO sqlite_sequence VALUES('stories',1000000001); INSERT INTO sqlite_sequence VALUES('system_one_receipts',1000000000); INSERT INTO sqlite_sequence VALUES('world_events',1000000000); INSERT INTO sqlite_sequence VALUES('world_mechanics',1000000000); +INSERT INTO sqlite_sequence VALUES('characters',1000000001); CREATE INDEX "index_characters_scenes_on_character_id_and_scene_id" ON "characters_scenes" ("character_id", "scene_id"); CREATE INDEX "index_characters_scenes_on_scene_id_and_character_id" ON "characters_scenes" ("scene_id", "character_id"); CREATE UNIQUE INDEX "index_players_on_name" ON "players" ("name"); @@ -489,13 +493,6 @@ CREATE INDEX "index_ruby_llm_usages_on_chat_type_and_chat_id" ON "ruby_llm_usage CREATE INDEX "index_ruby_llm_usages_on_message_type_and_message_id" ON "ruby_llm_usages" ("message_type", "message_id"); CREATE INDEX "index_ruby_llm_usages_on_status" ON "ruby_llm_usages" ("status"); CREATE UNIQUE INDEX "index_ruby_llm_v2_backfills_on_task" ON "ruby_llm_v2_backfills" ("task"); -CREATE UNIQUE INDEX "index_characters_on_story_id_and_lower_fullname" ON "characters" (story_id, LOWER(fullname)); -CREATE INDEX "index_characters_on_location_id_and_hostile" ON "characters" ("location_id", "hostile"); -CREATE INDEX "index_characters_on_location_id_and_id" ON "characters" ("location_id", "id"); -CREATE INDEX "index_characters_on_location_id" ON "characters" ("location_id"); -CREATE INDEX "index_characters_on_race_id" ON "characters" ("race_id"); -CREATE INDEX "index_characters_on_story_id_and_is_protagonist" ON "characters" ("story_id", "is_protagonist"); -CREATE INDEX "index_characters_on_story_id" ON "characters" ("story_id"); CREATE INDEX "index_chats_on_character_id" ON "chats" ("character_id"); CREATE INDEX "index_chats_on_player_id" ON "chats" ("player_id"); CREATE INDEX "index_chats_on_conversation_key" ON "chats" ("playthrough_id", "character_id", "purpose"); @@ -631,5 +628,13 @@ CREATE INDEX "index_playthrough_due_endings_on_quest_outcome_id" ON "playthrough CREATE INDEX "index_quest_steps_on_companion_id" ON "quest_steps" ("companion_id"); CREATE UNIQUE INDEX "index_floors_on_location_id" ON "floors" ("location_id"); CREATE INDEX "index_floors_on_down_location_id" ON "floors" ("down_location_id"); +CREATE UNIQUE INDEX "index_characters_on_story_id_and_lower_fullname" ON "characters" (story_id, LOWER(fullname)); +CREATE INDEX "index_characters_on_location_id_and_hostile" ON "characters" ("location_id", "hostile"); +CREATE INDEX "index_characters_on_location_id_and_id" ON "characters" ("location_id", "id"); +CREATE INDEX "index_characters_on_location_id" ON "characters" ("location_id"); +CREATE INDEX "index_characters_on_race_id" ON "characters" ("race_id"); +CREATE INDEX "index_characters_on_story_id_and_is_protagonist" ON "characters" ("story_id", "is_protagonist"); +CREATE INDEX "index_characters_on_story_id" ON "characters" ("story_id"); +CREATE INDEX "index_characters_on_posted_to_id" ON "characters" ("posted_to_id"); PRAGMA writable_schema=OFF; COMMIT; diff --git a/parity/worlds/the-unrecorded-hour.sql b/parity/worlds/the-unrecorded-hour.sql index 4b86853..0e75276 100644 --- a/parity/worlds/the-unrecorded-hour.sql +++ b/parity/worlds/the-unrecorded-hour.sql @@ -14,19 +14,6 @@ CREATE TABLE IF NOT EXISTS "ruby_llm_usages" ("id" integer PRIMARY KEY AUTOINCRE CREATE TABLE IF NOT EXISTS "ruby_llm_v2_backfills" ("task" varchar NOT NULL, "last_id" integer, "completed" boolean DEFAULT FALSE NOT NULL); CREATE TABLE IF NOT EXISTS "universes" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "physics" text, "technology" text, "weapons" text, "civilizations" text, "geographies" text, "history" text, "economics" text, "politics" text, "religion" text, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, "gravity" varchar, "carrying" varchar); INSERT INTO universes VALUES(1000000001,'Standard physical laws hold, with one exception: in the city of Ambry and the wards around it, a thing that is not written down stops being reliably itself. The effect is slow and dull rather than dramatic. An unrecorded corridor will, over a season, start to run a few feet short. An unregistered tool loses its temper and its edge. A person whose file is closed is not struck down; they are simply harder to remember, then harder to see, then gone from the accounts of everyone who knew them, until the only evidence they existed is what somebody wrote. Nothing about this is reversible by force. It is reversible by paperwork, which is why the Registry rules here.','Roughly late nineteenth century, and skewed hard toward anything that copies or preserves. Ambry has pneumatic tube post between every ward office, carbon paper, wax cylinders, a functioning telegraph and no electricity in the residential wards. Presses, seals, indelible inks and lamp-lit copy rooms are the industry the city actually runs on. There are no engines to speak of outside the pumping stations, because an engine that is not maintained on a filed schedule stops working in ways engineers find distressing rather than interesting.','Batons, seal-knives and single-shot service revolvers, all of them registered and most of them carried by people who have never drawn one. Physical violence is rarely the instrument of choice, because the Registry has a better one: a writ of closure ends a person''s file, and a closed file ends the person over the following months. Possession of an unregistered blade is a minor offence. Possession of a blank writ is not an offence at all, since officially none exist outside the Sealed Room.','One city, one institution, and a functional caste order underneath it. The Registry employs one adult in six and files the rest. Above the clerks sit the Inspectorate, who audit; above them the Sealed Room, which nobody in this story has seen. Below everyone are the Marginalia, whose records were damaged or amended and who consequently take the work that ought not be remembered. Out past the levee the Unwritten trade fish, salt and salvage into a city that will not put their names on a receipt. Everyone in Ambry is polite, and everyone counts the days since their file was last touched.','Ambry sits in a wide river delta that floods twice a year. The Registry occupies the whole of the high ground: eleven storeys of ward offices, copy rooms and stacks, built in rings around a courtyard nobody has permission to cross. Below it the Middle Wards spread out in numbered blocks, each one a warren of tenements and copying shops. Beyond the levee are the flood plains, unsurveyed and therefore unrecorded, where the Unwritten live in houses that move with the water. The Registry''s own basements run four floors below the flood line and are dry, which nobody has ever satisfactorily explained.','The Registry began four hundred years ago as a flood survey -- somebody had to write down which fields were underwater and whose they were. When the second survey found the unrecorded fields had drifted from the recorded ones, the survey stopped being about land. The Registry has never once been overthrown, though it has twice been reorganized so thoroughly that the previous version is unrecoverable. Eighty years ago the Great Amendment closed nine thousand files in a single winter for reasons the surviving minutes describe as procedural. The Marginalia date themselves from it.','Wages are paid in coin, but the real currency is standing in the record. A registered trade licence, a filed apprenticeship, a marriage entered in the ward book: these are the things people spend a life acquiring, because they are the things that hold. Copying is the largest industry, salt and fish the largest imports, and the Registry''s stationery budget is the single biggest line in the city accounts. Unrecorded work is cheap, quick, and done by the Marginalia, who charge more than their reputation suggests because they know exactly what they are worth and nobody can remember arguing with them.','The Inspectorate audits the ward offices, the ward offices resent the Inspectorate, and both fear the Sealed Room, which communicates by writ and never in person. Power in Ambry is the ability to make a document exist or stop existing, so the political struggle is entirely archival: misfiling, backdating, quiet amendment, and the occasional fire in a stack room. The Unwritten have no political existence at all, which turns out to make them the only people in the delta the Registry cannot touch. Several factions inside the Registry are extremely interested in why.','The city''s faith is called the Fair Copy, and it holds that the world is a document being transcribed, that the transcription contains errors, and that a life lived legibly is the only way to be carried into the next edition. Its clergy are archivists; its sacrament is having your name read aloud. The Marginalia keep a quieter heresy: that the errors are the only honest part of the document. The Unwritten find the whole argument funny, and say so, which is one more reason nobody in the city writes down what they say.','2026-10-29 02:09:16.939192','2026-10-29 02:09:16.939192',NULL,NULL); -CREATE TABLE IF NOT EXISTS "characters" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "story_id" integer NOT NULL, "fullname" varchar, "nickname" varchar, "age" integer, "sex" varchar, "personality" text, "appearance" text, "likes" text, "dislikes" text, "fears" text, "backstory" text, "is_companion" boolean, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, "race_id" integer NOT NULL, "is_protagonist" boolean DEFAULT FALSE NOT NULL, "location_id" integer, "deliberately_absent" boolean DEFAULT FALSE NOT NULL, "level" integer, "hit_die" integer, "strength" integer, "dexterity" integer, "will" integer, "hostile" boolean DEFAULT FALSE NOT NULL, "x" integer, "y" integer, "conscious_desire" text, "unconscious_desire" text, "recognized_need" text, "unrecognized_need" text, "desire_pursuit" varchar, "need_pursuit" varchar, CONSTRAINT "fk_rails_56a213cbe2" -FOREIGN KEY ("race_id") - REFERENCES "races" ("id") -, CONSTRAINT "fk_rails_0b4b445641" -FOREIGN KEY ("location_id") - REFERENCES "locations" ("id") -, CONSTRAINT "fk_rails_6608c248e8" -FOREIGN KEY ("story_id") - REFERENCES "stories" ("id") -); -INSERT INTO characters VALUES(1000000001,1000000001,'Odile Vance','Vance',41,'female','Precise, dry, and unhurried in a way that reads as calm and is actually stubbornness. She does not raise her voice or her hopes. She will not sign something she has not read, which has cost her every promotion she was ever considered for, and she considers this a fair trade she would make again.','Narrow, upright, greying at the front, with ink worn permanently into the first two fingers of her right hand.','A ledger that balances, the smell of a fresh cylinder of wax, rain on the window','Amendments in another hand, being told a query is still open','Signing her name to something that turns out to have closed a file','Fourth-generation Copyists'' Line, apprenticed at eleven, posted to Ward Office 12 at thirty and never moved because she asked not to be. She is the clerk other clerks send a queried entry to. Twice she has found an amendment in an incoming ledger that should not have been possible and twice she has filed a query about it and heard nothing back, which she has told herself is the system working slowly and has stopped believing.',0,'2026-10-29 02:09:17.181556','2026-10-29 02:09:17.181556',1000000001,1,NULL,0,3,8,9,11,15,0,NULL,NULL,'Odile Vance wants to reconcile the missing hour in Ward Office 12''s daybook.','Odile Vance wants to obtain the truth behind the impossible amendment.','Odile Vance needs to keep every query open until its source is recorded.','Odile Vance needs to reach the sealed registry and read what it contains.','keep','obtain'); -INSERT INTO characters VALUES(1000000002,1000000001,'Perrin Lasco','Perrin',29,'male','Quick, funny, and careful in a way people mistake for nerves. He asks one question too many and then apologizes for it. Being forgettable has taught him to be useful fast, and to write everything down twice.','Slight, dark-haired, with a face that will not stay in your memory for an hour after he leaves the room -- an effect people find restful and then cannot account for. He wears a copy-room apron over ordinary clothes and keeps a second pencil behind his ear he never uses.','An index that catches something, other people''s strong tea, the last hour of a shift','Being asked to repeat his name, doors that lock from one side','Being closed quietly, on a Tuesday, with nobody able to say what was lost','Third generation Marginalia; his grandmother''s file was closed in the Great Amendment and the damage came down the family with the name. Hired into Ward Office 12 four years ago for the entries nobody wanted their hand on, and good at it. He sat at the desk across from Vance, drank tea she made too strong, and kept a private index of every amendment that came through the ward -- because his people keep their own archive, and because he had begun to see a pattern in whose files got touched.',0,'2026-10-29 02:09:17.224554','2026-10-29 02:09:17.224554',1000000003,0,NULL,1,1,6,10,14,8,0,NULL,NULL,'Perrin Lasco wants to place his private index in the hands of a clerk whose file remains open.','Perrin Lasco wants to obtain a name that will remember him after his file closes.','Perrin Lasco needs to keep two copies of every amendment.','Perrin Lasco needs to reach a person who will hear his own name and not use him.','keep','withhold'); -INSERT INTO characters VALUES(1000000003,1000000001,'Halkett Rowe','Sub-Inspector Rowe',52,'male','Courteous, patient, genuinely likeable, and entirely unable to imagine that the institution which made him could be the thing doing harm. He listens well. He will hear you out, agree with you, and file the writ anyway, and he will be sorry about it in a way that costs him nothing.','Tall, unhurried, immaculate; the sharply consistent good looks of a family nobody has ever had to remember with effort. Grey coat, Inspectorate seal on a chain, and shoes you hear coming up the hallway a long time before he arrives.','A ward office that has nothing to hide, being met at the door, punctuality','An open query, a clerk who reads the file out loud to him','That the Sealed Room has a file on him, and that it is thicker than it should be','Ledger-Kept, four hundred years of unbroken record behind him, and a career built on being the man the Inspectorate sends when a ward office needs to be reminded that it is audited. He has signed eleven closures. He believes, sincerely, that each was procedurally correct, and he has stopped reading the supporting files before he signs because reading them was making the work harder to do.',0,'2026-10-29 02:09:17.230546','2026-10-29 02:09:17.284680',1000000002,0,1000000001,0,1,8,11,9,13,0,3,7,'Halkett Rowe wants to close Ward Office 12 with every query answered and the file clean.','Halkett Rowe wants to obtain the Registry''s absolution for his eleven closures.','Halkett Rowe needs to keep his appointment at the next ward on time.','Halkett Rowe needs to reach one supporting file and read it before he signs.','keep','obtain'); CREATE TABLE IF NOT EXISTS "chats" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "model_id_string" varchar, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, "ruby_llm_model_id" integer, "purpose" varchar, "playthrough_id" integer, "character_id" integer, "cancelled" boolean DEFAULT FALSE NOT NULL, "player_id" integer, CONSTRAINT "fk_rails_415520c982" FOREIGN KEY ("playthrough_id") REFERENCES "playthroughs" ("id") @@ -438,16 +425,33 @@ INSERT INTO schema_migrations VALUES('20250822184650'); INSERT INTO schema_migrations VALUES('20250822184649'); INSERT INTO schema_migrations VALUES('20261030120000'); INSERT INTO schema_migrations VALUES('20261030200000'); +INSERT INTO schema_migrations VALUES('20261030230000'); CREATE TABLE IF NOT EXISTS "ar_internal_metadata" ("key" varchar NOT NULL PRIMARY KEY, "value" varchar, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL); INSERT INTO ar_internal_metadata VALUES('environment','test','2026-10-29 02:06:59.131845','2026-10-29 02:06:59.131848'); INSERT INTO ar_internal_metadata VALUES('schema_sha1','9ea7d9a7496841168065535777683ed7c64300e0','2026-10-29 02:06:59.136646','2026-10-29 02:06:59.136649'); -CREATE TABLE IF NOT EXISTS "floors" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "location_id" integer NOT NULL, "rooms" integer NOT NULL, "danger" varchar, "hazard" varchar, "down_location_id" integer, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, CONSTRAINT "fk_rails_ce8b005cb2" +CREATE TABLE IF NOT EXISTS "floors" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "location_id" integer NOT NULL, "rooms" integer NOT NULL, "danger" varchar, "hazard" varchar, "down_location_id" integer, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, "monster_level" integer, CONSTRAINT "fk_rails_ce8b005cb2" FOREIGN KEY ("location_id") REFERENCES "locations" ("id") ON DELETE CASCADE, CONSTRAINT "fk_rails_8a0268155a" FOREIGN KEY ("down_location_id") REFERENCES "locations" ("id") ON DELETE SET NULL); +CREATE TABLE IF NOT EXISTS "characters" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "story_id" integer NOT NULL, "fullname" varchar, "nickname" varchar, "age" integer, "sex" varchar, "personality" text, "appearance" text, "likes" text, "dislikes" text, "fears" text, "backstory" text, "is_companion" boolean, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, "race_id" integer NOT NULL, "is_protagonist" boolean DEFAULT FALSE NOT NULL, "location_id" integer, "deliberately_absent" boolean DEFAULT FALSE NOT NULL, "level" integer, "hit_die" integer, "strength" integer, "dexterity" integer, "will" integer, "hostile" boolean DEFAULT FALSE NOT NULL, "x" integer, "y" integer, "conscious_desire" text, "unconscious_desire" text, "recognized_need" text, "unrecognized_need" text, "desire_pursuit" varchar, "need_pursuit" varchar, "posted_to_id" integer, CONSTRAINT "fk_rails_6608c248e8" +FOREIGN KEY ("story_id") + REFERENCES "stories" ("id") +, CONSTRAINT "fk_rails_0b4b445641" +FOREIGN KEY ("location_id") + REFERENCES "locations" ("id") +, CONSTRAINT "fk_rails_56a213cbe2" +FOREIGN KEY ("race_id") + REFERENCES "races" ("id") +, CONSTRAINT "fk_rails_4c54fc506e" +FOREIGN KEY ("posted_to_id") + REFERENCES "locations" ("id") + ON DELETE SET NULL); +INSERT INTO characters VALUES(1000000001,1000000001,'Odile Vance','Vance',41,'female','Precise, dry, and unhurried in a way that reads as calm and is actually stubbornness. She does not raise her voice or her hopes. She will not sign something she has not read, which has cost her every promotion she was ever considered for, and she considers this a fair trade she would make again.','Narrow, upright, greying at the front, with ink worn permanently into the first two fingers of her right hand.','A ledger that balances, the smell of a fresh cylinder of wax, rain on the window','Amendments in another hand, being told a query is still open','Signing her name to something that turns out to have closed a file','Fourth-generation Copyists'' Line, apprenticed at eleven, posted to Ward Office 12 at thirty and never moved because she asked not to be. She is the clerk other clerks send a queried entry to. Twice she has found an amendment in an incoming ledger that should not have been possible and twice she has filed a query about it and heard nothing back, which she has told herself is the system working slowly and has stopped believing.',0,'2026-10-29 02:09:17.181556','2026-10-29 02:09:17.181556',1000000001,1,NULL,0,3,8,9,11,15,0,NULL,NULL,'Odile Vance wants to reconcile the missing hour in Ward Office 12''s daybook.','Odile Vance wants to obtain the truth behind the impossible amendment.','Odile Vance needs to keep every query open until its source is recorded.','Odile Vance needs to reach the sealed registry and read what it contains.','keep','obtain',NULL); +INSERT INTO characters VALUES(1000000002,1000000001,'Perrin Lasco','Perrin',29,'male','Quick, funny, and careful in a way people mistake for nerves. He asks one question too many and then apologizes for it. Being forgettable has taught him to be useful fast, and to write everything down twice.','Slight, dark-haired, with a face that will not stay in your memory for an hour after he leaves the room -- an effect people find restful and then cannot account for. He wears a copy-room apron over ordinary clothes and keeps a second pencil behind his ear he never uses.','An index that catches something, other people''s strong tea, the last hour of a shift','Being asked to repeat his name, doors that lock from one side','Being closed quietly, on a Tuesday, with nobody able to say what was lost','Third generation Marginalia; his grandmother''s file was closed in the Great Amendment and the damage came down the family with the name. Hired into Ward Office 12 four years ago for the entries nobody wanted their hand on, and good at it. He sat at the desk across from Vance, drank tea she made too strong, and kept a private index of every amendment that came through the ward -- because his people keep their own archive, and because he had begun to see a pattern in whose files got touched.',0,'2026-10-29 02:09:17.224554','2026-10-29 02:09:17.224554',1000000003,0,NULL,1,1,6,10,14,8,0,NULL,NULL,'Perrin Lasco wants to place his private index in the hands of a clerk whose file remains open.','Perrin Lasco wants to obtain a name that will remember him after his file closes.','Perrin Lasco needs to keep two copies of every amendment.','Perrin Lasco needs to reach a person who will hear his own name and not use him.','keep','withhold',NULL); +INSERT INTO characters VALUES(1000000003,1000000001,'Halkett Rowe','Sub-Inspector Rowe',52,'male','Courteous, patient, genuinely likeable, and entirely unable to imagine that the institution which made him could be the thing doing harm. He listens well. He will hear you out, agree with you, and file the writ anyway, and he will be sorry about it in a way that costs him nothing.','Tall, unhurried, immaculate; the sharply consistent good looks of a family nobody has ever had to remember with effort. Grey coat, Inspectorate seal on a chain, and shoes you hear coming up the hallway a long time before he arrives.','A ward office that has nothing to hide, being met at the door, punctuality','An open query, a clerk who reads the file out loud to him','That the Sealed Room has a file on him, and that it is thicker than it should be','Ledger-Kept, four hundred years of unbroken record behind him, and a career built on being the man the Inspectorate sends when a ward office needs to be reminded that it is audited. He has signed eleven closures. He believes, sincerely, that each was procedurally correct, and he has stopped reading the supporting files before he signs because reading them was making the work harder to do.',0,'2026-10-29 02:09:17.230546','2026-10-29 02:09:17.284680',1000000002,0,1000000001,0,1,8,11,9,13,0,3,7,'Halkett Rowe wants to close Ward Office 12 with every query answered and the file clean.','Halkett Rowe wants to obtain the Registry''s absolution for his eleven closures.','Halkett Rowe needs to keep his appointment at the next ward on time.','Halkett Rowe needs to reach one supporting file and read it before he signs.','keep','obtain',NULL); PRAGMA writable_schema=ON; CREATE TABLE IF NOT EXISTS sqlite_sequence(name,seq); DELETE FROM sqlite_sequence; @@ -460,7 +464,6 @@ INSERT INTO sqlite_sequence VALUES('ruby_llm_models',1000000000); INSERT INTO sqlite_sequence VALUES('ruby_llm_tool_calls',1000000000); INSERT INTO sqlite_sequence VALUES('ruby_llm_usages',1000000000); INSERT INTO sqlite_sequence VALUES('universes',1000000001); -INSERT INTO sqlite_sequence VALUES('characters',1000000003); INSERT INTO sqlite_sequence VALUES('chats',1000000000); INSERT INTO sqlite_sequence VALUES('interactions',1000000000); INSERT INTO sqlite_sequence VALUES('items',1000000011); @@ -496,6 +499,7 @@ INSERT INTO sqlite_sequence VALUES('stories',1000000001); INSERT INTO sqlite_sequence VALUES('system_one_receipts',1000000000); INSERT INTO sqlite_sequence VALUES('world_events',1000000000); INSERT INTO sqlite_sequence VALUES('world_mechanics',1000000000); +INSERT INTO sqlite_sequence VALUES('characters',1000000003); CREATE INDEX "index_characters_scenes_on_character_id_and_scene_id" ON "characters_scenes" ("character_id", "scene_id"); CREATE INDEX "index_characters_scenes_on_scene_id_and_character_id" ON "characters_scenes" ("scene_id", "character_id"); CREATE UNIQUE INDEX "index_players_on_name" ON "players" ("name"); @@ -516,13 +520,6 @@ CREATE INDEX "index_ruby_llm_usages_on_chat_type_and_chat_id" ON "ruby_llm_usage CREATE INDEX "index_ruby_llm_usages_on_message_type_and_message_id" ON "ruby_llm_usages" ("message_type", "message_id"); CREATE INDEX "index_ruby_llm_usages_on_status" ON "ruby_llm_usages" ("status"); CREATE UNIQUE INDEX "index_ruby_llm_v2_backfills_on_task" ON "ruby_llm_v2_backfills" ("task"); -CREATE UNIQUE INDEX "index_characters_on_story_id_and_lower_fullname" ON "characters" (story_id, LOWER(fullname)); -CREATE INDEX "index_characters_on_location_id_and_hostile" ON "characters" ("location_id", "hostile"); -CREATE INDEX "index_characters_on_location_id_and_id" ON "characters" ("location_id", "id"); -CREATE INDEX "index_characters_on_location_id" ON "characters" ("location_id"); -CREATE INDEX "index_characters_on_race_id" ON "characters" ("race_id"); -CREATE INDEX "index_characters_on_story_id_and_is_protagonist" ON "characters" ("story_id", "is_protagonist"); -CREATE INDEX "index_characters_on_story_id" ON "characters" ("story_id"); CREATE INDEX "index_chats_on_character_id" ON "chats" ("character_id"); CREATE INDEX "index_chats_on_player_id" ON "chats" ("player_id"); CREATE INDEX "index_chats_on_conversation_key" ON "chats" ("playthrough_id", "character_id", "purpose"); @@ -658,5 +655,13 @@ CREATE UNIQUE INDEX "index_world_mechanics_on_story_id_and_name" ON "world_mecha CREATE INDEX "index_world_mechanics_on_story_id" ON "world_mechanics" ("story_id"); CREATE UNIQUE INDEX "index_floors_on_location_id" ON "floors" ("location_id"); CREATE INDEX "index_floors_on_down_location_id" ON "floors" ("down_location_id"); +CREATE UNIQUE INDEX "index_characters_on_story_id_and_lower_fullname" ON "characters" (story_id, LOWER(fullname)); +CREATE INDEX "index_characters_on_location_id_and_hostile" ON "characters" ("location_id", "hostile"); +CREATE INDEX "index_characters_on_location_id_and_id" ON "characters" ("location_id", "id"); +CREATE INDEX "index_characters_on_location_id" ON "characters" ("location_id"); +CREATE INDEX "index_characters_on_race_id" ON "characters" ("race_id"); +CREATE INDEX "index_characters_on_story_id_and_is_protagonist" ON "characters" ("story_id", "is_protagonist"); +CREATE INDEX "index_characters_on_story_id" ON "characters" ("story_id"); +CREATE INDEX "index_characters_on_posted_to_id" ON "characters" ("posted_to_id"); PRAGMA writable_schema=OFF; COMMIT; diff --git a/parity/worlds/the-weighing-house.sql b/parity/worlds/the-weighing-house.sql index a5208a4..25948b4 100644 --- a/parity/worlds/the-weighing-house.sql +++ b/parity/worlds/the-weighing-house.sql @@ -14,18 +14,6 @@ CREATE TABLE IF NOT EXISTS "ruby_llm_usages" ("id" integer PRIMARY KEY AUTOINCRE CREATE TABLE IF NOT EXISTS "ruby_llm_v2_backfills" ("completed" boolean DEFAULT FALSE NOT NULL, "last_id" integer, "task" varchar NOT NULL); CREATE TABLE IF NOT EXISTS "universes" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "civilizations" text, "created_at" datetime(6) NOT NULL, "economics" text, "geographies" text, "history" text, "physics" text, "politics" text, "religion" text, "technology" text, "updated_at" datetime(6) NOT NULL, "weapons" text, "gravity" varchar, "carrying" varchar); INSERT INTO universes VALUES(1000000001,'A town that weighs everything sold in it.','2026-10-25 22:57:24.500785','Goods by the stone, paid in copper and silver.','A market town on a river crossing.','The weighing house was built when the market outgrew honest guesses.','Ordinary physics. What is heavy is heavy for anybody.','The market warden, who keeps the weights true.','A blessing of the scales on market day.','Beams, scales, brass weights and stamped tickets.','2026-10-25 22:57:24.500785','A porter''s staff.',NULL,'limited'); -CREATE TABLE IF NOT EXISTS "characters" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "age" integer, "appearance" text, "backstory" text, "conscious_desire" text, "created_at" datetime(6) NOT NULL, "deliberately_absent" boolean DEFAULT FALSE NOT NULL, "desire_pursuit" varchar, "dexterity" integer, "dislikes" text, "fears" text, "fullname" varchar, "hit_die" integer, "hostile" boolean DEFAULT FALSE NOT NULL, "is_companion" boolean, "is_protagonist" boolean DEFAULT FALSE NOT NULL, "level" integer, "likes" text, "location_id" integer, "need_pursuit" varchar, "nickname" varchar, "personality" text, "race_id" integer NOT NULL, "recognized_need" text, "sex" varchar, "story_id" integer NOT NULL, "strength" integer, "unconscious_desire" text, "unrecognized_need" text, "updated_at" datetime(6) NOT NULL, "will" integer, "x" integer, "y" integer, CONSTRAINT "fk_rails_56a213cbe2" -FOREIGN KEY ("race_id") - REFERENCES "races" ("id") -, CONSTRAINT "fk_rails_0b4b445641" -FOREIGN KEY ("location_id") - REFERENCES "locations" ("id") -, CONSTRAINT "fk_rails_6608c248e8" -FOREIGN KEY ("story_id") - REFERENCES "stories" ("id") -); -INSERT INTO characters VALUES(1000000001,14,'Ink on her fingers and a satchel strap with no satchel.','The warden''s runner, sent with the tally and never with anything heavy.',NULL,'2026-10-25 22:57:24.717037',0,NULL,13,'Being told something is too heavy','Dropping the warden''s slate','Nell Fisk',6,0,0,1,1,'A tally that comes out even',NULL,NULL,'Nell','Quick, and certain she can carry more than she can.',1000000001,NULL,'female',1000000001,5,NULL,NULL,'2026-10-25 22:57:24.717037',12,NULL,NULL); -INSERT INTO characters VALUES(1000000002,41,'Shoulders like a door, and a weigh ticket tucked in his cap.','Has carried the market''s loads on the same yoke for twenty years.',NULL,'2026-10-25 22:57:24.767089',0,NULL,10,'A yoke set down in the wet','His back going','Bram Hale',8,0,0,0,2,'An even load',1000000001,NULL,'Bram','Slow to speak, and kind about other people''s arms.',1000000001,NULL,'male',1000000001,16,NULL,NULL,'2026-10-25 22:57:24.767089',10,NULL,NULL); CREATE TABLE IF NOT EXISTS "chats" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "cancelled" boolean DEFAULT FALSE NOT NULL, "character_id" integer, "created_at" datetime(6) NOT NULL, "model_id_string" varchar, "player_id" integer, "playthrough_id" integer, "purpose" varchar, "ruby_llm_model_id" integer, "updated_at" datetime(6) NOT NULL, CONSTRAINT "fk_rails_415520c982" FOREIGN KEY ("playthrough_id") REFERENCES "playthroughs" ("id") @@ -423,16 +411,32 @@ INSERT INTO schema_migrations VALUES('20250822184649'); INSERT INTO schema_migrations VALUES('20261027120117'); INSERT INTO schema_migrations VALUES('20261030120000'); INSERT INTO schema_migrations VALUES('20261030200000'); +INSERT INTO schema_migrations VALUES('20261030230000'); CREATE TABLE IF NOT EXISTS "ar_internal_metadata" ("key" varchar NOT NULL PRIMARY KEY, "value" varchar, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL); INSERT INTO ar_internal_metadata VALUES('environment','test','2026-10-25 22:57:23.355205','2026-10-25 22:57:23.355207'); INSERT INTO ar_internal_metadata VALUES('schema_sha1','9fcff787fff0ed60123ee84a65c7f80738f353f3','2026-10-25 22:57:23.358867','2026-10-25 22:57:23.358868'); -CREATE TABLE IF NOT EXISTS "floors" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "location_id" integer NOT NULL, "rooms" integer NOT NULL, "danger" varchar, "hazard" varchar, "down_location_id" integer, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, CONSTRAINT "fk_rails_ce8b005cb2" +CREATE TABLE IF NOT EXISTS "floors" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "location_id" integer NOT NULL, "rooms" integer NOT NULL, "danger" varchar, "hazard" varchar, "down_location_id" integer, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, "monster_level" integer, CONSTRAINT "fk_rails_ce8b005cb2" FOREIGN KEY ("location_id") REFERENCES "locations" ("id") ON DELETE CASCADE, CONSTRAINT "fk_rails_8a0268155a" FOREIGN KEY ("down_location_id") REFERENCES "locations" ("id") ON DELETE SET NULL); +CREATE TABLE IF NOT EXISTS "characters" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "age" integer, "appearance" text, "backstory" text, "conscious_desire" text, "created_at" datetime(6) NOT NULL, "deliberately_absent" boolean DEFAULT FALSE NOT NULL, "desire_pursuit" varchar, "dexterity" integer, "dislikes" text, "fears" text, "fullname" varchar, "hit_die" integer, "hostile" boolean DEFAULT FALSE NOT NULL, "is_companion" boolean, "is_protagonist" boolean DEFAULT FALSE NOT NULL, "level" integer, "likes" text, "location_id" integer, "need_pursuit" varchar, "nickname" varchar, "personality" text, "race_id" integer NOT NULL, "recognized_need" text, "sex" varchar, "story_id" integer NOT NULL, "strength" integer, "unconscious_desire" text, "unrecognized_need" text, "updated_at" datetime(6) NOT NULL, "will" integer, "x" integer, "y" integer, "posted_to_id" integer, CONSTRAINT "fk_rails_6608c248e8" +FOREIGN KEY ("story_id") + REFERENCES "stories" ("id") +, CONSTRAINT "fk_rails_0b4b445641" +FOREIGN KEY ("location_id") + REFERENCES "locations" ("id") +, CONSTRAINT "fk_rails_56a213cbe2" +FOREIGN KEY ("race_id") + REFERENCES "races" ("id") +, CONSTRAINT "fk_rails_4c54fc506e" +FOREIGN KEY ("posted_to_id") + REFERENCES "locations" ("id") + ON DELETE SET NULL); +INSERT INTO characters VALUES(1000000001,14,'Ink on her fingers and a satchel strap with no satchel.','The warden''s runner, sent with the tally and never with anything heavy.',NULL,'2026-10-25 22:57:24.717037',0,NULL,13,'Being told something is too heavy','Dropping the warden''s slate','Nell Fisk',6,0,0,1,1,'A tally that comes out even',NULL,NULL,'Nell','Quick, and certain she can carry more than she can.',1000000001,NULL,'female',1000000001,5,NULL,NULL,'2026-10-25 22:57:24.717037',12,NULL,NULL,NULL); +INSERT INTO characters VALUES(1000000002,41,'Shoulders like a door, and a weigh ticket tucked in his cap.','Has carried the market''s loads on the same yoke for twenty years.',NULL,'2026-10-25 22:57:24.767089',0,NULL,10,'A yoke set down in the wet','His back going','Bram Hale',8,0,0,0,2,'An even load',1000000001,NULL,'Bram','Slow to speak, and kind about other people''s arms.',1000000001,NULL,'male',1000000001,16,NULL,NULL,'2026-10-25 22:57:24.767089',10,NULL,NULL,NULL); PRAGMA writable_schema=ON; CREATE TABLE IF NOT EXISTS sqlite_sequence(name,seq); DELETE FROM sqlite_sequence; @@ -445,7 +449,6 @@ INSERT INTO sqlite_sequence VALUES('ruby_llm_models',1000000000); INSERT INTO sqlite_sequence VALUES('ruby_llm_tool_calls',1000000000); INSERT INTO sqlite_sequence VALUES('ruby_llm_usages',1000000000); INSERT INTO sqlite_sequence VALUES('universes',1000000001); -INSERT INTO sqlite_sequence VALUES('characters',1000000002); INSERT INTO sqlite_sequence VALUES('chats',1000000000); INSERT INTO sqlite_sequence VALUES('interactions',1000000000); INSERT INTO sqlite_sequence VALUES('items',1000000009); @@ -481,6 +484,7 @@ INSERT INTO sqlite_sequence VALUES('stories',1000000001); INSERT INTO sqlite_sequence VALUES('system_one_receipts',1000000000); INSERT INTO sqlite_sequence VALUES('world_events',1000000000); INSERT INTO sqlite_sequence VALUES('world_mechanics',1000000000); +INSERT INTO sqlite_sequence VALUES('characters',1000000002); CREATE INDEX "index_characters_scenes_on_character_id_and_scene_id" ON "characters_scenes" ("character_id", "scene_id"); CREATE INDEX "index_characters_scenes_on_scene_id_and_character_id" ON "characters_scenes" ("scene_id", "character_id"); CREATE UNIQUE INDEX "index_players_on_name" ON "players" ("name"); @@ -501,13 +505,6 @@ CREATE INDEX "index_ruby_llm_usages_on_chat_type_and_chat_id" ON "ruby_llm_usage CREATE INDEX "index_ruby_llm_usages_on_message_type_and_message_id" ON "ruby_llm_usages" ("message_type", "message_id"); CREATE INDEX "index_ruby_llm_usages_on_status" ON "ruby_llm_usages" ("status"); CREATE UNIQUE INDEX "index_ruby_llm_v2_backfills_on_task" ON "ruby_llm_v2_backfills" ("task"); -CREATE UNIQUE INDEX "index_characters_on_story_id_and_lower_fullname" ON "characters" (story_id, LOWER(fullname)); -CREATE INDEX "index_characters_on_location_id_and_hostile" ON "characters" ("location_id", "hostile"); -CREATE INDEX "index_characters_on_location_id_and_id" ON "characters" ("location_id", "id"); -CREATE INDEX "index_characters_on_location_id" ON "characters" ("location_id"); -CREATE INDEX "index_characters_on_race_id" ON "characters" ("race_id"); -CREATE INDEX "index_characters_on_story_id_and_is_protagonist" ON "characters" ("story_id", "is_protagonist"); -CREATE INDEX "index_characters_on_story_id" ON "characters" ("story_id"); CREATE INDEX "index_chats_on_character_id" ON "chats" ("character_id"); CREATE INDEX "index_chats_on_player_id" ON "chats" ("player_id"); CREATE INDEX "index_chats_on_conversation_key" ON "chats" ("playthrough_id", "character_id", "purpose"); @@ -643,5 +640,13 @@ CREATE INDEX "index_world_mechanics_on_story_id" ON "world_mechanics" ("story_id CREATE INDEX "index_quest_steps_on_companion_id" ON "quest_steps" ("companion_id"); CREATE UNIQUE INDEX "index_floors_on_location_id" ON "floors" ("location_id"); CREATE INDEX "index_floors_on_down_location_id" ON "floors" ("down_location_id"); +CREATE UNIQUE INDEX "index_characters_on_story_id_and_lower_fullname" ON "characters" (story_id, LOWER(fullname)); +CREATE INDEX "index_characters_on_location_id_and_hostile" ON "characters" ("location_id", "hostile"); +CREATE INDEX "index_characters_on_location_id_and_id" ON "characters" ("location_id", "id"); +CREATE INDEX "index_characters_on_location_id" ON "characters" ("location_id"); +CREATE INDEX "index_characters_on_race_id" ON "characters" ("race_id"); +CREATE INDEX "index_characters_on_story_id_and_is_protagonist" ON "characters" ("story_id", "is_protagonist"); +CREATE INDEX "index_characters_on_story_id" ON "characters" ("story_id"); +CREATE INDEX "index_characters_on_posted_to_id" ON "characters" ("posted_to_id"); PRAGMA writable_schema=OFF; COMMIT; diff --git a/parity/worlds/two-clerks-at-one-counter.sql b/parity/worlds/two-clerks-at-one-counter.sql index ead5a37..3b9d54f 100644 --- a/parity/worlds/two-clerks-at-one-counter.sql +++ b/parity/worlds/two-clerks-at-one-counter.sql @@ -15,19 +15,6 @@ CREATE TABLE IF NOT EXISTS "ruby_llm_usages" ("id" integer PRIMARY KEY AUTOINCRE CREATE TABLE IF NOT EXISTS "ruby_llm_v2_backfills" ("completed" boolean DEFAULT FALSE NOT NULL, "last_id" integer, "task" varchar NOT NULL); CREATE TABLE IF NOT EXISTS "universes" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "civilizations" text, "created_at" datetime(6) NOT NULL, "economics" text, "geographies" text, "history" text, "physics" text, "politics" text, "religion" text, "technology" text, "updated_at" datetime(6) NOT NULL, "weapons" text, "gravity" varchar, "carrying" varchar); INSERT INTO universes VALUES(1000000001,'A town that pawns what it cannot sell.','2026-10-02 13:25:55.847870','Pledges, tickets and redemptions.','A pawnbroker''s counter.','The shop has changed hands twice and its counter never.','Ordinary physics.','The shop answers to the town''s licensing clerk.','The town keeps a quiet calendar.','Ledgers, lamps and a brass till.','2026-10-02 13:25:55.847870','Nothing anybody carries.',NULL,NULL); -CREATE TABLE IF NOT EXISTS "characters" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "age" integer, "appearance" text, "backstory" text, "conscious_desire" text, "created_at" datetime(6) NOT NULL, "deliberately_absent" boolean DEFAULT FALSE NOT NULL, "desire_pursuit" varchar, "dexterity" integer, "dislikes" text, "fears" text, "fullname" varchar, "hit_die" integer, "hostile" boolean DEFAULT FALSE NOT NULL, "is_companion" boolean, "is_protagonist" boolean DEFAULT FALSE NOT NULL, "level" integer, "likes" text, "location_id" integer, "need_pursuit" varchar, "nickname" varchar, "personality" text, "race_id" integer NOT NULL, "recognized_need" text, "sex" varchar, "story_id" integer NOT NULL, "strength" integer, "unconscious_desire" text, "unrecognized_need" text, "updated_at" datetime(6) NOT NULL, "will" integer, "x" integer, "y" integer, CONSTRAINT "fk_rails_56a213cbe2" -FOREIGN KEY ("race_id") - REFERENCES "races" ("id") -, CONSTRAINT "fk_rails_0b4b445641" -FOREIGN KEY ("location_id") - REFERENCES "locations" ("id") -, CONSTRAINT "fk_rails_6608c248e8" -FOREIGN KEY ("story_id") - REFERENCES "stories" ("id") -); -INSERT INTO characters VALUES(1000000001,38,'A carter''s coat with straw on the shoulders.','A carter who pawned his late father''s watch and has come to get it back.','Ansel wants his father''s watch back across the counter before noon.','2026-10-02 13:25:56.043954',0,'obtain',12,'Being kept waiting at a counter.','Finding the watch already sold.','Ansel Mott',8,0,0,1,10,'A fair count.',NULL,'offer','Ansel','Quiet and determined.',1000000001,'Ansel needs to keep hold of the ticket, because it is the only proof he has.','male',1000000001,12,'Ansel wants somebody to tell him the pledge was not a betrayal.','Ansel needs to say out loud why he pawned it.','2026-10-02 13:25:56.043954',12,NULL,NULL); -INSERT INTO characters VALUES(1000000002,29,'Ink on her cuffs and a pencil behind one ear.','The pawnbroker''s niece, who keeps the ticket book and wants every stub back in it.','Tamsin wants every pawn ticket back in the book, starting with the one in Ansel''s hand.','2026-10-02 13:25:56.157492',0,'obtain',12,'A stub that walks out of the shop.','Her uncle finding the book short.','Tamsin Gale',8,0,0,0,10,'A ticket book with no gaps in it.',1000000001,'attend','Tamsin','Quick, friendly and acquisitive.',1000000001,'Tamsin needs to keep the book square.','female',1000000001,12,'Tamsin wants to be trusted with the till.','Tamsin needs to let one pledge go without a fight.','2026-10-02 13:25:56.157492',12,NULL,NULL); -INSERT INTO characters VALUES(1000000003,52,'A leather apron and a loupe on a cord.','The pawnbroker''s partner, who would rather close early than haggle.','Rowe wants the counter to himself and the door shut.','2026-10-02 13:25:56.174444',0,'keep',12,'Customers who lean on the counter.','Being made a fool of by a pledge.','Rowe Harker',8,0,0,0,10,'An empty shop at closing time.',1000000001,'withhold','Rowe','Gruff and territorial.',1000000001,'Rowe needs to keep the shop''s reputation.','male',1000000001,12,'Rowe wants somebody to ask his advice.','Rowe needs to admit the watch is already sold.','2026-10-02 13:25:56.174444',12,NULL,NULL); CREATE TABLE IF NOT EXISTS "chats" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "cancelled" boolean DEFAULT FALSE NOT NULL, "character_id" integer, "created_at" datetime(6) NOT NULL, "model_id_string" varchar, "player_id" integer, "playthrough_id" integer, "purpose" varchar, "ruby_llm_model_id" integer, "updated_at" datetime(6) NOT NULL, CONSTRAINT "fk_rails_415520c982" FOREIGN KEY ("playthrough_id") REFERENCES "playthroughs" ("id") @@ -411,6 +398,7 @@ INSERT INTO schema_migrations VALUES('20261025230000'); INSERT INTO schema_migrations VALUES('20261027120117'); INSERT INTO schema_migrations VALUES('20261030120000'); INSERT INTO schema_migrations VALUES('20261030200000'); +INSERT INTO schema_migrations VALUES('20261030230000'); CREATE TABLE IF NOT EXISTS "ar_internal_metadata" ("key" varchar NOT NULL PRIMARY KEY, "value" varchar, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL); INSERT INTO ar_internal_metadata VALUES('environment','test','2026-10-02 13:25:33.143313','2026-10-02 13:25:33.143316'); INSERT INTO ar_internal_metadata VALUES('schema_sha1','072abc2bfb80d111a1808df270e9210911f5e81d','2026-10-02 13:25:33.148224','2026-10-02 13:25:33.148228'); @@ -421,13 +409,29 @@ FOREIGN KEY ("playthrough_id") FOREIGN KEY ("quest_outcome_id") REFERENCES "quest_outcomes" ("id") ); -CREATE TABLE IF NOT EXISTS "floors" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "location_id" integer NOT NULL, "rooms" integer NOT NULL, "danger" varchar, "hazard" varchar, "down_location_id" integer, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, CONSTRAINT "fk_rails_ce8b005cb2" +CREATE TABLE IF NOT EXISTS "floors" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "location_id" integer NOT NULL, "rooms" integer NOT NULL, "danger" varchar, "hazard" varchar, "down_location_id" integer, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, "monster_level" integer, CONSTRAINT "fk_rails_ce8b005cb2" FOREIGN KEY ("location_id") REFERENCES "locations" ("id") ON DELETE CASCADE, CONSTRAINT "fk_rails_8a0268155a" FOREIGN KEY ("down_location_id") REFERENCES "locations" ("id") ON DELETE SET NULL); +CREATE TABLE IF NOT EXISTS "characters" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "age" integer, "appearance" text, "backstory" text, "conscious_desire" text, "created_at" datetime(6) NOT NULL, "deliberately_absent" boolean DEFAULT FALSE NOT NULL, "desire_pursuit" varchar, "dexterity" integer, "dislikes" text, "fears" text, "fullname" varchar, "hit_die" integer, "hostile" boolean DEFAULT FALSE NOT NULL, "is_companion" boolean, "is_protagonist" boolean DEFAULT FALSE NOT NULL, "level" integer, "likes" text, "location_id" integer, "need_pursuit" varchar, "nickname" varchar, "personality" text, "race_id" integer NOT NULL, "recognized_need" text, "sex" varchar, "story_id" integer NOT NULL, "strength" integer, "unconscious_desire" text, "unrecognized_need" text, "updated_at" datetime(6) NOT NULL, "will" integer, "x" integer, "y" integer, "posted_to_id" integer, CONSTRAINT "fk_rails_6608c248e8" +FOREIGN KEY ("story_id") + REFERENCES "stories" ("id") +, CONSTRAINT "fk_rails_0b4b445641" +FOREIGN KEY ("location_id") + REFERENCES "locations" ("id") +, CONSTRAINT "fk_rails_56a213cbe2" +FOREIGN KEY ("race_id") + REFERENCES "races" ("id") +, CONSTRAINT "fk_rails_4c54fc506e" +FOREIGN KEY ("posted_to_id") + REFERENCES "locations" ("id") + ON DELETE SET NULL); +INSERT INTO characters VALUES(1000000001,38,'A carter''s coat with straw on the shoulders.','A carter who pawned his late father''s watch and has come to get it back.','Ansel wants his father''s watch back across the counter before noon.','2026-10-02 13:25:56.043954',0,'obtain',12,'Being kept waiting at a counter.','Finding the watch already sold.','Ansel Mott',8,0,0,1,10,'A fair count.',NULL,'offer','Ansel','Quiet and determined.',1000000001,'Ansel needs to keep hold of the ticket, because it is the only proof he has.','male',1000000001,12,'Ansel wants somebody to tell him the pledge was not a betrayal.','Ansel needs to say out loud why he pawned it.','2026-10-02 13:25:56.043954',12,NULL,NULL,NULL); +INSERT INTO characters VALUES(1000000002,29,'Ink on her cuffs and a pencil behind one ear.','The pawnbroker''s niece, who keeps the ticket book and wants every stub back in it.','Tamsin wants every pawn ticket back in the book, starting with the one in Ansel''s hand.','2026-10-02 13:25:56.157492',0,'obtain',12,'A stub that walks out of the shop.','Her uncle finding the book short.','Tamsin Gale',8,0,0,0,10,'A ticket book with no gaps in it.',1000000001,'attend','Tamsin','Quick, friendly and acquisitive.',1000000001,'Tamsin needs to keep the book square.','female',1000000001,12,'Tamsin wants to be trusted with the till.','Tamsin needs to let one pledge go without a fight.','2026-10-02 13:25:56.157492',12,NULL,NULL,NULL); +INSERT INTO characters VALUES(1000000003,52,'A leather apron and a loupe on a cord.','The pawnbroker''s partner, who would rather close early than haggle.','Rowe wants the counter to himself and the door shut.','2026-10-02 13:25:56.174444',0,'keep',12,'Customers who lean on the counter.','Being made a fool of by a pledge.','Rowe Harker',8,0,0,0,10,'An empty shop at closing time.',1000000001,'withhold','Rowe','Gruff and territorial.',1000000001,'Rowe needs to keep the shop''s reputation.','male',1000000001,12,'Rowe wants somebody to ask his advice.','Rowe needs to admit the watch is already sold.','2026-10-02 13:25:56.174444',12,NULL,NULL,NULL); PRAGMA writable_schema=ON; CREATE TABLE IF NOT EXISTS sqlite_sequence(name,seq); DELETE FROM sqlite_sequence; @@ -440,7 +444,6 @@ INSERT INTO sqlite_sequence VALUES('ruby_llm_models',1000000000); INSERT INTO sqlite_sequence VALUES('ruby_llm_tool_calls',1000000000); INSERT INTO sqlite_sequence VALUES('ruby_llm_usages',1000000000); INSERT INTO sqlite_sequence VALUES('universes',1000000001); -INSERT INTO sqlite_sequence VALUES('characters',1000000003); INSERT INTO sqlite_sequence VALUES('chats',1000000000); INSERT INTO sqlite_sequence VALUES('interactions',1000000000); INSERT INTO sqlite_sequence VALUES('items',1000000002); @@ -475,6 +478,7 @@ INSERT INTO sqlite_sequence VALUES('stories',1000000001); INSERT INTO sqlite_sequence VALUES('system_one_receipts',1000000000); INSERT INTO sqlite_sequence VALUES('world_events',1000000000); INSERT INTO sqlite_sequence VALUES('world_mechanics',1000000000); +INSERT INTO sqlite_sequence VALUES('characters',1000000003); CREATE INDEX "index_characters_scenes_on_character_id_and_scene_id" ON "characters_scenes" ("character_id", "scene_id"); CREATE INDEX "index_characters_scenes_on_scene_id_and_character_id" ON "characters_scenes" ("scene_id", "character_id"); CREATE UNIQUE INDEX "index_players_on_name" ON "players" ("name"); @@ -495,13 +499,6 @@ CREATE INDEX "index_ruby_llm_usages_on_chat_type_and_chat_id" ON "ruby_llm_usage CREATE INDEX "index_ruby_llm_usages_on_message_type_and_message_id" ON "ruby_llm_usages" ("message_type", "message_id"); CREATE INDEX "index_ruby_llm_usages_on_status" ON "ruby_llm_usages" ("status"); CREATE UNIQUE INDEX "index_ruby_llm_v2_backfills_on_task" ON "ruby_llm_v2_backfills" ("task"); -CREATE UNIQUE INDEX "index_characters_on_story_id_and_lower_fullname" ON "characters" (story_id, LOWER(fullname)); -CREATE INDEX "index_characters_on_location_id_and_hostile" ON "characters" ("location_id", "hostile"); -CREATE INDEX "index_characters_on_location_id_and_id" ON "characters" ("location_id", "id"); -CREATE INDEX "index_characters_on_location_id" ON "characters" ("location_id"); -CREATE INDEX "index_characters_on_race_id" ON "characters" ("race_id"); -CREATE INDEX "index_characters_on_story_id_and_is_protagonist" ON "characters" ("story_id", "is_protagonist"); -CREATE INDEX "index_characters_on_story_id" ON "characters" ("story_id"); CREATE INDEX "index_chats_on_character_id" ON "chats" ("character_id"); CREATE INDEX "index_chats_on_player_id" ON "chats" ("player_id"); CREATE INDEX "index_chats_on_conversation_key" ON "chats" ("playthrough_id", "character_id", "purpose"); @@ -637,5 +634,13 @@ CREATE INDEX "index_playthrough_due_endings_on_quest_outcome_id" ON "playthrough CREATE INDEX "index_quest_steps_on_companion_id" ON "quest_steps" ("companion_id"); CREATE UNIQUE INDEX "index_floors_on_location_id" ON "floors" ("location_id"); CREATE INDEX "index_floors_on_down_location_id" ON "floors" ("down_location_id"); +CREATE UNIQUE INDEX "index_characters_on_story_id_and_lower_fullname" ON "characters" (story_id, LOWER(fullname)); +CREATE INDEX "index_characters_on_location_id_and_hostile" ON "characters" ("location_id", "hostile"); +CREATE INDEX "index_characters_on_location_id_and_id" ON "characters" ("location_id", "id"); +CREATE INDEX "index_characters_on_location_id" ON "characters" ("location_id"); +CREATE INDEX "index_characters_on_race_id" ON "characters" ("race_id"); +CREATE INDEX "index_characters_on_story_id_and_is_protagonist" ON "characters" ("story_id", "is_protagonist"); +CREATE INDEX "index_characters_on_story_id" ON "characters" ("story_id"); +CREATE INDEX "index_characters_on_posted_to_id" ON "characters" ("posted_to_id"); PRAGMA writable_schema=OFF; COMMIT; diff --git a/parity/worlds/two-floors-and-a-key.sql b/parity/worlds/two-floors-and-a-key.sql index 29e5d95..d221629 100644 --- a/parity/worlds/two-floors-and-a-key.sql +++ b/parity/worlds/two-floors-and-a-key.sql @@ -14,17 +14,6 @@ CREATE TABLE IF NOT EXISTS "ruby_llm_usages" ("id" integer PRIMARY KEY AUTOINCRE CREATE TABLE IF NOT EXISTS "ruby_llm_v2_backfills" ("task" varchar NOT NULL, "last_id" integer, "completed" boolean DEFAULT FALSE NOT NULL); CREATE TABLE IF NOT EXISTS "universes" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "physics" text, "technology" text, "weapons" text, "civilizations" text, "geographies" text, "history" text, "economics" text, "politics" text, "religion" text, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, "gravity" varchar, "carrying" varchar); INSERT INTO universes VALUES(1000000001,'Ordinary physics.','Lanterns, iron keys and hand tools.','Short blades and cudgels.','Delvers who sell what they bring up.','A dungeon cut down into black stone under a lantern-lit landing.','The dungeon was cut long ago by people nobody remembers.','Coin for what comes up from below.','Nobody governs the dungeon.','Delvers leave a coin on the landing for luck.','2026-10-30 12:22:18.247849','2026-10-30 12:22:18.247849',NULL,NULL); -CREATE TABLE IF NOT EXISTS "characters" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "story_id" integer NOT NULL, "fullname" varchar, "nickname" varchar, "age" integer, "sex" varchar, "personality" text, "appearance" text, "likes" text, "dislikes" text, "fears" text, "backstory" text, "is_companion" boolean, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, "race_id" integer NOT NULL, "is_protagonist" boolean DEFAULT FALSE NOT NULL, "location_id" integer, "deliberately_absent" boolean DEFAULT FALSE NOT NULL, "level" integer, "hit_die" integer, "strength" integer, "dexterity" integer, "will" integer, "hostile" boolean DEFAULT FALSE NOT NULL, "x" integer, "y" integer, "conscious_desire" text, "unconscious_desire" text, "recognized_need" text, "unrecognized_need" text, "desire_pursuit" varchar, "need_pursuit" varchar, CONSTRAINT "fk_rails_56a213cbe2" -FOREIGN KEY ("race_id") - REFERENCES "races" ("id") -, CONSTRAINT "fk_rails_0b4b445641" -FOREIGN KEY ("location_id") - REFERENCES "locations" ("id") -, CONSTRAINT "fk_rails_6608c248e8" -FOREIGN KEY ("story_id") - REFERENCES "stories" ("id") -); -INSERT INTO characters VALUES(1000000001,1000000001,'Odile','Odile',29,'female','Careful and stubborn.','A lantern on a pole and a coil of rope.','A clear way back.','Doors she cannot open.','The dark below the first floor.','A delver who has never been below the first floor.',0,'2026-10-30 12:22:18.511394','2026-10-30 12:22:18.511394',1000000001,1,NULL,0,3,8,12,12,12,0,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL); CREATE TABLE IF NOT EXISTS "chats" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "model_id_string" varchar, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, "ruby_llm_model_id" integer, "purpose" varchar, "playthrough_id" integer, "character_id" integer, "cancelled" boolean DEFAULT FALSE NOT NULL, "player_id" integer, CONSTRAINT "fk_rails_415520c982" FOREIGN KEY ("playthrough_id") REFERENCES "playthroughs" ("id") @@ -38,15 +27,15 @@ FOREIGN KEY ("player_id") FOREIGN KEY ("ruby_llm_model_id") REFERENCES "ruby_llm_models" ("id") ); -CREATE TABLE IF NOT EXISTS "floors" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "location_id" integer NOT NULL, "rooms" integer NOT NULL, "danger" varchar, "hazard" varchar, "down_location_id" integer, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, CONSTRAINT "fk_rails_8a0268155a" +CREATE TABLE IF NOT EXISTS "floors" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "location_id" integer NOT NULL, "rooms" integer NOT NULL, "danger" varchar, "hazard" varchar, "down_location_id" integer, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, "monster_level" integer, CONSTRAINT "fk_rails_8a0268155a" FOREIGN KEY ("down_location_id") REFERENCES "locations" ("id") ON DELETE SET NULL, CONSTRAINT "fk_rails_ce8b005cb2" FOREIGN KEY ("location_id") REFERENCES "locations" ("id") ON DELETE CASCADE); -INSERT INTO floors VALUES(1000000001,1000000002,4,'uneasy','none',1000000003,'2026-10-30 12:22:18.418505','2026-10-30 12:22:18.452529'); -INSERT INTO floors VALUES(1000000002,1000000003,2,'dangerous','unlit',NULL,'2026-10-30 12:22:18.431886','2026-10-30 12:22:18.466493'); +INSERT INTO floors VALUES(1000000001,1000000002,4,'uneasy','none',1000000003,'2026-10-30 12:22:18.418505','2026-10-30 12:22:18.452529',NULL); +INSERT INTO floors VALUES(1000000002,1000000003,2,'dangerous','unlit',NULL,'2026-10-30 12:22:18.431886','2026-10-30 12:22:18.466493',NULL); CREATE TABLE IF NOT EXISTS "interactions" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "character_id" integer NOT NULL, "scene_id" integer, "location_id" integer, "pre_thought" text, "pre_feeling" text, "action" text, "post_feeling" text, "post_thought" text, "inner_resolution" text, "summary" text, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, "user_input" text, "engine_action" varchar, "action_status" varchar, "action_fact" text, CONSTRAINT "fk_rails_294d4ae9f8" FOREIGN KEY ("location_id") REFERENCES "locations" ("id") @@ -431,9 +420,24 @@ INSERT INTO schema_migrations VALUES('20250823023925'); INSERT INTO schema_migrations VALUES('20250822184651'); INSERT INTO schema_migrations VALUES('20250822184650'); INSERT INTO schema_migrations VALUES('20250822184649'); +INSERT INTO schema_migrations VALUES('20261030230000'); CREATE TABLE IF NOT EXISTS "ar_internal_metadata" ("key" varchar NOT NULL PRIMARY KEY, "value" varchar, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL); INSERT INTO ar_internal_metadata VALUES('environment','test','2026-10-30 12:22:17.036817','2026-10-30 12:22:17.036819'); INSERT INTO ar_internal_metadata VALUES('schema_sha1','79cd55df42fd029083ae3c2121533177a39731eb','2026-10-30 12:22:17.042088','2026-10-30 12:22:17.042090'); +CREATE TABLE IF NOT EXISTS "characters" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "story_id" integer NOT NULL, "fullname" varchar, "nickname" varchar, "age" integer, "sex" varchar, "personality" text, "appearance" text, "likes" text, "dislikes" text, "fears" text, "backstory" text, "is_companion" boolean, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL, "race_id" integer NOT NULL, "is_protagonist" boolean DEFAULT FALSE NOT NULL, "location_id" integer, "deliberately_absent" boolean DEFAULT FALSE NOT NULL, "level" integer, "hit_die" integer, "strength" integer, "dexterity" integer, "will" integer, "hostile" boolean DEFAULT FALSE NOT NULL, "x" integer, "y" integer, "conscious_desire" text, "unconscious_desire" text, "recognized_need" text, "unrecognized_need" text, "desire_pursuit" varchar, "need_pursuit" varchar, "posted_to_id" integer, CONSTRAINT "fk_rails_6608c248e8" +FOREIGN KEY ("story_id") + REFERENCES "stories" ("id") +, CONSTRAINT "fk_rails_0b4b445641" +FOREIGN KEY ("location_id") + REFERENCES "locations" ("id") +, CONSTRAINT "fk_rails_56a213cbe2" +FOREIGN KEY ("race_id") + REFERENCES "races" ("id") +, CONSTRAINT "fk_rails_4c54fc506e" +FOREIGN KEY ("posted_to_id") + REFERENCES "locations" ("id") + ON DELETE SET NULL); +INSERT INTO characters VALUES(1000000001,1000000001,'Odile','Odile',29,'female','Careful and stubborn.','A lantern on a pole and a coil of rope.','A clear way back.','Doors she cannot open.','The dark below the first floor.','A delver who has never been below the first floor.',0,'2026-10-30 12:22:18.511394','2026-10-30 12:22:18.511394',1000000001,1,NULL,0,3,8,12,12,12,0,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL); PRAGMA writable_schema=ON; CREATE TABLE IF NOT EXISTS sqlite_sequence(name,seq); DELETE FROM sqlite_sequence; @@ -447,7 +451,6 @@ INSERT INTO sqlite_sequence VALUES('ruby_llm_models',1000000000); INSERT INTO sqlite_sequence VALUES('ruby_llm_tool_calls',1000000000); INSERT INTO sqlite_sequence VALUES('ruby_llm_usages',1000000000); INSERT INTO sqlite_sequence VALUES('universes',1000000001); -INSERT INTO sqlite_sequence VALUES('characters',1000000001); INSERT INTO sqlite_sequence VALUES('chats',1000000000); INSERT INTO sqlite_sequence VALUES('floors',1000000002); INSERT INTO sqlite_sequence VALUES('interactions',1000000000); @@ -484,6 +487,7 @@ INSERT INTO sqlite_sequence VALUES('stories',1000000001); INSERT INTO sqlite_sequence VALUES('system_one_receipts',1000000000); INSERT INTO sqlite_sequence VALUES('world_events',1000000000); INSERT INTO sqlite_sequence VALUES('world_mechanics',1000000000); +INSERT INTO sqlite_sequence VALUES('characters',1000000001); CREATE INDEX "index_characters_scenes_on_character_id_and_scene_id" ON "characters_scenes" ("character_id", "scene_id"); CREATE INDEX "index_characters_scenes_on_scene_id_and_character_id" ON "characters_scenes" ("scene_id", "character_id"); CREATE UNIQUE INDEX "index_players_on_name" ON "players" ("name"); @@ -505,13 +509,6 @@ CREATE INDEX "index_ruby_llm_usages_on_chat_type_and_chat_id" ON "ruby_llm_usage CREATE INDEX "index_ruby_llm_usages_on_message_type_and_message_id" ON "ruby_llm_usages" ("message_type", "message_id"); CREATE INDEX "index_ruby_llm_usages_on_status" ON "ruby_llm_usages" ("status"); CREATE UNIQUE INDEX "index_ruby_llm_v2_backfills_on_task" ON "ruby_llm_v2_backfills" ("task"); -CREATE UNIQUE INDEX "index_characters_on_story_id_and_lower_fullname" ON "characters" (story_id, LOWER(fullname)); -CREATE INDEX "index_characters_on_location_id_and_hostile" ON "characters" ("location_id", "hostile"); -CREATE INDEX "index_characters_on_location_id_and_id" ON "characters" ("location_id", "id"); -CREATE INDEX "index_characters_on_location_id" ON "characters" ("location_id"); -CREATE INDEX "index_characters_on_race_id" ON "characters" ("race_id"); -CREATE INDEX "index_characters_on_story_id_and_is_protagonist" ON "characters" ("story_id", "is_protagonist"); -CREATE INDEX "index_characters_on_story_id" ON "characters" ("story_id"); CREATE INDEX "index_chats_on_character_id" ON "chats" ("character_id"); CREATE INDEX "index_chats_on_player_id" ON "chats" ("player_id"); CREATE INDEX "index_chats_on_conversation_key" ON "chats" ("playthrough_id", "character_id", "purpose"); @@ -647,5 +644,13 @@ CREATE INDEX "index_world_events_on_story_id" ON "world_events" ("story_id"); CREATE INDEX "index_world_events_on_world_mechanic_id" ON "world_events" ("world_mechanic_id"); CREATE UNIQUE INDEX "index_world_mechanics_on_story_id_and_name" ON "world_mechanics" ("story_id", "name"); CREATE INDEX "index_world_mechanics_on_story_id" ON "world_mechanics" ("story_id"); +CREATE UNIQUE INDEX "index_characters_on_story_id_and_lower_fullname" ON "characters" (story_id, LOWER(fullname)); +CREATE INDEX "index_characters_on_location_id_and_hostile" ON "characters" ("location_id", "hostile"); +CREATE INDEX "index_characters_on_location_id_and_id" ON "characters" ("location_id", "id"); +CREATE INDEX "index_characters_on_location_id" ON "characters" ("location_id"); +CREATE INDEX "index_characters_on_race_id" ON "characters" ("race_id"); +CREATE INDEX "index_characters_on_story_id_and_is_protagonist" ON "characters" ("story_id", "is_protagonist"); +CREATE INDEX "index_characters_on_story_id" ON "characters" ("story_id"); +CREATE INDEX "index_characters_on_posted_to_id" ON "characters" ("posted_to_id"); PRAGMA writable_schema=OFF; COMMIT; diff --git a/src/floor.rs b/src/floor.rs index 1d76059..93ea7b8 100644 --- a/src/floor.rs +++ b/src/floor.rs @@ -7,7 +7,8 @@ //! Every draw comes from the place's own generator (`Roll::INTERIOR`), in //! this order: the grid's column widths, row depths and serpentine, each //! room's danger and hazard as it is created, the way-down room, then the -//! extra doors. A place with no `floors` row is laid out by `interior`, +//! extra doors, and last, on a guarded floor with no way down, the room its +//! guards stand in. A place with no `floors` row is laid out by `interior`, //! exactly as before. use crate::interior::{Builder, Layout, Place, WALKING}; @@ -34,6 +35,8 @@ pub struct Floor<'a> { pub hazard: Option<&'a str>, /// Whether the floor has a way down (`floors.down_location_id`). pub down: bool, + /// Whether anybody is posted to the floor (`characters.posted_to_id`). + pub guarded: bool, } /// A floor's rooms and doorways, and which room the way down lands on. @@ -42,6 +45,10 @@ pub struct Laid { pub layout: Layout, /// The index of the way-down room in `layout.rooms`; never 0, the entry. pub way_down: Option, + /// The index of the room a guarded floor's guards stand in: the way-down + /// room, or with no way down a room rolled after every other draw, never + /// the entry unless it is the only room. + pub guard_room: Option, } /// How many rooms a floor is drawn with: its own count, at least one, and @@ -112,6 +119,11 @@ pub fn lay_out(place: &Place, floor: &Floor) -> Laid { { room.kind = dealt; } + let guard_room = floor.guarded.then(|| match way_down { + Some(room) => room, + None if count > 1 => 1 + roll::one_of(count - 1, &mut builder.rng), + None => 0, + }); Laid { layout: Layout { footprint: (width, depth), @@ -119,6 +131,7 @@ pub fn lay_out(place: &Place, floor: &Floor) -> Laid { edges: builder.edges, }, way_down, + guard_room, } } @@ -176,10 +189,22 @@ mod tests { danger: Some("dangerous"), hazard: Some("unlit"), down, + guarded: id % 2 == 0, }; let laid = lay_out(&place(id * 1009, None), &floor); let layout = &laid.layout; let what = format!("{rooms} rooms, place {}, down {down}", id * 1009); + match laid.guard_room { + Some(room) => { + assert!(floor.guarded, "{what}"); + assert!(room < layout.rooms.len(), "{what}"); + assert!(room != 0 || layout.rooms.len() == 1, "{what}"); + if down { + assert_eq!(Some(room), laid.way_down, "{what}"); + } + } + None => assert!(!floor.guarded, "{what}"), + } assert_eq!(layout.rooms.len(), room_count(&floor), "{what}"); let mut reached = reachable(layout); reached.sort_unstable(); @@ -260,6 +285,7 @@ mod tests { danger: Some("uneasy"), hazard: Some("flooded"), down: true, + guarded: false, }; assert_eq!( lay_out(&place(42, Some("warren")), &floor), @@ -272,6 +298,55 @@ mod tests { ); } + /// Guards are the last draw: a guarded floor's rooms, doorways and way + /// down are the unguarded floor's, and its guards stand on the way down, + /// or with none in a room rolled off the entry. + #[test] + fn guards_stand_on_the_way_down_and_move_no_other_draw() { + for down in [false, true] { + for id in 1..=40 { + let floor = Floor { + rooms: 9, + danger: Some("uneasy"), + hazard: None, + down, + guarded: false, + }; + let open = lay_out(&place(id, Some("warren")), &floor); + let guarded = lay_out( + &place(id, Some("warren")), + &Floor { + guarded: true, + ..floor + }, + ); + assert_eq!(open.guard_room, None); + assert_eq!(guarded.layout, open.layout); + assert_eq!(guarded.way_down, open.way_down); + let room = guarded + .guard_room + .expect("a guarded floor has a guard room"); + assert_ne!(room, 0, "place {id}"); + if down { + assert_eq!(Some(room), open.way_down, "place {id}"); + } + } + } + let alone = lay_out( + &place(5, None), + &Floor { + rooms: 1, + guarded: true, + ..Floor::default() + }, + ); + assert_eq!( + alone.guard_room, + Some(0), + "one room is the only room to stand in" + ); + } + #[test] fn the_grid_is_as_square_as_it_goes_and_the_footprint_holds_it() { assert_eq!(grid(1), (1, 1)); @@ -304,6 +379,7 @@ mod tests { danger: Some("safe"), hazard: None, down: true, + guarded: false, }; let laid = lay_out(&place(9, None), &floor); let safe = Parameters::default().danger_for(0); diff --git a/src/store.rs b/src/store.rs index b031a4d..367f5f0 100644 --- a/src/store.rs +++ b/src/store.rs @@ -90,14 +90,21 @@ pub const REFUSED_LINES: &str = "refused_lines"; pub const OPTIONAL: &[&str] = &[REFUSED_LINES]; /// Columns the engine reads only where the database has them, each as the -/// fact [`shape`] would state: a story's own people and thing limits -/// (migration 20261030120000), which read as null, and so as the ordinary -/// limits, in a database from before them. [`shape`] leaves them out, so -/// a database opens with or without them; one declared any other way is -/// still a difference. +/// fact [`shape`] would state, with the key and index each comes with: a +/// story's own people and thing limits (migration 20261030120000), which +/// read as null, and so as the ordinary limits, in a database from before +/// them; and a floor's monster level and the floor a person is posted to +/// (migration 20261030230000), which read as null, and so as nobody born +/// stronger and nobody posted. [`shape`] leaves them out, so a database +/// opens with or without them; one declared any other way is still a +/// difference. pub const OPTIONAL_COLUMNS: &[&str] = &[ "column stories.people_limit integer pk=0", "column stories.things_limit integer pk=0", + "column floors.monster_level integer pk=0", + "column characters.posted_to_id integer pk=0", + "foreign key characters.posted_to_id -> locations.id on update NO ACTION on delete SET NULL", + "index characters (posted_to_id)", ]; /// A floor's own layout numbers, one row per place laid out as a floor diff --git a/src/turn/told/realize.rs b/src/turn/told/realize.rs index c16eea7..42ab761 100644 --- a/src/turn/told/realize.rs +++ b/src/turn/told/realize.rs @@ -33,7 +33,7 @@ use crate::records::{flag, id, int, string, text, Row}; use crate::roll::{self, Seed}; use crate::room::USE_KINDS; use crate::spot; -use crate::stat_block; +use crate::stat_block::{self, StatBlock}; use crate::store; use crate::text::{is_blank, natural_key, ruby_downcase}; use arc::Bound; @@ -911,6 +911,20 @@ impl Turn<'_, '_> { .len() as i64 } + /// The level everybody born at `location` is given when it is a floor or + /// a room of one whose row names one (`floors.monster_level`); none + /// leaves the ordinary starting level. + fn monster_level(&self, location: i64) -> Option { + let row = self.m.records.find("locations", location)?; + let places = [Some(location), int(row, "parent_location_id")]; + self.m + .records + .first(store::FLOORS, |floor| { + places.contains(&int(floor, "location_id")) + }) + .and_then(|floor| int(floor, "monster_level")) + } + /// A proposal naming somebody the story already has: they may be here /// already, or brought here from nowhere, and nobody is moved. fn admit_known(&mut self, location: i64, name: &str) -> Result, Error> { @@ -1015,7 +1029,12 @@ impl Turn<'_, '_> { .records .find("races", *race) .is_some_and(|row| flag(row, "monstrous")); - let block = stat_block::roll(story, self.m.clock(), slot as i64); + let block = StatBlock { + level: self + .monster_level(location) + .unwrap_or(stat_block::STARTING_LEVEL), + ..stat_block::roll(story, self.m.clock(), slot as i64) + }; let mut values: Vec<(&str, Value)> = vec![ ("story_id", Value::from(story)), ("fullname", Value::from(fullname.as_str())), @@ -1320,7 +1339,11 @@ impl Turn<'_, '_> { }, density: text(&row, "density"), }; - let (layout, way_down) = match &floor_row { + let guards = match floor_row { + Some(_) => self.posted_to(location), + None => Vec::new(), + }; + let (layout, way_down, guard_room) = match &floor_row { Some(floor_row) => { let laid = floor::lay_out( &place, @@ -1329,11 +1352,12 @@ impl Turn<'_, '_> { danger: text(floor_row, "danger"), hazard: text(floor_row, "hazard"), down: int(floor_row, "down_location_id").is_some(), + guarded: !guards.is_empty(), }, ); - (laid.layout, laid.way_down) + (laid.layout, laid.way_down, laid.guard_room) } - None => (interior::lay_out(&place, None, parameters), None), + None => (interior::lay_out(&place, None, parameters), None, None), }; self.m.update( "locations", @@ -1394,7 +1418,41 @@ impl Turn<'_, '_> { .and_then(|row| int(row, "down_location_id")), ) .map(|(index, down)| (rooms[index], down)); - self.open_the_way_in(location, &rooms, way_down) + self.open_the_way_in(location, &rooms, way_down)?; + if let Some(index) = guard_room { + self.post_guards(rooms[index], &guards)?; + } + Ok(()) + } + + /// The people the world posts to a floor (`characters.posted_to_id`) + /// who stand nowhere yet, in id order: its guards, whom the file keeps + /// absent until the floor is laid out. + fn posted_to(&self, floor: i64) -> Vec { + self.m + .records + .select("characters", |who| { + int(who, "posted_to_id") == Some(floor) && int(who, "location_id").is_none() + }) + .into_iter() + .map(id) + .collect() + } + + /// A floor's guards stood in `room`, each placed in it as an admitted + /// person is. This is part of laying the floor out, so it happens once, + /// for the world: no later game, and no later absence the file writes, + /// moves them again. + fn post_guards(&mut self, room: i64, guards: &[i64]) -> Result<(), Error> { + for guard in guards { + let mut values = vec![ + ("location_id", Value::from(room)), + ("deliberately_absent", Value::Bool(false)), + ]; + values.extend(self.placement(room, spot::Record::Character(*guard))); + self.m.update("characters", *guard, values)?; + } + Ok(()) } /// `#open_the_way_in!`: every doorway that led to the building now leads diff --git a/tests/engine.rs b/tests/engine.rs index b25325c..7795803 100644 --- a/tests/engine.rs +++ b/tests/engine.rs @@ -699,6 +699,107 @@ FOREIGN KEY ("down_location_id") ); } +/// The guarded world with `characters` rebuilt as it was before +/// migration 20261030230000, the way Rails' SQLite adapter rebuilds a +/// table: no `posted_to_id`, its key or its index; and `floors` without +/// `monster_level`. +fn from_before_the_guards() -> rusqlite::Connection { + let conn = rusqlite::Connection::open_in_memory().unwrap(); + conn.execute_batch(&world("a-guarded-stair")).unwrap(); + let create: String = conn + .query_row( + "SELECT sql FROM sqlite_master WHERE type = 'table' AND name = 'characters'", + [], + |row| row.get(0), + ) + .unwrap(); + let key = create + .find(r#", CONSTRAINT "fk_rails_4c54fc506e""#) + .expect("the posted_to key"); + let cascade = " ON DELETE SET NULL"; + let end = key + create[key..].find(cascade).expect("its action") + cascade.len(); + let older = format!("{}{}", &create[..key], &create[end..]) + .replace(r#", "posted_to_id" integer"#, "") + .replacen(r#""characters""#, r#""older_characters""#, 1); + let mut statement = conn + .prepare( + "SELECT sql FROM sqlite_master WHERE type = 'index' AND tbl_name = 'characters' \ + AND sql IS NOT NULL AND name <> 'index_characters_on_posted_to_id'", + ) + .unwrap(); + let indexes: Vec = statement + .query_map([], |row| row.get(0)) + .unwrap() + .collect::>() + .unwrap(); + drop(statement); + let mut statement = conn + .prepare("SELECT name FROM pragma_table_info('characters') WHERE name <> 'posted_to_id'") + .unwrap(); + let columns: Vec = statement + .query_map([], |row| row.get::<_, String>(0)) + .unwrap() + .map(|name| format!("\"{}\"", name.unwrap())) + .collect(); + drop(statement); + let columns = columns.join(", "); + conn.execute_batch(&format!( + r#"PRAGMA foreign_keys = OFF; + {older}; + INSERT INTO "older_characters" ({columns}) SELECT {columns} FROM "characters"; + DROP TABLE "characters"; + ALTER TABLE "older_characters" RENAME TO "characters"; + {}; + ALTER TABLE "floors" DROP COLUMN "monster_level"; + DELETE FROM schema_migrations WHERE version = '20261030230000'; + PRAGMA foreign_keys = ON;"#, + indexes.join(";\n") + )) + .unwrap(); + conn +} + +#[test] +fn a_database_from_before_the_guards_is_opened_and_plays() { + let conn = from_before_the_guards(); + assert_eq!( + conn.query_row( + "SELECT COUNT(*) FROM pragma_table_info('characters') WHERE name = 'posted_to_id'", + [], + |row| row.get::<_, i64>(0) + ) + .unwrap(), + 0 + ); + let mut engine = Engine::from_connection(conn).expect("the older shape is opened"); + let story = engine + .story_titled(&format!("A Guarded Stair{TITLE_SUFFIX}")) + .unwrap(); + let playthrough = engine.start(story).unwrap(); + let outcome = engine + .play(playthrough, "look around", &mut |_| {}) + .unwrap(); + assert!(outcome.state.location.is_some()); +} + +#[test] +fn a_monster_level_or_a_posting_declared_another_way_is_refused() { + let differences = changed(migrated( + "a-deeper-floor", + r#"ALTER TABLE "floors" DROP COLUMN "monster_level"; + ALTER TABLE "floors" ADD "monster_level" varchar;"#, + )); + assert_eq!( + differences, + ["found column floors.monster_level varchar pk=0"] + ); + let differences = changed(migrated( + "a-guarded-stair", + r#"CREATE INDEX "index_characters_on_posted_to_id_and_id" ON "characters" ("posted_to_id", "id");"#, + )); + assert_eq!(differences, ["found index characters (posted_to_id, id)"]); +} + #[test] fn a_newer_schema_that_adds_a_unique_index_to_a_table_the_engine_uses_is_refused() { let differences = changed(migrated( diff --git a/tests/floor_people.rs b/tests/floor_people.rs new file mode 100644 index 0000000..9e877d2 --- /dev/null +++ b/tests/floor_people.rs @@ -0,0 +1,371 @@ +//! Who stands on a floor, as a caller holds it: the guard a world posts to +//! a floor, stood on its way down when the floor is laid out, and the level +//! everybody born on a floor is given. A Guarded Stair and A Deeper Floor +//! walked into on the live path with a scripted model, and the rows read +//! back. +//! +//! In A Guarded Stair, Brask the Stairwarden is posted to The Upper Floor +//! (three rooms, a way down to The Lower Floor) and kept absent by the file. +//! In A Deeper Floor, The Bottom Floor's row names monster level 4 and The +//! Upper Floor's names none; each is one room. + +use renderedstep_engine::engine::Engine; +use renderedstep_engine::model::{Live, Route, Secret}; +use renderedstep_engine::parity::{open_world, TITLE_SUFFIX}; +use serde_json::{json, Value}; +use std::path::Path; + +fn world(name: &str) -> String { + let path = Path::new(env!("CARGO_MANIFEST_DIR")).join(format!("parity/worlds/{name}.sql")); + std::fs::read_to_string(path).expect("a world fixture") +} + +struct Scripted { + answers: Vec<(u16, Value)>, + sent: Vec, +} + +impl renderedstep_engine::model::http::Transport for Scripted { + fn post( + &mut self, + _endpoint: &renderedstep_engine::model::route::Endpoint, + body: &Value, + _options: &renderedstep_engine::model::http::Options, + _on_line: Option<&mut (dyn FnMut(&str) + '_)>, + ) -> Result + { + self.sent.push(body.clone()); + let (status, answer) = self.answers.remove(0); + Ok(renderedstep_engine::model::http::Posted { + status, + body: answer.to_string(), + }) + } +} + +fn answered(content: Value) -> (u16, Value) { + ( + 200, + json!({"choices": [{"message": {"content": content.to_string()}, "finish_reason": "stop"}]}), + ) +} + +fn place() -> Value { + json!({ + "description": "Black stone passages lit by nothing you can see.", + "lore": "Cut by people nobody remembers.", + "place_kind": "warren", + }) +} + +/// A room's answer, naming one person, whom the room may or may not have +/// a place for. +fn room() -> Value { + json!({ + "description": "A bare stone chamber.", + "lore": "Old.", + "items": [], + "people": [{ + "fullname": "Hollin Graves", "nickname": "Hollin", "appearance": "A grey coat.", + "personality": "Quiet.", "backstory": "A delver who stayed.", + "likes": "Silence.", "dislikes": "Lanterns.", "fears": "The surface.", + }], + }) +} + +fn arrival() -> Value { + json!({"description": "You arrive.", "summary": "Arrived."}) +} + +/// `line` submitted on the live path, the model answering `answers` in +/// turn; how many calls it made. +fn submitted(engine: &mut Engine, playthrough: i64, line: &str, answers: Vec) -> usize { + let transport = Scripted { + answers: answers.into_iter().map(answered).collect(), + sent: Vec::new(), + }; + let route = Route::Direct { + key: Secret::new("own"), + }; + let mut live = Live::with_transport(route, transport); + engine + .submit(playthrough, line, line, &mut live, &mut |_| {}) + .unwrap(); + live.transport().sent.len() +} + +fn played(engine: &mut Engine, playthrough: i64, line: &str) -> Option { + engine + .play(playthrough, line, &mut |_| {}) + .unwrap() + .report + .refusal +} + +fn opened(name: &str, title: &str) -> (Engine, i64) { + let engine = open_world(&world(name)).unwrap(); + let story = engine + .story_titled(&format!("{title}{TITLE_SUFFIX}")) + .unwrap(); + (engine, story) +} + +fn guarded() -> (Engine, i64) { + opened("a-guarded-stair", "A Guarded Stair") +} + +fn int(engine: &Engine, sql: &str) -> i64 { + engine + .store() + .connection() + .query_row(sql, [], |row| row.get(0)) + .unwrap() +} + +fn execute(engine: &Engine, sql: &str) { + engine.store().connection().execute_batch(sql).unwrap(); +} + +/// Every row `sql` selects, each column as text. +fn rows(engine: &Engine, sql: &str) -> Vec> { + let connection = engine.store().connection(); + let mut statement = connection.prepare(sql).unwrap(); + let width = statement.column_count(); + statement + .query_map([], |row| { + (0..width) + .map(|column| { + row.get::<_, rusqlite::types::Value>(column) + .map(|value| match value { + rusqlite::types::Value::Null => "null".to_string(), + rusqlite::types::Value::Integer(n) => n.to_string(), + rusqlite::types::Value::Text(text) => text, + other => format!("{other:?}"), + }) + }) + .collect() + }) + .unwrap() + .collect::>() + .unwrap() +} + +fn location(engine: &Engine, name: &str) -> i64 { + int( + engine, + &format!("SELECT id FROM locations WHERE name = '{name}'"), + ) +} + +fn brask(engine: &Engine) -> Vec { + rows( + engine, + "SELECT location_id, deliberately_absent, posted_to_id, x, y FROM characters \ + WHERE fullname = 'Brask the Stairwarden'", + ) + .pop() + .expect("Brask is in the world") +} + +/// The room of The Upper Floor its stair down to The Lower Floor leaves. +fn way_down(engine: &Engine) -> i64 { + let lower = location(engine, "The Lower Floor"); + int( + engine, + &format!( + "SELECT location_id FROM location_connections WHERE connected_location_id = {lower} \ + OR connected_location_id IN (SELECT id FROM locations WHERE parent_location_id = {lower})" + ), + ) +} + +fn entry(engine: &Engine, floor: i64) -> i64 { + int( + engine, + &format!("SELECT MIN(id) FROM locations WHERE parent_location_id = {floor}"), + ) +} + +#[test] +fn a_posted_guard_is_stood_on_the_way_down_when_the_floor_is_laid_out() { + let (mut engine, story) = guarded(); + let floor = location(&engine, "The Upper Floor"); + assert_eq!( + brask(&engine)[..3], + ["null", "1", &floor.to_string()], + "absent, and posted to the floor, until it is laid out" + ); + let game = engine.start(story).unwrap(); + submitted( + &mut engine, + game, + "/move The Upper Floor", + vec![place(), room(), arrival()], + ); + + let stood = brask(&engine); + let down = way_down(&engine); + assert_ne!(down, entry(&engine, floor)); + assert_eq!( + stood[..3], + [down.to_string(), "0".into(), floor.to_string()], + "in the way-down room, no longer absent, and still posted" + ); + let (x, y): (i64, i64) = (stood[3].parse().unwrap(), stood[4].parse().unwrap()); + let bounds: Vec = rows( + &engine, + &format!("SELECT x, y, width, depth FROM locations WHERE id = {down}"), + ) + .pop() + .unwrap() + .iter() + .map(|side| side.parse().unwrap()) + .collect(); + assert!( + (bounds[0]..bounds[0] + bounds[2]).contains(&x), + "{stood:?} in {bounds:?}" + ); + assert!( + (bounds[1]..bounds[1] + bounds[3]).contains(&y), + "{stood:?} in {bounds:?}" + ); +} + +#[test] +fn a_guard_is_stood_once_for_the_world_and_never_again() { + let (mut engine, story) = guarded(); + let first = engine.start(story).unwrap(); + submitted( + &mut engine, + first, + "/move The Upper Floor", + vec![place(), room(), arrival()], + ); + let stood = brask(&engine); + + // Another game walks the floor as it was laid out: he is where he was. + let second = engine.start(story).unwrap(); + submitted( + &mut engine, + second, + "/move The Upper Floor room 1", + vec![arrival()], + ); + assert_eq!(brask(&engine), stood); + + // The file writes him absent again after the layout: no game's walk + // onto the floor stands him anywhere a second time. + execute( + &engine, + "UPDATE characters SET location_id = NULL, deliberately_absent = 1, x = NULL, y = NULL \ + WHERE fullname = 'Brask the Stairwarden';", + ); + let third = engine.start(story).unwrap(); + submitted( + &mut engine, + third, + "/move The Upper Floor room 1", + vec![arrival()], + ); + assert_eq!( + played(&mut engine, first, "/move The Upper Floor room 2"), + None + ); + assert_eq!(brask(&engine)[..3], ["null", "1", &stood[2]]); +} + +#[test] +fn a_posted_guard_who_already_stands_somewhere_is_left_there() { + let (mut engine, story) = guarded(); + let landing = location(&engine, "The Landing"); + execute( + &engine, + &format!( + "UPDATE characters SET location_id = {landing}, deliberately_absent = 0 \ + WHERE fullname = 'Brask the Stairwarden';" + ), + ); + let game = engine.start(story).unwrap(); + submitted( + &mut engine, + game, + "/move The Upper Floor", + vec![place(), room(), arrival()], + ); + assert_eq!(brask(&engine)[..2], [landing.to_string(), "0".into()]); +} + +#[test] +fn on_a_floor_with_no_way_down_a_guard_stands_in_a_room_off_the_entry() { + let (mut engine, story) = guarded(); + let floor = location(&engine, "The Upper Floor"); + execute( + &engine, + &format!("UPDATE floors SET down_location_id = NULL WHERE location_id = {floor};"), + ); + let game = engine.start(story).unwrap(); + submitted( + &mut engine, + game, + "/move The Upper Floor", + vec![place(), room(), arrival()], + ); + let rooms = rows( + &engine, + &format!("SELECT id FROM locations WHERE parent_location_id = {floor} ORDER BY id"), + ); + assert_eq!(rooms.len(), 3); + let stood = brask(&engine); + assert_ne!(stood[0], rooms[0][0], "never the entry"); + assert!(rooms.iter().any(|room| room[0] == stood[0]), "{stood:?}"); +} + +/// The level, hit die and abilities of the person born on `floor`'s room +/// of A Deeper Floor, with the floors' monster levels set as `levels`. +fn born_on(floor: &str, levels: &str) -> Vec { + let (mut engine, story) = opened("a-deeper-floor", "A Deeper Floor"); + execute(&engine, levels); + let game = engine.start(story).unwrap(); + submitted( + &mut engine, + game, + &format!("/move {floor}"), + vec![place(), room(), arrival()], + ); + let room = entry(&engine, location(&engine, floor)); + rows( + &engine, + &format!( + "SELECT level, hit_die, strength, dexterity, will FROM characters \ + WHERE location_id = {room}" + ), + ) + .pop() + .expect("one person is born there") +} + +#[test] +fn everybody_born_on_a_floor_takes_its_monster_level_and_nothing_else_of_it() { + let as_loaded = ""; + let deep = born_on("The Bottom Floor", as_loaded); + assert_eq!(deep[0], "4"); + let unleveled = born_on( + "The Bottom Floor", + "UPDATE floors SET monster_level = NULL;", + ); + assert_eq!(unleveled[0], "1", "no monster level is the starting level"); + assert_eq!( + deep[1..], + unleveled[1..], + "the hit die and abilities are rolled as they would have been" + ); + assert_eq!(born_on("The Upper Floor", as_loaded)[0], "1"); + assert_eq!( + born_on( + "The Upper Floor", + "UPDATE floors SET monster_level = 7 WHERE location_id = \ + (SELECT id FROM locations WHERE name = 'The Upper Floor');" + )[0], + "7" + ); +}