- Title: FLUX marquee logo + 4 portrait class-select cards (keys 1-4 or tap) - Themes 4-5: WAREHOUSE RAVE (shazam zombie turrets firing slow requests, gatekeeper tanks) and FESTIVAL GROUNDS (wobbling warped records); tune pools extracted for both (acid/tech-house/minimal, trance/big-beat/prog) -> 1008 tunes - Spectral Conductor boss (level 8 of each 12-cycle): 20 HP, teleports to a corner every 5 hits, doubles generator rate + 1.5x enemy speed while alive - The Monstrous 78 RPM (level 12): 30 HP, bounces Pong-style, spits warped shards, only vulnerable while "changing sides" (dimmed); killing it reveals THE WHITE LABEL (??? - ???) in the notebook and starts NG+ density - Boss arenas have no exit until the boss falls - the booth appears where it died - Fix: makeGlow crashes on scene restart (createCanvas returns null if key exists) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
109 lines
4.3 KiB
Python
109 lines
4.3 KiB
Python
#!/usr/bin/env python3
|
|
"""Extract the trainspotting tune pools from local discogs_full -> public/tunes.json.
|
|
|
|
Canon = most-pressed masters (repress count across 10.4M releases = mass-owned classic).
|
|
The club pool additionally requires the main release to be a 12" — keeps it club wax,
|
|
not Madonna albums tagged House. Grail flag = top ~8% of each chunk (certified classics,
|
|
gold flash in-game). Null label renders as "White Label", which is exactly right.
|
|
|
|
Read-only against discogs_full (canonical). release.master_id is indexed; each chunk
|
|
query returns in ~1s.
|
|
|
|
python3 assets/extract_tunes.py
|
|
"""
|
|
import json
|
|
import subprocess
|
|
from pathlib import Path
|
|
|
|
PSQL = "/opt/homebrew/opt/postgresql@16/bin/psql"
|
|
OUT = Path(__file__).resolve().parent.parent / "public" / "tunes.json"
|
|
|
|
# pool -> chunks of (tag, is_genre, year_lo, year_hi, n, twelve_inch_only)
|
|
POOLS = {
|
|
"shop": [ # RECORD SHOP: disco / soul / funk
|
|
("Disco", False, 1968, 1989, 70, False),
|
|
("Soul", False, 1962, 1985, 70, False),
|
|
("Funk", False, 1968, 1985, 70, False),
|
|
],
|
|
"fair": [ # RECORD FAIR: hip hop / jazz-funk
|
|
("Hip Hop", True, 1979, 2005, 100, False),
|
|
("Jazz-Funk", False, 1968, 1995, 70, False),
|
|
],
|
|
"club": [ # CLUB NIGHT: house / techno / jungle, 12"s only
|
|
("House", False, 1985, 2005, 70, True),
|
|
("Techno", False, 1985, 2005, 70, True),
|
|
("Jungle", False, 1990, 2000, 50, True),
|
|
],
|
|
"warehouse": [ # WAREHOUSE RAVE: acid / minimal / tech house, 12"s only
|
|
("Acid House", False, 1987, 1995, 50, True),
|
|
("Tech House", False, 1992, 2005, 50, True),
|
|
("Minimal", False, 1992, 2005, 50, True),
|
|
],
|
|
"festival": [ # FESTIVAL GROUNDS: trance / big beat / prog, 12"s only
|
|
("Trance", False, 1992, 2005, 60, True),
|
|
("Big Beat", False, 1994, 2002, 40, True),
|
|
("Progressive House", False, 1992, 2005, 50, True),
|
|
],
|
|
"archive": [ # THE ARCHIVE: the deep stuff, 12"s only
|
|
("Drum n Bass", False, 1992, 2005, 60, True),
|
|
("Hardcore", False, 1990, 1997, 50, True),
|
|
("Breakbeat", False, 1988, 2005, 50, True),
|
|
("Electro", False, 1982, 2005, 50, True),
|
|
],
|
|
}
|
|
GRAIL_FRACTION = 0.08 # top of each ranked chunk = certified classic
|
|
|
|
|
|
def q(sql):
|
|
r = subprocess.run([PSQL, "-d", "discogs_full", "--csv", "-t", "-c", sql],
|
|
capture_output=True, text=True, check=True)
|
|
import csv, io
|
|
return list(csv.reader(io.StringIO(r.stdout)))
|
|
|
|
|
|
def fetch(tag, is_genre, ylo, yhi, limit, twelve):
|
|
join = ("JOIN master_genre mg ON mg.master_id=m.id AND mg.genre=" if is_genre
|
|
else "JOIN master_style ms ON ms.master_id=m.id AND ms.style=")
|
|
twelve_sql = ("""AND EXISTS (SELECT 1 FROM release_format rf
|
|
WHERE rf.release_id=m.main_release AND rf.descriptions LIKE '%12"%')"""
|
|
if twelve else "")
|
|
rows = q(f"""
|
|
SELECT m.title, m.year, COUNT(r.id) AS presses,
|
|
(SELECT ma.artist_name FROM master_artist ma WHERE ma.master_id=m.id
|
|
ORDER BY ma.position NULLS LAST LIMIT 1),
|
|
(SELECT rl.label_name FROM release_label rl WHERE rl.release_id=m.main_release LIMIT 1)
|
|
FROM master m {join}'{tag}'
|
|
JOIN release r ON r.master_id=m.id
|
|
WHERE m.year BETWEEN {ylo} AND {yhi} {twelve_sql}
|
|
GROUP BY m.id ORDER BY presses DESC LIMIT {limit}""")
|
|
grails = max(1, int(len(rows) * GRAIL_FRACTION))
|
|
out = []
|
|
for i, r in enumerate(rows):
|
|
if len(r) < 5 or not r[0] or not r[3]:
|
|
continue
|
|
out.append({
|
|
"a": r[3], "t": r[0],
|
|
"l": r[4] or "White Label",
|
|
"y": int(r[1]) if r[1] else None,
|
|
**({"g": 1} if i < grails else {}),
|
|
})
|
|
return out
|
|
|
|
|
|
pools = {}
|
|
for pool, chunks in POOLS.items():
|
|
seen = set()
|
|
tunes = []
|
|
for chunk in chunks:
|
|
for tune in fetch(*chunk):
|
|
key = (tune["a"].lower(), tune["t"].lower())
|
|
if key in seen:
|
|
continue
|
|
seen.add(key)
|
|
tunes.append(tune)
|
|
pools[pool] = tunes
|
|
print(f"{pool}: {len(tunes)} tunes ({sum(1 for t in tunes if t.get('g'))} grails)")
|
|
|
|
OUT.write_text(json.dumps(pools, ensure_ascii=False, separators=(",", ":")))
|
|
print(f"wrote {OUT} ({OUT.stat().st_size // 1024}KB, {sum(len(v) for v in pools.values())} total)")
|