MRPCI/games/neon-precinct/rooms/room2.json
type-two ce42d69a3a v0.5 — playtest-driven fixes + engine/UX upgrades
Bugs found by playing the game over the bus:
- hotspots now have positions: 'walk to precinct door' pathfinds there,
  and touch verbs walk over to hotspots like they do to props/NPCs
- GameState::settle(): the MCP surface resolves walk-then-act in one call
  instead of returning zero events on a distant verb
- typed 'use X on Y' walks over like a clicked use (was unlimited range)
- taking/deleting a solid prop rebakes — no more lingering invisible walls
- sergeant dialogue no longer loops back to its greeting
- serde-facing maps are BTreeMap: room JSON and --sample are byte-stable

Engine:
- sprite-sheet actor views: <name>-sheet<C>x<R>.png slices into C frames x
  R direction loops (vend-bot now rolls on animated treads)
- pixel-accurate click targeting with 1px slop; typed commands click
  mid-sprite (a feet click lands between an actor's legs)
- parser: compass walking (go east / n), 'bye' ends conversations,
  'look' lists exits

GUI:
- hover hot-text: cursor reads the sentence a click would say
- death banner + '+N' score toast

Tests: full-playthrough regression suite (win path, death rewind,
walk-over dialogue, solid-prop footprint) + sheet slicing unit test.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 13:38:33 +10:00

714 lines
12 KiB
JSON

{
"name": "Rain Alley",
"enter_text": "The alley smells like rust and secrets. Crates wall off the far corner. Something glints behind them.",
"background": "",
"ops": [
{
"VGradient": {
"x": 0,
"y": 0,
"w": 320,
"h": 100,
"ramp": [
33,
76
],
"ink": {
"color": 0,
"pri": "Keep",
"ctl": {
"Set": 1
},
"hot": null
}
}
},
{
"Polygon": {
"pts": [
[
0,
0
],
[
110,
0
],
[
86,
100
],
[
0,
100
]
],
"ink": {
"color": 75,
"pri": "Keep",
"ctl": {
"Set": 1
},
"hot": null
}
}
},
{
"Polygon": {
"pts": [
[
210,
0
],
[
320,
0
],
[
320,
100
],
[
234,
100
]
],
"ink": {
"color": 111,
"pri": "Keep",
"ctl": {
"Set": 1
},
"hot": null
}
}
},
{
"Line": {
"pts": [
[
0,
10
],
[
101,
10
]
],
"ink": {
"color": 33,
"pri": "Keep",
"ctl": "Keep",
"hot": null
}
}
},
{
"Line": {
"pts": [
[
217,
10
],
[
320,
10
]
],
"ink": {
"color": 68,
"pri": "Keep",
"ctl": "Keep",
"hot": null
}
}
},
{
"Line": {
"pts": [
[
0,
24
],
[
98,
24
]
],
"ink": {
"color": 33,
"pri": "Keep",
"ctl": "Keep",
"hot": null
}
}
},
{
"Line": {
"pts": [
[
220,
24
],
[
320,
24
]
],
"ink": {
"color": 68,
"pri": "Keep",
"ctl": "Keep",
"hot": null
}
}
},
{
"Line": {
"pts": [
[
0,
38
],
[
96,
38
]
],
"ink": {
"color": 33,
"pri": "Keep",
"ctl": "Keep",
"hot": null
}
}
},
{
"Line": {
"pts": [
[
222,
38
],
[
320,
38
]
],
"ink": {
"color": 68,
"pri": "Keep",
"ctl": "Keep",
"hot": null
}
}
},
{
"Line": {
"pts": [
[
0,
52
],
[
94,
52
]
],
"ink": {
"color": 33,
"pri": "Keep",
"ctl": "Keep",
"hot": null
}
}
},
{
"Line": {
"pts": [
[
224,
52
],
[
320,
52
]
],
"ink": {
"color": 68,
"pri": "Keep",
"ctl": "Keep",
"hot": null
}
}
},
{
"Line": {
"pts": [
[
0,
66
],
[
91,
66
]
],
"ink": {
"color": 33,
"pri": "Keep",
"ctl": "Keep",
"hot": null
}
}
},
{
"Line": {
"pts": [
[
227,
66
],
[
320,
66
]
],
"ink": {
"color": 68,
"pri": "Keep",
"ctl": "Keep",
"hot": null
}
}
},
{
"Line": {
"pts": [
[
0,
80
],
[
89,
80
]
],
"ink": {
"color": 33,
"pri": "Keep",
"ctl": "Keep",
"hot": null
}
}
},
{
"Line": {
"pts": [
[
229,
80
],
[
320,
80
]
],
"ink": {
"color": 68,
"pri": "Keep",
"ctl": "Keep",
"hot": null
}
}
},
{
"Line": {
"pts": [
[
0,
94
],
[
87,
94
]
],
"ink": {
"color": 33,
"pri": "Keep",
"ctl": "Keep",
"hot": null
}
}
},
{
"Line": {
"pts": [
[
231,
94
],
[
320,
94
]
],
"ink": {
"color": 68,
"pri": "Keep",
"ctl": "Keep",
"hot": null
}
}
},
{
"Rect": {
"x": 250,
"y": 30,
"w": 4,
"h": 16,
"ink": {
"color": 248,
"pri": "Keep",
"ctl": {
"Set": 1
},
"hot": null
}
}
},
{
"Rect": {
"x": 254,
"y": 30,
"w": 6,
"h": 4,
"ink": {
"color": 250,
"pri": "Keep",
"ctl": {
"Set": 1
},
"hot": null
}
}
},
{
"Rect": {
"x": 254,
"y": 36,
"w": 6,
"h": 4,
"ink": {
"color": 252,
"pri": "Keep",
"ctl": {
"Set": 1
},
"hot": null
}
}
},
{
"Rect": {
"x": 254,
"y": 42,
"w": 6,
"h": 4,
"ink": {
"color": 254,
"pri": "Keep",
"ctl": {
"Set": 1
},
"hot": null
}
}
},
{
"VoronoiFill": {
"x": 0,
"y": 100,
"w": 320,
"h": 90,
"cell": 11,
"colors": [
20,
21,
19,
76
],
"edge_color": 17,
"edge": 1.4,
"seed": 21,
"ink": {
"color": 0,
"pri": "Band",
"ctl": {
"Set": 0
},
"hot": null
}
}
},
{
"Scatter": {
"x": 0,
"y": 100,
"w": 320,
"h": 90,
"colors": [
24,
77
],
"density": 7,
"seed": 22,
"ink": {
"color": 0,
"pri": "Keep",
"ctl": "Keep",
"hot": null
}
}
},
{
"Ellipse": {
"cx": 150,
"cy": 152,
"rx": 44,
"ry": 8,
"ink": {
"color": 77,
"pri": "Keep",
"ctl": {
"Set": 2
},
"hot": null
}
}
},
{
"Rect": {
"x": 60,
"y": 20,
"w": 6,
"h": 66,
"ink": {
"color": 24,
"pri": "Keep",
"ctl": {
"Set": 1
},
"hot": null
}
}
},
{
"Rect": {
"x": 60,
"y": 86,
"w": 6,
"h": 4,
"ink": {
"color": 26,
"pri": "Keep",
"ctl": {
"Set": 1
},
"hot": null
}
}
}
],
"palette": null,
"cycles": [
{
"start": 248,
"len": 8,
"period": 4,
"reverse": true,
"active": true
}
],
"scale": {
"horizon_y": 95,
"min_scale": 0.6,
"full_y": 185
},
"spawn": [
24,
160
],
"exits": [
null,
null,
null,
0
],
"exit_flags": [
"",
"",
"",
""
],
"exit_blocked": [
"",
"",
"",
""
],
"hotspots": [
{
"name": "pipe",
"rect": [
58,
18,
10,
74
],
"poly": [],
"msgs": {
"listen": "Drip. Drip. Drip. It's in 7/8 time, somehow.",
"look": "A drainage pipe keeping its own beat."
},
"exit_to": null,
"arrive": null,
"requires_flag": "",
"blocked_text": "",
"trigger": false,
"blocks": false
},
{
"name": "bar sign",
"rect": [
246,
26,
20,
24
],
"poly": [],
"msgs": {
"look": "BAR, says the little neon, economically."
},
"exit_to": null,
"arrive": null,
"requires_flag": "",
"blocked_text": "",
"trigger": false,
"blocks": false
}
],
"props": [
{
"name": "crate stack",
"sprite": "crate",
"x": 226,
"y": 138,
"msgs": {
"look": "Shipping crates. Stenciled: PROPERTY OF NOBODY, HONEST."
},
"takeable": false,
"synonyms": "crates boxes",
"use_text": "",
"needs": "",
"wins": false,
"requires_flag": "",
"sets_flag": "",
"dialogue": [],
"portrait": "",
"points": 0,
"consumes": false,
"kills": false,
"visible_flag": "",
"hidden_by_flag": "",
"solid": true,
"fixed_priority": null,
"fixed_scale": null
},
{
"name": "more crates",
"sprite": "crate",
"x": 258,
"y": 130,
"msgs": {
"look": "More crates. The alley is basically municipal Tetris."
},
"takeable": false,
"synonyms": "",
"use_text": "",
"needs": "",
"wins": false,
"requires_flag": "",
"sets_flag": "",
"dialogue": [],
"portrait": "",
"points": 0,
"consumes": false,
"kills": false,
"visible_flag": "",
"hidden_by_flag": "",
"solid": true,
"fixed_priority": null,
"fixed_scale": null
},
{
"name": "keycard",
"sprite": "keycard",
"x": 268,
"y": 112,
"msgs": {
"look": "A precinct keycard, dropped in a hurry. Evidence locker 47, if you had to guess."
},
"takeable": true,
"synonyms": "",
"use_text": "",
"needs": "",
"wins": false,
"requires_flag": "",
"sets_flag": "",
"dialogue": [],
"portrait": "",
"points": 5,
"consumes": false,
"kills": false,
"visible_flag": "",
"hidden_by_flag": "",
"solid": false,
"fixed_priority": null,
"fixed_scale": null
},
{
"name": "fusebox",
"sprite": "fusebox",
"x": 96,
"y": 96,
"msgs": {
"look": "A junction box, sparking gently. Every instinct you have files a hazard report."
},
"takeable": false,
"synonyms": "",
"use_text": "You touch the sparking fusebox. For 0.4 glorious seconds you are the brightest thing on Neon Row.",
"needs": "",
"wins": false,
"requires_flag": "",
"sets_flag": "",
"dialogue": [],
"portrait": "",
"points": 0,
"consumes": false,
"kills": true,
"visible_flag": "",
"hidden_by_flag": "",
"solid": false,
"fixed_priority": 15,
"fixed_scale": null
}
],
"npcs": [],
"music": 2,
"weather": 1,
"defaults": {},
"script": "room2.rhai"
}