site 1.7.0: builder covers every mod in any combination

Stage 1 (arnoldasr's code) still ships as checked data; stage 2 (our mods)
runs the patcher's own Python in the browser via self-hosted Pyodide 0.26.4,
byte-identical to the patcher. Added cuenames, played, endwarn, quicksearch,
usbautoload; phasemeter REVERSE and WAVEFORM modes included.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Thiemo TizianiandClaude Opus 5.5 committed 2026-10-20 19:20:38 +02:00
1 parent 2f9c182279
commit f955e37927
19 files changed
+566 -93

No files matched your search

+33 -29
View File
@@ -139,6 +139,25 @@ def apply_ops(app, stock, ops, hk, size):
return bytes(out)
MODS_ALL = ('extended_bpm', 'phasemeter', 'advanced_waveforms', 'beatjump', 'cuecolours', 'hotcuekeys',
'ledsaver', 'keynotation', 'usbautoload', 'quicksearch', 'cuenames', 'played', 'endwarn', 'credit')
def python_bundle(repo):
"""Our patch code for the browser: mods/patch_cdj900nxs.py and every module
in mods/cdj900nxs (not arnoldasr's checkout, not the tests)."""
files = {'mods/patch_cdj900nxs.py': os.path.join(repo, 'mods', 'patch_cdj900nxs.py')}
d = os.path.join(repo, 'mods', 'cdj900nxs')
for n in sorted(os.listdir(d)):
if n.endswith('.py'):
files['mods/cdj900nxs/' + n] = os.path.join(d, n)
out = {}
for rel, path in files.items():
with open(path, encoding='utf-8') as f:
out[rel] = f.read()
return out
def main():
ap = argparse.ArgumentParser()
ap.add_argument('--repo', default=os.path.join(os.path.dirname(WEB), 'CDJ900NXSVirtual'))
@@ -188,7 +207,7 @@ def main():
upstream, up_apps = {}, {(): stock}
for k in range(1, len(p.UPSTREAM) + 1):
for up in itertools.combinations(p.UPSTREAM, k):
app = p.patch(stock, list(up), public=True)
app = p.patch_upstream(stock, list(up))
# the asset blob itself (code of ours after it, advanced_waveforms'
# fix, goes as data)
blob_len = min(len(ASSETS(stock)[0]), len(app) - (p.DATA_BASE - BASE))
@@ -201,30 +220,18 @@ def main():
upstream[','.join(up)] = {'size': len(app), 'ops': encode(ops)}
up_apps[up] = app
print('%-40s %3d operations' % (','.join(up), len(ops)))
for combo in combos:
final = p.patch(stock, list(combo), public=True)
ops = ops_for(stock, final, boxes if 'hotcuekeys' in combo else [])
if apply_ops(stock, stock, ops, hk, len(final)) != final:
raise SystemExit('%s: the operations do not give the patcher\'s result' % ','.join(combo))
variants[','.join(combo)] = {'size': len(final), 'ops': encode(ops)}
print('%-40s %3d operations' % (','.join(combo), len(ops)))
# the browser applies ours on top of whatever of arnoldasr's was applied:
# check every pair, and note the result's SHA-256 for the browser to check
expect = {}
for k in range(0, len(p.UPSTREAM) + 1):
for up in itertools.combinations(p.UPSTREAM, k):
base = up_apps[up]
if up:
expect[','.join(up)] = sha(base)
for combo in combos:
final = p.patch(stock, list(up) + list(combo), public=True)
alone = p.patch(stock, list(combo), public=True)
ops = ops_for(stock, alone, boxes if 'hotcuekeys' in combo else [])
if apply_ops(base, stock, ops, hk, len(alone)) != final:
raise SystemExit('%s + %s: differs from the patcher' % (','.join(up), ','.join(combo)))
expect[','.join(list(up) + list(combo))] = sha(final)
print('checked: every combination of arnoldasr and ours gives the patcher bytes')
# stage 1 only: the browser checks its result against these; stage 2 (ours)
# runs there as Python, the patcher's own code (builder/py/bundle.json)
expect = {','.join(up): sha(app) for up, app in up_apps.items() if up}
bundle = python_bundle(args.repo)
with open(os.path.join(WEB, 'builder', 'py', 'bundle.json'), 'w', encoding='utf-8', newline='\n') as f:
json.dump(bundle, f, separators=(',', ':'))
# stage 2 as the browser runs it: patch_ours on stage 1's application
for names in (['credit'], list(MODS_ALL), ['phasemeter'], ['advanced_waveforms', 'cuecolours', 'cuenames']):
up = tuple(n for n in p.UPSTREAM if n in names)
if p.patch_ours(up_apps[up], stock, p.check_names(names)) != p.patch(stock, names):
raise SystemExit('%s: stage 1 + stage 2 differs from the patcher' % ','.join(names))
print('checked: stage 1 data + stage 2 Python give the patcher bytes; %d Python files' % len(bundle))
glyphs = {c: rows for c, rows in hk.GLYPHS.items()}
data = {
@@ -233,10 +240,7 @@ def main():
'appSize': p.APP_SIZE,
'dataBase': p.DATA_BASE,
'upstream': upstream,
'ours': variants,
'expect': expect,
'box': {'resTable': hk.RES_TABLE, 'width': w, 'height': h, 'glyphs': glyphs,
'gap': 3, 'space': 5},
'chunks': chunks,
}
sys.path.insert(0, os.path.join(os.path.dirname(WEB), 'CDJ900NXSPatcher'))
@@ -255,7 +259,7 @@ def main():
f.write('window.BUILDER_DATA = ')
json.dump(data, f, separators=(',', ':'))
f.write(';\n')
print('wrote %s (%d bytes, %d + %d variants, %d chunks)' % (out, os.path.getsize(out), len(upstream), len(variants), len(chunks)))
print('wrote %s (%d bytes, %d variants, %d chunks)' % (out, os.path.getsize(out), len(upstream), len(chunks)))
write_versions(p)