from pathlib import Path
# Original OUT BY 6 NES minigame. MIT, 2026. 6502 machine code assembled below.
code=bytearray(); labels={}; fix=[]
def label(n): labels[n]=0x8000+len(code)
def b(*v): code.extend(v)
def abs_(op,x):
 b(op)
 if isinstance(x,str): fix.append((len(code),x,'abs'));b(0,0)
 else:b(x&255,x>>8)
def rel(op,x):b(op);fix.append((len(code),x,'rel'));b(0)
def lda(v):b(0xa9,v)
def sta(x):abs_(0x8d,x)
def zp(op,x):b(op,x)
def jsr(n):abs_(0x20,n)
label('reset');b(0x78,0xd8,0xa2,0xff,0x9a,0xe8);sta(0x2000);sta(0x2001);sta(0x4010)
label('wait1');abs_(0x2c,0x2002);rel(0x10,'wait1')
label('wait2');abs_(0x2c,0x2002);rel(0x10,'wait2')
lda(0);b(0xa2,0)
label('clear');sta(0x0000); # first page via indexed below replaces previous instruction
code[-3:]=bytes([0x9d,0,0]);sta(0x0100);code[-3:]=bytes([0x9d,0,1]);sta(0x0300);code[-3:]=bytes([0x9d,0,3]);b(0xe8);rel(0xd0,'clear')
lda(0xff);b(0xa2,0)
label('sprites');b(0x9d,0,2,0xe8);rel(0xd0,'sprites')
lda(0x20);sta(0x2006);lda(0);sta(0x2006);b(0xa2,4,0xa0,0)
label('names');sta(0x2007);b(0xc8);rel(0xd0,'names');b(0xca);rel(0xd0,'names')
lda(0x3f);sta(0x2006);lda(0);sta(0x2006);b(0xa2,0)
label('pal');abs_(0xbd,'palette');sta(0x2007);b(0xe8,0xe0,32);rel(0xd0,'pal')
lda(0x20);sta(0x2006);lda(0x88);sta(0x2006);b(0xa2,0)
label('text');abs_(0xbd,'title');sta(0x2007);b(0xe8,0xe0,16);rel(0xd0,'text')
lda(0x20);sta(0x2006);lda(0xc8);sta(0x2006);b(0xa2,0)
label('help');abs_(0xbd,'helptext');sta(0x2007);b(0xe8,0xe0,16);rel(0xd0,'help')
jsr('init');lda(0);sta(0x2005);sta(0x2005);lda(0x80);sta(0x2000);lda(0x1e);sta(0x2001)
label('forever');abs_(0x4c,'forever')
label('init');lda(0x20);sta(0x2006);lda(0xc8);sta(0x2006);b(0xa2,0)
label('inithelp');abs_(0xbd,'helptext');sta(0x2007);b(0xe8,0xe0,16);rel(0xd0,'inithelp')
lda(120);zp(0x85,0);zp(0x85,1);lda(0);zp(0x85,3);lda(60);zp(0x85,4);lda(160);zp(0x85,5);b(0x60)
label('nmi');b(0x48,0x8a,0x48,0x98,0x48)
lda(1);sta(0x4016);lda(0);sta(0x4016);zp(0x85,2);b(0xa2,8)
label('read');abs_(0xad,0x4016);b(0x4a);zp(0x26,2);b(0xca);rel(0xd0,'read')
# bits: A=128 B=64 Select=32 Start=16 Up=8 Down=4 Left=2 Right=1
zp(0xa5,2);b(0x29,16);rel(0xf0,'nostart');jsr('init')
label('nostart');zp(0xa5,3);b(0xc9,6);rel(0x90,'playing');abs_(0x4c,'draw');label('playing')
lda(1);zp(0x85,7);zp(0xa5,2);b(0x29,128);rel(0xf0,'speed');b(0xe6,7)
label('speed');zp(0xa5,2);b(0x29,8);rel(0xf0,'down');zp(0xa5,1);b(0xc9,64);rel(0x90,'down');b(0x38);zp(0xe5,7);zp(0x85,1)
label('down');zp(0xa5,2);b(0x29,4);rel(0xf0,'left');zp(0xa5,1);b(0xc9,216);rel(0xb0,'left');b(0x18);zp(0x65,7);zp(0x85,1)
label('left');zp(0xa5,2);b(0x29,2);rel(0xf0,'right');zp(0xa5,0);b(0xc9,16);rel(0x90,'right');b(0x38);zp(0xe5,7);zp(0x85,0)
label('right');zp(0xa5,2);b(0x29,1);rel(0xf0,'collision');zp(0xa5,0);b(0xc9,232);rel(0xb0,'collision');b(0x18);zp(0x65,7);zp(0x85,0)
label('collision');zp(0xa5,0);b(0x38);zp(0xe5,4);b(0x18,0x69,7,0xc9,15);rel(0xb0,'draw');zp(0xa5,1);b(0x38);zp(0xe5,5);b(0x18,0x69,7,0xc9,15);rel(0xb0,'draw')
b(0xe6,3);zp(0xa6,3);abs_(0xbd,'targetX');zp(0x85,4);abs_(0xbd,'targetY');zp(0x85,5)
lda(1);sta(0x4015);lda(0xbf);sta(0x4000);lda(0x80);sta(0x4002);lda(0x08);sta(0x4003)
label('draw');zp(0xa5,1);sta(0x200);lda(1);sta(0x201);lda(0);sta(0x202);zp(0xa5,0);sta(0x203);zp(0xa5,5);sta(0x204);lda(2);sta(0x205);lda(1);sta(0x206);zp(0xa5,4);sta(0x207)
lda(0x20);sta(0x2006);lda(0xa8);sta(0x2006);zp(0xa5,3);b(0x18,0x69,48);sta(0x2007)
zp(0xa5,3);b(0xc9,6);rel(0x90,'normal');lda(0x20);sta(0x2006);lda(0xc8);sta(0x2006);b(0xa2,0)
label('wintext');abs_(0xbd,'win');sta(0x2007);b(0xe8,0xe0,16);rel(0xd0,'wintext')
label('normal');lda(0);sta(0x2003);lda(2);sta(0x4014);lda(0);sta(0x2005);sta(0x2005);b(0x68,0xa8,0x68,0xaa,0x68,0x40)
label('palette');b(*([0x0f,0x21,0x30,0x16]+[0x0f,0x27,0x30,0x16]*3+[0x0f,0x30,0x21,0x16]+[0x0f,0x28,0x30,0x16]*3))
for n,s in [('title','OUT BY 6        '),('helptext','COLLECT 6 STARS '),('win','YOU ARE OUT!    ')]:label(n);b(*s.encode())
label('targetX');b(60,196,40,190,128,60,248)
label('targetY');b(160,88,88,190,120,190,248)
for pos,n,t in fix:
 val=labels[n]
 if t=='abs':code[pos:pos+2]=bytes([val&255,val>>8])
 else:
  diff=val-(0x8000+pos+1)
  assert -128<=diff<=127,(n,diff)
  code[pos]=diff&255
