Act II, full colour EGA. Six rooms, 100 points, verified end-to-end headlessly at 100/100 with the win firing, plus two authored Sierra deaths. The mechanic: Drebin is fired, so he has no power to search anyone — but a home plate umpire can crouch beside a professional athlete, take his bat off him and inspect his equipment, and nobody in a stadium of sixty thousand thinks twice. So every frisk needs an umpire-legitimate pretext, in order: brush the plate, inspect the mitt, check the bat for pine tar, then Number 44. Announcing yourself as police is right there, and correct, and fatal. Matching inversion at the reception: tackling a head of state is correct procedure, it ends your career, and being fired is the only way to progress. Two authoring traps found and documented for future cases: - Object names must not be prefixes of each other — 'ludwigdesk' shadowed 'ludwig' in the loose parser match, so 'talk ludwig' addressed the furniture. - Flag-gated edge exits need walkable floor into the trigger zone (y<=21 for north); the upper deck sat behind a painted Wall band and was unreachable on foot. Walk every exit to verify — goto_room hides this. tools/sprites.py now retries farm 429/401 with backoff (that status is the guest concurrency cap, not auth) and honours MB_POOL. LoRA finding recorded in DESIGN.md §9: the farm's style LoRAs are FLUX.1-dev architecture and are a silent no-op on the only ungated model, and the pixel look comes from the nearest-neighbour downscale anyway, not the model. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
332 lines
18 KiB
Markdown
332 lines
18 KiB
Markdown
# Police Squad Quest — design bible
|
||
|
||
A Sierra-style adventure game in the *Police Squad!* / *Naked Gun* universe,
|
||
built on [MRPGI](../MRPGI). Source research: [RESEARCH.md](RESEARCH.md).
|
||
|
||
---
|
||
|
||
## 1. The joke engine
|
||
|
||
The premise isn't "a funny adventure game". It's a specific collision:
|
||
|
||
> **Sierra's *Police Quest* (1987) × ZAZ's *Police Squad!* (1982).**
|
||
|
||
*Police Quest* was designed by Jim Walls, a real ex-cop, and was famously
|
||
**pedantic**: you had to walk around your car and check the tyres, radio in
|
||
correctly, draw your weapon *before* firing (or you shot yourself in the leg),
|
||
and read the Miranda rights in the right order. Get the procedure wrong → you
|
||
die. It is the single most self-serious game Sierra ever shipped.
|
||
|
||
*Police Squad!* is the same subject matter played as total farce.
|
||
|
||
**So the game grades you on procedure with a completely straight face, and the
|
||
reward for perfect procedure is catastrophe anyway.** That's the machine. It's
|
||
mechanical, not just written — the player *performs* the joke rather than
|
||
watching it.
|
||
|
||
Three rules fall out of that:
|
||
|
||
1. **The game never winks.** All system text is flat police-procedural prose.
|
||
Score readouts, death cards, refusals — deadpan. The comedy is what the flat
|
||
text is describing. (See RESEARCH.md §4.)
|
||
2. **Correct procedure is mandatory and useless.** You *must* do it. It never
|
||
helps.
|
||
3. **The parser is Frank's brain.** A text parser is a literal-minded machine,
|
||
and literalism is ZAZ's core joke. `look` answers take things at face value.
|
||
|
||
### Worked example — the opening of Case One
|
||
|
||
```
|
||
> take badge
|
||
Taken. (+2)
|
||
> east
|
||
You have not signed for a sidearm. Regulations are regulations.
|
||
> take revolver
|
||
Taken. (+2)
|
||
> east
|
||
```
|
||
Two points for procedure. Then you get to the crime scene and the body is gone
|
||
because Nordberg filed it.
|
||
|
||
---
|
||
|
||
## 2. The two Drebins, and the three eras
|
||
|
||
John's instinct that Frank Drebin Sr should be "a 1950s style cop" is exactly
|
||
right, and it's supported by the source: *Police Squad!* was a straight parody of
|
||
***M Squad*** (1957–60) and *Felony Squad*. The 2025 film then makes Neeson's
|
||
Frank Drebin **Jr.** canon — and gives Sr. a supernatural cameo as an owl. A
|
||
two-Drebin game is the franchise's own structure, not a liberty.
|
||
|
||
| Act | Who | Look | Palette |
|
||
|-----|-----|------|---------|
|
||
| **I** | Frank Drebin **Sr** — the TV series | 1950s hardboiled cop show, shot on videotape | EGA **0 / 7 / 8 / 15** + brown ego. Monochrome. |
|
||
| **II** | Frank Drebin **Sr** — the films | 1988–94 blockbuster | Full 16-colour EGA |
|
||
| **III** | Frank Drebin **Jr** (Neeson) — 2025 | modern LA, tech-money | Full EGA, cold blues/cyans |
|
||
|
||
**The palette restriction is free and does all the work.** MRPGI is EGA anyway;
|
||
Act I simply doesn't use the other twelve colours, so the era switch is visible
|
||
the instant a case loads. Act III's owl is the transition device: Sr. shows up
|
||
to help Jr., which is canon and also lets both Drebins share a screen.
|
||
|
||
**Drebin Jr's homage** (John's specific ask): Neeson's register is *sincerity*
|
||
where Nielsen's was obliviousness. Jr. isn't stupid — he's earnest, gravel-voiced,
|
||
and completely committed to a plan that makes no sense. Write Jr's `look` text as
|
||
genuinely moving. Then have it be about a vending machine.
|
||
|
||
---
|
||
|
||
## 3. Structure — one folder per case
|
||
|
||
MRPGI loads any directory with `--game DIR`, so **each mission is a standalone
|
||
game folder** under `cases/`. No global room numbering, no hub-state plumbing,
|
||
cases can be built in any order and in parallel.
|
||
|
||
```
|
||
cases/case-01-substantial-gift/
|
||
game.json manifest: name, start_room, intro, extra verbs, flags, max_score
|
||
sprites.json MODELBEAST prompts (input to tools/sprites.py — not read by the engine)
|
||
sprites/*.png generated art; ego.png is the player
|
||
rooms/room*.json the case
|
||
```
|
||
|
||
`./play.sh` lists cases and runs one. Skipped: a hub world and cross-case save —
|
||
add when there are enough cases to want them.
|
||
|
||
---
|
||
|
||
## 4. Mechanics — all of it is pure JSON
|
||
|
||
MRPGI already has every field this design needs. **No engine work.**
|
||
|
||
| Want | Field | Use |
|
||
|------|-------|-----|
|
||
| Procedure gate on a doorway | `exit_flags` + `exit_blocked` | "You have not signed for a sidearm." |
|
||
| Sierra death | `kills` on object or dialogue choice | The `use_text` is the death card |
|
||
| Police Quest scoring | `points` + manifest `max_score` | Points for *procedure*, not progress |
|
||
| Evidence that appears once found | `visible_flag` | Dusting reveals the print |
|
||
| Things that vanish | `hidden_by_flag` | The body Nordberg files |
|
||
| Item locks | `needs`, `consumes` | Warrant, evidence bag |
|
||
| Interrogation | `dialogue` + `requires_flag` / `sets_flag` | Miranda rights in order |
|
||
|
||
Two small engine changes were made upstream in MRPGI, both general-purpose and
|
||
useful to any game: a folder can supply **`sprites/ego.png`** to replace the
|
||
default party robot (plus optional `ego1.png` for the walk bob), and
|
||
**`death_text`** in `game.json` overrides the death banner, which was previously
|
||
hardcoded to another game's flavour text. Nothing else was added — every
|
||
mechanic below already existed.
|
||
|
||
### Custom verbs
|
||
|
||
`game.json` merges extra synonyms into the verb table, and the AI-parser
|
||
whitelist is generated from the same table so they can't drift:
|
||
|
||
```json
|
||
"verbs": [
|
||
{ "name": "use", "words": ["dust", "cuff", "book", "frisk", "radio", "mirandize", "arrest"] },
|
||
{ "name": "look", "words": ["investigate", "detect"] },
|
||
{ "name": "talk", "words": ["interrogate", "question"] }
|
||
]
|
||
```
|
||
|
||
---
|
||
|
||
## 5. The campaign
|
||
|
||
### Act I — Frank Drebin Sr, *Police Squad!* (monochrome)
|
||
|
||
Six cases, one per episode. Each opens with a **special guest star being murdered
|
||
in the title card** — a real object in the room whose only purpose is to die.
|
||
|
||
| # | Case | From | Hook | Procedure gag |
|
||
|---|------|------|------|---------------|
|
||
| 01 | **A Substantial Gift** | Ep 1 | Credit union manager shot; teller Sally Decker did it for orthodontist money and framed a customer | Sign out badge + sidearm; dust for prints; Mirandize in order — **built, see §6** |
|
||
| 02 | **Ring of Fear** | Ep 2 | Undercover as a boxing manager; a fighter's wife kidnapped to force a dive | You must maintain your cover identity *and* file correct overtime paperwork |
|
||
| 03 | **Rendezvous at Big Gulch** | Ep 3 | Protection racket; Frank and Norberg run a **locksmith shop** as a front | The front business is graded on customer service. Real locks. Real customers. |
|
||
| 04 | **Revenge and Remorse** | Ep 4 | Courthouse bombing, Eddie Casales framed | Bomb disposal by the manual, in order. The manual is wrong. |
|
||
| 05 | **The Butler Did It** | Ep 5 | Kidnapping at a rich man's birthday party | Chain of custody on a birthday cake |
|
||
| 06 | **Testimony of Evil** | Ep 6 | Comedian dies; nightclub drug ring | Undercover as a stand-up. You must actually pick punchlines. Dying on stage = a Sierra death card. |
|
||
|
||
### Act II — Frank Drebin Sr, the films (full colour)
|
||
|
||
| # | Case | From | Hook |
|
||
|---|------|------|------|
|
||
| 07 | **From the Files** | *Naked Gun* (1988) | **BUILT — see §7.** Nordberg shot on Ludwig's freighter; the Queen's reception; then Anaheim Stadium, where you **umpire a baseball game in order to legally frisk the players**. |
|
||
| 08 | **The Smell of Fear** | *Naked Gun 2½* (1991) | Spot the fake Dr. Meinheimer by testing a photographic memory the double doesn't have. Includes the zoo, the SWAT tank, and disarming a nuclear device — solution: unplug it. |
|
||
| 09 | **The Final Insult** | *33⅓* (1994) | Retired Frank goes undercover in prison as "Nick the Slasher McGurk". Bomb in the Best Picture envelope. Marriage counselling is a live stat you must not tank. |
|
||
|
||
### Act III — Frank Drebin Jr, 2025 (full colour, cold palette)
|
||
|
||
| # | Case | From | Hook |
|
||
|---|------|------|------|
|
||
| 10 | **The P.L.O.T. Device** | *Naked Gun* (2025) | Open in the schoolgirl disguise during the bank robbery — you're the distraction and don't know it. Investigate Simon Davenport's "suicide" with Beth. Richard Cane's bunker plan. Police Squad is decommissioned mid-game and your badge stops opening doors. |
|
||
| 11 | **Ponzi-scheme.com Arena** | *Naked Gun* (2025) finale | New Year's ball drop. You lose your trousers retrieving the device — and the game keeps scoring you on uniform regulations. **Frank Drebin Sr arrives as an owl**: Act I's monochrome palette flies into Act III's colour one. |
|
||
|
||
### The prologue — "1958"
|
||
|
||
A short unlockable Act 0: **Drebin Sr as a rookie**, played completely straight
|
||
as *M Squad*. Same monochrome palette, no jokes in the system text at all — until
|
||
the last screen. It's the origin of the owl.
|
||
|
||
---
|
||
|
||
## 6. Case One — as built
|
||
|
||
**`cases/case-01-substantial-gift/`** — the vertical slice, playable now.
|
||
|
||
Five rooms, 100 points.
|
||
|
||
| Room | Place | Contains |
|
||
|------|-------|----------|
|
||
| 0 | Police Squad HQ, squad room | Hocken (briefing), Nordberg (injured), **Al** (74px — his head leaves the top of the screen and no one comments), your desk, badge, sidearm, case file |
|
||
| 1 | The street | Johnny the Snitch's shoeshine stand, the squad car |
|
||
| 2 | The credit union | The body, the vault, the teller counter, **Sally Decker** |
|
||
| 3 | The crime lab | Ted Olson and the microscope |
|
||
| 4 | The orthodontist's | The dental chair, the braces cast, the arrest |
|
||
|
||
**The chain:** sign out badge + revolver (procedure gate on the HQ exit) → read
|
||
the case file → crime scene → dust the counter for prints → take the evidence bag
|
||
→ lab: Olson matches the print → the orthodontist's bill names Sally → return and
|
||
Mirandize her **in the correct order** (wrong order = she walks and you get a
|
||
Sierra death card from Hocken, which is not a real death, which is the joke) →
|
||
arrest.
|
||
|
||
**Running gags wired in as mechanics:**
|
||
- **Al** — a 74px sprite in a 168px room. Placed so his head is above the frame.
|
||
His `look` text describes only his shoes.
|
||
- **Johnny** — `talk johnny` costs a dollar and will answer *anything*. His
|
||
dialogue tree has more nodes about orthodontics and macroeconomics than the case.
|
||
- **Nordberg** — appears in every room in worse condition. Never mentioned.
|
||
- **The guest star** — dies in the title card. `look guest` in room 0 before he
|
||
dies, and he's already dead.
|
||
- **The freeze-frame ending** — the win text describes the cast holding still
|
||
while things audibly keep going wrong.
|
||
|
||
---
|
||
|
||
## 7. Case Seven — the umpire problem
|
||
|
||
**`cases/case-07-from-the-files/`** — Act II, full-colour EGA. Six rooms, 100 points.
|
||
|
||
Hospital → Ludwig Shipping → the Queen's reception → the officials' room →
|
||
home plate → the upper deck.
|
||
|
||
**The central mechanic: the strike zone is the puzzle.** Drebin has been fired,
|
||
so he has no power to search anyone. But a home plate umpire can crouch six
|
||
inches from a professional athlete, take his bat off him, and inspect his
|
||
equipment — and nobody in a stadium of sixty thousand thinks twice, because
|
||
that is simply what umpires do. **So every frisk needs an umpire-legitimate
|
||
pretext, in order:**
|
||
|
||
1. `use homeplate` — brush the plate. The only reason a masked man on his knees
|
||
beside an athlete is acceptable. Sets `at_plate`.
|
||
2. `use catcher` — inspect the mitt for a foreign substance. Clean.
|
||
3. `use batter` — check the bat for pine tar above the legal limit. Clean.
|
||
4. `use slugger` — Number 44, who has been staring at the owner's box since the
|
||
second inning. A pager taped inside the batting glove.
|
||
|
||
Skip the pretext and the object simply refuses (`requires_flag`). Announce
|
||
yourself as police — a dialogue choice that is right there, and correct, and
|
||
what a police officer would do — and sixty thousand people come over the
|
||
railings. Sierra death.
|
||
|
||
**The other inversion:** at the Queen's reception, Ludwig hands a two-hundred-
|
||
year-old firearm to the person you are there to protect, and you tackle a head
|
||
of state. That is *correct procedure*, it ends your career, and **being fired is
|
||
the only way to progress** — a private citizen can't walk onto that field, but
|
||
an umpire can walk anywhere.
|
||
|
||
Also in: Nordberg conducting an entire briefing through a wired jaw; Ludwig
|
||
owning two hundred and six ships and one of them being called I LUV LUDWIG;
|
||
the anthem lyric sheet; the steamroller, established in Room 5 as scenery an
|
||
hour before it is needed.
|
||
|
||
**Two authoring traps this case caught, worth knowing before writing Case 08:**
|
||
- **Object names must not be prefixes of each other.** `ludwigdesk` shadowed
|
||
`ludwig`, so `talk ludwig` addressed the furniture. The parser matches
|
||
loosely; renaming it to `desk` fixed it.
|
||
- **Flag-gated edge exits need walkable floor into the trigger zone.** The north
|
||
exit fires at `y ≤ 21`, which was inside the painted stands (a `Wall` band) —
|
||
the upper deck was unreachable on foot until a tunnel was carved through it.
|
||
Verify every exit by *walking* it, not with `goto_room`.
|
||
|
||
---
|
||
|
||
## 8. The glitches are canon
|
||
|
||
*Police Quest II* shipped with bugs that are, without alteration, Naked Gun
|
||
jokes. A player who has never heard of Sierra reads them as Drebin being Drebin;
|
||
a player who has reads them as a love letter. **So we ship them deliberately.**
|
||
|
||
| Real PQ2 bug | In Police Squad Quest |
|
||
|---|---|
|
||
| Keys locked in the car, station doors lock behind you → **softlocked in the parking lot on screen one** | The squad car in Case One: *"The keys are locked inside, on the seat, in plain view, behind your own reflection."* Non-fatal. Nobody mentions it. |
|
||
| **Keith**, your partner, slacks off and smokes; driving back to the station makes him go do "paperwork" | **Nordberg.** Casts, traction, cigarette, assigned as your partner, has not stood up. His paperwork is a magazine. |
|
||
| Firing without raising the weapon → **you shoot yourself in the foot** | Hocken dialogue: *"Permission to test-fire my sidearm indoors, Captain."* → *"Granted."* → Sierra death. An optional trap, exactly as Sierra used it. |
|
||
| Holding a direction while drawing → **hands and gun detach and float above your head** | Act II gag: a `raise` verb that reports the weapon is now above the ceiling, and stays there for the rest of the case. |
|
||
| **"Headless chicken" pathfinding** — NPCs spiral endlessly around obstacles | Al, in Case One, *"continues walking in a tight, rapid circle around the water cooler, as he has all morning."* |
|
||
| **Metal detector only checks if you're facing up** — walk diagonally past it | Act III airport screen. The detector is correct and the regulation is correct and you go through diagonally. |
|
||
| **Waving a drawn gun at airport security** with no reaction until you cross a specific vector | Same screen. The guard reacts to geometry, not to guns. |
|
||
| **Air-walking** — keep the swimming animation on dry land | Act II, after the zoo. Frank swims across the car park. |
|
||
| **Levitating into the sky** off a transition | Act III finale — but it's the owl. |
|
||
| Typing **`halt`** resets the enemy's shot counter → infinite standoff | `halt` is already a verb in Case One's manifest. In the Act II standoff it works, forever, and the game never acknowledges why. |
|
||
| **Infinite score** by re-dusting the ashtray / re-questioning the amnesiac witness | Case One, dusting the counter: *"The department pays out on a lifted print once. You may dust this counter as many times as you like. It has been looked into."* |
|
||
|
||
The rule: **play the bug straight.** Never nudge the player. The comedy is that
|
||
the game is behaving exactly as documented.
|
||
|
||
---
|
||
|
||
## 9. Art pipeline
|
||
|
||
`tools/sprites.py <case>` reads that case's `sprites.json` and generates every
|
||
missing sprite on the **MODELBEAST** farm (free, local, m3ultra):
|
||
|
||
```
|
||
flux_local (text→image, 512²) → bg_remove_local (RMBG-2.0 cutout) → Pillow
|
||
nearest-neighbour trim+downscale to the sprite's target height
|
||
```
|
||
|
||
Trimming to the alpha bounding box before scaling is what puts the subject's feet
|
||
on the bottom edge, which is how MRPGI positions and depth-sorts sprites. The
|
||
engine quantizes to EGA itself on load, so the script only owes it a small
|
||
transparent PNG.
|
||
|
||
A shared `STYLE` constant in the script enforces the Sierra look; per-sprite
|
||
prompts stay short. Act I prompts end in *"black and white monochrome grayscale
|
||
only"* — that one phrase is the whole 1950s era treatment.
|
||
|
||
```bash
|
||
python3 tools/sprites.py cases/case-01-substantial-gift # fill gaps
|
||
python3 tools/sprites.py cases/case-01-substantial-gift --force # redo all
|
||
python3 tools/sprites.py cases/case-01-substantial-gift --only ego,al
|
||
```
|
||
|
||
Seeds are derived from the sprite name, so a rerun reproduces the same art.
|
||
|
||
### On LoRAs (tested, negative result)
|
||
|
||
The farm has ~40 LoRAs, but **none of them currently apply to this pipeline**, and
|
||
it's worth writing down why so nobody burns an afternoon on it twice:
|
||
|
||
- The style LoRAs worth wanting (`style_retro-flux`, `old_timey_v4`) live in
|
||
`vendor/comfyui/models/loras/`, which is **not** in `MB_LORA_DIRS` — a bare
|
||
name 404s. A full path loads them.
|
||
- Loaded that way they are still a **no-op**. They're FLUX.1-dev LoRAs and the
|
||
only ungated model is `flux2-klein-4b`, a different architecture. An A/B at a
|
||
fixed seed gives three visually identical images (checksums differ by a
|
||
hair; nothing you can see). `Iso-Pixel-05` is SD-architecture — same result.
|
||
- Using them for real needs either the HF-gated `dev` model, or routing
|
||
SD/SDXL LoRAs through the `comfyui_sd` operator instead.
|
||
|
||
**And it wouldn't help much anyway.** The pixel-art look comes from the
|
||
nearest-neighbour crush to ~24px, not from the model — a style LoRA at 512px
|
||
is thrown away by the downscale. Plain `flux2-klein-4b` is the right tool here.
|
||
Where the farm's bulk capacity *does* pay off is quantity: 48 sprites across two
|
||
cases, free, in a few minutes.
|
||
|
||
**Farm gotcha:** the guest token caps concurrent jobs, and going over returns
|
||
`429`/`401` — which reads like an auth failure and isn't. `tools/sprites.py`
|
||
retries with backoff; if a whole batch fails, something else of yours is using
|
||
the farm. Don't run two clients at once.
|