commit 3241b6f14dc18fb6b59b9c6f160ba68356d9fb63 Author: type-two Date: Sun Jul 5 14:56:30 2026 +1000 MORPQUEST: Record Store Day — a Beyond Morp graphical adaptation Fourteen EGA screens for MRPGI: nano banana backgrounds squeezed into the engine's 160x168 double-wide-pixel frame, hand-pixelled item sprites, the full RSD flag chain (poster frenzy -> yard door secret -> roof -> side door -> clerk favours -> back-room seam -> payment -> notice -> BeckyWall dispersal), the five-word true ending, and two scripted test paths (golden win + gate locks), both green. Co-Authored-By: Claude Fable 5 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9f2e848 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.DS_Store +__pycache__/ diff --git a/DESIGN.md b/DESIGN.md new file mode 100644 index 0000000..5d9959c --- /dev/null +++ b/DESIGN.md @@ -0,0 +1,107 @@ +# MORPQUEST — Record Store Day +### A Beyond Morp graphical adaptation for MRPGI + +You're on a quick cat-food run. Your Uncle's list is in the glovebox. The record +shop's front door is a wall of Taylor Swift superfans. Morp is not cruel; Morp +is accurate. + +Adapted from the Beyond Morp text adventure (RSD storyline). Fourteen screens, +one day, five words, one white label. + +## The map (room / exits N,E,S,W — as built, matching the art) + +``` +[4 Toilet]--(W door)--[3 Alley]--(N)--[5 Yard]--(E, vine)--[6 Roof] + | | + (S) (N, side door) + | | +[0 Car]-(E)-[1 Car Park]-(N)-[2 Entrance] [7 Kitchen] + (E->alley) | + (N) + | + [9 Rock]-(E)-[8 Shop Floor]-(E)-[10 Dance]-(N)-[11 Listening] + | + (N) + | + [12 Counter]-(N, beads)-[13 Back Room] +``` + +| # | room | N | E | S | W | music | +|---|------|---|---|---|---|-------| +| 0 | Your Car (interior) | – | 1 | – | – | calm | +| 1 | Car Park | 2 | – | – | 0 | calm | +| 2 | Shop Entrance (BeckyWall) | – | 3 | 1 | – | tense | +| 3 | Alley — "Scenic Rim" | 5 | – | 2 | 4 | eerie | +| 4 | Toilet — "Pacific Rim" | – | – | 3 | – | spooky | +| 5 | Yard behind shop | 7 | 6 | 3 | – | eerie | +| 6 | Roof — "Above the Rim" | – | – | – | 5 | calm | +| 7 | Kitchen | 8 | – | 5 | – | tense | +| 8 | Shop Floor (hub) | 12 | 10 | 7 | 9 | jolly | +| 9 | Rock Aisle | – | 8 | – | – | jolly | +| 10 | Dance Racks | 11 | – | – | 8 | jolly | +| 11 | Listening Station | – | – | 10 | – | calm | +| 12 | Counter | 13 | – | 8 | – | tense | +| 13 | Back Room | – | – | 12 | – | spooky | + +Physical access is open (the engine has no flag-gated exits); progression is +gated Infocom-style through flags, dialogue, and the win condition. + +## Main chain (flags in order) + +1. Car: take `paperclip` (under seat). **seatbelt** `needs paperclip` → `seatbelt-freed`. + Take `car-keys`, `wallet`, `45-adapter`, `tote-bag`. (Kebab: examine-only warning.) +2. Entrance: Becky Theologian dialogue → ask which variant is canon → `poster-frenzy` + (the hive shreds a Taylor poster). +3. Entrance: **shredded poster fragments** use `requires poster-frenzy` → `knows-yard-door-secret` + (fragments spell YARD DOOR SECRET). +4. Toilet: take `elastic-band` (cupboard). Yard: **vinyl vine** use `requires knows-yard-door-secret` + → `noticed-vine` ("It isn't a ladder. But it's climbable.") — narratively unlocks the roof (east edge). +5. Roof: **door-shaped absence** use → `seen-door-outline`. +6. Entrance: Dave dialogue, choice gated on `seen-door-outline` → `side-door-open` + ("Yeah nah. That was NOT there." He shoulders it anyway.) +7. Kitchen: take `consolation-lollipop` (sink bench, under a soggy flyer). +8. Listening Station: **turntable** `needs 45-adapter` → `station-working`. + **sad balloon** `needs elastic-band` → `eyepatch-made`. +9. Shop Floor: take `clean-sleeve` (by the crates). **half-sleeved record** `needs clean-sleeve` + → `shop-tidied` ("Clerk notices. Pretends not to. But it counts.") +10. Counter: Clerk dialogue — favour 1 gated on `shop-tidied` → `clerk-warm`; favour 2 gated + on `eyepatch-made` (node reachable once warm) → `back-room-open` ("The beaded curtain is + no longer purely decorative.") +11. Back Room: **hidden seam** use `requires back-room-open` → `found-grail`; take + `white-label` + `delay-notice` (they were "always there"; the seam look points at them). +12. Counter: **card terminal** `needs wallet` → `paid`. +13. Entrance: **notice board** `needs delay-notice` → `notice-posted`. Becky Crier dialogue: + read-the-notice choice gated on `notice-posted`, then lollipop choice → `beckies-dispersed`. +14. Car: **ignition** `requires beckies-dispersed`, `needs car-keys`, **wins**. + +## True-ending layer — the five words (strict order via flag chain) + +`use` each surface; each requires the previous word: +- Car **rear-view mirror** → `word-rsd` ("RSD", scratched in the plastic) +- Toilet **lipstick mirror** `requires word-rsd` → `word-first` +- Kitchen **greasy splashback** `requires word-first` → `word-global` +- Listening **spinning record** `requires word-global` (and station fixed, narratively) → `word-ambassador` +- Counter **receipt spike** `requires word-ambassador` → `word-version` + +Becky Crier's final node: bonus choice "SAY THE WORDS" `requires word-version` → +`curse-broken` → the hive blinks awake ("…why are we here?" The door is a door again.) + +## Side quests +- **S1 Rock Aisle:** take `mystery-disc` from the chud box; **yellow Daly-Wilson sleeve** + `needs mystery-disc` → `break-ancestor-revealed`; Clerk dialogue bonus choice. +- **S2 Dance Racks:** **overstuffed crate** `needs tote-bag` → `made-space`; DJ Someguy + dialogue choice gated on it ("Keep your face normal. Your face is loud.") +- **S3 Mirror liturgy** is the word chain above. + +## Voice rules (from the tone bible) +Second person, present tense, parser-deadpan. Short declaratives; the last beat +turns. Colon-verdicts. Parenthetical counter-voice. Morp is accurate, not cruel. +Never zany, never explains a joke, never punches down, no exclamation marks in +narration. + +## Build pipeline +- `tools/gen_art.py` — nano banana (OpenRouter) backgrounds + NPC sprites, + squeezed to 160×168 (the GUI shows 2×-wide pixels, so art is generated wide). +- `tools/gen_items.py` — deterministic tiny item sprites (PIL, hand-placed pixels). +- Engine quantizes PNGs to EGA on load; walls/priority painted as strokes over the art. +- Playtest: `mrpgi-headless --script tools/playtest.jsonl --game .` diff --git a/README.md b/README.md new file mode 100644 index 0000000..fc85932 --- /dev/null +++ b/README.md @@ -0,0 +1,47 @@ +# MORPQUEST: Record Store Day + +A Sierra-AGI-style graphical adventure for the [MRPGI engine](https://monsterrobot.games/engine/manual.html), +adapted from the **Beyond Morp** text adventure. Fourteen EGA screens, one day, +five words, one white label. + +> Record Store Day. You're meant to be doing a quick cat-food run; Uncle's list +> is in the glovebox, and the cat can smell weakness. Something is wrong at the +> shop today. Morp itself can feel it. + +![all fourteen rooms](art/render_contact.png) + +## Play it + +From the MRPGI repo: + +```sh +cargo run -p mrpgi --release -- --game /path/to/morpquest # the GUI +mrpgi-headless --game /path/to/morpquest # JSONL repl +mrpgi-headless --serve 127.0.0.1:7777 --game /path/to/morpquest # HTTP + frame.png +``` + +Arrows (or click) walk. Verbs: `look take use talk drop inventory help`, plus +`climb wipe play pay post give fix …` — the parser is generous. The BeckyWall +will not move for you. Documents move it. There is a true ending; the walls of +the toilet know the shape of it. + +## What's here + +- `game.json`, `rooms/room0..13.json` — the whole game: art stamps, walkability, + objects, flags, dialogue. Hand-authored, engine-validated, fully winnable + (see `tools/playtest.jsonl` — the scripted golden path — and + `tools/gatetest.jsonl` — proof the locks lock). +- `sprites/` — 14 backgrounds + 6 NPCs generated with nano banana + (`google/gemini-3.1-flash-image` via OpenRouter), squeezed to the engine's + 160×168 double-wide-pixel frame and EGA-quantized by the engine on load; + tiny item sprites are hand-placed pixels (`tools/gen_items.py`). +- `tools/gen_art.py` + `tools/art_manifest.py` — the whole art pipeline, + re-runnable (deletes nothing, regenerates only what's missing; `-f` forces). +- `art/raw/` — original generations, kept for archaeology. +- `DESIGN.md` — the map, the flag chains, the voice rules. + +## Provenance + +Story, puzzles, characters, and most of the best lines are adapted from the +Beyond Morp design corpus (RSD storyline). The tone bible rule that matters: +Morp is not cruel; Morp is accurate. diff --git a/art/bg_contact.png b/art/bg_contact.png new file mode 100644 index 0000000..1b6d40a Binary files /dev/null and b/art/bg_contact.png differ diff --git a/art/raw/bg_alley.png b/art/raw/bg_alley.png new file mode 100644 index 0000000..0ba263c Binary files /dev/null and b/art/raw/bg_alley.png differ diff --git a/art/raw/bg_backroom.png b/art/raw/bg_backroom.png new file mode 100644 index 0000000..182c004 Binary files /dev/null and b/art/raw/bg_backroom.png differ diff --git a/art/raw/bg_car.png b/art/raw/bg_car.png new file mode 100644 index 0000000..c2a7150 Binary files /dev/null and b/art/raw/bg_car.png differ diff --git a/art/raw/bg_carpark.png b/art/raw/bg_carpark.png new file mode 100644 index 0000000..5366a11 Binary files /dev/null and b/art/raw/bg_carpark.png differ diff --git a/art/raw/bg_counter.png b/art/raw/bg_counter.png new file mode 100644 index 0000000..3b961ee Binary files /dev/null and b/art/raw/bg_counter.png differ diff --git a/art/raw/bg_dance.png b/art/raw/bg_dance.png new file mode 100644 index 0000000..5c08d98 Binary files /dev/null and b/art/raw/bg_dance.png differ diff --git a/art/raw/bg_entrance.png b/art/raw/bg_entrance.png new file mode 100644 index 0000000..adb48f6 Binary files /dev/null and b/art/raw/bg_entrance.png differ diff --git a/art/raw/bg_kitchen.png b/art/raw/bg_kitchen.png new file mode 100644 index 0000000..4b39e25 Binary files /dev/null and b/art/raw/bg_kitchen.png differ diff --git a/art/raw/bg_listening.png b/art/raw/bg_listening.png new file mode 100644 index 0000000..46aff5f Binary files /dev/null and b/art/raw/bg_listening.png differ diff --git a/art/raw/bg_rock.png b/art/raw/bg_rock.png new file mode 100644 index 0000000..7d410aa Binary files /dev/null and b/art/raw/bg_rock.png differ diff --git a/art/raw/bg_roof.png b/art/raw/bg_roof.png new file mode 100644 index 0000000..2d783ff Binary files /dev/null and b/art/raw/bg_roof.png differ diff --git a/art/raw/bg_shopfloor.png b/art/raw/bg_shopfloor.png new file mode 100644 index 0000000..2873b3b Binary files /dev/null and b/art/raw/bg_shopfloor.png differ diff --git a/art/raw/bg_toilet.png b/art/raw/bg_toilet.png new file mode 100644 index 0000000..76b8b21 Binary files /dev/null and b/art/raw/bg_toilet.png differ diff --git a/art/raw/bg_yard.png b/art/raw/bg_yard.png new file mode 100644 index 0000000..393c3be Binary files /dev/null and b/art/raw/bg_yard.png differ diff --git a/art/raw/sp_becky.png b/art/raw/sp_becky.png new file mode 100644 index 0000000..1bb3e01 Binary files /dev/null and b/art/raw/sp_becky.png differ diff --git a/art/raw/sp_clerk.png b/art/raw/sp_clerk.png new file mode 100644 index 0000000..c4cf452 Binary files /dev/null and b/art/raw/sp_clerk.png differ diff --git a/art/raw/sp_crier.png b/art/raw/sp_crier.png new file mode 100644 index 0000000..9131bfa Binary files /dev/null and b/art/raw/sp_crier.png differ diff --git a/art/raw/sp_dave.png b/art/raw/sp_dave.png new file mode 100644 index 0000000..bd6758e Binary files /dev/null and b/art/raw/sp_dave.png differ diff --git a/art/raw/sp_djsomeguy.png b/art/raw/sp_djsomeguy.png new file mode 100644 index 0000000..d138cfc Binary files /dev/null and b/art/raw/sp_djsomeguy.png differ diff --git a/art/raw/sp_kid.png b/art/raw/sp_kid.png new file mode 100644 index 0000000..546aa01 Binary files /dev/null and b/art/raw/sp_kid.png differ diff --git a/art/render_0.png b/art/render_0.png new file mode 100644 index 0000000..fea5048 Binary files /dev/null and b/art/render_0.png differ diff --git a/art/render_1.png b/art/render_1.png new file mode 100644 index 0000000..fd3d50c Binary files /dev/null and b/art/render_1.png differ diff --git a/art/render_10.png b/art/render_10.png new file mode 100644 index 0000000..2401ded Binary files /dev/null and b/art/render_10.png differ diff --git a/art/render_11.png b/art/render_11.png new file mode 100644 index 0000000..6bfc188 Binary files /dev/null and b/art/render_11.png differ diff --git a/art/render_12.png b/art/render_12.png new file mode 100644 index 0000000..9e714c9 Binary files /dev/null and b/art/render_12.png differ diff --git a/art/render_13.png b/art/render_13.png new file mode 100644 index 0000000..6ce1dac Binary files /dev/null and b/art/render_13.png differ diff --git a/art/render_2.png b/art/render_2.png new file mode 100644 index 0000000..dbf54c1 Binary files /dev/null and b/art/render_2.png differ diff --git a/art/render_3.png b/art/render_3.png new file mode 100644 index 0000000..ead24c8 Binary files /dev/null and b/art/render_3.png differ diff --git a/art/render_4.png b/art/render_4.png new file mode 100644 index 0000000..0f93c2c Binary files /dev/null and b/art/render_4.png differ diff --git a/art/render_5.png b/art/render_5.png new file mode 100644 index 0000000..1bf6b47 Binary files /dev/null and b/art/render_5.png differ diff --git a/art/render_6.png b/art/render_6.png new file mode 100644 index 0000000..63d5d44 Binary files /dev/null and b/art/render_6.png differ diff --git a/art/render_7.png b/art/render_7.png new file mode 100644 index 0000000..ee269fd Binary files /dev/null and b/art/render_7.png differ diff --git a/art/render_8.png b/art/render_8.png new file mode 100644 index 0000000..40069c3 Binary files /dev/null and b/art/render_8.png differ diff --git a/art/render_9.png b/art/render_9.png new file mode 100644 index 0000000..7c949ba Binary files /dev/null and b/art/render_9.png differ diff --git a/art/render_contact.png b/art/render_contact.png new file mode 100644 index 0000000..ef624fd Binary files /dev/null and b/art/render_contact.png differ diff --git a/game.json b/game.json new file mode 100644 index 0000000..ce21e89 --- /dev/null +++ b/game.json @@ -0,0 +1,10 @@ +{ + "name": "MORPQUEST: Record Store Day", + "start_room": 0, + "intro_text": "Record Store Day. You're meant to be doing a quick cat-food run; Uncle's list is in the glovebox, and the cat can smell weakness. Something is wrong at the shop today. Morp itself can feel it. (Arrows walk. Type 'help' for verbs.)", + "verbs": [ + { "name": "use", "words": ["climb", "wipe", "play", "pay", "post", "give", "plug", "press", "pin", "fix"] }, + { "name": "look", "words": ["search"] } + ], + "flags": {} +} diff --git a/rooms/room0.json b/rooms/room0.json new file mode 100644 index 0000000..9f1d16d --- /dev/null +++ b/rooms/room0.json @@ -0,0 +1,79 @@ +{ + "strokes": [ + { "Image": { "name": "bg_car", "x": 80, "y": 84, "meaning": "None" } }, + { "Rect": { "x": 0, "y": 0, "w": 160, "h": 138, "color": 0, "meaning": "Wall" } }, + { "Rect": { "x": 0, "y": 138, "w": 160, "h": 30, "color": 8, "meaning": "Floor" } }, + { "Image": { "name": "bg_car", "x": 80, "y": 84, "meaning": "None" } } + ], + "spawn": [80, 152], + "exits": [null, 1, null, null], + "music": 1, + "objects": [ + { + "name": "paperclip", "sprite": "paperclip", "x": 58, "y": 150, + "look": "It glints under the seat with the confidence of a tool that knows its moment has come.", + "takeable": true, "synonyms": "clip" + }, + { + "name": "car keys", "sprite": "keys", "x": 100, "y": 148, + "look": "Your keys. The fob battery died last week, same as the clock. The metal part still believes in you.", + "takeable": true, "synonyms": "key keys fob" + }, + { + "name": "wallet", "sprite": "wallet", "x": 122, "y": 152, + "look": "Your wallet. Inside: a bank card, three receipts you insist are important, and the faint scent of consequences. It is both your identity and your final boss.", + "takeable": true + }, + { + "name": "45 adapter", "sprite": "adapter", "x": 138, "y": 146, + "look": "A tiny plastic ring, loose in the glovebox. It is somehow the difference between listening and pretending. Small enough to lose. Important enough to ruin your day.", + "takeable": true, "synonyms": "adapter ring spider" + }, + { + "name": "tote bag", "sprite": "tote", "x": 30, "y": 150, + "look": "A cotton tote from a festival you don't remember enjoying. Structurally, it is 90% intention.", + "takeable": true, "synonyms": "tote bag" + }, + { + "name": "seatbelt", "sprite": "none", "x": 45, "y": 130, + "look": "The buckle is jammed. It has chosen violence, quietly.", + "synonyms": "belt buckle", + "needs": "paperclip", + "use_text": "You pick the jammed buckle with the paperclip. The belt releases with a noise like a sigh. Freedom, sponsored by office supplies.", + "sets_flag": "seatbelt-freed" + }, + { + "name": "mirror", "sprite": "none", "x": 80, "y": 125, + "look": "The rear-view mirror. Something is scratched into the plastic edge, under the dust.", + "synonyms": "rearview rear-view", + "use_text": "You wipe the dust from the mirror's edge. Scratched underneath, one word: RSD. It reads less like an acronym and more like an instruction.", + "sets_flag": "word-rsd" + }, + { + "name": "list", "sprite": "none", "x": 130, "y": 140, + "look": "Uncle's list, in handwriting like a cardiogram: RAMP (1977). A The The 10-inch. Model 500, 'Initial'. Three records that are basically rumours. At the bottom: DON'T COME BACK WITH NOTHING.", + "synonyms": "note uncles" + }, + { + "name": "kebab", "sprite": "none", "x": 20, "y": 145, + "look": "It is clearly mouldy. It is also somehow still in your car. This is a warning. This is another warning.", + "synonyms": "falafel food", + "use_text": "No. Some doors stay closed. (Morp applauds politely.)" + }, + { + "name": "ignition", "sprite": "none", "x": 62, "y": 128, + "look": "The ignition. The car will start when you have somewhere to be and a reason to leave. Currently you have neither.", + "synonyms": "engine start drive home", + "needs": "car keys", + "requires_flag": "beckies-dispersed", + "sets_flag": "went-home", + "wins": true, + "use_text": "The engine turns over first try — even the car is surprised. You pull out past the empty footpath, the white label on the passenger seat like a sleeping child. Uncle calls: furious, then quiet, then — BUT ALL OF THESE RECORDS ARE AWESOME. The cat gets fed. The shop is there all year, by the way. Buy records on the other 364 days too." + }, + { + "name": "dashboard", "sprite": "none", "x": 110, "y": 130, + "look": "The car interior is a small museum of your habits: receipts, cables, and the faint hum of unfinished plans. The dashboard clock blinks 12:00 (the battery died last week). Outside, through the windscreen, the shop waits.", + "synonyms": "dash clock windscreen glovebox wheel car" + } + ] +} diff --git a/rooms/room1.json b/rooms/room1.json new file mode 100644 index 0000000..4411171 --- /dev/null +++ b/rooms/room1.json @@ -0,0 +1,154 @@ +{ + "strokes": [ + { + "Image": { + "name": "bg_carpark", + "x": 80, + "y": 84, + "meaning": "None" + } + }, + { + "Rect": { + "x": 0, + "y": 0, + "w": 160, + "h": 78, + "color": 0, + "meaning": "Wall" + } + }, + { + "Rect": { + "x": 0, + "y": 78, + "w": 160, + "h": 90, + "color": 8, + "meaning": "Floor" + } + }, + { + "Rect": { + "x": 8, + "y": 78, + "w": 52, + "h": 32, + "color": 0, + "meaning": "Wall" + } + }, + { + "Rect": { + "x": 128, + "y": 78, + "w": 32, + "h": 26, + "color": 0, + "meaning": "Wall" + } + }, + { + "Rect": { + "x": 86, + "y": 16, + "w": 18, + "h": 66, + "color": 8, + "meaning": "Floor" + } + }, + { + "Image": { + "name": "bg_carpark", + "x": 80, + "y": 84, + "meaning": "None" + } + } + ], + "spawn": [ + 80, + 130 + ], + "exits": [ + 2, + null, + null, + 0 + ], + "music": 1, + "objects": [ + { + "name": "car", + "sprite": "none", + "x": 34, + "y": 108, + "look": "Your car. Maroon, faithful, embarrassing. It will wait. It's good at that.", + "synonyms": "sedan vehicle", + "use_text": "You lean on it companionably. The car does not judge you. The car has seen worse." + }, + { + "name": "bins", + "sprite": "none", + "x": 142, + "y": 106, + "look": "Two wheelie bins stand by the wall like bouncers for a club nobody wants into. The bins are where destiny hides from marketing.", + "synonyms": "bin wheelie rubbish", + "use_text": "You lift the lid. Inside: flattened boxes and one (1) single rollerblade. Destiny is hiding somewhere else today." + }, + { + "name": "kids", + "sprite": "kid", + "x": 118, + "y": 126, + "look": "Two school kids ranked against the wall, generating judgement at industrial scale. One of them is smoking, or committed to the bit of it.", + "synonyms": "kid teenagers teens smokers", + "dialogue": [ + { + "says": "Nah, look at this guy. He's doing quests in a record store. Respect though.", + "choices": [ + { + "text": "What's going on out front?", + "goto": 1 + }, + { + "text": "Ignore the commentary.", + "goto": -1 + } + ] + }, + { + "says": "Swift wall. Been there since sunrise. That makes them morally correct, apparently. (He says this like he wants to die.)", + "choices": [ + { + "text": "Anything else I should know?", + "goto": 2 + }, + { + "text": "Thanks.", + "goto": -1 + } + ] + }, + { + "says": "If you get in the side door you're basically a cheat code. That's illegal. That's awesome.", + "choices": [ + { + "text": "Noted.", + "goto": -1 + } + ] + } + ] + }, + { + "name": "asphalt", + "sprite": "none", + "x": 80, + "y": 150, + "look": "The air tastes like exhaust and ambition. Asphalt cracked like your resolve. Beyond the shop strip, a queue is busy being a wall.", + "synonyms": "carpark ground cracks lines shop strip" + } + ] +} \ No newline at end of file diff --git a/rooms/room10.json b/rooms/room10.json new file mode 100644 index 0000000..2d9c720 --- /dev/null +++ b/rooms/room10.json @@ -0,0 +1,155 @@ +{ + "strokes": [ + { + "Image": { + "name": "bg_dance", + "x": 80, + "y": 84, + "meaning": "None" + } + }, + { + "Rect": { + "x": 0, + "y": 0, + "w": 160, + "h": 92, + "color": 0, + "meaning": "Wall" + } + }, + { + "Rect": { + "x": 0, + "y": 92, + "w": 160, + "h": 76, + "color": 8, + "meaning": "Floor" + } + }, + { + "Rect": { + "x": 0, + "y": 92, + "w": 28, + "h": 40, + "color": 0, + "meaning": "Wall" + } + }, + { + "Rect": { + "x": 118, + "y": 92, + "w": 42, + "h": 30, + "color": 0, + "meaning": "Wall" + } + }, + { + "Rect": { + "x": 66, + "y": 16, + "w": 24, + "h": 80, + "color": 8, + "meaning": "Floor" + } + }, + { + "Image": { + "name": "bg_dance", + "x": 80, + "y": 84, + "meaning": "None" + } + } + ], + "spawn": [ + 80, + 150 + ], + "exits": [ + 11, + null, + null, + 8 + ], + "music": 4, + "objects": [ + { + "name": "crate", + "sprite": "none", + "x": 58, + "y": 122, + "look": "One crate is packed too tight to flip. Records under this much pressure become opinions.", + "synonyms": "overstuffed tight packed", + "needs": "tote bag", + "sets_flag": "made-space", + "use_text": "You decant a handful of twelve-inches into the tote. The crate exhales like it's been holding its breath for years. A gap is a doorway. A gap is a philosophy." + }, + { + "name": "someguy", + "sprite": "none", + "x": 84, + "y": 92, + "look": "DJ Someguy. Headphones around his neck, plugged into nothing, receiving signal anyway. He has been standing at that rack since before the shop had a name.", + "synonyms": "dj whale figure man", + "dialogue": [ + { + "says": "He communicates first in BPMs, then in matrix etchings, then \u2014 reluctantly \u2014 in words. 'You're digging. Good. Dig with your hands, not your hopes.'", + "choices": [ + { + "text": "I made space in the tight crate.", + "goto": 1, + "requires_flag": "made-space" + }, + { + "text": "What are you looking for?", + "goto": 2 + }, + { + "text": "Nod. Leave.", + "goto": -1 + } + ] + }, + { + "says": "'Then you already know the secret.' He leans in. 'There's a seam in the back room where the mould meets the crate. Things get filed there that were never meant to be found. Keep your face normal. Your face is loud.'", + "choices": [ + { + "text": "Normal face. Got it.", + "goto": -1 + } + ] + }, + { + "says": "'A white label not filed under white label.' He looks at the counter, then away. Doesn't want the counter to know he wants it, you think. He wants you to think that.", + "choices": [ + { + "text": "Cryptic. Thanks.", + "goto": -1 + } + ] + } + ] + }, + { + "name": "racks", + "sprite": "none", + "x": 120, + "y": 105, + "look": "Twelve-inches in ranks, genre cards like tarot. DEEP HOUSE \u2014 no big drops, just gradual emotional damage. WITCH HOUSE \u2014 if you hear your name in the reverb, no you didn't.", + "synonyms": "records house genre cards around twelve" + }, + { + "name": "fairy lights", + "sprite": "none", + "x": 30, + "y": 88, + "look": "A string of dead fairy lights. They died doing what they loved: implying atmosphere." + } + ] +} \ No newline at end of file diff --git a/rooms/room11.json b/rooms/room11.json new file mode 100644 index 0000000..7887994 --- /dev/null +++ b/rooms/room11.json @@ -0,0 +1,43 @@ +{ + "strokes": [ + { "Image": { "name": "bg_listening", "x": 80, "y": 84, "meaning": "None" } }, + { "Rect": { "x": 0, "y": 0, "w": 160, "h": 102, "color": 0, "meaning": "Wall" } }, + { "Rect": { "x": 0, "y": 102, "w": 160, "h": 66, "color": 8, "meaning": "Floor" } }, + { "Rect": { "x": 0, "y": 102, "w": 44, "h": 30, "color": 0, "meaning": "Wall" } }, + { "Image": { "name": "bg_listening", "x": 80, "y": 84, "meaning": "None" } } + ], + "spawn": [100, 150], + "exits": [null, null, 10, null], + "music": 1, + "objects": [ + { + "name": "turntable", "sprite": "none", "x": 62, "y": 100, + "look": "A sign asks for restraint. It will not be obeyed. The station waits for your decision like a small judge. The 45 adapter slot mocks you if you don't have one.", + "synonyms": "deck platter station", + "needs": "45 adapter", + "sets_flag": "station-working", + "use_text": "You seat the adapter, drop the needle. The turntable stops judging you for a second. Sound happens — real, warm, undeserved." + }, + { + "name": "balloon", "sprite": "none", "x": 122, "y": 66, + "look": "A sad promotional balloon on a string, half-deflated, taped to the shelf. It has seen release dates come and go.", + "synonyms": "red promotional", + "needs": "elastic band", + "sets_flag": "eyepatch-made", + "use_text": "Balloon plus elastic band equals — against every law of craft — a functional eyepatch. You are not proud. You are exactly proud." + }, + { + "name": "headphones", "sprite": "none", "x": 52, "y": 62, + "look": "Big padded cans, warm from nobody.", + "synonyms": "cans phones", + "requires_flag": "word-global", + "sets_flag": "word-ambassador", + "use_text": "Through the headphones, under the shop's hum, the runout groove of whatever last played is still circling. A voice in the vinyl, patient as a checkout queue: AMBASSADOR. You take the headphones off slowly." + }, + { + "name": "corner", "sprite": "none", "x": 120, "y": 140, + "look": "A corner where taste becomes audible and mistakes become public. Crates of contenders wait under the plinth, pretending not to audition.", + "synonyms": "listening around crates plinth sign" + } + ] +} diff --git a/rooms/room12.json b/rooms/room12.json new file mode 100644 index 0000000..be787ba --- /dev/null +++ b/rooms/room12.json @@ -0,0 +1,202 @@ +{ + "strokes": [ + { + "Image": { + "name": "bg_counter", + "x": 80, + "y": 84, + "meaning": "None" + } + }, + { + "Rect": { + "x": 0, + "y": 0, + "w": 160, + "h": 96, + "color": 0, + "meaning": "Wall" + } + }, + { + "Rect": { + "x": 0, + "y": 96, + "w": 160, + "h": 72, + "color": 8, + "meaning": "Floor" + } + }, + { + "Rect": { + "x": 84, + "y": 96, + "w": 76, + "h": 50, + "color": 0, + "meaning": "Wall" + } + }, + { + "Rect": { + "x": 60, + "y": 16, + "w": 26, + "h": 84, + "color": 8, + "meaning": "Floor" + } + }, + { + "Image": { + "name": "bg_counter", + "x": 80, + "y": 84, + "meaning": "None" + } + } + ], + "spawn": [ + 40, + 150 + ], + "exits": [ + 13, + null, + 8, + null + ], + "music": 3, + "objects": [ + { + "name": "clerk", + "sprite": "clerk", + "x": 70, + "y": 108, + "look": "The Clerk. Sister Pressing, the kids call her. One eye ruined (barcode scanner, Tuesday, don't). Procedure is her religion and you are, at best, a lapsed parishioner.", + "synonyms": "sister pressing staff woman", + "dialogue": [ + { + "says": "The Clerk regards you with the eye that still works. 'Browsing is free. Questions cost patience. Mine.'", + "choices": [ + { + "text": "I resleeved the record someone left out.", + "goto": 1, + "requires_flag": "shop-tidied", + "sets_flag": "clerk-warm" + }, + { + "text": "Found the break ancestor. Yellow sleeve.", + "goto": 4, + "requires_flag": "break-ancestor-revealed" + }, + { + "text": "What happened to your eye?", + "goto": 2 + }, + { + "text": "Ask about the back room.", + "goto": 3 + }, + { + "text": "Leave her be.", + "goto": -1 + } + ] + }, + { + "says": "'Saw that.' A pause long enough to press a record in. 'You did well today. Most people don't.'", + "choices": [ + { + "text": "Made you an eyepatch. Balloon and elastic. Don't ask.", + "goto": 5, + "requires_flag": "eyepatch-made", + "sets_flag": "back-room-open" + }, + { + "text": "Just doing my bit.", + "goto": -1 + } + ] + }, + { + "says": "'Barcode scanner. Tuesday. Don't.' The subject is closed the way a register drawer closes.", + "choices": [ + { + "text": "Understood.", + "goto": -1 + } + ] + }, + { + "says": "'Back room opens for people who fix problems. Not for people who make them.' She restocks a divider labelled REGRET.", + "choices": [ + { + "text": "Noted.", + "goto": -1 + } + ] + }, + { + "says": "'Yeah.' She looks at you like a person, for a full second. 'That one.'", + "choices": [ + { + "text": "(That's the highest praise there is.)", + "goto": -1 + } + ] + }, + { + "says": "She tries it on. It fits. It absolutely should not fit. 'The beaded curtain,' she says, 'is no longer purely decorative.' This is the warmest thing anyone has said to you in years.", + "choices": [ + { + "text": "(Nod. Say nothing. Perfect.)", + "goto": -1 + } + ] + } + ] + }, + { + "name": "terminal", + "sprite": "none", + "x": 96, + "y": 90, + "look": "The card terminal, and a sign that tells you to be ready. You are not ready.", + "synonyms": "card register eftpos machine", + "needs": "wallet", + "requires_flag": "found-grail", + "sets_flag": "paid", + "use_text": "Card. Beep. The pause. The long pause. The pause develops a personality. APPROVED. You feel a wave of relief so intense it should be taxable. Terminal beeps are judgment sounds; today, acquittal." + }, + { + "name": "spike", + "sprite": "none", + "x": 132, + "y": 96, + "look": "A metal spike stacked with the day's regrets, in chronological order.", + "synonyms": "receipt receipts papers", + "requires_flag": "word-ambassador", + "sets_flag": "word-version", + "use_text": "You flick through to the freshest receipt. Under TOTAL, printed where the loyalty points should be: VERSION. The printer knew. Printers always know." + }, + { + "name": "curtain", + "sprite": "none", + "x": 70, + "y": 82, + "look": "A beaded curtain radiating pure judgement. Behind it, a different temperature of reality. (The back room is through the doorway, north.)", + "synonyms": "beads beaded doorway", + "use_text": "The beads part exactly as much as they choose to. It's a curtain-shaped personality test." + }, + { + "name": "counter", + "sprite": "none", + "x": 110, + "y": 120, + "look": "Where your taste is priced, your budget is tested, and your dignity is processed at speed. A brass bell waits beside rolls of price stickers.", + "synonyms": "bell stickers around desk" + } + ] +} \ No newline at end of file diff --git a/rooms/room13.json b/rooms/room13.json new file mode 100644 index 0000000..8721db2 --- /dev/null +++ b/rooms/room13.json @@ -0,0 +1,43 @@ +{ + "strokes": [ + { "Image": { "name": "bg_backroom", "x": 80, "y": 84, "meaning": "None" } }, + { "Rect": { "x": 0, "y": 0, "w": 160, "h": 104, "color": 0, "meaning": "Wall" } }, + { "Rect": { "x": 0, "y": 104, "w": 160, "h": 64, "color": 8, "meaning": "Floor" } }, + { "Rect": { "x": 108, "y": 104, "w": 52, "h": 34, "color": 0, "meaning": "Wall" } }, + { "Image": { "name": "bg_backroom", "x": 80, "y": 84, "meaning": "None" } } + ], + "spawn": [70, 150], + "exits": [null, null, 12, null], + "music": 5, + "objects": [ + { + "name": "seam", "sprite": "none", "x": 28, "y": 110, + "look": "A dark corner where mould negotiates with wood. The shadow has a straight edge. Shadows don't.", + "synonyms": "corner mould gap shadow", + "requires_flag": "back-room-open", + "sets_flag": "found-grail", + "use_text": "You reach into the seam (cough tax: one cough). Fingers find cardboard: a white label — THE white label — and, taped to it, an envelope marked OFFICIAL. Filed where the counter couldn't want it." + }, + { + "name": "white label", "sprite": "whitelabel", "x": 24, "y": 126, + "look": "No artist. No title. A single-digit catalogue number. The packaging is so minimal it becomes smug. Finding one is a miracle; buying one is a financial decision. It is, impossibly, adjacent to Uncle's list — filed under nothing.", + "takeable": true, "synonyms": "label record grail vinyl" + }, + { + "name": "delay notice", "sprite": "notice", "x": 40, "y": 128, + "look": "OFFICIAL RSD NOTICE: ALL SPECIAL RELEASES DELAYED IN TRANSIT. (VAN BROKE DOWN NEAR A GLITTER FACTORY.) Stamped, signed, real. Documents are the only thing the wall trusts.", + "takeable": true, "synonyms": "notice envelope official" + }, + { + "name": "throne", "sprite": "none", "x": 128, "y": 130, + "look": "A milk crate waits like a throne for the exhausted.", + "synonyms": "milk crate seat", + "use_text": "You sit. For eleven seconds, nothing is required of you. Best eleven seconds of the day." + }, + { + "name": "ruins", "sprite": "none", "x": 90, "y": 112, + "look": "Unsorted crates stacked like ruins. No dividers. No mercy. It's unpriced, unsorted, and smells like the history of dust. Chalk marks on the shelf slats spell out chords for furniture.", + "synonyms": "crates stacks around room boxes" + } + ] +} diff --git a/rooms/room2.json b/rooms/room2.json new file mode 100644 index 0000000..47bfb09 --- /dev/null +++ b/rooms/room2.json @@ -0,0 +1,243 @@ +{ + "strokes": [ + { + "Image": { + "name": "bg_entrance", + "x": 80, + "y": 84, + "meaning": "None" + } + }, + { + "Rect": { + "x": 0, + "y": 0, + "w": 160, + "h": 92, + "color": 0, + "meaning": "Wall" + } + }, + { + "Rect": { + "x": 0, + "y": 92, + "w": 160, + "h": 76, + "color": 8, + "meaning": "Floor" + } + }, + { + "Image": { + "name": "bg_entrance", + "x": 80, + "y": 84, + "meaning": "None" + } + } + ], + "spawn": [ + 80, + 140 + ], + "exits": [ + null, + 3, + 1, + null + ], + "music": 3, + "objects": [ + { + "name": "becky", + "sprite": "becky", + "x": 52, + "y": 112, + "look": "A scout at the edge of the wall. Cardigan: rose quartz with a blush overtone. Smile: pre-approved. She has been here since sunrise and it has made her stronger.", + "synonyms": "theologian fan swiftie scout", + "dialogue": [ + { + "says": "It's not 'pink'. It's 'rose quartz with a blush overtone'. Please respect the canon.", + "choices": [ + { + "text": "Which variant is actually canon today?", + "goto": 1, + "sets_flag": "poster-frenzy" + }, + { + "text": "Are you\u2026 okay?", + "goto": 2 + }, + { + "text": "Back away slowly.", + "goto": -1 + } + ] + }, + { + "says": "CANON? The word travels down the wall like a current. Someone screams about overtones. In the scuffle, the big poster is torn to confetti \u2014 it snows Taylor over the footpath. The fragments settle against the brick. Some of them look like words.", + "choices": [ + { + "text": "Interesting weather.", + "goto": -1 + } + ] + }, + { + "says": "We've been here since sunrise. That makes us morally correct. Also: 'Are you in the era?' You are not in the era. You are in a record store.", + "choices": [ + { + "text": "Fair.", + "goto": -1 + } + ] + } + ] + }, + { + "name": "fragments", + "sprite": "none", + "x": 68, + "y": 130, + "look": "Poster confetti, clinging to the damp brick and the footpath. From this angle it's noise.", + "synonyms": "confetti poster pieces scraps", + "requires_flag": "poster-frenzy", + "sets_flag": "knows-yard-door-secret", + "use_text": "You crouch and read the fragments the way pigeons read chip packets. Rearranged by wind and intent, they spell: YARD DOOR SECRET. The brick wall suddenly has opinions." + }, + { + "name": "dave", + "sprite": "dave", + "x": 126, + "y": 128, + "look": "Someone You Know. Flannel, beanie, the posture of a man who has already accepted the day's bullshit. He nods at you like continuity.", + "synonyms": "friend someone mate man", + "dialogue": [ + { + "says": "Don't go through the front. It's a Swift wall today. Come here. Don't look guilty. Looking guilty is how you get caught.", + "choices": [ + { + "text": "There's a door outline behind the shop. Painted over.", + "goto": 1, + "requires_flag": "seen-door-outline", + "sets_flag": "side-door-open" + }, + { + "text": "Who are you again?", + "goto": 2 + }, + { + "text": "Later.", + "goto": -1 + } + ] + }, + { + "says": "'Yeah nah. That was NOT there.' He walks you round the alley and shoulders the outline like it owes him money. It opens. 'I owe you one. This is me paying it back in the currency of mild trespass. Two moves in that kitchen before Owner Energy spots you. Use them wisely. Or don't. Morp loves an unwise move.'", + "choices": [ + { + "text": "Legend.", + "goto": -1 + } + ] + }, + { + "says": "Someone You Know. That's the whole bit. They're not angry because of you, by the way. They're angry because scarcity makes people religious.", + "choices": [ + { + "text": "Deep.", + "goto": -1 + } + ] + } + ] + }, + { + "name": "crier", + "sprite": "crier", + "x": 96, + "y": 118, + "look": "A Becky decoupled from the wall, crying with impressive throughput. The mascara has annexed both cheeks.", + "synonyms": "crying becky2 girl", + "needs": "lollipop", + "requires_flag": "read-aloud", + "sets_flag": "beckies-dispersed", + "use_text": "She takes the lollipop like a communion wafer. Somewhere in the wall, a decision is reached collectively. The Beckies disperse toward the coffee van in perfect formation, discussing brunch. The store can breathe again.", + "dialogue": [ + { + "says": "I skipped brunch for this. I SKIPPED BRUNCH. Do you have\u2026 water? Or a lollipop? Or like\u2026 proof the universe isn't mean?", + "choices": [ + { + "text": "Read the notice aloud. Like it's law.", + "goto": 1, + "requires_flag": "notice-posted", + "sets_flag": "read-aloud" + }, + { + "text": "The universe is mostly mean.", + "goto": 2 + }, + { + "text": "Leave her to it.", + "goto": -1 + } + ] + }, + { + "says": "'DELAYED IN TRANSIT,' you read, loudly, officially, like a better timeline's lawyer. The hive stills. DELAYED IN TRANSIT. EXTENDED IN DESIRE. They need one more thing now. A ritual. A parting gift.", + "choices": [ + { + "text": "SAY THE WORDS.", + "goto": 3, + "requires_flag": "word-version", + "sets_flag": "curse-broken" + }, + { + "text": "(Something in your pocket, maybe.)", + "goto": -1 + } + ] + }, + { + "says": "'It's not parasocial. It's community,' she says \u2014 to you, a stranger she is now holding by the sleeve.", + "choices": [ + { + "text": "Um.", + "goto": -1 + } + ] + }, + { + "says": "RSD. FIRST. GLOBAL. AMBASSADOR. VERSION. You say it like a legal notice from a better timeline. The hive stops. Blinks. Somebody asks, in a small human voice: '\u2026why are we here?' 'I have a parking ticket,' says another, amazed, like it's a birth certificate. The door is a door again.", + "choices": [ + { + "text": "(Say nothing. Let them wake.)", + "goto": -1 + } + ] + } + ] + }, + { + "name": "notice board", + "sprite": "none", + "x": 140, + "y": 108, + "look": "A community notice board: lost cats, found gods, a flyer for a band called Regret Harvest. Space in the middle for something official.", + "synonyms": "board noticeboard", + "needs": "delay notice", + "sets_flag": "notice-posted", + "use_text": "You pin the official delay notice dead centre, straightening it with the care of a museum curator. It hangs there like law.", + "requires_flag": "paid" + }, + { + "name": "queue", + "sprite": "none", + "x": 40, + "y": 100, + "look": "It's not a crowd. It's a wall. A living wall in pastel cardigans, breathing as one. The door is close enough to taste, far enough to suffer.", + "synonyms": "wall beckies beckywall fans door shop" + } + ] +} \ No newline at end of file diff --git a/rooms/room3.json b/rooms/room3.json new file mode 100644 index 0000000..4fc414a --- /dev/null +++ b/rooms/room3.json @@ -0,0 +1,124 @@ +{ + "strokes": [ + { + "Image": { + "name": "bg_alley", + "x": 80, + "y": 84, + "meaning": "None" + } + }, + { + "Rect": { + "x": 0, + "y": 0, + "w": 160, + "h": 88, + "color": 0, + "meaning": "Wall" + } + }, + { + "Rect": { + "x": 0, + "y": 88, + "w": 160, + "h": 80, + "color": 8, + "meaning": "Floor" + } + }, + { + "Rect": { + "x": 0, + "y": 88, + "w": 10, + "h": 30, + "color": 8, + "meaning": "Floor" + } + }, + { + "Rect": { + "x": 148, + "y": 88, + "w": 12, + "h": 80, + "color": 0, + "meaning": "Wall" + } + }, + { + "Rect": { + "x": 62, + "y": 16, + "w": 22, + "h": 76, + "color": 8, + "meaning": "Floor" + } + }, + { + "Image": { + "name": "bg_alley", + "x": 80, + "y": 84, + "meaning": "None" + } + } + ], + "spawn": [ + 80, + 140 + ], + "exits": [ + 5, + null, + 2, + 4 + ], + "music": 2, + "objects": [ + { + "name": "toilet door", + "sprite": "none", + "x": 22, + "y": 100, + "look": "A red door in the west wall, hand-painted sign long surrendered to weather. Under it, in newer paint: THE PACIFIC RIM. From inside: nothing. Mercifully, nothing.", + "synonyms": "red door toilets", + "use_text": "It's ajar. You could just walk in (west). The door respects that you asked, though." + }, + { + "name": "leaves", + "sprite": "none", + "x": 148, + "y": 110, + "look": "Vinyl leaves climb the wall in slow motion, glossy and stubborn, like the building is trying to grow its own record collection.", + "synonyms": "vine ivy plant wall" + }, + { + "name": "graffiti", + "sprite": "none", + "x": 110, + "y": 96, + "look": "Graffiti that reads like patch notes for reality. MORP WAS HERE, and under it, smaller: MORP IS ALWAYS HERE.", + "synonyms": "mural tags writing" + }, + { + "name": "milk crates", + "sprite": "none", + "x": 60, + "y": 104, + "look": "Stacked milk crates, the load-bearing furniture of the entire music industry.", + "synonyms": "crates stack" + }, + { + "name": "alley", + "sprite": "none", + "x": 80, + "y": 150, + "look": "A narrow throat of brick and bitumen between the shop and the rest of the suburb. The back of the alley opens onto the service yard (north). The street is behind you (south).", + "synonyms": "bitumen puddles ground around" + } + ] +} \ No newline at end of file diff --git a/rooms/room4.json b/rooms/room4.json new file mode 100644 index 0000000..f36f849 --- /dev/null +++ b/rooms/room4.json @@ -0,0 +1,48 @@ +{ + "strokes": [ + { "Image": { "name": "bg_toilet", "x": 80, "y": 84, "meaning": "None" } }, + { "Rect": { "x": 0, "y": 0, "w": 160, "h": 108, "color": 0, "meaning": "Wall" } }, + { "Rect": { "x": 0, "y": 108, "w": 160, "h": 60, "color": 8, "meaning": "Floor" } }, + { "Rect": { "x": 110, "y": 100, "w": 50, "h": 68, "color": 0, "meaning": "Wall" } }, + { "Image": { "name": "bg_toilet", "x": 80, "y": 84, "meaning": "None" } } + ], + "spawn": [60, 145], + "exits": [null, null, 3, null], + "music": 5, + "objects": [ + { + "name": "elastic band", "sprite": "elastic", "x": 118, "y": 118, + "look": "In the cupboard under the sink: one fat rubber band, the kind that holds together things that matter. Mail. Asparagus. Lives.", + "takeable": true, "synonyms": "elastic rubber band" + }, + { + "name": "mirror", "sprite": "none", "x": 130, "y": 90, + "look": "Someone has written on the glass in lipstick: DON'T ARGUE. REPEAT THE NOTICE. Under the smear, something older is scratched into the corner.", + "synonyms": "glass lipstick", + "requires_flag": "word-rsd", + "sets_flag": "word-first", + "use_text": "You breathe on the glass and wipe. Scratched in the corner, one word: FIRST. The mirror fogs over again immediately, embarrassed." + }, + { + "name": "cupboard", "sprite": "none", "x": 122, "y": 112, + "look": "A cupboard under the sink, one door ajar in the universal posture of nothing-to-hide. Cleaning products from discontinued brands. Something rubbery glints on the shelf.", + "synonyms": "sink cabinet shelf" + }, + { + "name": "graffiti", "sprite": "none", "x": 50, "y": 95, + "look": "The wall says, in four different hands: ELASTIC. EYEPATCH. GRATITUDE. DOOR. Then: DELAYS HAPPEN. DON'T PANIC. PANIC IS FOR TOURISTS. Then, smaller: the back room is real. the back room is also a trap. both can be true.", + "synonyms": "walls writing wall notes" + }, + { + "name": "dryer", "sprite": "none", "x": 12, "y": 92, + "look": "The hand dryer is aggressive and judgmental. You do not give it the opportunity.", + "synonyms": "hand dryer", + "use_text": "It roars at a frequency that means something personal. Your hands were not even wet." + }, + { + "name": "mop", "sprite": "none", "x": 30, "y": 112, + "look": "A mop in a bucket, retired in place. It has seen the whole history of this floor and chosen silence.", + "synonyms": "bucket" + } + ] +} diff --git a/rooms/room5.json b/rooms/room5.json new file mode 100644 index 0000000..0ed1e30 --- /dev/null +++ b/rooms/room5.json @@ -0,0 +1,109 @@ +{ + "strokes": [ + { + "Image": { + "name": "bg_yard", + "x": 80, + "y": 84, + "meaning": "None" + } + }, + { + "Rect": { + "x": 0, + "y": 0, + "w": 160, + "h": 96, + "color": 0, + "meaning": "Wall" + } + }, + { + "Rect": { + "x": 0, + "y": 96, + "w": 160, + "h": 72, + "color": 8, + "meaning": "Floor" + } + }, + { + "Rect": { + "x": 112, + "y": 96, + "w": 22, + "h": 20, + "color": 0, + "meaning": "Wall" + } + }, + { + "Rect": { + "x": 56, + "y": 16, + "w": 24, + "h": 84, + "color": 8, + "meaning": "Floor" + } + }, + { + "Image": { + "name": "bg_yard", + "x": 80, + "y": 84, + "meaning": "None" + } + } + ], + "spawn": [ + 40, + 140 + ], + "exits": [ + 7, + 6, + 3, + null + ], + "music": 2, + "objects": [ + { + "name": "vine", + "sprite": "none", + "x": 146, + "y": 110, + "look": "Glossy green leaves all the way up the wall and over the roofline. Decorative, probably.", + "synonyms": "ivy leaves plant", + "requires_flag": "knows-yard-door-secret", + "sets_flag": "noticed-vine", + "use_text": "Now that you know the phrase, you notice the vine properly. It isn't a ladder. But it's climbable, if you're the kind of person who treats architecture as a suggestion. You get the sudden, stupid certainty that you could climb this. (The roof is up the vine, to the east.)" + }, + { + "name": "side door", + "sprite": "none", + "x": 68, + "y": 88, + "look": "A steel door that says STAFF in tone alone. Beside it, under the paint: a door-shaped nothing. The kind of nothing that used to be a something.", + "synonyms": "steel door staff kitchen", + "use_text": "Locked, in the way of doors that have opinions about you specifically. If someone who belonged here opened it instead, that would be different." + }, + { + "name": "crates", + "sprite": "none", + "x": 120, + "y": 104, + "look": "Broken record crates stacked like a settlement that lost its funding. A dead pot plant oversees them.", + "synonyms": "boxes junk pot plant" + }, + { + "name": "yard", + "sprite": "none", + "x": 80, + "y": 150, + "look": "A cramped concrete service yard where the shop keeps its honesty. A washing-line shadow crosses the ground like a diagram of a better day.", + "synonyms": "concrete ground around downpipe" + } + ] +} \ No newline at end of file diff --git a/rooms/room6.json b/rooms/room6.json new file mode 100644 index 0000000..a1ee468 --- /dev/null +++ b/rooms/room6.json @@ -0,0 +1,37 @@ +{ + "strokes": [ + { "Image": { "name": "bg_roof", "x": 80, "y": 84, "meaning": "None" } }, + { "Rect": { "x": 0, "y": 0, "w": 160, "h": 100, "color": 0, "meaning": "Wall" } }, + { "Rect": { "x": 0, "y": 100, "w": 160, "h": 68, "color": 8, "meaning": "Floor" } }, + { "Rect": { "x": 0, "y": 155, "w": 160, "h": 13, "color": 0, "meaning": "Wall" } }, + { "Image": { "name": "bg_roof", "x": 80, "y": 84, "meaning": "None" } } + ], + "spawn": [30, 130], + "exits": [null, null, null, 5], + "music": 1, + "objects": [ + { + "name": "door outline", "sprite": "none", "x": 24, "y": 148, + "look": "Below, behind the shop, the yard wall catches the light wrong.", + "synonyms": "outline absence seam wall below", + "sets_flag": "seen-door-outline", + "use_text": "From this exact angle the light gives it up: a painted-over door beside the STAFF door. A door outline. Like someone erased it and hoped. The 'front door' is theatre. That kitchen door is logistics." + }, + { + "name": "view", "sprite": "none", "x": 80, "y": 115, + "look": "From up here the car park looks calm in the way a trap looks calm. The queue looks like choreography. Everything looks smaller. Your problems look the same size.", + "synonyms": "carpark queue edge parapet" + }, + { + "name": "skylight", "sprite": "none", "x": 108, "y": 120, + "look": "Through the skylight: crates, dust, one bare bulb. The mythical back room, from above, looking like an ordinary room. Myths usually do.", + "synonyms": "window glass", + "use_text": "Painted shut since federation. The back room keeps its front door policy even for its ceiling." + }, + { + "name": "roof", "sprite": "none", "x": 120, "y": 140, + "look": "Tar, gravel, one air-conditioning unit humming the note between B and complaint. Above: sky. Below: retail.", + "synonyms": "gravel tar aircon unit around" + } + ] +} diff --git a/rooms/room7.json b/rooms/room7.json new file mode 100644 index 0000000..138f3ec --- /dev/null +++ b/rooms/room7.json @@ -0,0 +1,120 @@ +{ + "strokes": [ + { + "Image": { + "name": "bg_kitchen", + "x": 80, + "y": 84, + "meaning": "None" + } + }, + { + "Rect": { + "x": 0, + "y": 0, + "w": 160, + "h": 100, + "color": 0, + "meaning": "Wall" + } + }, + { + "Rect": { + "x": 0, + "y": 100, + "w": 160, + "h": 68, + "color": 8, + "meaning": "Floor" + } + }, + { + "Rect": { + "x": 0, + "y": 100, + "w": 26, + "h": 40, + "color": 0, + "meaning": "Wall" + } + }, + { + "Rect": { + "x": 128, + "y": 100, + "w": 32, + "h": 40, + "color": 0, + "meaning": "Wall" + } + }, + { + "Rect": { + "x": 66, + "y": 16, + "w": 24, + "h": 88, + "color": 8, + "meaning": "Floor" + } + }, + { + "Image": { + "name": "bg_kitchen", + "x": 80, + "y": 84, + "meaning": "None" + } + } + ], + "spawn": [ + 80, + 150 + ], + "exits": [ + 8, + null, + 5, + null + ], + "music": 3, + "objects": [ + { + "name": "lollipop", + "sprite": "lollipop", + "x": 38, + "y": 96, + "look": "One consolation-grade lollipop under a soggy flyer, still wrapped. Somebody's emergency kindness, forgotten in place.", + "takeable": true, + "synonyms": "lolly sweet" + }, + { + "name": "splashback", + "sprite": "none", + "x": 60, + "y": 84, + "look": "Stainless steel, greasy enough to hold information.", + "synonyms": "steel grease stove", + "requires_flag": "word-first", + "sets_flag": "word-global", + "use_text": "You wipe a porthole in the grease. Scratched into the steel beneath: GLOBAL. The grease begins closing over it immediately, like water." + }, + { + "name": "benches", + "sprite": "none", + "x": 110, + "y": 105, + "look": "Three stainless benches. One holds soggy flyers, one holds nothing, one holds the specific silence of an owner about to appear. You have, at a guess, two moves before Owner Energy finds you. Use them wisely. Or don't.", + "synonyms": "bench flyers sink" + }, + { + "name": "urn", + "sprite": "none", + "x": 96, + "y": 88, + "look": "The hot-water urn rumbles like it knows things. It probably does. Old coffee and new anxiety.", + "synonyms": "hot water", + "use_text": "You do not have a cup. The urn respects your restraint. Barely." + } + ] +} \ No newline at end of file diff --git a/rooms/room8.json b/rooms/room8.json new file mode 100644 index 0000000..5b8de30 --- /dev/null +++ b/rooms/room8.json @@ -0,0 +1,118 @@ +{ + "strokes": [ + { + "Image": { + "name": "bg_shopfloor", + "x": 80, + "y": 84, + "meaning": "None" + } + }, + { + "Rect": { + "x": 0, + "y": 0, + "w": 160, + "h": 96, + "color": 0, + "meaning": "Wall" + } + }, + { + "Rect": { + "x": 0, + "y": 96, + "w": 160, + "h": 72, + "color": 8, + "meaning": "Floor" + } + }, + { + "Rect": { + "x": 0, + "y": 96, + "w": 34, + "h": 34, + "color": 0, + "meaning": "Wall" + } + }, + { + "Rect": { + "x": 112, + "y": 96, + "w": 48, + "h": 28, + "color": 0, + "meaning": "Wall" + } + }, + { + "Rect": { + "x": 66, + "y": 16, + "w": 24, + "h": 84, + "color": 8, + "meaning": "Floor" + } + }, + { + "Image": { + "name": "bg_shopfloor", + "x": 80, + "y": 84, + "meaning": "None" + } + } + ], + "spawn": [ + 80, + 150 + ], + "exits": [ + 12, + 10, + 7, + 9 + ], + "music": 4, + "objects": [ + { + "name": "clean sleeve", + "sprite": "sleeve", + "x": 90, + "y": 148, + "look": "A crisp outer sleeve, unpriced, unclaimed. It catches light the way a fresh sleeve catches light: accusatory.", + "takeable": true, + "synonyms": "sleeve outer plastic" + }, + { + "name": "messy record", + "sprite": "none", + "x": 128, + "y": 118, + "look": "A record left half out of its sleeve on top of the crate, like someone woke it up and wandered off. Physically painful to look at.", + "synonyms": "record half loose vinyl", + "needs": "clean sleeve", + "sets_flag": "shop-tidied", + "use_text": "You slide it into the clean sleeve, square the corners, file it under its own name. Across the shop, the Clerk notices. Pretends not to. But it counts." + }, + { + "name": "bins", + "sprite": "none", + "x": 50, + "y": 110, + "look": "You step inside and the air changes: dust, ink, and people taking themselves very seriously. Bins stretch out in rows like a low-budget labyrinth. Posters stare. Staff picks whisper. The counter is up the back (north); genre country spreads east and west.", + "synonyms": "crates rows records shop racks around aisle" + }, + { + "name": "staff picks", + "sprite": "none", + "x": 20, + "y": 92, + "look": "Handwritten cards of increasing confidence. One just says 'yes.' One says 'you're not ready' \u2014 it's filed under AMBIENT." + } + ] +} \ No newline at end of file diff --git a/rooms/room9.json b/rooms/room9.json new file mode 100644 index 0000000..ae5e58f --- /dev/null +++ b/rooms/room9.json @@ -0,0 +1,44 @@ +{ + "strokes": [ + { "Image": { "name": "bg_rock", "x": 80, "y": 84, "meaning": "None" } }, + { "Rect": { "x": 0, "y": 0, "w": 160, "h": 98, "color": 0, "meaning": "Wall" } }, + { "Rect": { "x": 0, "y": 98, "w": 160, "h": 70, "color": 8, "meaning": "Floor" } }, + { "Rect": { "x": 92, "y": 98, "w": 68, "h": 22, "color": 0, "meaning": "Wall" } }, + { "Rect": { "x": 0, "y": 98, "w": 20, "h": 26, "color": 0, "meaning": "Wall" } }, + { "Image": { "name": "bg_rock", "x": 80, "y": 84, "meaning": "None" } } + ], + "spawn": [120, 150], + "exits": [null, 8, null, null], + "music": 4, + "objects": [ + { + "name": "mystery disc", "sprite": "disc", "x": 40, "y": 132, + "look": "A disc sitting in the wrong sleeve like it woke up in the wrong life. The label says big band. The grooves say otherwise.", + "takeable": true, "synonyms": "disc record mislabeled" + }, + { + "name": "chud box", "sprite": "none", "x": 32, "y": 118, + "look": "A massive box of $2 bargain-bin trash that has achieved sentience. It permits browsing. World Record Club editions breed in here; a Kamahl 7-inch boxset watches you like a curse.", + "synonyms": "box bargain chud cardboard", + "use_text": "You dig. Dust archaeology. Near the bottom, something is filed under a lie — a disc in the wrong sleeve entirely." + }, + { + "name": "yellow sleeve", "sprite": "none", "x": 122, "y": 76, + "look": "A bright yellow big-band sleeve pinned to the wall, empty for years. Daly-Wilson Big Band — the Benson & Hedges pressing. Waiting.", + "synonyms": "yellow daly wilson", + "needs": "mystery disc", + "sets_flag": "break-ancestor-revealed", + "use_text": "Disc, meet sleeve. Sleeve, disc. Somewhere down the hallway of crates a drum break plays itself, briefly, like an ancestor exhaling. You keep your face normal. Your face is loud." + }, + { + "name": "posters", "sprite": "none", "x": 130, "y": 92, + "look": "Faded rock posters, curled at every corner. A NO REFUNDS sign greets you like an old, bitter friend.", + "synonyms": "wall sign refunds" + }, + { + "name": "crates", "sprite": "none", "x": 110, "y": 112, + "look": "Rock, alphabetised loosely, enforced never. The dividers are handwritten in the voice of someone who has explained The The's filing twice today already and will not do it a third time.", + "synonyms": "rock records bins around" + } + ] +} diff --git a/sprites/adapter.png b/sprites/adapter.png new file mode 100644 index 0000000..3c8e4c6 Binary files /dev/null and b/sprites/adapter.png differ diff --git a/sprites/becky.png b/sprites/becky.png new file mode 100644 index 0000000..9320b9c Binary files /dev/null and b/sprites/becky.png differ diff --git a/sprites/bg_alley.png b/sprites/bg_alley.png new file mode 100644 index 0000000..c7ed5be Binary files /dev/null and b/sprites/bg_alley.png differ diff --git a/sprites/bg_backroom.png b/sprites/bg_backroom.png new file mode 100644 index 0000000..6bc3762 Binary files /dev/null and b/sprites/bg_backroom.png differ diff --git a/sprites/bg_car.png b/sprites/bg_car.png new file mode 100644 index 0000000..ea73480 Binary files /dev/null and b/sprites/bg_car.png differ diff --git a/sprites/bg_carpark.png b/sprites/bg_carpark.png new file mode 100644 index 0000000..8b75e32 Binary files /dev/null and b/sprites/bg_carpark.png differ diff --git a/sprites/bg_counter.png b/sprites/bg_counter.png new file mode 100644 index 0000000..48fe78c Binary files /dev/null and b/sprites/bg_counter.png differ diff --git a/sprites/bg_dance.png b/sprites/bg_dance.png new file mode 100644 index 0000000..05ad2cf Binary files /dev/null and b/sprites/bg_dance.png differ diff --git a/sprites/bg_entrance.png b/sprites/bg_entrance.png new file mode 100644 index 0000000..41a418f Binary files /dev/null and b/sprites/bg_entrance.png differ diff --git a/sprites/bg_kitchen.png b/sprites/bg_kitchen.png new file mode 100644 index 0000000..6628557 Binary files /dev/null and b/sprites/bg_kitchen.png differ diff --git a/sprites/bg_listening.png b/sprites/bg_listening.png new file mode 100644 index 0000000..82c7fed Binary files /dev/null and b/sprites/bg_listening.png differ diff --git a/sprites/bg_rock.png b/sprites/bg_rock.png new file mode 100644 index 0000000..dcbac8f Binary files /dev/null and b/sprites/bg_rock.png differ diff --git a/sprites/bg_roof.png b/sprites/bg_roof.png new file mode 100644 index 0000000..e60b1e5 Binary files /dev/null and b/sprites/bg_roof.png differ diff --git a/sprites/bg_shopfloor.png b/sprites/bg_shopfloor.png new file mode 100644 index 0000000..20d4f52 Binary files /dev/null and b/sprites/bg_shopfloor.png differ diff --git a/sprites/bg_toilet.png b/sprites/bg_toilet.png new file mode 100644 index 0000000..aac2a18 Binary files /dev/null and b/sprites/bg_toilet.png differ diff --git a/sprites/bg_yard.png b/sprites/bg_yard.png new file mode 100644 index 0000000..0f7cae4 Binary files /dev/null and b/sprites/bg_yard.png differ diff --git a/sprites/clerk.png b/sprites/clerk.png new file mode 100644 index 0000000..81a0a2e Binary files /dev/null and b/sprites/clerk.png differ diff --git a/sprites/crier.png b/sprites/crier.png new file mode 100644 index 0000000..b02ba39 Binary files /dev/null and b/sprites/crier.png differ diff --git a/sprites/dave.png b/sprites/dave.png new file mode 100644 index 0000000..1404c66 Binary files /dev/null and b/sprites/dave.png differ diff --git a/sprites/disc.png b/sprites/disc.png new file mode 100644 index 0000000..410e026 Binary files /dev/null and b/sprites/disc.png differ diff --git a/sprites/djsomeguy.png b/sprites/djsomeguy.png new file mode 100644 index 0000000..b7c5a39 Binary files /dev/null and b/sprites/djsomeguy.png differ diff --git a/sprites/elastic.png b/sprites/elastic.png new file mode 100644 index 0000000..771aaeb Binary files /dev/null and b/sprites/elastic.png differ diff --git a/sprites/keys.png b/sprites/keys.png new file mode 100644 index 0000000..44bac77 Binary files /dev/null and b/sprites/keys.png differ diff --git a/sprites/kid.png b/sprites/kid.png new file mode 100644 index 0000000..3c02699 Binary files /dev/null and b/sprites/kid.png differ diff --git a/sprites/lollipop.png b/sprites/lollipop.png new file mode 100644 index 0000000..dd02968 Binary files /dev/null and b/sprites/lollipop.png differ diff --git a/sprites/notice.png b/sprites/notice.png new file mode 100644 index 0000000..6e0f58b Binary files /dev/null and b/sprites/notice.png differ diff --git a/sprites/paperclip.png b/sprites/paperclip.png new file mode 100644 index 0000000..211ab41 Binary files /dev/null and b/sprites/paperclip.png differ diff --git a/sprites/sleeve.png b/sprites/sleeve.png new file mode 100644 index 0000000..6a9f8bd Binary files /dev/null and b/sprites/sleeve.png differ diff --git a/sprites/tote.png b/sprites/tote.png new file mode 100644 index 0000000..503544f Binary files /dev/null and b/sprites/tote.png differ diff --git a/sprites/wallet.png b/sprites/wallet.png new file mode 100644 index 0000000..0d974b1 Binary files /dev/null and b/sprites/wallet.png differ diff --git a/sprites/whitelabel.png b/sprites/whitelabel.png new file mode 100644 index 0000000..97ddb0c Binary files /dev/null and b/sprites/whitelabel.png differ diff --git a/tools/art_manifest.py b/tools/art_manifest.py new file mode 100644 index 0000000..3cea69e --- /dev/null +++ b/tools/art_manifest.py @@ -0,0 +1,130 @@ +"""Every generated asset in MORPQUEST: prompts for gen_art.py. +bg: slug -> scene prompt (BG_STYLE is prepended). +sprite: slug -> (prompt, target_height_px) (SPRITE_STYLE is prepended).""" + +PROMPTS = { + 'bg': { + # 0 — inside the car, first-person-ish Sierra interior + 'car': ('Interior of a slightly daggy 1990s sedan seen from the back seat looking ' + 'forward: dashboard with a blinking digital clock, a rear-view mirror up top ' + 'center, steering wheel left, open glovebox on the right, cassette deck with ' + 'a missing knob, drink bottles and old receipts in the footwell, windscreen ' + 'showing a car park and a low brick shop strip beyond, open floor area at ' + 'the bottom of frame.'), + # 1 — the car park (already generated and gorgeous) + 'carpark': ('An Australian suburban record-shop car park early on a grey Saturday ' + 'morning: cracked asphalt with faded white parking lines filling the ' + 'lower half, one slightly daggy 1990s maroon sedan parked at the left ' + 'side, a low flat-roofed brick shop strip across the upper background ' + 'with a record store front and blank signboard, power lines against a ' + 'pale overcast sky, two wheelie bins by a brick wall on the right, ' + 'cool early light.'), + # 2 — shop entrance blocked by the BeckyWall + 'entrance': ('The front of a small Australian record shop with a sign reading ' + '"RECORD SHOP", the glass door completely blocked by a dense queue of ' + 'twelve identical young women in pastel pink cardigans standing ' + 'shoulder to shoulder in eerie synchrony, friendship bracelets, a torn ' + 'pop-star poster on the brick wall beside the door, a community notice ' + 'board on the right, wide empty footpath across the lower half of the ' + 'frame, morning light.'), + # 3 — alley with toilet door and vine wall + 'alley': ('A narrow brick alley beside a record shop: a battered toilet-block door ' + 'in the upper-left wall with a hand-painted sign, milk crates stacked ' + 'against the walls, a big graffiti mural, glossy dark-green vinyl leaves ' + 'climbing the right-hand brick wall like ivy, bitumen ground with puddles ' + 'across the lower half, strip of pale sky above.'), + # 4 — the toilet + 'toilet': ('Inside a grim single-stall public toilet at a record shop: cracked ' + 'mirror over a stained sink on the right wall with writing smeared on ' + 'the glass in lipstick, a cupboard under the sink with one door ajar, ' + 'graffiti covering every wall like patch notes for reality, a mop in a ' + 'bucket in the corner, an aggressive wall-mounted hand dryer, grimy ' + 'checkerboard lino floor across the lower half, one flickering ' + 'fluorescent tube.'), + # 5 — yard behind the shop + 'yard': ('A cramped concrete service yard behind an Australian record shop: a ' + 'plain steel side door in the upper wall with a faint door-shaped outline ' + 'painted over beside it, stacked broken record crates and a dead pot ' + 'plant, thick glossy vinyl-green ivy climbing the right-hand wall all the ' + 'way up past the roofline, a rusty downpipe, cracked concrete ground ' + 'across the lower half, washing-line shadow.'), + # 6 — the roof + 'roof': ('The flat tar-and-gravel roof of a single-storey Australian shop strip, ' + 'seen from a slightly raised angle: air-conditioning unit, a skylight, ' + 'ivy spilling over the left parapet edge, and below beyond the parapet a ' + 'view down onto a car park where a queue of tiny pink-cardigan figures ' + 'snakes in perfect choreography outside the shop door, pale morning sky ' + 'with power lines, walkable gravel roof surface across the lower half.'), + # 7 — kitchen + 'kitchen': ('A cramped fluorescent-lit staff kitchen behind a record shop: three ' + 'stainless steel benches, the sink-side bench cluttered with soggy ' + 'promotional flyers, a greasy stainless splashback catching the light ' + 'above the stove, a hot-water urn, a swinging door to the shop floor ' + 'at the top, mop bucket, worn vinyl floor across the lower half, ' + 'a laminated OH&S poster peeling off the wall.'), + # 8 — shop floor hub + 'shopfloor': ('Inside a beloved cluttered Australian record shop, main floor: rows ' + 'of wooden record bins full of vinyl stretching back like a ' + 'low-budget labyrinth, band posters staring from every wall, ' + 'handwritten genre dividers, a staff-picks shelf, a record left ' + 'half out of its sleeve on top of a crate in the foreground-right, ' + 'warm dusty light through the front window, worn floorboards across ' + 'the lower half of the frame.'), + # 9 — rock aisle + 'rock': ('A record shop aisle walled with rock vinyl: crates labelled with ' + 'handwritten dividers, a huge dusty cardboard box overflowing with ' + 'battered box sets and chud records on the floor at the left, a bright ' + 'yellow big-band record sleeve pinned on the wall among faded rock ' + 'posters, a NO REFUNDS sign, worn floorboards across the lower half, ' + 'warm afternoon dust motes.'), + # 10 — dance racks + 'dance': ('The dance music corner of a record shop, slightly darker and cooler ' + 'light: racks of 12-inch singles with genre cards, one overstuffed ' + 'crate visibly too packed to flip, a glitter-edged HOUSE section sign, ' + 'a tall mysterious DJ figure with headphones around his neck standing ' + 'motionless at the back racks, string of dead fairy lights, dark ' + 'carpet floor across the lower half.'), + # 11 — listening station + 'listening': ('A record shop listening corner: a turntable on a wooden plinth ' + 'with big padded headphones on a hook, a small judge-like sign ' + 'asking for restraint, a sad deflated promotional balloon taped to ' + 'the shelf beside it, tangle of cables with a wall preamp switch, ' + 'crates of records waiting underneath, warm lamp light, floorboards ' + 'across the lower half.'), + # 12 — counter + 'counter': ('The front counter of a record shop: a scratched wooden counter with ' + 'an old card payment terminal, a metal receipt spike stacked with ' + 'receipts, a brass bell, rolls of price stickers, behind the counter ' + 'a doorway hung with a beaded curtain radiating pure judgement, a ' + 'BE READY sign taped to the register, floorboards across the lower ' + 'half of the frame, warm light.'), + # 13 — back room + 'backroom': ('The mythical back room of a record shop, dim and dusty: unsorted ' + 'crates stacked like ruins with no dividers, a milk crate sitting ' + 'like a throne, chalk marks on wooden shelf slats, a dark mouldy ' + 'corner where the wall meets a wooden crate leaving a shadowed seam, ' + 'one bare hanging bulb, concrete floor across the lower half, ' + 'a different temperature of reality.'), + }, + 'sprite': { + # NPCs — full figures, magenta-keyed, engine-scale heights + 'becky': ('A young woman record-shop superfan in a pastel pink cardigan and ' + 'pleated skirt, friendship bracelets, serene unsettling smile, ' + 'standing straight facing the viewer.', 18), + 'crier': ('A young woman superfan in a pastel cardigan mid-cry, mascara ' + 'streaks, clutching a phone to her chest, standing facing the ' + 'viewer.', 18), + 'dave': ('A calm thirty-something Australian man in a flannel shirt and ' + 'beanie, hands in pockets, unbothered posture, faint knowing smile, ' + 'standing facing the viewer.', 19), + 'clerk': ('A wiry record-shop clerk woman with severe dark fringe, band ' + 't-shirt, lanyard with keys, arms crossed, unimpressed expression, ' + 'standing facing the viewer.', 19), + 'kid': ('A teenage school kid in an oversized hoodie leaning back slightly, ' + 'smirking, holding a cigarette, standing facing the viewer.', 17), + 'djsomeguy': ('A tall mysterious middle-aged DJ standing alone, large headphones ' + 'resting around his neck, long dark coat, one blank white record ' + 'sleeve under his arm, nothing else in frame, standing facing the ' + 'viewer.', 20), + }, +} diff --git a/tools/gatetest.jsonl b/tools/gatetest.jsonl new file mode 100644 index 0000000..d04c4ad --- /dev/null +++ b/tools/gatetest.jsonl @@ -0,0 +1,32 @@ +# Negative tests: every gate must hold when its prerequisite is missing +# fragments before poster-frenzy +{"cmd":"goto_room","n":2} +{"cmd":"parse","text":"use fragments"} +# dave's door choice hidden before seen-door-outline; crier's read-notice hidden before notice-posted +{"cmd":"parse","text":"talk dave"} +{"cmd":"end_dialogue"} +{"cmd":"parse","text":"talk crier"} +{"cmd":"end_dialogue"} +# give lollipop before anything: no lollipop item AND no read-aloud +{"cmd":"parse","text":"use crier"} +# toilet mirror before word-rsd +{"cmd":"goto_room","n":4} +{"cmd":"parse","text":"wipe mirror"} +# vine before knowing the secret +{"cmd":"goto_room","n":5} +{"cmd":"parse","text":"climb vine"} +# seam before back-room-open +{"cmd":"goto_room","n":13} +{"cmd":"parse","text":"use seam"} +# terminal before found-grail (with no wallet either) +{"cmd":"goto_room","n":12} +{"cmd":"parse","text":"pay terminal"} +# clerk: favour choices hidden with no flags +{"cmd":"parse","text":"talk clerk"} +{"cmd":"end_dialogue"} +# turntable with no adapter +{"cmd":"goto_room","n":11} +{"cmd":"parse","text":"use turntable"} +# ignition with no keys, no dispersal +{"cmd":"goto_room","n":0} +{"cmd":"parse","text":"use ignition"} diff --git a/tools/gen_art.py b/tools/gen_art.py new file mode 100644 index 0000000..bf4f314 --- /dev/null +++ b/tools/gen_art.py @@ -0,0 +1,146 @@ +#!/usr/bin/env python3 +"""MORPQUEST art pipeline — nano banana (OpenRouter) -> Sierra EGA assets. + +Usage: + python3 tools/gen_art.py bg one background from PROMPTS + python3 tools/gen_art.py sprite one magenta-keyed sprite + python3 tools/gen_art.py all everything not already on disk + +Raw generations cached in art/raw/ (git-ignored); processed PNGs land in +sprites/ where the engine quantizes them to EGA on load (alpha<128 = clear). +""" +import base64, json, os, re, sys, urllib.request + +ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +RAW = os.path.join(ROOT, 'art', 'raw') +SPRITES = os.path.join(ROOT, 'sprites') +os.makedirs(RAW, exist_ok=True) +os.makedirs(SPRITES, exist_ok=True) + +MODEL = os.environ.get('TG_ART_MODEL', 'google/gemini-3.1-flash-image') +KEY = os.environ.get('OPENROUTER_KEY') +if not KEY: # family convention — key lives in dealgod, never in a repo + for line in open(os.path.expanduser('~/Documents/dealgod/lore/keys.env')): + if line.startswith('OPENROUTER_KEY='): + KEY = line.strip().split('=', 1)[1] + +W, H = 160, 168 # engine picture resolution; GUI displays it 320x168 (2x-wide pixels) + +EGA = ('strict 16-color EGA palette only (black, dark blue, dark green, dark cyan, dark red, ' + 'dark magenta, brown, light gray, dark gray, bright blue, bright green, bright cyan, ' + 'bright red, bright magenta, yellow, white)') + +BG_STYLE = ('Background art from a 1989 Sierra On-Line EGA adventure game, King\'s Quest / ' + f'Police Quest II era. {EGA}. Flat color fills with occasional coarse dither ' + 'patterns, hard aliased pixel edges, no gradients, no smooth shading, no ' + 'anti-aliasing, no text, no letters, no UI, no people or animals. Wide landscape ' + 'scene from a slightly raised third-person view, with an open walkable ground ' + 'plane across the lower half of the frame. ') + +SPRITE_STYLE = (f'A single video game sprite from a 1989 Sierra EGA adventure game. {EGA}. ' + 'Flat colors, hard pixel edges, no anti-aliasing, no gradients, no text. ' + 'Full figure, facing the viewer, centered, on a SOLID PURE MAGENTA ' + 'background (#FF00FF) with nothing else. ') + +PROMPTS = {'bg': {}, 'sprite': {}} # filled by tools/art_manifest.py or edits below +try: + from art_manifest import PROMPTS # noqa: F401 +except ImportError: + pass + + +def gen(prompt): + body = json.dumps({'model': MODEL, 'modalities': ['image', 'text'], + 'messages': [{'role': 'user', 'content': prompt}]}).encode() + req = urllib.request.Request('https://openrouter.ai/api/v1/chat/completions', data=body, + headers={'Authorization': f'Bearer {KEY}', + 'Content-Type': 'application/json'}) + with urllib.request.urlopen(req, timeout=180) as r: + d = json.load(r) + msg = d['choices'][0]['message'] + imgs = msg.get('images') or [] + url = imgs[0]['image_url']['url'] if imgs else '' + m = re.match(r'data:image/(\w+);base64,(.+)', url, re.S) + if not m: + raise RuntimeError(f'no image in response: {str(msg)[:200]}') + return base64.b64decode(m.group(2)) + + +def bg(slug, force=False): + out = os.path.join(SPRITES, f'bg_{slug}.png') + if os.path.exists(out) and not force: + return out + raw = os.path.join(RAW, f'bg_{slug}.png') + if not os.path.exists(raw) or force: + open(raw, 'wb').write(gen(BG_STYLE + PROMPTS['bg'][slug])) + from PIL import Image + img = Image.open(raw).convert('RGB') + img = img.resize((W, H), Image.LANCZOS) # squeeze wide gen -> 2x-wide-pixel frame + img.save(out) + print('bg ', slug, '->', out) + return out + + +def sprite(slug, force=False): + """Chroma-keyed sprite. PROMPTS['sprite'][slug] = (prompt, target_height_px).""" + out = os.path.join(SPRITES, f'{slug}.png') + if os.path.exists(out) and not force: + return out + prompt, target_h = PROMPTS['sprite'][slug] + raw = os.path.join(RAW, f'sp_{slug}.png') + if not os.path.exists(raw) or force: + open(raw, 'wb').write(gen(SPRITE_STYLE + prompt)) + from PIL import Image + img = Image.open(raw).convert('RGB') + img.thumbnail((256, 256), Image.LANCZOS) # work small: fast + smooths jpeg noise + px = img.load() + w, h = img.size + # key by distance to the sampled corner colors (the model's magenta varies) + corners = {px[0, 0], px[w-1, 0], px[0, h-1], px[w-1, h-1]} + def bg(p): + return any((p[0]-c[0])**2 + (p[1]-c[1])**2 + (p[2]-c[2])**2 < 3600 for c in corners) + solid = [[not bg(px[x, y]) for x in range(w)] for y in range(h)] + # keep only the largest connected blob (kills stray decals/debris) + seen = [[False]*w for _ in range(h)] + best = [] + for y0 in range(h): + for x0 in range(w): + if solid[y0][x0] and not seen[y0][x0]: + blob, stack = [], [(x0, y0)] + seen[y0][x0] = True + while stack: + x, y = stack.pop() + blob.append((x, y)) + for nx, ny in ((x+1,y),(x-1,y),(x,y+1),(x,y-1)): + if 0 <= nx < w and 0 <= ny < h and solid[ny][nx] and not seen[ny][nx]: + seen[ny][nx] = True + stack.append((nx, ny)) + if len(blob) > len(best): + best = blob + alpha = Image.new('L', (w, h), 0) + ap = alpha.load() + for x, y in best: + ap[x, y] = 255 + img = img.convert('RGBA') + img.putalpha(alpha) + img = img.crop(img.getbbox()) + # engine pixels display 2x wide; squeeze width, but keep Sierra-chunky + ratio = target_h / img.height + img = img.resize((max(3, round(img.width * ratio * 0.75)), target_h), Image.LANCZOS) + img.putalpha(img.getchannel('A').point(lambda a: 255 if a >= 100 else 0)) + img.save(out) + print('spr', slug, f'{img.width}x{img.height}', '->', out) + return out + + +if __name__ == '__main__': + mode = sys.argv[1] if len(sys.argv) > 1 else 'all' + if mode == 'bg': + bg(sys.argv[2], force='-f' in sys.argv) + elif mode == 'sprite': + sprite(sys.argv[2], force='-f' in sys.argv) + else: + for s in PROMPTS['bg']: + bg(s) + for s in PROMPTS['sprite']: + sprite(s) diff --git a/tools/gen_items.py b/tools/gen_items.py new file mode 100644 index 0000000..8125ea1 --- /dev/null +++ b/tools/gen_items.py @@ -0,0 +1,123 @@ +#!/usr/bin/env python3 +"""Tiny takeable-item sprites, hand-placed pixels -> sprites/*.png. +Deterministic and free; nano banana is for backgrounds and people. +Letters index the EGA palette (hex 0-F), '.' = transparent.""" +import os +from PIL import Image + +EGA = [(0,0,0),(0,0,170),(0,170,0),(0,170,170),(170,0,0),(170,0,170), + (170,85,0),(170,170,170),(85,85,85),(85,85,255),(85,255,85), + (85,255,255),(255,85,85),(255,85,255),(255,255,85),(255,255,255)] + +SPRITES = { + 'paperclip': """ +.77777. +7.....7 +7.777.7 +7.7...7 +.77777. +""", + 'keys': """ +.777..... +7...7.... +7...7EEEE +7...7E.EE +.777..EE. +""", + 'wallet': """ +6666666666 +6888888886 +6666666666 +6666666666 +6666666686 +6666666666 +""", + 'adapter': """ +.EEEEE. +EE...EE +E..7..E +E.777.E +E..7..E +EE...EE +.EEEEE. +""", + 'elastic': """ +.666666. +6......6 +6......6 +.666666. +""", + 'lollipop': """ +.CCC. +CCCCC +CCCCC +.CCC. +..F.. +..F.. +..F.. +""", + 'sleeve': """ +7777777777 +7FFFFFFFF7 +7FFF77FFF7 +7FF7..7FF7 +7FF7..7FF7 +7FFF77FFF7 +7FFFFFFFF7 +7777777777 +""", + 'notice': """ +FFFFFFFF +F777777F +FFFFFFFF +F777777F +FFFFFFFF +F77CC77F +FFFCC7FF +FFFFFFFF +F777777F +FFFFFFFF +""", + 'whitelabel': """ +..000000.. +.00000000. +0000000000 +000FFFF000 +000FFFF000 +0000000000 +.00000000. +..000000.. +""", + 'tote': """ +..3...3.. +.3.3.3.3. +..33333.. +.3333333. +.3333333. +.3333333. +.3333333. +..33333.. +""", + 'disc': """ +..000000.. +.00000000. +000CCCC000 +000CCCC000 +.00000000. +..000000.. +""", +} + +out_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'sprites') +os.makedirs(out_dir, exist_ok=True) +for name, grid in SPRITES.items(): + rows = [r for r in grid.strip().splitlines()] + w, h = max(len(r) for r in rows), len(rows) + img = Image.new('RGBA', (w, h), (0, 0, 0, 0)) + px = img.load() + for y, row in enumerate(rows): + for x, ch in enumerate(row): + if ch != '.': + px[x, y] = EGA[int(ch, 16)] + (255,) + img.save(os.path.join(out_dir, f'{name}.png')) + print(name, f'{w}x{h}') diff --git a/tools/playtest.jsonl b/tools/playtest.jsonl new file mode 100644 index 0000000..59926d3 --- /dev/null +++ b/tools/playtest.jsonl @@ -0,0 +1,92 @@ +# MORPQUEST full win-path playtest (deterministic; run with --script) +# --- Act 1: the car --- +{"cmd":"parse","text":"take paperclip"} +{"cmd":"parse","text":"use seatbelt"} +{"cmd":"parse","text":"take keys"} +{"cmd":"parse","text":"take wallet"} +{"cmd":"parse","text":"take adapter"} +{"cmd":"parse","text":"take tote"} +{"cmd":"parse","text":"use mirror"} +# --- entrance: poster frenzy --- +{"cmd":"goto_room","n":2} +{"cmd":"parse","text":"talk becky"} +{"cmd":"choose","n":1} +{"cmd":"choose","n":1} +{"cmd":"parse","text":"use fragments"} +# --- toilet: elastic + word 2 --- +{"cmd":"goto_room","n":4} +{"cmd":"parse","text":"take elastic"} +{"cmd":"parse","text":"wipe mirror"} +# --- yard + roof --- +{"cmd":"goto_room","n":5} +{"cmd":"parse","text":"climb vine"} +{"cmd":"goto_room","n":6} +{"cmd":"parse","text":"use outline"} +# --- dave opens the side door --- +{"cmd":"goto_room","n":2} +{"cmd":"parse","text":"talk dave"} +{"cmd":"choose","n":1} +{"cmd":"choose","n":1} +# --- kitchen: lollipop + word 3 --- +{"cmd":"goto_room","n":7} +{"cmd":"parse","text":"take lollipop"} +{"cmd":"parse","text":"wipe splashback"} +# --- listening station --- +{"cmd":"goto_room","n":11} +{"cmd":"parse","text":"use turntable"} +{"cmd":"parse","text":"fix balloon"} +{"cmd":"parse","text":"use headphones"} +# --- shop floor favour --- +{"cmd":"goto_room","n":8} +{"cmd":"parse","text":"take sleeve"} +{"cmd":"parse","text":"use messy record"} +# --- rock aisle side quest --- +{"cmd":"goto_room","n":9} +{"cmd":"parse","text":"take disc"} +{"cmd":"parse","text":"use yellow sleeve"} +# --- dance racks side quest --- +{"cmd":"goto_room","n":10} +{"cmd":"parse","text":"use crate"} +{"cmd":"parse","text":"talk someguy"} +{"cmd":"choose","n":1} +{"cmd":"choose","n":1} +# --- clerk: two favours --- +{"cmd":"goto_room","n":12} +{"cmd":"parse","text":"talk clerk"} +{"cmd":"choose","n":1} +{"cmd":"choose","n":1} +{"cmd":"choose","n":1} +# --- back room: the grail --- +{"cmd":"goto_room","n":13} +{"cmd":"parse","text":"use seam"} +{"cmd":"parse","text":"take white label"} +{"cmd":"parse","text":"take delay notice"} +# --- pay + word 5 --- +{"cmd":"goto_room","n":12} +{"cmd":"parse","text":"pay terminal"} +{"cmd":"parse","text":"use spike"} +# --- disperse the wall --- +{"cmd":"goto_room","n":2} +{"cmd":"parse","text":"post notice board"} +{"cmd":"parse","text":"talk crier"} +{"cmd":"choose","n":1} +{"cmd":"choose","n":1} +{"cmd":"choose","n":1} +{"cmd":"parse","text":"give lollipop to crier"} +# --- drive home --- +{"cmd":"goto_room","n":0} +{"cmd":"parse","text":"use ignition"} +{"cmd":"query"} +# --- walkability spot-checks (fresh game state persists; just walking) --- +{"cmd":"goto_room","n":1} +{"cmd":"walk_to","x":95,"y":20} +{"cmd":"query"} +{"cmd":"goto_room","n":7} +{"cmd":"walk_to","x":78,"y":18} +{"cmd":"query"} +{"cmd":"goto_room","n":8} +{"cmd":"walk_to","x":156,"y":130} +{"cmd":"query"} +{"cmd":"goto_room","n":0} +{"cmd":"walk_to","x":156,"y":150} +{"cmd":"query"}