vinylgauntlet/assets/extract_tunes.py
type-two ea2bfd0eaa Tier 3: bosses, warehouse+festival themes, portrait title screen
- 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>
2026-07-16 19:27:34 +10:00

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)")