v1.1 — full MODELBEAST painting pass + jazzcore soundtrack

All six rooms are now flux_local matte paintings quantized by the engine (basement, gig, alley, office, Roskilde, field), with rooms reduced to invisible sim ops: walkability, depth, water, the footlight chases, and the NO MEANS NO! spray painted OVER the brick painting. The office desk gets a real walk-behind from the priority bands. Soundtrack: tools/gen_music.py synthesizes five loop-safe mood overrides (music/*.wav) in the band's own meters — 7/4 walking bass for the basement, the 9/8 riff floored for gig and Roskilde, chromatic noir for the alley, puck-rock bounce for the office, a 5/4 drone for the field. Fixed the office east-door lane (crates pinched the exit trigger) and removed the alley's unreachable north exit. Playthrough: 60/60, won, byte-identical twice, one intentional death.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
m3ultra 2026-07-26 21:18:00 +10:00
parent 50817ced59
commit 2650c8b024
19 changed files with 3471 additions and 5224 deletions

BIN
music/calm.wav Normal file

Binary file not shown.

BIN
music/eerie.wav Normal file

Binary file not shown.

BIN
music/jolly.wav Normal file

Binary file not shown.

BIN
music/noir.wav Normal file

Binary file not shown.

BIN
music/tense.wav Normal file

Binary file not shown.

BIN
pics/alley.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

BIN
pics/basement.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 967 KiB

BIN
pics/field.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 918 KiB

BIN
pics/gig.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 963 KiB

BIN
pics/office.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

View File

@ -1,310 +1,16 @@
{
"name": "The Basement, 1979",
"enter_text": "Wood panel, concrete, one bare bulb. John's kit crowds the rug; the 180-pound Portastudio owns the workbench. Through the wall, Mom's washer keeps better time than most drummers.",
"background": "basement",
"ops": [
{
"FbmFill": {
"Rect": {
"x": 0,
"y": 0,
"w": 320,
"h": 100,
"ramp": [
110,
110,
153,
110
],
"scale": 34.0,
"octaves": 2,
"seed": 3,
"h": 118,
"ink": {
"color": 0,
"pri": "Keep",
"ctl": {
"Set": 1
},
"hot": null
}
}
},
{
"Line": {
"pts": [
[
0,
0
],
[
0,
99
]
],
"ink": {
"color": 68,
"pri": "Keep",
"ctl": {
"Set": 1
},
"hot": null
}
}
},
{
"Line": {
"pts": [
[
26,
0
],
[
26,
99
]
],
"ink": {
"color": 68,
"pri": "Keep",
"ctl": {
"Set": 1
},
"hot": null
}
}
},
{
"Line": {
"pts": [
[
52,
0
],
[
52,
99
]
],
"ink": {
"color": 68,
"pri": "Keep",
"ctl": {
"Set": 1
},
"hot": null
}
}
},
{
"Line": {
"pts": [
[
78,
0
],
[
78,
99
]
],
"ink": {
"color": 68,
"pri": "Keep",
"ctl": {
"Set": 1
},
"hot": null
}
}
},
{
"Line": {
"pts": [
[
104,
0
],
[
104,
99
]
],
"ink": {
"color": 68,
"pri": "Keep",
"ctl": {
"Set": 1
},
"hot": null
}
}
},
{
"Line": {
"pts": [
[
130,
0
],
[
130,
99
]
],
"ink": {
"color": 68,
"pri": "Keep",
"ctl": {
"Set": 1
},
"hot": null
}
}
},
{
"Line": {
"pts": [
[
156,
0
],
[
156,
99
]
],
"ink": {
"color": 68,
"pri": "Keep",
"ctl": {
"Set": 1
},
"hot": null
}
}
},
{
"Line": {
"pts": [
[
182,
0
],
[
182,
99
]
],
"ink": {
"color": 68,
"pri": "Keep",
"ctl": {
"Set": 1
},
"hot": null
}
}
},
{
"Line": {
"pts": [
[
208,
0
],
[
208,
99
]
],
"ink": {
"color": 68,
"pri": "Keep",
"ctl": {
"Set": 1
},
"hot": null
}
}
},
{
"Line": {
"pts": [
[
234,
0
],
[
234,
99
]
],
"ink": {
"color": 68,
"pri": "Keep",
"ctl": {
"Set": 1
},
"hot": null
}
}
},
{
"Line": {
"pts": [
[
260,
0
],
[
260,
99
]
],
"ink": {
"color": 68,
"pri": "Keep",
"ctl": {
"Set": 1
},
"hot": null
}
}
},
{
"Line": {
"pts": [
[
286,
0
],
[
286,
99
]
],
"ink": {
"color": 68,
"pri": "Keep",
"ctl": {
"Set": 1
},
"hot": null
}
}
},
{
"Line": {
"pts": [
[
312,
0
],
[
312,
99
]
],
"ink": {
"color": 68,
"color": null,
"pri": "Keep",
"ctl": {
"Set": 1
@ -315,76 +21,12 @@
},
{
"Rect": {
"x": 268,
"y": 10,
"w": 34,
"h": 22,
"ink": {
"color": 20,
"pri": "Keep",
"ctl": {
"Set": 1
},
"hot": null
}
}
},
{
"Rect": {
"x": 271,
"y": 13,
"w": 28,
"h": 16,
"ink": {
"color": 162,
"pri": "Keep",
"ctl": {
"Set": 1
},
"hot": null
}
}
},
{
"Line": {
"pts": [
[
285,
13
],
[
285,
28
]
],
"ink": {
"color": 20,
"pri": "Keep",
"ctl": {
"Set": 1
},
"hot": null
}
}
},
{
"VoronoiFill": {
"x": 0,
"y": 100,
"y": 118,
"w": 320,
"h": 90,
"cell": 44,
"colors": [
22,
22,
21,
22
],
"edge_color": 21,
"edge": 1.0,
"seed": 9,
"h": 72,
"ink": {
"color": 0,
"color": null,
"pri": "Band",
"ctl": {
"Set": 0
@ -395,12 +37,12 @@
},
{
"Rect": {
"x": 6,
"y": 62,
"w": 30,
"h": 38,
"x": 0,
"y": 118,
"w": 84,
"h": 44,
"ink": {
"color": 28,
"color": null,
"pri": "Keep",
"ctl": {
"Set": 1
@ -411,28 +53,12 @@
},
{
"Rect": {
"x": 10,
"y": 68,
"w": 22,
"h": 22,
"x": 240,
"y": 118,
"w": 80,
"h": 24,
"ink": {
"color": 25,
"pri": "Keep",
"ctl": {
"Set": 1
},
"hot": null
}
}
},
{
"Ellipse": {
"cx": 21,
"cy": 79,
"rx": 8,
"ry": 8,
"ink": {
"color": 126,
"color": null,
"pri": "Keep",
"ctl": {
"Set": 1
@ -443,122 +69,10 @@
},
{
"Rect": {
"x": 24,
"y": 64,
"w": 6,
"h": 3,
"ink": {
"color": 183,
"pri": "Keep",
"ctl": {
"Set": 1
},
"hot": null
}
}
},
{
"Rect": {
"x": 4,
"y": 40,
"w": 36,
"h": 6,
"ink": {
"color": 110,
"pri": "Keep",
"ctl": {
"Set": 1
},
"hot": null
}
}
},
{
"Rect": {
"x": 8,
"y": 30,
"w": 8,
"h": 10,
"ink": {
"color": 200,
"pri": "Keep",
"ctl": {
"Set": 1
},
"hot": null
}
}
},
{
"Rect": {
"x": 20,
"y": 32,
"w": 8,
"h": 8,
"ink": {
"color": 89,
"pri": "Keep",
"ctl": {
"Set": 1
},
"hot": null
}
}
},
{
"Ellipse": {
"cx": 96,
"cy": 148,
"rx": 44,
"ry": 14,
"ink": {
"color": 68,
"pri": "Band",
"ctl": {
"Set": 0
},
"hot": null
}
}
},
{
"Rect": {
"x": 128,
"y": 84,
"w": 66,
"h": 16,
"ink": {
"color": 110,
"pri": "Keep",
"ctl": {
"Set": 1
},
"hot": null
}
}
},
{
"Rect": {
"x": 128,
"y": 84,
"w": 66,
"h": 3,
"ink": {
"color": 153,
"pri": "Keep",
"ctl": {
"Set": 1
},
"hot": null
}
}
},
{
"Rect": {
"x": 214,
"y": 38,
"x": 244,
"y": 36,
"w": 26,
"h": 34,
"h": 36,
"ink": {
"color": 30,
"pri": "Keep",
@ -571,10 +85,10 @@
},
{
"Rect": {
"x": 217,
"y": 42,
"x": 247,
"y": 40,
"w": 20,
"h": 8,
"h": 9,
"ink": {
"color": 176,
"pri": "Keep",
@ -587,8 +101,8 @@
},
{
"Rect": {
"x": 217,
"y": 54,
"x": 247,
"y": 53,
"w": 20,
"h": 3,
"ink": {
@ -603,8 +117,8 @@
},
{
"Rect": {
"x": 217,
"y": 60,
"x": 247,
"y": 59,
"w": 14,
"h": 3,
"ink": {
@ -619,12 +133,12 @@
},
{
"Rect": {
"x": 292,
"y": 96,
"w": 28,
"h": 8,
"x": 247,
"y": 65,
"w": 17,
"h": 3,
"ink": {
"color": 110,
"color": 19,
"pri": "Keep",
"ctl": {
"Set": 1
@ -632,96 +146,16 @@
"hot": null
}
}
},
{
"Rect": {
"x": 292,
"y": 88,
"w": 28,
"h": 8,
"ink": {
"color": 153,
"pri": "Keep",
"ctl": {
"Set": 1
},
"hot": null
}
}
},
{
"Rect": {
"x": 292,
"y": 80,
"w": 28,
"h": 8,
"ink": {
"color": 110,
"pri": "Keep",
"ctl": {
"Set": 1
},
"hot": null
}
}
},
{
"Rect": {
"x": 292,
"y": 72,
"w": 28,
"h": 8,
"ink": {
"color": 153,
"pri": "Keep",
"ctl": {
"Set": 1
},
"hot": null
}
}
},
{
"Rect": {
"x": 292,
"y": 64,
"w": 28,
"h": 8,
"ink": {
"color": 110,
"pri": "Keep",
"ctl": {
"Set": 1
},
"hot": null
}
}
},
{
"Rect": {
"x": 292,
"y": 100,
"w": 28,
"h": 90,
"ink": {
"color": 21,
"pri": "Band",
"ctl": {
"Set": 0
},
"hot": null
}
}
}
],
"scale": {
"horizon_y": 92,
"horizon_y": 100,
"min_scale": 0.72,
"full_y": 186
},
"spawn": [
160,
150
180,
160
],
"exits": [
null,
@ -745,23 +179,23 @@
{
"name": "gig poster",
"rect": [
212,
36,
242,
34,
30,
38
40
],
"msgs": {
"look": "D.O.A. — TONIGHT — UVic. Vancouver's finest, one ferry away. John already circled it.",
"do": "You're going. Obviously you're going. (Walk east, up the stairs.)"
"do": "You're going. Obviously you're going. (Up the stairs, east.)"
}
},
{
"name": "washer",
"rect": [
4,
60,
34,
42
0,
84,
84,
76
],
"msgs": {
"look": "Mom's washing machine, mid-cycle. A wet 4/4 that never rushes and never drags.",
@ -772,47 +206,58 @@
{
"name": "window",
"rect": [
266,
8,
38,
26
186,
34,
46,
36
],
"msgs": {
"look": "Grey Victoria daylight. The Island doesn't hurry, and neither does the rain."
"look": "Grey Victoria daylight through glass that hasn't been washed since the Diefenbaker administration."
}
},
{
"name": "workbench",
"rect": [
126,
116,
88,
82,
70,
20
28
],
"msgs": {
"look": "The workbench holds the TEAC four-track like an altar holds a relic."
}
},
{
"name": "stairs",
"rect": [
244,
60,
76,
56
],
"msgs": {
"look": "Up to daylight, dinner, and — tonight — a ferry to a D.O.A. show."
}
}
],
"props": [
{
"name": "portastudio",
"sprite": "portastudio",
"x": 160,
"y": 104,
"x": 156,
"y": 101,
"synonyms": "four-track teac tascam recorder deck",
"msgs": {
"look": "The TEAC Portastudio. 180 pounds of overdubbing possibility. It wants tape and it wants a reason.",
"do": "It powers up with a hum like a small appliance dreaming big.",
"listen": "Tape hiss. The sound of potential."
},
"solid": true
}
},
{
"name": "castle box",
"sprite": "castlebox",
"x": 224,
"y": 146,
"x": 218,
"y": 154,
"synonyms": "box junk castle",
"msgs": {
"look": "A box of gear from Castle, the cover band you both did time in. Cables, setlists nobody needs, and — is that a fresh reel of tape?"
@ -822,8 +267,8 @@
{
"name": "drum kit",
"sprite": "drumkit",
"x": 92,
"y": 136,
"x": 112,
"y": 150,
"synonyms": "drums kit",
"msgs": {
"look": "John's kit, tuned like a jazz kit because he IS a jazz kid. The high school band has no idea what it's incubating.",
@ -836,10 +281,10 @@
{
"name": "John",
"sprite": "john",
"x": 66,
"y": 148,
"x": 152,
"y": 164,
"portrait": "john-face",
"wander": 12,
"wander": 10,
"msgs": {
"look": "Your kid brother. Eight years younger, already swings harder than drummers twice his age."
},
@ -884,8 +329,8 @@
{
"name": "Mom",
"sprite": "mom",
"x": 26,
"y": 126,
"x": 98,
"y": 146,
"portrait": "mom-face",
"wander": 6,
"msgs": {

View File

@ -1,80 +1,16 @@
{
"name": "UVic — D.O.A., 1980",
"enter_text": "A university hall pretending it's a bunker. D.O.A. detonate on stage. The floor moves like weather.",
"background": "gig",
"ops": [
{
"VGradient": {
"Rect": {
"x": 0,
"y": 0,
"w": 320,
"h": 96,
"ramp": [
33,
69,
32
],
"h": 122,
"ink": {
"color": 0,
"pri": "Keep",
"ctl": {
"Set": 1
},
"hot": null
}
}
},
{
"Polygon": {
"pts": [
[
70,
0
],
[
130,
0
],
[
170,
92
],
[
110,
92
]
],
"ink": {
"color": 113,
"pri": "Keep",
"ctl": {
"Set": 1
},
"hot": null
}
}
},
{
"Polygon": {
"pts": [
[
200,
0
],
[
250,
0
],
[
210,
92
],
[
160,
92
]
],
"ink": {
"color": 147,
"color": null,
"pri": "Keep",
"ctl": {
"Set": 1
@ -85,384 +21,12 @@
},
{
"Rect": {
"x": 56,
"y": 58,
"w": 208,
"h": 38,
"ink": {
"color": 68,
"pri": "Keep",
"ctl": {
"Set": 1
},
"hot": null
}
}
},
{
"Rect": {
"x": 56,
"y": 58,
"w": 208,
"h": 4,
"ink": {
"color": 110,
"pri": "Keep",
"ctl": {
"Set": 1
},
"hot": null
}
}
},
{
"Rect": {
"x": 90,
"y": 40,
"w": 14,
"h": 22,
"ink": {
"color": 17,
"pri": "Keep",
"ctl": {
"Set": 1
},
"hot": null
}
}
},
{
"Rect": {
"x": 220,
"y": 44,
"w": 16,
"h": 18,
"ink": {
"color": 17,
"pri": "Keep",
"ctl": {
"Set": 1
},
"hot": null
}
}
},
{
"Ellipse": {
"cx": 97,
"cy": 38,
"rx": 4,
"ry": 4,
"ink": {
"color": 17,
"pri": "Keep",
"ctl": {
"Set": 1
},
"hot": null
}
}
},
{
"Ellipse": {
"cx": 228,
"cy": 42,
"rx": 4,
"ry": 4,
"ink": {
"color": 17,
"pri": "Keep",
"ctl": {
"Set": 1
},
"hot": null
}
}
},
{
"Rect": {
"x": 58,
"y": 92,
"w": 8,
"h": 4,
"ink": {
"color": 248,
"pri": "Keep",
"ctl": {
"Set": 1
},
"hot": null
}
}
},
{
"Rect": {
"x": 70,
"y": 92,
"w": 8,
"h": 4,
"ink": {
"color": 249,
"pri": "Keep",
"ctl": {
"Set": 1
},
"hot": null
}
}
},
{
"Rect": {
"x": 82,
"y": 92,
"w": 8,
"h": 4,
"ink": {
"color": 250,
"pri": "Keep",
"ctl": {
"Set": 1
},
"hot": null
}
}
},
{
"Rect": {
"x": 94,
"y": 92,
"w": 8,
"h": 4,
"ink": {
"color": 251,
"pri": "Keep",
"ctl": {
"Set": 1
},
"hot": null
}
}
},
{
"Rect": {
"x": 106,
"y": 92,
"w": 8,
"h": 4,
"ink": {
"color": 252,
"pri": "Keep",
"ctl": {
"Set": 1
},
"hot": null
}
}
},
{
"Rect": {
"x": 118,
"y": 92,
"w": 8,
"h": 4,
"ink": {
"color": 253,
"pri": "Keep",
"ctl": {
"Set": 1
},
"hot": null
}
}
},
{
"Rect": {
"x": 130,
"y": 92,
"w": 8,
"h": 4,
"ink": {
"color": 254,
"pri": "Keep",
"ctl": {
"Set": 1
},
"hot": null
}
}
},
{
"Rect": {
"x": 142,
"y": 92,
"w": 8,
"h": 4,
"ink": {
"color": 255,
"pri": "Keep",
"ctl": {
"Set": 1
},
"hot": null
}
}
},
{
"Rect": {
"x": 154,
"y": 92,
"w": 8,
"h": 4,
"ink": {
"color": 248,
"pri": "Keep",
"ctl": {
"Set": 1
},
"hot": null
}
}
},
{
"Rect": {
"x": 166,
"y": 92,
"w": 8,
"h": 4,
"ink": {
"color": 249,
"pri": "Keep",
"ctl": {
"Set": 1
},
"hot": null
}
}
},
{
"Rect": {
"x": 178,
"y": 92,
"w": 8,
"h": 4,
"ink": {
"color": 250,
"pri": "Keep",
"ctl": {
"Set": 1
},
"hot": null
}
}
},
{
"Rect": {
"x": 190,
"y": 92,
"w": 8,
"h": 4,
"ink": {
"color": 251,
"pri": "Keep",
"ctl": {
"Set": 1
},
"hot": null
}
}
},
{
"Rect": {
"x": 202,
"y": 92,
"w": 8,
"h": 4,
"ink": {
"color": 252,
"pri": "Keep",
"ctl": {
"Set": 1
},
"hot": null
}
}
},
{
"Rect": {
"x": 214,
"y": 92,
"w": 8,
"h": 4,
"ink": {
"color": 253,
"pri": "Keep",
"ctl": {
"Set": 1
},
"hot": null
}
}
},
{
"Rect": {
"x": 226,
"y": 92,
"w": 8,
"h": 4,
"ink": {
"color": 254,
"pri": "Keep",
"ctl": {
"Set": 1
},
"hot": null
}
}
},
{
"Rect": {
"x": 238,
"y": 92,
"w": 8,
"h": 4,
"ink": {
"color": 255,
"pri": "Keep",
"ctl": {
"Set": 1
},
"hot": null
}
}
},
{
"Rect": {
"x": 250,
"y": 92,
"w": 8,
"h": 4,
"ink": {
"color": 248,
"pri": "Keep",
"ctl": {
"Set": 1
},
"hot": null
}
}
},
{
"VGradient": {
"x": 0,
"y": 96,
"y": 122,
"w": 320,
"h": 94,
"ramp": [
18,
17
],
"h": 68,
"ink": {
"color": 0,
"color": null,
"pri": "Band",
"ctl": {
"Set": 0
@ -472,202 +36,343 @@
}
},
{
"Ellipse": {
"cx": 16,
"cy": 108,
"rx": 6,
"ry": 5,
"Rect": {
"x": 44,
"y": 118,
"w": 8,
"h": 3,
"ink": {
"color": 17,
"color": 248,
"pri": "Keep",
"ctl": "Keep",
"ctl": {
"Set": 1
},
"hot": null
}
}
},
{
"Ellipse": {
"cx": 40,
"cy": 117,
"rx": 6,
"ry": 5,
"Rect": {
"x": 56,
"y": 118,
"w": 8,
"h": 3,
"ink": {
"color": 17,
"color": 249,
"pri": "Keep",
"ctl": "Keep",
"ctl": {
"Set": 1
},
"hot": null
}
}
},
{
"Ellipse": {
"cx": 64,
"cy": 126,
"rx": 6,
"ry": 5,
"Rect": {
"x": 68,
"y": 118,
"w": 8,
"h": 3,
"ink": {
"color": 17,
"color": 250,
"pri": "Keep",
"ctl": "Keep",
"ctl": {
"Set": 1
},
"hot": null
}
}
},
{
"Ellipse": {
"cx": 88,
"cy": 108,
"rx": 6,
"ry": 5,
"Rect": {
"x": 80,
"y": 118,
"w": 8,
"h": 3,
"ink": {
"color": 17,
"color": 251,
"pri": "Keep",
"ctl": "Keep",
"ctl": {
"Set": 1
},
"hot": null
}
}
},
{
"Ellipse": {
"cx": 112,
"cy": 117,
"rx": 6,
"ry": 5,
"Rect": {
"x": 92,
"y": 118,
"w": 8,
"h": 3,
"ink": {
"color": 17,
"color": 252,
"pri": "Keep",
"ctl": "Keep",
"ctl": {
"Set": 1
},
"hot": null
}
}
},
{
"Ellipse": {
"cx": 136,
"cy": 126,
"rx": 6,
"ry": 5,
"Rect": {
"x": 104,
"y": 118,
"w": 8,
"h": 3,
"ink": {
"color": 17,
"color": 253,
"pri": "Keep",
"ctl": "Keep",
"ctl": {
"Set": 1
},
"hot": null
}
}
},
{
"Ellipse": {
"cx": 160,
"cy": 108,
"rx": 6,
"ry": 5,
"Rect": {
"x": 116,
"y": 118,
"w": 8,
"h": 3,
"ink": {
"color": 17,
"color": 254,
"pri": "Keep",
"ctl": "Keep",
"ctl": {
"Set": 1
},
"hot": null
}
}
},
{
"Ellipse": {
"cx": 184,
"cy": 117,
"rx": 6,
"ry": 5,
"Rect": {
"x": 128,
"y": 118,
"w": 8,
"h": 3,
"ink": {
"color": 17,
"color": 255,
"pri": "Keep",
"ctl": "Keep",
"ctl": {
"Set": 1
},
"hot": null
}
}
},
{
"Ellipse": {
"cx": 208,
"cy": 126,
"rx": 6,
"ry": 5,
"Rect": {
"x": 140,
"y": 118,
"w": 8,
"h": 3,
"ink": {
"color": 17,
"color": 248,
"pri": "Keep",
"ctl": "Keep",
"ctl": {
"Set": 1
},
"hot": null
}
}
},
{
"Ellipse": {
"cx": 232,
"cy": 108,
"rx": 6,
"ry": 5,
"Rect": {
"x": 152,
"y": 118,
"w": 8,
"h": 3,
"ink": {
"color": 17,
"color": 249,
"pri": "Keep",
"ctl": "Keep",
"ctl": {
"Set": 1
},
"hot": null
}
}
},
{
"Ellipse": {
"cx": 256,
"cy": 117,
"rx": 6,
"ry": 5,
"Rect": {
"x": 164,
"y": 118,
"w": 8,
"h": 3,
"ink": {
"color": 17,
"color": 250,
"pri": "Keep",
"ctl": "Keep",
"ctl": {
"Set": 1
},
"hot": null
}
}
},
{
"Ellipse": {
"cx": 280,
"cy": 126,
"rx": 6,
"ry": 5,
"Rect": {
"x": 176,
"y": 118,
"w": 8,
"h": 3,
"ink": {
"color": 17,
"color": 251,
"pri": "Keep",
"ctl": "Keep",
"ctl": {
"Set": 1
},
"hot": null
}
}
},
{
"Scatter": {
"x": 0,
"y": 96,
"w": 320,
"h": 40,
"colors": [
19,
18
],
"density": 14,
"seed": 21,
"Rect": {
"x": 188,
"y": 118,
"w": 8,
"h": 3,
"ink": {
"color": 0,
"color": 252,
"pri": "Keep",
"ctl": "Keep",
"ctl": {
"Set": 1
},
"hot": null
}
}
},
{
"Rect": {
"x": 200,
"y": 118,
"w": 8,
"h": 3,
"ink": {
"color": 253,
"pri": "Keep",
"ctl": {
"Set": 1
},
"hot": null
}
}
},
{
"Rect": {
"x": 212,
"y": 118,
"w": 8,
"h": 3,
"ink": {
"color": 254,
"pri": "Keep",
"ctl": {
"Set": 1
},
"hot": null
}
}
},
{
"Rect": {
"x": 224,
"y": 118,
"w": 8,
"h": 3,
"ink": {
"color": 255,
"pri": "Keep",
"ctl": {
"Set": 1
},
"hot": null
}
}
},
{
"Rect": {
"x": 236,
"y": 118,
"w": 8,
"h": 3,
"ink": {
"color": 248,
"pri": "Keep",
"ctl": {
"Set": 1
},
"hot": null
}
}
},
{
"Rect": {
"x": 248,
"y": 118,
"w": 8,
"h": 3,
"ink": {
"color": 249,
"pri": "Keep",
"ctl": {
"Set": 1
},
"hot": null
}
}
},
{
"Rect": {
"x": 260,
"y": 118,
"w": 8,
"h": 3,
"ink": {
"color": 250,
"pri": "Keep",
"ctl": {
"Set": 1
},
"hot": null
}
}
},
{
"Rect": {
"x": 272,
"y": 118,
"w": 8,
"h": 3,
"ink": {
"color": 251,
"pri": "Keep",
"ctl": {
"Set": 1
},
"hot": null
}
}
}
],
"cycles": [
{
"start": 248,
"len": 8,
"period": 2,
"reverse": false,
"active": true
}
],
"scale": {
"horizon_y": 96,
"min_scale": 0.7,
"horizon_y": 118,
"min_scale": 0.8,
"full_y": 186
},
"spawn": [
160,
164
165
],
"exits": [
null,
@ -679,10 +384,10 @@
{
"name": "stage",
"rect": [
56,
20,
208,
76
40,
10,
240,
110
],
"msgs": {
"do": "Security gives you the look that says: no.",
@ -693,7 +398,7 @@
"name": "pit",
"rect": [
80,
110,
130,
160,
50
],
@ -707,8 +412,8 @@
{
"name": "flyer",
"sprite": "flyer",
"x": 288,
"y": 150,
"x": 294,
"y": 170,
"synonyms": "handbill paper",
"msgs": {
"look": "A gig flyer, boot-printed. You'll keep it forever and lie about why."
@ -721,8 +426,8 @@
"name": "Joey",
"sprite": "joey",
"x": 150,
"y": 90,
"fixed_scale": 0.85,
"y": 116,
"fixed_scale": 0.8,
"msgs": {
"look": "Joey Shithead, mid-war-cry. Vancouver hardcore, delivered at terminal velocity.",
"talk": "He can't hear you. Nobody will hear anything until Thursday."

File diff suppressed because it is too large Load Diff

View File

@ -1,19 +1,16 @@
{
"name": "Alternative Tentacles, 1987",
"enter_text": "San Francisco. The office smells of newsprint, coffee, and litigation. Every poster on the wall is somebody's whole life.",
"background": "office",
"ops": [
{
"VGradient": {
"Rect": {
"x": 0,
"y": 0,
"w": 320,
"h": 96,
"ramp": [
196,
154
],
"h": 147,
"ink": {
"color": 0,
"color": null,
"pri": "Keep",
"ctl": {
"Set": 1
@ -24,426 +21,12 @@
},
{
"Rect": {
"x": 20,
"y": 14,
"w": 60,
"h": 44,
"ink": {
"color": 18,
"pri": "Keep",
"ctl": {
"Set": 1
},
"hot": null
}
}
},
{
"Rect": {
"x": 24,
"y": 18,
"w": 52,
"h": 36,
"ink": {
"color": 127,
"pri": "Keep",
"ctl": {
"Set": 1
},
"hot": null
}
}
},
{
"Line": {
"pts": [
[
24,
40
],
[
75,
34
]
],
"ink": {
"color": 182,
"pri": "Keep",
"ctl": {
"Set": 1
},
"hot": null
}
}
},
{
"Rect": {
"x": 38,
"y": 44,
"w": 4,
"h": 10,
"ink": {
"color": 22,
"pri": "Keep",
"ctl": {
"Set": 1
},
"hot": null
}
}
},
{
"Rect": {
"x": 56,
"y": 40,
"w": 4,
"h": 14,
"ink": {
"color": 22,
"pri": "Keep",
"ctl": {
"Set": 1
},
"hot": null
}
}
},
{
"Rect": {
"x": 110,
"y": 12,
"w": 30,
"h": 26,
"ink": {
"color": 176,
"pri": "Keep",
"ctl": {
"Set": 1
},
"hot": null
}
}
},
{
"Rect": {
"x": 114,
"y": 16,
"w": 22,
"h": 4,
"ink": {
"color": 17,
"pri": "Keep",
"ctl": {
"Set": 1
},
"hot": null
}
}
},
{
"Rect": {
"x": 148,
"y": 12,
"w": 30,
"h": 26,
"ink": {
"color": 53,
"pri": "Keep",
"ctl": {
"Set": 1
},
"hot": null
}
}
},
{
"Rect": {
"x": 152,
"y": 16,
"w": 22,
"h": 4,
"ink": {
"color": 17,
"pri": "Keep",
"ctl": {
"Set": 1
},
"hot": null
}
}
},
{
"Rect": {
"x": 186,
"y": 12,
"w": 30,
"h": 26,
"ink": {
"color": 194,
"pri": "Keep",
"ctl": {
"Set": 1
},
"hot": null
}
}
},
{
"Rect": {
"x": 190,
"y": 16,
"w": 22,
"h": 4,
"ink": {
"color": 17,
"pri": "Keep",
"ctl": {
"Set": 1
},
"hot": null
}
}
},
{
"Rect": {
"x": 224,
"y": 12,
"w": 30,
"h": 26,
"ink": {
"color": 107,
"pri": "Keep",
"ctl": {
"Set": 1
},
"hot": null
}
}
},
{
"Rect": {
"x": 228,
"y": 16,
"w": 22,
"h": 4,
"ink": {
"color": 17,
"pri": "Keep",
"ctl": {
"Set": 1
},
"hot": null
}
}
},
{
"Rect": {
"x": 262,
"y": 12,
"w": 30,
"h": 26,
"ink": {
"color": 57,
"pri": "Keep",
"ctl": {
"Set": 1
},
"hot": null
}
}
},
{
"Rect": {
"x": 266,
"y": 16,
"w": 22,
"h": 4,
"ink": {
"color": 17,
"pri": "Keep",
"ctl": {
"Set": 1
},
"hot": null
}
}
},
{
"Rect": {
"x": 110,
"y": 46,
"w": 30,
"h": 26,
"ink": {
"color": 176,
"pri": "Keep",
"ctl": {
"Set": 1
},
"hot": null
}
}
},
{
"Rect": {
"x": 114,
"y": 50,
"w": 22,
"h": 4,
"ink": {
"color": 17,
"pri": "Keep",
"ctl": {
"Set": 1
},
"hot": null
}
}
},
{
"Rect": {
"x": 148,
"y": 46,
"w": 30,
"h": 26,
"ink": {
"color": 53,
"pri": "Keep",
"ctl": {
"Set": 1
},
"hot": null
}
}
},
{
"Rect": {
"x": 152,
"y": 50,
"w": 22,
"h": 4,
"ink": {
"color": 17,
"pri": "Keep",
"ctl": {
"Set": 1
},
"hot": null
}
}
},
{
"Rect": {
"x": 186,
"y": 46,
"w": 30,
"h": 26,
"ink": {
"color": 194,
"pri": "Keep",
"ctl": {
"Set": 1
},
"hot": null
}
}
},
{
"Rect": {
"x": 190,
"y": 50,
"w": 22,
"h": 4,
"ink": {
"color": 17,
"pri": "Keep",
"ctl": {
"Set": 1
},
"hot": null
}
}
},
{
"Rect": {
"x": 224,
"y": 46,
"w": 30,
"h": 26,
"ink": {
"color": 107,
"pri": "Keep",
"ctl": {
"Set": 1
},
"hot": null
}
}
},
{
"Rect": {
"x": 228,
"y": 50,
"w": 22,
"h": 4,
"ink": {
"color": 17,
"pri": "Keep",
"ctl": {
"Set": 1
},
"hot": null
}
}
},
{
"Rect": {
"x": 262,
"y": 46,
"w": 30,
"h": 26,
"ink": {
"color": 57,
"pri": "Keep",
"ctl": {
"Set": 1
},
"hot": null
}
}
},
{
"Rect": {
"x": 266,
"y": 50,
"w": 22,
"h": 4,
"ink": {
"color": 17,
"pri": "Keep",
"ctl": {
"Set": 1
},
"hot": null
}
}
},
{
"FbmFill": {
"x": 0,
"y": 96,
"y": 147,
"w": 320,
"h": 94,
"ramp": [
153,
110,
153
],
"scale": 22.0,
"octaves": 2,
"seed": 41,
"h": 43,
"ink": {
"color": 0,
"color": null,
"pri": "Band",
"ctl": {
"Set": 0
@ -453,161 +36,17 @@
}
},
{
"Line": {
"pts": [
[
0,
104
],
[
319,
104
]
],
"Rect": {
"x": 112,
"y": 147,
"w": 122,
"h": 30,
"ink": {
"color": 110,
"color": null,
"pri": "Keep",
"ctl": "Keep",
"hot": null
}
}
},
{
"Line": {
"pts": [
[
0,
116
],
[
319,
116
]
],
"ink": {
"color": 110,
"pri": "Keep",
"ctl": "Keep",
"hot": null
}
}
},
{
"Line": {
"pts": [
[
0,
128
],
[
319,
128
]
],
"ink": {
"color": 110,
"pri": "Keep",
"ctl": "Keep",
"hot": null
}
}
},
{
"Line": {
"pts": [
[
0,
140
],
[
319,
140
]
],
"ink": {
"color": 110,
"pri": "Keep",
"ctl": "Keep",
"hot": null
}
}
},
{
"Line": {
"pts": [
[
0,
152
],
[
319,
152
]
],
"ink": {
"color": 110,
"pri": "Keep",
"ctl": "Keep",
"hot": null
}
}
},
{
"Line": {
"pts": [
[
0,
164
],
[
319,
164
]
],
"ink": {
"color": 110,
"pri": "Keep",
"ctl": "Keep",
"hot": null
}
}
},
{
"Line": {
"pts": [
[
0,
176
],
[
319,
176
]
],
"ink": {
"color": 110,
"pri": "Keep",
"ctl": "Keep",
"hot": null
}
}
},
{
"Line": {
"pts": [
[
0,
188
],
[
319,
188
]
],
"ink": {
"color": 110,
"pri": "Keep",
"ctl": "Keep",
"ctl": {
"Set": 1
},
"hot": null
}
}
@ -615,53 +54,12 @@
{
"Rect": {
"x": 0,
"y": 168,
"w": 90,
"h": 22,
"ink": {
"color": 110,
"pri": "Keep",
"ctl": {
"Set": 1
},
"hot": null
}
}
},
{
"VoronoiFill": {
"x": 2,
"y": 170,
"w": 86,
"h": 18,
"cell": 6,
"colors": [
176,
36,
200,
19,
51
],
"edge_color": null,
"edge": 0.6,
"seed": 42,
"y": 160,
"w": 66,
"h": 30,
"ink": {
"color": null,
"pri": "Keep",
"ctl": "Keep",
"hot": null
}
}
},
{
"Rect": {
"x": 230,
"y": 168,
"w": 90,
"h": 22,
"ink": {
"color": 110,
"pri": "Keep",
"ctl": {
"Set": 1
},
@ -670,88 +68,30 @@
}
},
{
"VoronoiFill": {
"x": 232,
"y": 170,
"w": 86,
"h": 18,
"cell": 6,
"colors": [
143,
48,
188,
20
],
"edge_color": null,
"edge": 0.6,
"seed": 43,
"Rect": {
"x": 258,
"y": 168,
"w": 62,
"h": 22,
"ink": {
"color": null,
"pri": "Keep",
"ctl": "Keep",
"hot": null
}
}
},
{
"Rect": {
"x": 150,
"y": 100,
"w": 74,
"h": 22,
"ink": {
"color": 110,
"pri": {
"Set": 10
},
"ctl": {
"Set": 1
},
"hot": null
}
}
},
{
"Rect": {
"x": 150,
"y": 100,
"w": 74,
"h": 4,
"ink": {
"color": 153,
"pri": {
"Set": 10
},
"ctl": "Keep",
"hot": null
}
}
},
{
"Rect": {
"x": 158,
"y": 92,
"w": 18,
"h": 8,
"ink": {
"color": 29,
"pri": {
"Set": 10
},
"ctl": "Keep",
"hot": null
}
}
}
],
"scale": {
"horizon_y": 90,
"min_scale": 0.72,
"horizon_y": 130,
"min_scale": 0.8,
"full_y": 186
},
"spawn": [
60,
140
88,
183
],
"exits": [
null,
@ -775,10 +115,10 @@
{
"name": "poster wall",
"rect": [
108,
8,
200,
74
104,
0,
216,
100
],
"msgs": {
"look": "Dead Kennedys. D.O.A. Butthole Surfers. A wall of bands that never asked permission.",
@ -788,10 +128,10 @@
{
"name": "window",
"rect": [
18,
12,
64,
48
0,
0,
104,
110
],
"msgs": {
"look": "Fog rolling off the bay, gold light on the bridge. Even the weather here has a record deal."
@ -801,9 +141,9 @@
"name": "record crates",
"rect": [
0,
160,
92,
30
148,
74,
42
],
"msgs": {
"look": "Crates of vinyl heading to distributors on four continents. The machine, fed."
@ -812,13 +152,13 @@
{
"name": "desk",
"rect": [
148,
96,
78,
30
110,
100,
126,
76
],
"msgs": {
"look": "The desk: contracts, cassettes, and a rubber tarantula used as a paperweight."
"look": "The desk: contracts, a mountain of cassettes, and a rubber tarantula used as a paperweight."
}
}
],
@ -827,10 +167,10 @@
{
"name": "Jello",
"sprite": "biafra",
"x": 246,
"y": 132,
"x": 214,
"y": 184,
"portrait": "biafra-face",
"wander": 14,
"wander": 8,
"msgs": {
"look": "Jello Biafra, sorting demos like a heron reads a river."
},

View File

@ -466,7 +466,7 @@
"octaves": 2,
"seed": 54,
"ink": {
"color": 0,
"color": 68,
"pri": "Band",
"ctl": {
"Set": 0
@ -717,9 +717,9 @@
"name": "crowd",
"rect": [
0,
40,
30,
320,
70
80
],
"msgs": {
"look": "A field of lighters to the horizon. Sixty thousand Danes who came for Sepultura and stayed for whatever this is about to be.",

View File

@ -1,97 +1,16 @@
{
"name": "A Field Outside Poznan, 1997",
"enter_text": "Poland. Dawn the color of dishwater. Your van — your entire livelihood with wheels on it — sits in a farm field, and a man in a tracksuit is leaning on it.",
"background": "field",
"ops": [
{
"VGradient": {
"Rect": {
"x": 0,
"y": 0,
"w": 320,
"h": 66,
"ramp": [
23,
21,
22
],
"h": 112,
"ink": {
"color": 0,
"pri": "Keep",
"ctl": {
"Set": 1
},
"hot": null
}
}
},
{
"Scatter": {
"x": 0,
"y": 8,
"w": 320,
"h": 30,
"colors": [
18
],
"density": 2,
"seed": 61,
"ink": {
"color": 0,
"pri": "Keep",
"ctl": "Keep",
"hot": null
}
}
},
{
"Rect": {
"x": 30,
"y": 44,
"w": 26,
"h": 20,
"ink": {
"color": 110,
"pri": "Keep",
"ctl": {
"Set": 1
},
"hot": null
}
}
},
{
"Polygon": {
"pts": [
[
26,
46
],
[
43,
34
],
[
60,
46
]
],
"ink": {
"color": 68,
"pri": "Keep",
"ctl": {
"Set": 1
},
"hot": null
}
}
},
{
"Ellipse": {
"cx": 84,
"cy": 52,
"rx": 12,
"ry": 12,
"ink": {
"color": 38,
"color": null,
"pri": "Keep",
"ctl": {
"Set": 1
@ -102,12 +21,28 @@
},
{
"Rect": {
"x": 82,
"y": 60,
"w": 4,
"h": 8,
"x": 0,
"y": 112,
"w": 320,
"h": 78,
"ink": {
"color": 110,
"color": null,
"pri": "Band",
"ctl": {
"Set": 0
},
"hot": null
}
}
},
{
"Rect": {
"x": 0,
"y": 124,
"w": 52,
"h": 10,
"ink": {
"color": null,
"pri": "Keep",
"ctl": {
"Set": 1
@ -117,50 +52,16 @@
}
},
{
"FbmFill": {
"x": 0,
"y": 66,
"w": 320,
"h": 124,
"ramp": [
80,
38,
81,
116
],
"scale": 14.0,
"octaves": 3,
"seed": 62,
"Rect": {
"x": 268,
"y": 124,
"w": 52,
"h": 10,
"ink": {
"color": 0,
"pri": "Band",
"color": null,
"pri": "Keep",
"ctl": {
"Set": 0
},
"hot": null
}
}
},
{
"VoronoiFill": {
"x": 60,
"y": 120,
"w": 200,
"h": 40,
"cell": 16,
"colors": [
110,
68,
117
],
"edge_color": 68,
"edge": 1.2,
"seed": 63,
"ink": {
"color": 0,
"pri": "Band",
"ctl": {
"Set": 0
"Set": 1
},
"hot": null
}
@ -168,12 +69,28 @@
},
{
"Ellipse": {
"cx": 150,
"cy": 168,
"rx": 30,
"ry": 6,
"cx": 152,
"cy": 166,
"rx": 22,
"ry": 4,
"ink": {
"color": 76,
"color": null,
"pri": "Keep",
"ctl": {
"Set": 2
},
"hot": null
}
}
},
{
"Ellipse": {
"cx": 176,
"cy": 180,
"rx": 18,
"ry": 4,
"ink": {
"color": null,
"pri": "Keep",
"ctl": {
"Set": 2
@ -184,13 +101,13 @@
}
],
"scale": {
"horizon_y": 70,
"min_scale": 0.55,
"horizon_y": 106,
"min_scale": 0.5,
"full_y": 186
},
"spawn": [
30,
160
36,
168
],
"exits": [
null,
@ -202,22 +119,35 @@
{
"name": "farmhouse",
"rect": [
24,
30,
40,
36
66,
88,
48,
22
],
"msgs": {
"look": "A farmhouse minding its own business at a professional level."
}
},
{
"name": "crows",
"rect": [
120,
20,
180,
60
],
"msgs": {
"look": "Crows, circling like they've been paid to set the tone.",
"listen": "They have opinions about your situation."
}
}
],
"props": [
{
"name": "van",
"sprite": "van",
"x": 180,
"y": 118,
"x": 170,
"y": 126,
"synonyms": "tourvan vehicle",
"msgs": {
"look": "The van. Every amp, every pedal, every unwashed sleeping bag — the whole band, parked in the wrong country."
@ -229,8 +159,8 @@
{
"name": "mobster",
"sprite": "mobster",
"x": 236,
"y": 142,
"x": 226,
"y": 146,
"portrait": "mobster-face",
"msgs": {
"look": "Tracksuit, flat cap, and the calm of a man who has never once hurried."

View File

@ -26,7 +26,7 @@
{"cmd":"choose","n":1}
{"cmd":"choose","n":1}
# east to Europe, eventually Roskilde
{"cmd":"walk_to","x":316,"y":140}
{"cmd":"walk_to","x":319,"y":154}
{"cmd":"parse","text":"talk to the stage manager"}
{"cmd":"tick","n":500}
{"cmd":"choose","n":1}

222
tools/gen_music.py Normal file
View File

@ -0,0 +1,222 @@
#!/usr/bin/env python3
"""Synthesize the NOMEANSNO QUEST soundtrack — stdlib only.
Five mood-override loops for MRPCI (music/<mood>.wav), built from the
band's own vocabulary: overdriven bass riffs in odd meters, brushed noise
hats, kick sweeps. 22050 Hz mono 16-bit, loop-safe (bar-aligned, short
fades). The engine loops them at low volume under the room.
python3 tools/gen_music.py
"""
import math
import os
import random
import struct
import wave
SR = 22050
OUT = os.path.join(os.path.dirname(__file__), "..", "music")
def clip(x):
return math.tanh(x) # soft overdrive — the Rob Wright pedal
def note_hz(semi): # semitones from A1 (55 Hz)
return 55.0 * (2 ** (semi / 12.0))
class Mix:
def __init__(self, seconds):
self.buf = [0.0] * int(SR * seconds)
def add(self, t0, samples, gain=1.0):
i0 = int(t0 * SR)
for i, s in enumerate(samples):
j = i0 + i
if 0 <= j < len(self.buf):
self.buf[j] += s * gain
def wav(self, path):
n = len(self.buf)
fade = int(SR * 0.02)
out = bytearray()
for i, v in enumerate(self.buf):
g = min(1.0, i / fade, (n - 1 - i) / fade)
out += struct.pack("<h", int(max(-1, min(1, clip(v))) * 30000 * g))
with wave.open(path, "wb") as w:
w.setnchannels(1)
w.setsampwidth(2)
w.setframerate(SR)
w.writeframes(bytes(out))
print(f" {os.path.basename(path)} {n / SR:.1f}s")
def bass(semi, dur, drive=2.2, decay=6.0):
"""Picked, overdriven bass: saw+square blend through the clipper."""
hz = note_hz(semi)
n = int(dur * SR)
out = []
for i in range(n):
t = i / SR
ph = (t * hz) % 1.0
saw = 2.0 * ph - 1.0
sq = 1.0 if ph < 0.5 else -1.0
env = math.exp(-t * decay) * min(1.0, i / 80.0)
out.append(clip((0.6 * saw + 0.4 * sq) * drive) * env * 0.5)
return out
def kick(dur=0.14):
n = int(dur * SR)
return [math.sin(2 * math.pi * (90 * math.exp(-i / SR * 22)) * (i / SR)) * math.exp(-i / SR * 30) * 0.9 for i in range(n)]
def snare(dur=0.12, rng=None):
n = int(dur * SR)
rng = rng or random.Random(7)
return [(rng.random() * 2 - 1) * math.exp(-i / SR * 34) * 0.55 for i in range(n)]
def hat(dur=0.05, rng=None, gain=0.2):
n = int(dur * SR)
rng = rng or random.Random(11)
return [(rng.random() * 2 - 1) * math.exp(-i / SR * 90) * gain for i in range(n)]
def stab(semis, dur, decay=9.0):
"""An angular chord stab (thin pulse voices) — the Kerr guitar ghost."""
n = int(dur * SR)
out = [0.0] * n
for semi in semis:
hz = note_hz(semi) * 2
for i in range(n):
t = i / SR
ph = (t * hz) % 1.0
v = 1.0 if ph < 0.25 else -1.0
out[i] += v * math.exp(-t * decay) * 0.16
return out
def drone(semi, dur, wob=0.3):
hz = note_hz(semi)
n = int(dur * SR)
return [
(math.sin(2 * math.pi * hz * (i / SR)) + 0.5 * math.sin(2 * math.pi * hz * 1.5 * (i / SR) + wob * math.sin(i / SR * 0.7)))
* 0.22
for i in range(n)
]
def build_calm():
"""Basement, 1979 — walking jazz bass in 7/4, brushes. John's homework."""
bpm, beats, bars = 92, 7, 4
spb = 60.0 / bpm
m = Mix(bars * beats * spb)
rng = random.Random(1979)
walk = [0, 3, 5, 7, 10, 7, 5, 3, 0, -2, 0, 3, 2, 5, 3, 7, 0, 3, 5, 7, 10, 12, 10, 7, 5, 3, 2, 0]
k = 0
for bar in range(bars):
for b in range(beats):
t = (bar * beats + b) * spb
m.add(t, bass(walk[k % len(walk)] - 12, spb * 0.95, drive=1.2, decay=3.0), 0.8)
k += 1
m.add(t, hat(rng=rng, gain=0.12))
if b in (2, 5):
m.add(t + spb * 0.5, hat(rng=rng, gain=0.08))
return m
def build_tense():
"""The gig / Roskilde — the 9/8 riff, floored."""
bpm, beats, bars = 168, 9, 8
spb = 60.0 / bpm
m = Mix(bars * beats * spb)
rng = random.Random(1994)
riff = [0, 0, 3, 0, 5, 6, 5, 3, 1]
for bar in range(bars):
root = -12 if bar % 4 < 2 else -7
for b in range(beats):
t = (bar * beats + b) * spb
m.add(t, bass(riff[b] + root, spb * 0.9, drive=3.0, decay=8.0), 0.9)
if b in (0, 3, 5):
m.add(t, kick())
if b in (2, 6, 8):
m.add(t, snare(rng=rng))
m.add(t, hat(rng=rng, gain=0.14))
if bar % 2 == 1:
m.add((bar * beats + 6) * spb, stab([12 + root, 18 + root, 22 + root], spb * 2))
return m
def build_noir():
"""The alley — rain, chromatic descent, someone's future on a wall."""
bpm, beats, bars = 66, 4, 6
spb = 60.0 / bpm
m = Mix(bars * beats * spb)
rng = random.Random(1981)
seq = [0, -1, -3, -5, -6, -5, -3, -1]
for bar in range(bars):
for b in range(beats):
t = (bar * beats + b) * spb
if b % 2 == 0:
m.add(t, bass(seq[(bar * 2 + b // 2) % len(seq)] - 12, spb * 1.8, drive=1.4, decay=2.2), 0.7)
m.add(t + spb * 0.5, hat(dur=0.09, rng=rng, gain=0.07))
if bar % 3 == 2:
m.add(bar * beats * spb, stab([7, 13], spb * 3, decay=3.0), 0.6)
return m
def build_jolly():
"""Alternative Tentacles — puck-rock bounce; the Hansons drop by."""
bpm, beats, bars = 150, 4, 8
spb = 60.0 / bpm
m = Mix(bars * beats * spb)
rng = random.Random(1987)
roots = [0, 0, 5, 3]
for bar in range(bars):
root = roots[bar % 4] - 12
for b in range(beats):
t = (bar * beats + b) * spb
m.add(t, bass(root, spb * 0.45, drive=2.4, decay=10.0), 0.85)
m.add(t + spb * 0.5, bass(root + 7, spb * 0.4, drive=2.0, decay=11.0), 0.6)
m.add(t, kick() if b % 2 == 0 else snare(rng=rng))
m.add(t + spb * 0.5, hat(rng=rng, gain=0.16))
if bar % 2 == 1:
m.add((bar * beats + 3) * spb, stab([root + 24, root + 28, root + 31], spb * 0.8))
return m
def build_eerie():
"""The field at dawn — a drone in fifths, toms like far thunder, 5/4."""
bpm, beats, bars = 76, 5, 4
spb = 60.0 / bpm
m = Mix(bars * beats * spb)
rng = random.Random(1997)
m.add(0, drone(-24, bars * beats * spb), 0.8)
m.add(0, drone(-17, bars * beats * spb, wob=0.8), 0.4)
for bar in range(bars):
for b in range(beats):
t = (bar * beats + b) * spb
if b in (0, 3):
m.add(t, kick(0.2), 0.5)
if b == 4 and bar % 2 == 1:
m.add(t, bass(-5 - 12, spb * 2, drive=1.1, decay=1.6), 0.5)
if rng.random() < 0.2:
m.add(t + spb * 0.5, hat(dur=0.12, rng=rng, gain=0.05))
return m
def main():
os.makedirs(OUT, exist_ok=True)
print("writing music:")
build_calm().wav(os.path.join(OUT, "calm.wav"))
build_tense().wav(os.path.join(OUT, "tense.wav"))
build_noir().wav(os.path.join(OUT, "noir.wav"))
build_jolly().wav(os.path.join(OUT, "jolly.wav"))
build_eerie().wav(os.path.join(OUT, "eerie.wav"))
if __name__ == "__main__":
main()

View File

@ -1,9 +1,10 @@
#!/usr/bin/env python3
"""Generate NOMEANSNO QUEST rooms as MRPCI RoomDoc JSON.
Six rooms, one band history: the basement, the D.O.A. gig, the alley wall,
Alternative Tentacles, Roskilde '94, and a Polish farm field in '97.
Run from the repo root: python3 tools/gen_rooms.py
Every room is a MODELBEAST matte painting (pics/*.png, quantized by the
engine at load) plus *invisible* ops: walkability, depth, water and the
few visible overlays that must move or glow (footlight chases, the spray
paint). Run from the repo root: python3 tools/gen_rooms.py
"""
import json
@ -11,16 +12,13 @@ import os
OUT = os.path.join(os.path.dirname(__file__), "..", "rooms")
# --- palette helpers (MRPCI base palette layout) ---------------------------
def cube(r, g, b): # 6x6x6 color cube at 32..248
def cube(r, g, b):
return 32 + 36 * r + 6 * g + b
def gray(i): # 16-step gray ramp at 16..32
def gray(i):
return 16 + min(i, 15)
EMBER = list(range(248, 256))
# --- ink helpers ------------------------------------------------------------
KEEP = "Keep"
def ink(color=None, pri=KEEP, ctl=KEEP, hot=None):
@ -29,22 +27,18 @@ def ink(color=None, pri=KEEP, ctl=KEEP, hot=None):
def look(c):
return ink(color=c)
def floor(c):
return ink(color=c, pri="Band", ctl={"Set": 0})
def wall(c):
def wall(c=None):
return ink(color=c, ctl={"Set": 1})
def water(c):
return ink(color=c, ctl={"Set": 2})
def open_floor():
return ink(pri="Band", ctl={"Set": 0})
def depth(c, band):
return ink(color=c, pri={"Set": band})
def water(c=None):
return ink(color=c, ctl={"Set": 2})
def band_of(y):
return min(15, y * 16 // 190)
# --- op helpers ---------------------------------------------------------------
def rect(x, y, w, h, i):
return {"Rect": {"x": x, "y": y, "w": w, "h": h, "ink": i}}
@ -57,19 +51,18 @@ def ellipse(cx, cy, rx, ry, i):
def poly(pts, i):
return {"Polygon": {"pts": pts, "ink": i}}
def vgrad(x, y, w, h, ramp, i):
return {"VGradient": {"x": x, "y": y, "w": w, "h": h, "ramp": ramp, "ink": i}}
def fbm(x, y, w, h, ramp, scale, octaves, seed, i):
return {"FbmFill": {"x": x, "y": y, "w": w, "h": h, "ramp": ramp, "scale": scale, "octaves": octaves, "seed": seed, "ink": i}}
def voronoi(x, y, w, h, cell, colors, edge_color, edge, seed, i):
return {"VoronoiFill": {"x": x, "y": y, "w": w, "h": h, "cell": cell, "colors": colors, "edge_color": edge_color, "edge": edge, "seed": seed, "ink": i}}
# The standard sim skeleton over a painting: everything above floor_y is
# scenery (blocked), everything below walks and depth-sorts by row.
def sim_base(floor_y):
return [
rect(0, 0, 320, floor_y, wall()),
rect(0, floor_y, 320, 190 - floor_y, open_floor()),
]
def scatter(x, y, w, h, colors, density, seed, i):
return {"Scatter": {"x": x, "y": y, "w": w, "h": h, "colors": colors, "density": density, "seed": seed, "ink": i}}
# Crude 5x7-ish spray letters for the graffiti wall (scaled by s).
# Spray letters for the wall (drawn OVER the painting).
SEGS = {
"N": [((0, 6), (0, 0)), ((0, 0), (4, 6)), ((4, 6), (4, 0))],
"O": [((0, 0), (4, 0)), ((4, 0), (4, 6)), ((4, 6), (0, 6)), ((0, 6), (0, 0))],
@ -90,7 +83,6 @@ def spray(text, x0, y0, s, color):
for (a, b) in SEGS.get(ch, []):
pts = [[x + a[0] * s, y0 + a[1] * s], [x + b[0] * s, y0 + b[1] * s]]
ops.append(line(pts, look(color)))
# a second pass one pixel off = fat spray line
ops.append(line([[p[0] + 1, p[1]] for p in pts], look(color)))
x += 6 * s
return ops
@ -126,84 +118,63 @@ def choice(text, goto, **kw):
ROOMS = {}
# =============================================================================
# Room 0 — The Basement, Victoria BC, 1979
# Room 0 — The Basement, Victoria BC, 1979 (pics/basement.png)
# =============================================================================
ops = []
# Wood-panel walls with vertical seams.
ops.append(fbm(0, 0, 320, 100, [cube(2, 1, 0), cube(2, 1, 0), cube(3, 2, 1), cube(2, 1, 0)], 34.0, 2, 3, wall(0)))
for x in range(0, 320, 26):
ops.append(line([[x, 0], [x, 99]], ink(color=cube(1, 0, 0), ctl={"Set": 1})))
# Window well, top right — grey Victoria daylight.
ops.append(rect(268, 10, 34, 22, wall(gray(4))))
ops.append(rect(271, 13, 28, 16, wall(cube(3, 3, 4))))
ops.append(line([[285, 13], [285, 28]], wall(gray(4))))
# Concrete floor.
ops.append(voronoi(0, 100, 320, 90, 44, [gray(6), gray(6), gray(5), gray(6)], gray(5), 1.0, 9, floor(0)))
# Laundry corner, west: washer + a shelf of detergent.
ops.append(rect(6, 62, 30, 38, wall(gray(12))))
ops.append(rect(10, 68, 22, 22, wall(gray(9))))
ops.append(ellipse(21, 79, 8, 8, wall(cube(2, 3, 4))))
ops.append(rect(24, 64, 6, 3, wall(cube(4, 1, 1))))
ops.append(rect(4, 40, 36, 6, wall(cube(2, 1, 0))))
ops.append(rect(8, 30, 8, 10, wall(cube(4, 4, 0))))
ops.append(rect(20, 32, 8, 8, wall(cube(1, 3, 3))))
# The jam corner rug.
ops.append(ellipse(96, 148, 44, 14, floor(cube(1, 0, 0))))
# Workbench table along the north wall for the Portastudio.
ops.append(rect(128, 84, 66, 16, wall(cube(2, 1, 0))))
ops.append(rect(128, 84, 66, 3, wall(cube(3, 2, 1))))
# Gig poster on the wall.
ops.append(rect(214, 38, 26, 34, wall(gray(14))))
ops.append(rect(217, 42, 20, 8, wall(cube(4, 0, 0))))
ops.append(rect(217, 54, 20, 3, wall(gray(3))))
ops.append(rect(217, 60, 14, 3, wall(gray(3))))
# Stairs up, east edge.
for i in range(5):
ops.append(rect(292 - 0, 96 - i * 8, 28, 8, wall(cube(2, 1, 0)) if i % 2 == 0 else wall(cube(3, 2, 1))))
ops.append(rect(292, 100, 28, 90, floor(gray(5))))
ops = sim_base(118)
ops.append(rect(0, 118, 84, 44, wall())) # washer + cabinet footprint
ops.append(rect(240, 118, 80, 24, wall())) # under-stair clutter line
# The D.O.A. poster is OUR overlay (the painting left that wall bare).
ops.append(rect(244, 36, 26, 36, wall(gray(14))))
ops.append(rect(247, 40, 20, 9, wall(cube(4, 0, 0))))
ops.append(rect(247, 53, 20, 3, wall(gray(3))))
ops.append(rect(247, 59, 14, 3, wall(gray(3))))
ops.append(rect(247, 65, 17, 3, wall(gray(3))))
ROOMS[0] = {
"name": "The Basement, 1979",
"enter_text": "Wood panel, concrete, one bare bulb. John's kit crowds the rug; the 180-pound Portastudio owns the workbench. Through the wall, Mom's washer keeps better time than most drummers.",
"background": "basement",
"ops": ops,
"scale": {"horizon_y": 92, "min_scale": 0.72, "full_y": 186},
"spawn": [160, 150],
"scale": {"horizon_y": 100, "min_scale": 0.72, "full_y": 186},
"spawn": [180, 160],
"exits": [None, 1, 3, None],
"exit_flags": ["", "", "demo_done", ""],
"exit_blocked": ["", "", "No demo, no destiny. The Portastudio is right there.", ""],
"hotspots": [
hotspot("gig poster", [212, 36, 30, 38], msgs={
hotspot("gig poster", [242, 34, 30, 40], msgs={
"look": "D.O.A. — TONIGHT — UVic. Vancouver's finest, one ferry away. John already circled it.",
"do": "You're going. Obviously you're going. (Walk east, up the stairs.)",
"do": "You're going. Obviously you're going. (Up the stairs, east.)",
}),
hotspot("washer", [4, 60, 34, 42], msgs={
hotspot("washer", [0, 84, 84, 76], msgs={
"look": "Mom's washing machine, mid-cycle. A wet 4/4 that never rushes and never drags.",
"listen": "Chunk. Chunk. Chunk. Honestly? Tighter than most bands you've seen.",
"do": "It's Mom's. Some things are sacred.",
}),
hotspot("window", [266, 8, 38, 26], msgs={
"look": "Grey Victoria daylight. The Island doesn't hurry, and neither does the rain.",
hotspot("window", [186, 34, 46, 36], msgs={
"look": "Grey Victoria daylight through glass that hasn't been washed since the Diefenbaker administration.",
}),
hotspot("workbench", [126, 82, 70, 20], msgs={
hotspot("workbench", [116, 88, 82, 28], msgs={
"look": "The workbench holds the TEAC four-track like an altar holds a relic.",
}),
hotspot("stairs", [244, 60, 76, 56], msgs={
"look": "Up to daylight, dinner, and — tonight — a ferry to a D.O.A. show.",
}),
],
"props": [
prop("portastudio", "portastudio", 160, 104,
prop("portastudio", "portastudio", 156, 101,
synonyms="four-track teac tascam recorder deck",
msgs={
"look": "The TEAC Portastudio. 180 pounds of overdubbing possibility. It wants tape and it wants a reason.",
"do": "It powers up with a hum like a small appliance dreaming big.",
"listen": "Tape hiss. The sound of potential.",
},
solid=True),
prop("castle box", "castlebox", 224, 146,
}),
prop("castle box", "castlebox", 218, 154,
synonyms="box junk castle",
msgs={
"look": "A box of gear from Castle, the cover band you both did time in. Cables, setlists nobody needs, and — is that a fresh reel of tape?",
},
solid=True),
prop("drum kit", "drumkit", 92, 136,
prop("drum kit", "drumkit", 112, 150,
synonyms="drums kit",
msgs={
"look": "John's kit, tuned like a jazz kit because he IS a jazz kid. The high school band has no idea what it's incubating.",
@ -212,7 +183,7 @@ ROOMS[0] = {
solid=True),
],
"npcs": [
npc("John", "john", 66, 148, portrait="john-face", wander=12,
npc("John", "john", 152, 164, portrait="john-face", wander=10,
msgs={"look": "Your kid brother. Eight years younger, already swings harder than drummers twice his age."},
dialogue=[
node("Sticks spin in his fingers. “So? Are we a band, or are we two guys who own instruments?”",
@ -224,7 +195,7 @@ ROOMS[0] = {
node("“Then what are we still doing in the basement? GO. Take notes. Loud ones.”",
choice("On it.", -1)),
]),
npc("Mom", "mom", 26, 126, portrait="mom-face", wander=6,
npc("Mom", "mom", 98, 146, portrait="mom-face", wander=6,
msgs={"look": "Mom, running the laundry room like mission control."},
dialogue=[
node("“Play all you like, love — the whites are done at eight.” She pauses. “The loud one in 7/4 was nice.”",
@ -239,56 +210,39 @@ ROOMS[0] = {
}
# =============================================================================
# Room 1 — D.O.A. at UVic, 1980
# Room 1 — D.O.A. at UVic, 1980 (pics/gig.png)
# =============================================================================
ops = []
ops.append(vgrad(0, 0, 320, 96, [cube(0, 0, 1), cube(1, 0, 1), cube(0, 0, 0)], wall(0)))
# Spotlight cones.
ops.append(poly([[70, 0], [130, 0], [170, 92], [110, 92]], wall(cube(2, 1, 3))))
ops.append(poly([[200, 0], [250, 0], [210, 92], [160, 92]], wall(cube(3, 1, 1))))
# Stage.
ops.append(rect(56, 58, 208, 38, wall(cube(1, 0, 0))))
ops.append(rect(56, 58, 208, 4, wall(cube(2, 1, 0))))
# Backline silhouettes (the rest of D.O.A.).
ops.append(rect(90, 40, 14, 22, wall(gray(1))))
ops.append(rect(220, 44, 16, 18, wall(gray(1))))
ops.append(ellipse(97, 38, 4, 4, wall(gray(1))))
ops.append(ellipse(228, 42, 4, 4, wall(gray(1))))
# Footlight chase strip — the ember cycle makes it run.
for i, x in enumerate(range(58, 262, 12)):
ops.append(rect(x, 92, 8, 4, wall(EMBER[i % 8])))
# Crowd floor.
ops.append(vgrad(0, 96, 320, 94, [gray(2), gray(1)], floor(0)))
# Heads in the dark (front rows).
for i, x in enumerate(range(16, 300, 24)):
y = 108 + (i % 3) * 9
ops.append(ellipse(x, y, 6, 5, look(gray(1))))
ops.append(scatter(0, 96, 320, 40, [gray(3), gray(2)], 14, 21, look(0)))
ops = sim_base(122)
# Footlight chase across the painted stage lip.
for i, x in enumerate(range(44, 276, 12)):
ops.append(rect(x, 118, 8, 3, wall(EMBER[i % 8])))
ROOMS[1] = {
"name": "UVic — D.O.A., 1980",
"enter_text": "A university hall pretending it's a bunker. D.O.A. detonate on stage. The floor moves like weather.",
"background": "gig",
"ops": ops,
"scale": {"horizon_y": 96, "min_scale": 0.7, "full_y": 186},
"spawn": [160, 164],
"cycles": [{"start": 248, "len": 8, "period": 2, "reverse": False, "active": True}],
"scale": {"horizon_y": 118, "min_scale": 0.8, "full_y": 186},
"spawn": [160, 165],
"exits": [None, None, 2, 0],
"hotspots": [
hotspot("stage", [56, 20, 208, 76], msgs={
hotspot("stage", [40, 10, 240, 110], msgs={
"do": "Security gives you the look that says: no.",
"listen": "Loud enough to reset your heartbeat to theirs.",
}),
hotspot("pit", [80, 110, 160, 50], trigger=True, msgs={
hotspot("pit", [80, 130, 160, 50], trigger=True, msgs={
"look": "The pit. A weather system with elbows.",
}),
],
"props": [
prop("flyer", "flyer", 288, 150,
prop("flyer", "flyer", 294, 170,
synonyms="handbill paper",
msgs={"look": "A gig flyer, boot-printed. You'll keep it forever and lie about why."},
takeable=True),
],
"npcs": [
npc("Joey", "joey", 150, 90, fixed_scale=0.85,
npc("Joey", "joey", 150, 116, fixed_scale=0.8,
msgs={
"look": "Joey Shithead, mid-war-cry. Vancouver hardcore, delivered at terminal velocity.",
"talk": "He can't hear you. Nobody will hear anything until Thursday.",
@ -299,40 +253,32 @@ ROOMS[1] = {
}
# =============================================================================
# Room 2 — The alley, and the wall
# Room 2 — The alley, and the wall (pics/alley.png)
# =============================================================================
ops = []
# Face-on brick wall.
ops.append(fbm(0, 0, 320, 112, [cube(2, 1, 1), cube(2, 1, 1), cube(3, 1, 1)], 24.0, 2, 31, wall(0)))
for y in range(6, 110, 9):
ops.append(line([[0, y], [319, y]], ink(color=cube(1, 0, 0), ctl={"Set": 1})))
# Drips of old paint and rust.
ops.append(scatter(0, 0, 320, 112, [cube(1, 0, 0), gray(6)], 8, 32, ink(color=None, ctl={"Set": 1})))
# THE WORDS. White spray, uneven, perfect.
ops += spray("NO MEANS NO!", 46, 40, 4, gray(15))
ops += spray("NO MEANS NO!", 47, 41, 4, gray(12))
# Wet pavement.
ops.append(voronoi(0, 112, 320, 78, 12, [gray(4), gray(5), gray(3), cube(1, 1, 2)], gray(1), 1.4, 33, floor(0)))
ops.append(ellipse(120, 156, 34, 7, water(cube(1, 1, 3))))
ops.append(scatter(0, 112, 320, 78, [gray(8), cube(1, 1, 3)], 6, 34, look(0)))
ops = sim_base(137)
ops += spray("NO MEANS NO!", 34, 48, 4, gray(15))
ops += spray("NO MEANS NO!", 35, 49, 4, gray(12))
ops.append(ellipse(45, 162, 26, 6, water()))
ops.append(ellipse(250, 160, 22, 5, water()))
ROOMS[2] = {
"name": "The Alley",
"enter_text": "Behind the venue, rain stitching the puddles. Someone has written three words on the brick, big enough to be a law.",
"background": "alley",
"ops": ops,
"scale": {"horizon_y": 105, "min_scale": 0.75, "full_y": 186},
"spawn": [160, 160],
"exits": [1, None, None, 0],
"scale": {"horizon_y": 128, "min_scale": 0.8, "full_y": 186},
"spawn": [160, 165],
"exits": [None, None, None, 0],
"hotspots": [
hotspot("graffiti", [40, 30, 250, 74], msgs={
hotspot("graffiti", [28, 28, 264, 106], msgs={
"do": "You touch the brick. It's cold. The words stay.",
}, synonyms=None),
hotspot("puddle", [86, 148, 70, 16], msgs={
}),
hotspot("puddle", [16, 150, 90, 22], msgs={
"look": "The words float upside-down in the puddle, still true both ways.",
}),
],
"props": [
prop("dumpster", "dumpster", 282, 150,
prop("dumpster", "dumpster", 284, 158,
msgs={
"look": "A dumpster keeping the alley's secrets. Every scene in every city has this exact dumpster.",
"smell": "1980 called. It wants nothing back.",
@ -346,63 +292,42 @@ ROOMS[2] = {
}
# =============================================================================
# Room 3 — Alternative Tentacles, San Francisco, 1987
# Room 3 — Alternative Tentacles, San Francisco, 1987 (pics/office.png)
# =============================================================================
ops = []
ops.append(vgrad(0, 0, 320, 96, [cube(4, 3, 2), cube(3, 2, 2)], wall(0)))
# Bay window with a slice of San Francisco.
ops.append(rect(20, 14, 60, 44, wall(gray(2))))
ops.append(rect(24, 18, 52, 36, wall(cube(2, 3, 5))))
ops.append(line([[24, 40], [75, 34]], wall(cube(4, 1, 0)))) # a bridge, roughly
ops.append(rect(38, 44, 4, 10, wall(gray(6))))
ops.append(rect(56, 40, 4, 14, wall(gray(6))))
# The poster wall: a decade of Alternative Tentacles in thumbtacks.
px, py = 110, 12
for i in range(10):
c = [cube(4, 0, 0), cube(0, 3, 3), cube(4, 3, 0), cube(2, 0, 3), cube(0, 4, 1)][i % 5]
ops.append(rect(px + (i % 5) * 38, py + (i // 5) * 34, 30, 26, wall(c)))
ops.append(rect(px + (i % 5) * 38 + 4, py + (i // 5) * 34 + 4, 22, 4, wall(gray(1))))
# Wood floor.
ops.append(fbm(0, 96, 320, 94, [cube(3, 2, 1), cube(2, 1, 0), cube(3, 2, 1)], 22.0, 2, 41, floor(0)))
for y in range(104, 190, 12):
ops.append(line([[0, y], [319, y]], look(cube(2, 1, 0))))
# Crates of records, south wall (walk around them).
ops.append(rect(0, 168, 90, 22, ink(color=cube(2, 1, 0), ctl={"Set": 1})))
ops.append(voronoi(2, 170, 86, 18, 6, [cube(4, 0, 0), cube(0, 0, 4), cube(4, 4, 0), gray(3), cube(0, 3, 1)], None, 0.6, 42, ink(color=None, ctl=KEEP)))
ops.append(rect(230, 168, 90, 22, ink(color=cube(2, 1, 0), ctl={"Set": 1})))
ops.append(voronoi(232, 170, 86, 18, 6, [cube(3, 0, 3), cube(0, 2, 4), cube(4, 2, 0), gray(4)], None, 0.6, 43, ink(color=None, ctl=KEEP)))
# The desk (Jello's), mid-room, depth-correct.
ops.append(rect(150, 100, 74, 22, ink(color=cube(2, 1, 0), pri={"Set": band_of(122)}, ctl={"Set": 1})))
ops.append(rect(150, 100, 74, 4, ink(color=cube(3, 2, 1), pri={"Set": band_of(122)}, ctl=KEEP)))
ops.append(rect(158, 92, 18, 8, ink(color=gray(13), pri={"Set": band_of(122)}, ctl=KEEP))) # the demo pile
ops = sim_base(147)
ops.append(rect(112, 147, 122, 30, wall())) # the desk: walk behind it, not through it
# Record crates hug the corners but leave the door lanes open.
ops.append(rect(0, 160, 66, 30, wall())) # record crates, left
ops.append(rect(258, 168, 62, 22, wall())) # record crates, right (the east door lane stays open)
ROOMS[3] = {
"name": "Alternative Tentacles, 1987",
"enter_text": "San Francisco. The office smells of newsprint, coffee, and litigation. Every poster on the wall is somebody's whole life.",
"background": "office",
"ops": ops,
"scale": {"horizon_y": 90, "min_scale": 0.72, "full_y": 186},
"spawn": [60, 140],
"scale": {"horizon_y": 130, "min_scale": 0.8, "full_y": 186},
"spawn": [88, 183],
"exits": [None, 4, None, 0],
"exit_flags": ["", "signed", "", ""],
"exit_blocked": ["", "Europe doesn't book the unsigned. Talk to Jello.", "", ""],
"hotspots": [
hotspot("poster wall", [108, 8, 200, 74], msgs={
hotspot("poster wall", [104, 0, 216, 100], msgs={
"look": "Dead Kennedys. D.O.A. Butthole Surfers. A wall of bands that never asked permission.",
"do": "You straighten one thumbtack. History looks marginally tidier.",
}),
hotspot("window", [18, 12, 64, 48], msgs={
hotspot("window", [0, 0, 104, 110], msgs={
"look": "Fog rolling off the bay, gold light on the bridge. Even the weather here has a record deal.",
}),
hotspot("record crates", [0, 160, 92, 30], msgs={
hotspot("record crates", [0, 148, 74, 42], msgs={
"look": "Crates of vinyl heading to distributors on four continents. The machine, fed.",
}),
hotspot("desk", [148, 96, 78, 30], msgs={
"look": "The desk: contracts, cassettes, and a rubber tarantula used as a paperweight.",
hotspot("desk", [110, 100, 126, 76], msgs={
"look": "The desk: contracts, a mountain of cassettes, and a rubber tarantula used as a paperweight.",
}),
],
"props": [],
"npcs": [
npc("Jello", "biafra", 246, 132, portrait="biafra-face", wander=14,
npc("Jello", "biafra", 214, 184, portrait="biafra-face", wander=8,
msgs={"look": "Jello Biafra, sorting demos like a heron reads a river."},
dialogue=[
node("The Canadians. The brothers. The ones who sound like — what did Trouser Press say — Wire on psychotic steroids?",
@ -420,25 +345,18 @@ ROOMS[3] = {
}
# =============================================================================
# Room 4 — Roskilde main stage, 1994
# Room 4 — Roskilde main stage, 1994 (pics/roskilde.png)
# =============================================================================
ops = []
# The MODELBEAST matte painting (pics/roskilde.png) is the sky, the towers
# and sixty thousand lighters. Ops only add what the sim needs: walls where
# the crowd is, and the stage you actually stand on.
ops.append(rect(0, 0, 320, 112, ink(color=None, ctl={"Set": 1})))
# Stage-edge shadow line over the painting's seam.
ops.append(rect(0, 0, 320, 112, wall()))
ops.append(rect(0, 110, 320, 6, wall(gray(1))))
# Footlight chase along the stage lip (the ember cycle runs over the art).
for i, x in enumerate(range(4, 316, 12)):
ops.append(rect(x, 113, 8, 4, wall(EMBER[i % 8])))
# The stage itself — you stand ON it. Boards run toward the crowd.
ops.append(fbm(0, 118, 320, 72, [cube(1, 0, 0), cube(2, 1, 0), cube(1, 0, 0)], 20.0, 2, 54, floor(0)))
ops.append(fbm(0, 118, 320, 72, [cube(1, 0, 0), cube(2, 1, 0), cube(1, 0, 0)], 20.0, 2, 54, ink(color=cube(1, 0, 0), pri="Band", ctl={"Set": 0})))
for y in range(126, 190, 10):
ops.append(line([[0, y], [319, y]], look(cube(0, 0, 0))))
# Monitor wedges facing away.
ops.append(poly([[70, 122], [96, 122], [88, 132], [78, 132]], depth(gray(3), band_of(132))))
ops.append(poly([[200, 122], [226, 122], [218, 132], [208, 132]], depth(gray(3), band_of(132))))
ops.append(poly([[70, 122], [96, 122], [88, 132], [78, 132]], ink(color=gray(3), pri={"Set": band_of(132)})))
ops.append(poly([[200, 122], [226, 122], [218, 132], [208, 132]], ink(color=gray(3), pri={"Set": band_of(132)})))
ROOMS[4] = {
"name": "Roskilde Main Stage, 1994",
@ -452,7 +370,7 @@ ROOMS[4] = {
"exit_flags": ["", "legend", "", ""],
"exit_blocked": ["", "Not before the encore. Play the show.", "", ""],
"hotspots": [
hotspot("crowd", [0, 40, 320, 70], msgs={
hotspot("crowd", [0, 30, 320, 80], msgs={
"look": "A field of lighters to the horizon. Sixty thousand Danes who came for Sepultura and stayed for whatever this is about to be.",
"listen": "The roar arrives in waves, like surf that learned your name.",
"talk": "You raise one fist. SIXTY THOUSAND PEOPLE RAISE IT BACK.",
@ -487,36 +405,33 @@ ROOMS[4] = {
}
# =============================================================================
# Room 5 — A farm field outside Poznan, 1997
# Room 5 — A farm field outside Poznan, 1997 (pics/field.png)
# =============================================================================
ops = []
ops.append(vgrad(0, 0, 320, 66, [gray(7), gray(5), gray(6)], wall(0)))
ops.append(scatter(0, 8, 320, 30, [gray(2)], 2, 61, look(0))) # distant crows
# Distant farm.
ops.append(rect(30, 44, 26, 20, wall(cube(2, 1, 0))))
ops.append(poly([[26, 46], [43, 34], [60, 46]], wall(cube(1, 0, 0))))
ops.append(ellipse(84, 52, 12, 12, wall(cube(0, 1, 0))))
ops.append(rect(82, 60, 4, 8, wall(cube(2, 1, 0))))
# The field.
ops.append(fbm(0, 66, 320, 124, [cube(1, 2, 0), cube(0, 1, 0), cube(1, 2, 1), cube(2, 2, 0)], 14.0, 3, 62, floor(0)))
# Mud track where a van was definitely driven in the dark.
ops.append(voronoi(60, 120, 200, 40, 16, [cube(2, 1, 0), cube(1, 0, 0), cube(2, 2, 1)], cube(1, 0, 0), 1.2, 63, floor(0)))
ops.append(ellipse(150, 168, 30, 6, water(cube(1, 1, 2))))
ops = sim_base(112)
ops.append(rect(0, 124, 52, 10, wall())) # fence line, left
ops.append(rect(268, 124, 52, 10, wall())) # fence line, right
ops.append(ellipse(152, 166, 22, 4, water()))
ops.append(ellipse(176, 180, 18, 4, water()))
ROOMS[5] = {
"name": "A Field Outside Poznan, 1997",
"enter_text": "Poland. Dawn the color of dishwater. Your van — your entire livelihood with wheels on it — sits in a farm field, and a man in a tracksuit is leaning on it.",
"background": "field",
"ops": ops,
"scale": {"horizon_y": 70, "min_scale": 0.55, "full_y": 186},
"spawn": [30, 160],
"scale": {"horizon_y": 106, "min_scale": 0.5, "full_y": 186},
"spawn": [36, 168],
"exits": [None, None, None, 4],
"hotspots": [
hotspot("farmhouse", [24, 30, 40, 36], msgs={
hotspot("farmhouse", [66, 88, 48, 22], msgs={
"look": "A farmhouse minding its own business at a professional level.",
}),
hotspot("crows", [120, 20, 180, 60], msgs={
"look": "Crows, circling like they've been paid to set the tone.",
"listen": "They have opinions about your situation.",
}),
],
"props": [
prop("van", "van", 180, 118,
prop("van", "van", 170, 126,
synonyms="tourvan vehicle",
msgs={
"look": "The van. Every amp, every pedal, every unwashed sleeping bag — the whole band, parked in the wrong country.",
@ -524,7 +439,7 @@ ROOMS[5] = {
solid=True),
],
"npcs": [
npc("mobster", "mobster", 236, 142, portrait="mobster-face",
npc("mobster", "mobster", 226, 146, portrait="mobster-face",
msgs={"look": "Tracksuit, flat cap, and the calm of a man who has never once hurried."},
dialogue=[
node("“Nice van,” he says, in better English than yours. “Shame about the neighborhood. Storage fees in this district are… irregular.”",
@ -548,9 +463,6 @@ ROOMS[5] = {
def main():
os.makedirs(OUT, exist_ok=True)
for n, doc in ROOMS.items():
# strip helper keys serde doesn't know
for h in doc.get("hotspots", []):
h.pop("synonyms", None)
path = os.path.join(OUT, f"room{n}.json")
with open(path, "w") as f:
json.dump(doc, f, indent=1, ensure_ascii=False)