prg=code+bytes(16384-len(code));prg[-6:]=bytes([labels['nmi']&255,labels['nmi']>>8,0,0x80,0,0x80])
chr=bytearray(8192)
font={'O':['01110','10001','10001','10001','10001','10001','01110'],'U':['10001','10001','10001','10001','10001','10001','01110'],'T':['11111','00100','00100','00100','00100','00100','00100'],'B':['11110','10001','10001','11110','10001','10001','11110'],'Y':['10001','10001','01010','00100','00100','00100','00100'],'6':['01110','10000','10000','11110','10001','10001','01110'],'C':['01110','10001','10000','10000','10000','10001','01110'],'L':['10000','10000','10000','10000','10000','10000','11111'],'E':['11111','10000','10000','11110','10000','10000','11111'],'S':['01111','10000','10000','01110','00001','00001','11110'],'A':['01110','10001','10001','11111','10001','10001','10001'],'R':['11110','10001','10001','11110','10100','10010','10001'],'!':['00100','00100','00100','00100','00100','00000','00100'],'0':['01110','10001','10011','10101','11001','10001','01110'],'1':['00100','01100','00100','00100','00100','00100','01110'],'2':['01110','10001','00001','00010','00100','01000','11111'],'3':['11110','00001','00001','01110','00001','00001','11110'],'4':['00010','00110','01010','10010','11111','00010','00010'],'5':['11111','10000','10000','11110','00001','00001','11110']}
for c,rows in font.items():
 for y,row in enumerate(rows):chr[ord(c)*16+y]=int(row,2)<<2
chr[16:24]=bytes([0x3c,0x7e,0xdb,0xff,0xbd,0x7e,0x24,0x66])
chr[32:40]=bytes([0x18,0x18,0x7e,0x3c,0xff,0x7e,0x24,0x42])
path=Path('site/arcade/demo.nes');path.write_bytes(b'NES\x1a'+bytes([1,1,0,0])+bytes(8)+prg+chr)
print(f'Wrote original demo: {len(code)} bytes program, {path.stat().st_size} bytes ROM')
