vinylgauntlet/assets/extract_tunes.py
type-two 8e967407c1 Tier 2 complete: digging, treasure rooms, the Archive, infinite mazes
- Digging: hold E (or DIG touch button) by a crate wall, 2s rooted channel, once
  per tile. Loot: 70% +200 vibe, 25% gold record, 4% grail (full vibe), 1%
  bootleg (every bin on the level erupts).
- Treasure rooms: every 3rd level, 20s bonus room strewn with gold, no enemies,
  booth exits instantly, "CLOSING TIME" when the timer dies.
- THE ARCHIVE (4th theme): FLUX-generated dark tileset, real darkness via
  RenderTexture with radial glow holes at player + lamps, dust mites spawn in
  packs of 3, Mite Queen miniboss (12 HP, broods mites every 5s, airhorn-immune,
  drops 3 gold + 1000 pts). Archive tune pool: 195 dnb/hardcore/breakbeat/
  electro 12"s (718 total tunes now).
- Infinite: levels past the 4 authored maps are deterministic seeded mazes
  (recursive backtracker + loops + rooms), difficulty scales with depth.

All verified in-browser via engine stepping.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 17:35:42 +10:00

99 lines
3.8 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),
],
"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)")