From f3590e212ce34d45067576270e5d159d2f477dbc Mon Sep 17 00:00:00 2001 From: Igor Chernakov Date: Wed, 30 Sep 2026 15:05:40 +0200 Subject: [PATCH] Books start verbose, deaths end the story, and every command answers Walking back UP to Blackwood's Basement Stairs printed only "Basement Stairs": the books inherit Zork I's BRIEF default, and a reader that shows room names as headings showed nothing. Every book's GO now sets VERBOSE. Playing Blackwood through found more: - JIGS-UP was the bootstrap stub that printed and carried on, so the grue, the cold and the serum killed no one. It now ends the story through FINISH (Wondertown's dawn timer had the same hole). - Ten scenery routines ended in a bare , so CLIMB STAIRCASE, PUSH WALLPAPER and most verbs on them printed nothing. - SCORE printed garbage after "Rank:" (Blackwood and Limehouse indexed the LTABLE's length slot). - The entrance hall put the grand staircase in the east, where the patient ward is; UP now answers with the collapsed landing. The lit basement corridor called itself pitch black and put the dripping water east; it now warns that the north passage is dark. - Dropped objects printed fragments or their first placement ("A logbook rests on a desk."). Takeable LDESCs are sentences that hold anywhere; placements moved to FDESC. - INJECT ME never matched WINNER; the shock-chair death was unreachable and is removed. PEWS/CANDLES capitals and a stray space are fixed. make lint-zil now plays common verbs on every object (check-responses) and fails on silence or a GO without VERBOSE. WRITING_ADVENTURES, the zil-implementation skill and AGENTS document the rules. The Limehouse and llm tests that expected brief revisits or a first-visit room name match prose instead, and a QUIT with no text returns nil again. Co-Authored-By: Claude Opus 5.5 --- .opencode/skills/zil-implementation/SKILL.md | 9 +- AGENTS.md | 3 +- Makefile | 5 +- books/blackwood-horror/actions.zil | 56 ++++------ books/blackwood-horror/dungeon.zil | 68 ++++++------ .../test/test-playtest-navigation.zil | 55 +++++++++ books/limehouse-killings/actions.zil | 3 +- books/wondertown/actions.zil | 1 + docs/WRITING_ADVENTURES.md | 75 ++++++++++++- scripts/check-responses.lua | 105 ++++++++++++++++++ skills/source_writing_adventures.md | 3 + tests/test_limehouse_walkthrough.lua | 6 +- tests/test_llm.lua | 2 +- zilscript/bootstrap.lua | 8 +- zilscript/runtime.lua | 5 +- 15 files changed, 322 insertions(+), 82 deletions(-) create mode 100644 books/blackwood-horror/test/test-playtest-navigation.zil create mode 100644 scripts/check-responses.lua diff --git a/.opencode/skills/zil-implementation/SKILL.md b/.opencode/skills/zil-implementation/SKILL.md index 0416eff..f41a2cd 100644 --- a/.opencode/skills/zil-implementation/SKILL.md +++ b/.opencode/skills/zil-implementation/SKILL.md @@ -113,7 +113,14 @@ On this substrate, untouched objects with `FDESC` are printed directly by `PRINT Missing `>` is the most common and hardest-to-spot bug. ### 3. Define `ROUTINE GO ()` in actions.zil -The game entry point must exist. Set up HERE, LIT, WINNER, PLAYER, call V-LOOK, call MAIN-LOOP. +The game entry point must exist. Set up HERE, ``, LIT, WINNER, PLAYER, call V-LOOK, call MAIN-LOOP. +VERBOSE is not optional. The substrate starts in Infocom's BRIEF mode, where a +revisited room prints only its name. Readers show that name as a heading, so +returning to a room shows no text at all. `make lint-zil` fails without it. + +Deaths use `JIGS-UP`, which ends the story through `FINISH` (score, then +RESTART/RESTORE/QUIT). Never advertise a death that no command can reach; test +each one and expect the FINISH prompt. ### 4. Custom SYNTAX uses `= V-ROUTINE` Only add syntax for verbs absent from the substrate. Search `infocom/zork1/syntax.zil` first. diff --git a/AGENTS.md b/AGENTS.md index 12099da..469a956 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -20,7 +20,8 @@ Start with [ARCHITECTURE.md](ARCHITECTURE.md). It is the canonical high-level su 4. For content or gameplay issues, inspect the relevant adventure folder under [infocom](infocom) or [books](books) before changing engine code. 5. When debugging ZIL-related failures, prefer adding temporary `TELL`/print commands directly in the relevant ZIL routines to expose values, branch choices, and object locations while narrowing the problem. Remove or clearly quarantine this instrumentation before finishing unless it is intentionally part of a test. 6. Validate with the smallest relevant target from [Makefile](Makefile); for broad gameplay regressions, use `make test-pure-zil`. -7. Story prose withholds what must be examined and links what can be acted on. Follow "Prose Links" and "Withholding What Must Be Examined" in [docs/WRITING_ADVENTURES.md](docs/WRITING_ADVENTURES.md), and run `make lint-zil`, which checks vocabulary, link words, and every link target through the parser. +7. Story prose withholds what must be examined and links what can be acted on. Follow "Prose Links" and "Withholding What Must Be Examined" in [docs/WRITING_ADVENTURES.md](docs/WRITING_ADVENTURES.md), and run `make lint-zil`, which checks vocabulary, link words, and every link target through the parser, and plays common verbs on every object to catch commands that print nothing. +8. Books start VERBOSE, so a revisited room never prints only its name, and every death ends the story through `FINISH`. See "The GO Routine" in [docs/WRITING_ADVENTURES.md](docs/WRITING_ADVENTURES.md). ## Playing Games diff --git a/Makefile b/Makefile index 9cae1cf..b8896a7 100644 --- a/Makefile +++ b/Makefile @@ -38,7 +38,7 @@ help: @echo " test-llm - Run LLM persistence tests" @echo " test-limehouse-walkthrough - Run Limehouse golden-path LLM test" @echo " test-wondertown-descriptions - Run Wondertown rendered description regressions" - @echo " lint-zil - Check printed object names against parser vocabulary" + @echo " lint-zil - Check vocabulary, prose links, and that every command answers" @echo "" @echo "Horror game tests:" @echo " test-horror-helpers - Run horror test helpers" @@ -201,6 +201,7 @@ test-horror-playtest-regressions: @lua5.4 run-zil-test.lua books/blackwood-horror/test/test-playtest-lore @lua5.4 run-zil-test.lua books/blackwood-horror/test/test-playtest-systems @lua5.4 run-zil-test.lua books/blackwood-horror/test/test-prose-reveal + @lua5.4 run-zil-test.lua books/blackwood-horror/test/test-playtest-navigation test-horror-all: test-horror-helpers test-horror-partial test-horror-failures test-horror-playtest-regressions test-horror @echo "All horror tests completed!" @@ -237,6 +238,8 @@ lint-zil: @lua5.4 scripts/check-links.lua books/limehouse-killings books/blackwood-horror books/wondertown @echo "Examining every prose link through the parser..." @lua5.4 scripts/check-link-targets.lua books/limehouse-killings books/blackwood-horror books/wondertown + @echo "Playing common verbs on every object..." + @lua5.4 scripts/check-responses.lua books/limehouse-killings books/blackwood-horror books/wondertown zip: @if [ -z "$(filter-out zip,$(MAKECMDGOALS))" ]; then echo "Usage: make zip "; echo "Example: make zip limehouse-killings"; exit 1; fi diff --git a/books/blackwood-horror/actions.zil b/books/blackwood-horror/actions.zil index 0ed3eaa..261de73 100644 --- a/books/blackwood-horror/actions.zil +++ b/books/blackwood-horror/actions.zil @@ -2,15 +2,13 @@ - )> - > + )>> ) ( - )> - > + )>> ; === DISCOVERY === @@ -63,10 +61,10 @@ searched, as Zork I hides the grating under the leaves." - up]] into darkness."> + up]] toward the entrance hall."> )> - west]] lies what might have been storage. [[North->north]], another corridor descends toward deeper chambers." CR>)>> + west]] lies what might have been storage. [[North->north]], another corridor descends into total darkness, toward the sound of dripping water." CR>)>> @@ -518,10 +516,7 @@ ajar, so examining it swings the door wide, as opening it does." ) ( - - ) - (T - )> + ) ( @@ -775,11 +770,11 @@ moved: its frame sits proud of the paneling, and it swings on a hinge." - ) + ) ( - ) + ) (T - )> + )> @@ -862,7 +857,7 @@ moved: its frame sits proud of the paneling, and it swings on a hinge." > ) - ( + ( ) (> ) @@ -955,7 +950,7 @@ moved: its frame sits proud of the paneling, and it swings on a hinge." - + ) ( @@ -979,7 +974,7 @@ moved: its frame sits proud of the paneling, and it swings on a hinge." )>) ( - + ) ( @@ -1320,23 +1315,19 @@ moved: its frame sits proud of the paneling, and it swings on a hinge." - )> - > + )>> - )> - > + )>> - )> - > + )>> - )> - > + )>> ; === LOCAL-GLOBALS ACTION HANDLERS === @@ -1365,27 +1356,23 @@ moved: its frame sits proud of the paneling, and it swings on a hinge." ( ) ( - )> - > + )>> - )> - > + + )>> ) ( - )> - > + )>> ) ( - )> - > + )>> ; === CLOCK-DRIVEN ATMOSPHERIC ROUTINES === @@ -1499,13 +1486,14 @@ moved: its frame sits proud of the paneling, and it swings on a hinge." ) (T )> - > "." CR> + 1>> "." CR> ,SCORE> ; === ENTRY POINT === + diff --git a/books/blackwood-horror/dungeon.zil b/books/blackwood-horror/dungeon.zil index 144878a..f50c50e 100644 --- a/books/blackwood-horror/dungeon.zil +++ b/books/blackwood-horror/dungeon.zil @@ -24,8 +24,7 @@ > @@ -49,12 +48,13 @@ staircase]] ascends to darkness in the east. To the [[west]], a doorway leads to what might have been a reception area. A narrow staircase descends [[into the basement->down]].") + (LDESC "The entrance hall reeks of mildew and decay. A [[grand staircase->staircase]] rises into darkness and ends at a collapsed landing. To the [[west]], a doorway leads to what might have been a reception area; to the [[east]], a corridor opens onto the patient ward. A narrow staircase descends [[into the basement->down]].") (SOUTH TO SANITARIUM-GATE) (WEST TO RECEPTION-ROOM) (NORTH TO OPERATING-THEATER) (EAST TO PATIENT-WARD) (DOWN TO BASEMENT-STAIRS) + (UP "The grand staircase ends at a collapsed landing. There is no way up.") (FLAGS RLANDBIT ONBIT) (GLOBAL SANITARIUM-BUILDING DEAD-OAK-TREE)> @@ -103,7 +103,7 @@ (IN ROOMS) (DESC "Basement Corridor") (ACTION BASEMENT-CORRIDOR-FCN) - (LDESC "The basement corridor is pitch black, stretching into shadow. Stone stairs climb upward into darkness. To the east, a passage leads toward the sound of dripping water. West lies what might have been storage. North, another corridor descends toward deeper chambers.") + (LDESC "The basement corridor is dim, stretching into shadow. Stone stairs climb upward toward the entrance hall. To the east, a passage smells of coal and old smoke. West lies what might have been storage. North, another corridor descends into total darkness, toward the sound of dripping water.") (UP TO BASEMENT-STAIRS) (EAST TO BOILER-ROOM) (WEST TO STORAGE-ROOM) @@ -275,7 +275,7 @@ (ADJECTIVE PATIENT CONFIDENTIAL) (DESC "patient file") (FDESC "A confidential [[patient file->file]] lies open in the drawer, its contents disturbing.") - (LDESC "A [[file folder->file]] marked 'Patient 189 - CONFIDENTIAL'.") + (LDESC "A [[file folder->file]] marked 'Patient 189 - CONFIDENTIAL' lies here.") (FLAGS TAKEBIT READBIT) (SIZE 3) (VALUE 2) @@ -325,7 +325,7 @@ (SYNONYM SCALPEL KNIFE BLADE) (ADJECTIVE SURGICAL RUSTY) (DESC "rusty scalpel") - (LDESC "A surgical [[scalpel]], its blade dulled by rust but still sharp enough to cut.") + (LDESC "A rusty surgical [[scalpel]] lies here.") (FLAGS TAKEBIT WEAPONBIT TOOLBIT) (SIZE 3) (ACTION SCALPEL-F)> @@ -335,7 +335,7 @@ (SYNONYM BOTTLE ETHER CHLOROFORM) (ADJECTIVE GLASS) (DESC "bottle of ether") - (LDESC "A glass [[bottle]] labeled 'Ether - Handle with Care'. Some liquid remains inside.") + (LDESC "A glass [[bottle]] labeled 'Ether - Handle with Care' stands here.") (FLAGS TAKEBIT) (SIZE 5) (ACTION ETHER-F)> @@ -400,7 +400,7 @@ (ADJECTIVE WRAPPED) (DESC "canvas bundle") (FDESC "On the dissection table, a human-shaped [[bundle]] wrapped in stained canvas awaits examination.") - (LDESC "A human-shaped [[bundle]] wrapped in stained canvas. You'd rather not investigate further.") + (LDESC "A human-shaped [[bundle]] wrapped in stained canvas lies here.") (FLAGS TAKEBIT) (SIZE 50) (ACTION BUNDLE-F)> @@ -411,7 +411,7 @@ (ADJECTIVE STRANGE GLOWING) (DESC "vial of serum") (FDESC "Inside the open refrigerated drawer, a glass [[vial]] glows with an eerie luminescence.") - (LDESC "A glass [[vial]] of luminescent liquid, its label too small to read at a glance.") + (LDESC "A glass [[vial]] of luminescent liquid lies here, glowing faintly.") (FLAGS TAKEBIT) (SIZE 4) (ACTION SERUM-F)> @@ -422,7 +422,7 @@ (ADJECTIVE DOCTOR MORDECAI) (DESC "doctor's journal") (FDESC "A personal [[journal]] rests on a small desk in the corner, its pages filled with manic handwriting.") - (LDESC "A [[journal]] rests on a small desk in the corner.") + (LDESC "Dr. Mordecai's [[journal]] lies here.") (FLAGS READBIT TAKEBIT) (TEXT "The subject showed remarkable resilience. But the serum... it changed something fundamental. Patient 237 died on the table, yet I swear I saw movement hours later. The eyes... the eyes opened. I have made a terrible mistake. God forgive me, I must seal this place.") (SIZE 6) @@ -441,7 +441,7 @@ (SYNONYM VALVE WHEEL) (ADJECTIVE PIPE METAL) (DESC "metal valve") - (LDESC "A wheel [[valve]] on one of the pipes, crusted with rust.") + (LDESC "A wheel [[valve]] juts from one of the pipes, crusted with rust.") (FLAGS TURNBIT) (ACTION VALVE-F)> @@ -460,7 +460,7 @@ (SYNONYM SHOVEL SPADE) (ADJECTIVE COAL) (DESC "coal shovel") - (LDESC "A sturdy [[coal shovel->shovel]] with a wooden handle.") + (LDESC "A sturdy [[coal shovel->shovel]] lies here.") (FLAGS TAKEBIT TOOLBIT) (SIZE 10) (ACTION SHOVEL-F)> @@ -479,7 +479,7 @@ (SYNONYM COAL LUMP FUEL) (ADJECTIVE BLACK DUSTY) (DESC "lump of coal") - (LDESC "A usable [[lump of coal->coal]] rests among the damp slack.") + (LDESC "A [[lump of coal->coal]] lies here.") (FLAGS TAKEBIT) (SIZE 4) (ACTION COAL-F)> @@ -498,7 +498,7 @@ (SYNONYM FLASHLIGHT LIGHT TORCH) (ADJECTIVE ELECTRIC) (DESC "flashlight") - (LDESC "An old-fashioned electric [[flashlight]], surprisingly heavy.") + (LDESC "An old-fashioned electric [[flashlight]] lies here.") (FLAGS TAKEBIT LIGHTBIT) (SIZE 5) (ACTION FLASHLIGHT-F)> @@ -517,7 +517,7 @@ (SYNONYM BAG SATCHEL) (ADJECTIVE MEDICAL LEATHER DOCTOR) (DESC "medical bag") - (LDESC "An old leather [[medical bag->bag]] sits on one of the shelves, its clasp shut.") + (LDESC "An old leather [[medical bag->bag]] lies here.") (FLAGS CONTBIT OPENABLEBIT TAKEBIT) (CAPACITY 15) (SIZE 10) @@ -525,10 +525,10 @@ @@ -538,7 +538,7 @@ (SYNONYM VIAL MORPHINE BOTTLE) (ADJECTIVE GLASS SMALL) (DESC "morphine vial") - (LDESC "A sealed glass vial of [[morphine]].") + (LDESC "A sealed glass vial of [[morphine]] lies here.") (FLAGS TAKEBIT) (SIZE 2) (ACTION MORPHINE-VIAL-F)> @@ -548,7 +548,7 @@ (SYNONYM LANTERN LAMP) (ADJECTIVE OIL) (DESC "oil lantern") - (LDESC "An old [[oil lantern->lantern]]. It still has fuel inside.") + (LDESC "An old [[oil lantern->lantern]] stands here.") (FLAGS TAKEBIT LIGHTBIT FLAMEBIT) (SIZE 8) (ACTION LANTERN-F)> @@ -558,7 +558,7 @@ (SYNONYM RECORDS FILES PAPERS) (ADJECTIVE MEDICAL OLD) (DESC "medical records") - (LDESC "Yellowed files containing patient [[records]] from the 1940s.") + (LDESC "A sheaf of yellowed patient [[records]] lies here.") (FLAGS TAKEBIT READBIT) (TEXT "Most records are water-damaged, but one file remains legible: Patient 189 - Subject shows unusual resistance to sedation. Transferred to isolation ward for observation.") (SIZE 4) @@ -595,7 +595,7 @@ (SYNONYM NOTEBOOK BOOK DIARY) (ADJECTIVE SOGGY WET) (DESC "soggy notebook") - (LDESC "A water-damaged [[notebook]], barely legible.") + (LDESC "A water-damaged [[notebook]] lies here.") (FLAGS TAKEBIT READBIT) (TEXT "...water treatment...hours submerged...screaming stopped...Dr. M approved extended sessions...") (SIZE 3) @@ -616,7 +616,7 @@ (SYNONYM SYRINGE NEEDLE) (ADJECTIVE MEDICAL) (DESC "syringe") - (LDESC "A glass [[syringe]] with a steel needle.") + (LDESC "A glass [[syringe]] lies here.") (FLAGS TAKEBIT) (SIZE 2) (VALUE 3) @@ -679,7 +679,7 @@ (ADJECTIVE STRAIT NAME COLLAR) (DESC "straitjacket") (FDESC "A [[straitjacket]] lies in the corner, its straps unbuckled as if someone left in a hurry.") - (LDESC "A [[straitjacket]] lies in the corner.") + (LDESC "A [[straitjacket]] lies here.") (FLAGS TAKEBIT READBIT) (TEXT "The tag reads a name you know. Your name. Dated 1947. Five years before the sanitarium closed.") (SIZE 15) @@ -707,7 +707,8 @@ (SYNONYM LOGBOOK LOG BOOK) (ADJECTIVE OBSERVATION) (DESC "observation logbook") - (LDESC "A [[logbook]] rests on a desk.") + (FDESC "A [[logbook]] rests on a desk.") + (LDESC "The observation [[logbook]] lies here.") (FLAGS TAKEBIT READBIT) (TEXT "Session 47 - Patient 189. Subject required maximum voltage. Seizure lasted 4 minutes. Memory loss total. Subject claims to be 'someone else' now. Dr. Mordecai pleased with results.") (SIZE 7) @@ -744,7 +745,7 @@ (ADJECTIVE RED LEATHER HOLLOW) (DESC "red leather book") (FDESC "Among the medical texts, one book stands out -- a red leather [[tome->book]], its spine blank where all others are labeled.") - (LDESC "A red leather [[book]] with a blank spine sits among the medical volumes.") + (LDESC "A red leather [[book]] with a blank spine lies here.") (FLAGS CONTBIT OPENABLEBIT TAKEBIT) (CAPACITY 5) (SIZE 6) @@ -780,7 +781,7 @@ (SYNONYM KEY) (ADJECTIVE SAFE NUMBERED) (DESC "safe key") - (LDESC "A small [[key->safe key]] with a numbered tag: S-001.") + (LDESC "A small [[key->safe key]] with a numbered tag lies here.") (FLAGS TAKEBIT) (SIZE 1) (VALUE 2) @@ -792,7 +793,7 @@ (ADJECTIVE PRIVATE MORDECAI) (DESC "Dr. Mordecai's notes") (FDESC "Private [[notes]] in a tight, obsessive hand detail experiments that should never have been conducted.") - (LDESC "Personal [[notes]] in Dr. Mordecai's handwriting.") + (LDESC "Dr. Mordecai's private [[notes]] lie here.") (FLAGS TAKEBIT READBIT) (TEXT "The experiment succeeded beyond expectations. Patient 189 has transcended death itself. But the cost... the screaming never stops. I hear it in my sleep. The chapel must remain locked. What I've created must never escape.") (SIZE 5) @@ -804,7 +805,7 @@ (ADJECTIVE CHAPEL IRON) (DESC "chapel key") (FDESC "A heavy iron [[key->chapel key]] lies among the safe's contents, its head carved with a cross.") - (LDESC "A large iron [[key->chapel key]] with a cross engraved on the head.") + (LDESC "A large iron [[key->chapel key]] engraved with a cross lies here.") (FLAGS TAKEBIT) (SIZE 6) (VALUE 3) @@ -824,7 +825,7 @@ (SYNONYM PHOTOGRAPH PHOTO PICTURE) (ADJECTIVE OLD) (DESC "photograph") - (LDESC "A faded [[photograph]] of the sanitarium staff.") + (LDESC "A faded [[photograph]] of the sanitarium staff lies here.") (FLAGS TAKEBIT) (SIZE 1) (ACTION PHOTOGRAPH-F)> @@ -843,7 +844,7 @@ (SYNONYM BELL) (ADJECTIVE SERVICE COUNTER) (DESC "service bell") - (LDESC "A small brass [[bell]] for summoning staff.") + (LDESC "A small brass service [[bell]] sits here.") (FLAGS TAKEBIT) (SIZE 2) (ACTION BELL-F)> @@ -893,7 +894,8 @@ (SYNONYM BOX CASE) (ADJECTIVE WOODEN LOCKED SMALL) (DESC "wooden box") - (LDESC "A small wooden [[box]] sits beneath the altar, its surface carved with disturbing symbols.") + (FDESC "A small wooden [[box]] sits beneath the altar, its surface carved with disturbing symbols.") + (LDESC "A small wooden [[box]] carved with disturbing symbols sits here.") (FLAGS CONTBIT OPENABLEBIT TAKEBIT) (CAPACITY 10) (SIZE 8) @@ -905,7 +907,7 @@ (ADJECTIVE ANCIENT SILVER TARNISHED) (DESC "ancient relic") (FDESC "Inside the wooden box, an ancient silver [[cross->relic]] gleams with writhing symbols etched into its surface.") - (LDESC "An ancient silver [[cross->relic]] with writhing symbols etched into its surface.") + (LDESC "An ancient silver [[cross->relic]] lies here, symbols writhing across its surface.") (FLAGS TAKEBIT) (SIZE 4) (VALUE 3) diff --git a/books/blackwood-horror/test/test-playtest-navigation.zil b/books/blackwood-horror/test/test-playtest-navigation.zil new file mode 100644 index 0000000..002c181 --- /dev/null +++ b/books/blackwood-horror/test/test-playtest-navigation.zil @@ -0,0 +1,55 @@ + + +> + + 'Basement Stairs' and nothing else." + ;"Expected: the story starts VERBOSE, so a revisited room is described in full." + + + + + > + > + + ;"Observed: the lit corridor called itself pitch black and put the dripping water east." + ;"Expected: the dark flooded chamber to the north is the one announced as dark and wet." + > + + ;"Observed: CLIMB STAIRCASE and other verbs on scenery printed nothing at all." + ;"Expected: handled verbs answer, and unhandled verbs fall through to the defaults." + + + > + > + > + > + > + > + + + > + + ;"Observed: a dropped object printed a fragment or its original placement." + ;"Expected: its room description reads as a sentence wherever it lies." + + + + > + + + + > + + ;"Observed: SCORE printed a run of garbage characters after 'Rank:'." + ;"Expected: the first rank names the starting player." + > + + ;"Observed: dying printed its message and play simply continued." + ;"Expected: a death ends the story with the RESTART, RESTORE, or QUIT prompt." + + > + > + + > +> diff --git a/books/limehouse-killings/actions.zil b/books/limehouse-killings/actions.zil index 1532f24..911aacf 100644 --- a/books/limehouse-killings/actions.zil +++ b/books/limehouse-killings/actions.zil @@ -1205,7 +1205,7 @@ player has had the lid up, a closed trunk stays closed to a glance." ) (T )> - > "." CR> + 1>> "." CR> ,SCORE> @@ -1363,6 +1363,7 @@ player has had the lid up, a closed trunk stays closed to a glance." + diff --git a/books/wondertown/actions.zil b/books/wondertown/actions.zil index 4a78c40..9bfa6ca 100644 --- a/books/wondertown/actions.zil +++ b/books/wondertown/actions.zil @@ -27,6 +27,7 @@ + diff --git a/docs/WRITING_ADVENTURES.md b/docs/WRITING_ADVENTURES.md index 9f62e06..f97d289 100644 --- a/docs/WRITING_ADVENTURES.md +++ b/docs/WRITING_ADVENTURES.md @@ -360,7 +360,7 @@ Rooms are the locations the player moves between. **Required properties:** - `(IN ROOMS)` — marks this as a room (always use `ROOMS`) - `(DESC "Short Name")` — short name shown in the status line / when entering -- `(LDESC "Long description...")` — full description shown on first visit or with LOOK +- `(LDESC "Long description...")` — full description, shown on every visit (books run VERBOSE; see The GO Routine) **Direction connections:** - `(NORTH TO ROOM-NAME)` — unconditional exit @@ -371,6 +371,16 @@ Rooms are the locations the player moves between. - `RLANDBIT` — this is a land-based room (standard) - `ONBIT` — room is lit (no light source needed). Omit for dark rooms. +**The prose is the map.** Every direction the prose names must be an exit +that goes there, and every exit must be named. A staircase that "ascends in +the east" while EAST leads to a ward sends the reader the wrong way. A +described route that goes nowhere, such as a collapsed staircase, gets a +string exit, `(UP "The staircase ends at a collapsed landing.")`, so that +the direction answers. A lit room never calls itself "pitch black". The +prose announces the dark room next door instead ("North, a corridor +descends into total darkness"), so that a player without a light is warned +before the parser's darkness rules can kill them. + ### Objects Objects are anything the player can see, examine, or interact with. @@ -568,6 +578,31 @@ Routines are functions that handle player interactions with objects. ``` +**Never end an object's action routine with a bare `` after its +COND.** That trailing `` claims every verb the COND did not handle. +CLIMB, PUSH, TAKE and the rest then print nothing, and the reader's command +seems to vanish. Return true inside each branch that handles a verb. Let the +COND fall through (false) so that unhandled verbs reach the parser's default +reply: + +```zil +; Wrong: "climb staircase" and "push wallpaper" print nothing + + )> + > + +; Right: the TELL returns true for EXAMINE and READ; every other verb falls through + + )>> +``` + +Match the action names the syntax produces, not the typed word. CLIMB X is +`CLIMB-FOO`, CLIMB UP X is `CLIMB-UP`, and a bare `CLIMB` never matches. +`make lint-zil` plays common verbs on every object in every room and fails +on any command that prints nothing. + ### Global Variables (Flags) Global flags track puzzle state across the game: @@ -817,6 +852,7 @@ Every game must have a `GO` routine: ```zil + @@ -830,6 +866,19 @@ Every game must have a `GO` routine: > ``` +**`` is required.** The shared engine in `infocom/zork1/` +starts in Infocom's BRIEF mode. In that mode a room the player has already +visited prints only its name. A reader such as AdventureArena shows the room +name as a heading, so walking back UP a staircase would show a title and no +text at all. Books always start verbose. Players may still type BRIEF +themselves. `make lint-zil` fails a story whose GO leaves VERBOSE off. + +**Deaths end the story.** `JIGS-UP` prints its message and then calls +`FINISH`, which reports the score and asks for RESTART, RESTORE or QUIT. A +book does not define its own JIGS-UP unless it needs Zork-style resurrection. +Every death an action routine advertises must be reachable. The regression +test that plays it must expect the FINISH prompt. + ### LET (Local Bindings) ```zil @@ -962,12 +1011,19 @@ The format is pairs of `"WORD" ROUTINE-NAME` — when the player refers to that (DESC "sword") (FLAGS TAKEBIT WEAPONBIT) (FDESC "Above the trophy case hangs an elvish sword of great antiquity.") - (LDESC "There is a brass lantern (battery-powered) here.") + (LDESC "An elvish sword lies here.") (SIZE 30)> ``` First visit: "Above the trophy case hangs an elvish sword of great antiquity." -After being dropped somewhere: "There is an elvish sword here." +After being dropped somewhere: "An elvish sword lies here." + +A takeable object's LDESC is printed in whatever room the player drops it. +It must be a whole sentence that stays true there: "A glass syringe lies +here." A fragment ("A glass syringe with a steel needle.") reads as broken +prose. So does a placement ("A logbook rests on a desk.") printed on the +cafeteria floor. The original placement goes in FDESC, and detail goes in +EXAMINE. ### Room Action Routines (Dynamic Descriptions) @@ -1253,7 +1309,18 @@ For variety, Zork uses tables of responses selected randomly: CR>> ``` -`PICK-ONE` selects from the table without repeating recent choices. +`PICK-ONE` selects from the table without repeating recent choices. The +leading `0` is PICK-ONE's own bookkeeping slot. Other tables leave it out. +`` of an LTABLE is its length, and its first entry is element +1. A rank table therefore lists only names and adds one to the index: + +```zil +> + 1>> "." CR> +``` + +Without the `+ 1`, a score of zero prints the length as if it were a string +address, and garbage appears. ### PERFORM (Redirecting Actions) diff --git a/scripts/check-responses.lua b/scripts/check-responses.lua new file mode 100644 index 0000000..fad0fe4 --- /dev/null +++ b/scripts/check-responses.lua @@ -0,0 +1,105 @@ +#!/usr/bin/env lua +-- check-responses.lua: Every command gets an answer, and every visit a room. +-- +-- Usage: +-- lua5.4 scripts/check-responses.lua books/blackwood-horror +-- +-- A reader who types a command and sees nothing believes the app is broken. +-- Two story defects produce that silence: +-- +-- * BRIEF mode. The substrate starts Infocom-style: VERBOSE is off, so a +-- revisited room prints only its name, and a reader that shows room names +-- as headings shows nothing at all. GO must set VERBOSE. +-- * An action routine that ends with an unconditional after its +-- COND claims every verb it did not handle, so CLIMB, PUSH or TAKE on +-- that object prints nothing instead of the parser's default reply. +-- +-- The story is started, every object that sits in a room is visited in a lit +-- room, and a set of common verbs is played on it. An empty reply is a defect. + +package.path = "./?.lua;./?/init.lua;" .. package.path +local runtime = require("zilscript.runtime") + +local VERBS = { + "take %s", "drop %s", "climb %s", "push %s", "pull %s", "open %s", "close %s", + "read %s", "touch %s", "search %s", "move %s", "kick %s", "eat %s", "smell %s", + "listen to %s", "look under %s", "look behind %s", "look in %s", "turn %s", + "enter %s", "sit on %s", "break %s", "wear %s", "knock on %s", "light %s", +} + +local ENDED = "RESTART, RESTORE, or QUIT" + +local function new_game(story) + local env = runtime.create_game_env() + assert(runtime.init(env, true)) + env.require("zilscript") + assert(runtime.load_modules(env, { story:gsub("/", ".") .. "." .. story:match("([^/]+)$") }, { silent = true })) + local game = runtime.create_game(env, true) + game:start() + return env, game +end + +-- The words a reader would use for each object, from its definition. +local function nouns(story) + local file = assert(io.open(story .. "/dungeon.zil")) + local source = file:read("a") + file:close() + local list = {} + for name, body in source:gmatch(" 0 then + print(string.format("%d silent response(s)", problems)) + os.exit(1) +end diff --git a/skills/source_writing_adventures.md b/skills/source_writing_adventures.md index dac95e5..4b692e7 100644 --- a/skills/source_writing_adventures.md +++ b/skills/source_writing_adventures.md @@ -311,6 +311,7 @@ The walkthrough (`walkthrough.zil`) also serves as the entry point that wires th + @@ -710,6 +711,7 @@ Every game must have a `GO` routine: ```zil + @@ -1597,6 +1599,7 @@ Here's a minimal but complete adventure demonstrating all major features: + diff --git a/tests/test_limehouse_walkthrough.lua b/tests/test_limehouse_walkthrough.lua index e3b6d59..7ea6eff 100644 --- a/tests/test_limehouse_walkthrough.lua +++ b/tests/test_limehouse_walkthrough.lua @@ -173,9 +173,9 @@ test.describe("Limehouse Killings walkthrough", function(t) {"go west", "overgrown hedges"}, {"go south", "narrow beds"}, {"ask hudson about key", "hands you the keyring"}, - {"go north", "Garden"}, - {"go east", "Kitchen"}, - {"go up", "Entrance Hall"}, + {"go north", "dry stone fountain"}, + {"go east", "seen better days"}, + {"go up", "Doorways lead south to the gate"}, {"look", "closed and locked"}, {"unlock study door with keyring", "study door is now unlocked"}, {"look", "closed but unlocked"}, diff --git a/tests/test_llm.lua b/tests/test_llm.lua index 4f44a30..c699654 100644 --- a/tests/test_llm.lua +++ b/tests/test_llm.lua @@ -65,7 +65,7 @@ test.describe("LLM Mode", function(t) local exit_code, output = run_command("lua5.4 llm.lua --new-game --save " .. shell_quote(savefile)) assert.assert_equal(exit_code, 0) - assert.assert_match(output, "West of House") + assert.assert_match(output, "open field west of a white house") assert.assert_equal(read_file(savefile .. ".actions"), "") cleanup(savefile) diff --git a/zilscript/bootstrap.lua b/zilscript/bootstrap.lua index 709e338..7d9d995 100644 --- a/zilscript/bootstrap.lua +++ b/zilscript/bootstrap.lua @@ -1099,10 +1099,14 @@ function PRINTC(ch) io_write(string.char(ch)) return true end function PRINC(n) io_write(tostring(n)) return true end function CRLF() io_write("\n") return true end +-- The default death for stories that do not define their own JIGS-UP (Zork I +-- does; the books do not). A death ends the story: FINISH reports the score +-- and offers RESTART, RESTORE or QUIT. Printing the message and carrying on +-- let a player walk away from being devoured or electrocuted. function JIGS_UP(msg) TELL(msg, CR) - MOVE(WINNER, HERE) - -- os.exit(1) + if type(FINISH) == "function" then return FINISH() end + return QUIT() end local routes = { diff --git a/zilscript/runtime.lua b/zilscript/runtime.lua index 70d330f..e1f66c8 100644 --- a/zilscript/runtime.lua +++ b/zilscript/runtime.lua @@ -302,7 +302,10 @@ end function M.create_game(env, silent) -- The ending's text: printed after the last READ, so no prompt yields it. local function final_output() - if type(env.TAKE_OUTPUT) == "function" then return env.TAKE_OUTPUT() end + if type(env.TAKE_OUTPUT) == "function" then + local text = env.TAKE_OUTPUT() + if text ~= "" then return text end + end end local co co = coroutine.create(function()