Aluode commited on
Commit
398c88c
·
verified ·
1 Parent(s): c94460c

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +155 -157
index.html CHANGED
@@ -88,10 +88,9 @@
88
  the <em>resonance</em> meter runs hot as you near the key. no enemies, no clocks —<br>
89
  only geometry, depth, and the hash. nothing is stored; walk back and every<br>
90
  room is re-dreamed identically. click / drag to enter · mouse look is inverted.<br><br>
91
- <em>first launch is slow:</em> your GPU driver must build the whole shader once <br>
92
- this can take a while (on some machines, minutes). it is not stuck; the counter<br>
93
- below is real elapsed time. the driver caches the result, so every launch<br>
94
- after this one is near-instant.</div>
95
  <div id="bootStat">waking up…</div>
96
  </div>
97
 
@@ -120,6 +119,7 @@
120
  <button id="m0" class="on">Boundary</button>
121
  <button id="m1">Projection</button>
122
  <button id="m2">Cascade</button>
 
123
  </div>
124
  <div class="field"><label>Mass gain <var id="m_l">1.0</var></label>
125
  <input id="m" type="range" min="0.2" max="2.2" step="0.05" value="1.0"></div>
@@ -184,12 +184,17 @@ uniform vec2 iRes;
184
  uniform float iTime;
185
  uniform vec3 camP,camR,camU,camF;
186
  uniform int nWell,mode,keyWell;
 
 
 
 
187
  uniform vec4 wells[${MAXW}]; /* xyz pos · w size */
188
  uniform vec3 wellCol[${MAXW}];
189
- uniform vec4 wellPar[${MAXW}]; /* x shape · y angA · z angB · w star-n */
190
  uniform vec3 roomPos[3]; /* prev · current · next */
191
- uniform vec4 roomPar[3]; /* x shape · y size · z half-height · w star-n */
192
- uniform vec4 roomMod[3]; /* x twist · y ripple amp · z ripple freq · w mass */
 
193
  uniform vec3 roomTintU[3];
194
  uniform vec4 seal; /* xyz pos · w radius (<0 = open) */
195
  uniform float mGain,lensK;
@@ -197,65 +202,46 @@ uniform sampler2D camTex;
197
 
198
  /* ---------- primitives ---------- */
199
  vec2 rot2(vec2 v,float a){float c=cos(a),s=sin(a);return vec2(c*v.x-s*v.y,s*v.x+c*v.y);}
200
- float sdBox3(vec3 p,vec3 b){vec3 q=abs(p)-b;return length(max(q,0.))+min(max(q.x,max(q.y,q.z)),0.);}
201
- float sdHexP(vec3 p,vec2 h){ /* cross-section xy · half-height z */
202
- const vec3 k=vec3(-0.8660254,0.5,0.57735);
203
- p=abs(p);
204
- p.xy-=2.0*min(dot(k.xy,p.xy),0.0)*k.xy;
205
- vec2 d=vec2(length(p.xy-vec2(clamp(p.x,-k.z*h.x,k.z*h.x),h.x))*sign(p.y-h.x),p.z-h.y);
206
- return min(max(d.x,d.y),0.0)+length(max(d,0.0));}
207
- float sdStarN(vec2 p,float r,float n,float m){ /* n-pointed star, m in (2,n) */
208
- float an=3.1415927/n, en=3.1415927/m;
209
- vec2 acs=vec2(cos(an),sin(an)), ecs=vec2(cos(en),sin(en));
210
- float bn=mod(atan(p.x,p.y),2.0*an)-an;
211
- p=length(p)*vec2(cos(bn),abs(sin(bn)));
212
- p-=r*acs;
213
- p+=ecs*clamp(-dot(p,ecs),0.0,r*acs.y/ecs.y);
214
- return length(p)*sign(p.x);}
215
- float sdOcta(vec3 p,float s){p=abs(p);return (p.x+p.y+p.z-s)*0.57735;}
216
- float opExtr(float d2,float z,float h){vec2 w=vec2(d2,abs(z)-h);
217
- return min(max(w.x,w.y),0.0)+length(max(w,0.0));}
218
  float sdSeg(vec3 p,vec3 a,vec3 b){vec3 pa=p-a,ba=b-a;
219
  float h=clamp(dot(pa,ba)/dot(ba,ba),0.0,1.0);return length(pa-ba*h);}
220
 
221
- /* ---------- the twelve shapes. rooms and moons share this ONE chain ----------
222
- signed, negative inside. s = radius-ish, h = half-height, n = star points */
223
- float shapeSD(vec3 q,float sh,float s,float h,float n){
224
- int i=int(sh+0.5);
225
- if(i==0) return sdBox3(q,vec3(s,h,s));
226
- if(i==1) return length(q)-s;
227
- if(i==2) return sdHexP(vec3(q.x,q.z,q.y),vec2(s,h));
228
- if(i==3){float m=2.0+(n-2.0)*0.45;
229
- return opExtr(sdStarN(q.xz,s*1.15,n,m),q.y,h);}
230
- if(i==4) return sdOcta(q,s*1.5);
231
- if(i==5) return max(length(q.xz)-s,abs(q.y)-h); /* cylinder */
232
- if(i==6){vec3 c=q; c.y-=clamp(c.y,-h*0.55,h*0.55); /* capsule vault */
233
- return length(c)-s*0.90;}
234
- if(i==7) return max(sdBox3(q,vec3(s*0.95)),sdOcta(q,s*1.45)); /* cut crystal */
235
- if(i==8) return (length(vec3(q.x,q.y*1.35,q.z))-s*1.08)*0.72; /* ellipsoid dome */
236
- if(i==9) return min(sdBox3(q,vec3(s,h*0.55,s*0.38)), /* cross cathedral */
237
- min(sdBox3(q,vec3(s*0.38,h*0.55,s)),
238
- sdBox3(q,vec3(s*0.45,h,s*0.45))));
239
- if(i==10){float a=atan(q.z,q.x); /* gear */
240
- float rr=s*(0.88+0.11*cos(a*n*2.0));
241
- return max(length(q.xz)-rr,abs(q.y)-h)*0.68;}
242
- /* 11 = wheel: torus + hub + four spokes, all walkable */
243
- float tor=length(vec2(length(q.xz)-s*0.72,q.y))-s*0.40;
244
- float hub=length(q)-s*0.40;
245
- float sp=min(min(sdSeg(q,vec3(0.),vec3(s*0.72,0.,0.)),
246
- sdSeg(q,vec3(0.),vec3(-s*0.72,0.,0.))),
247
- min(sdSeg(q,vec3(0.),vec3(0.,0.,s*0.72)),
248
- sdSeg(q,vec3(0.),vec3(0.,0.,-s*0.72))))-s*0.17;
249
- return min(tor,min(hub,sp));
250
  }
251
  float roomSD(vec3 p,int r){
252
  vec3 q=p-roomPos[r];
253
  float tw=roomMod[r].x;
254
  if(abs(tw)>0.0005) q.xz=rot2(q.xz,q.y*tw);
255
- float d=shapeSD(q,roomPar[r].x,roomPar[r].y,roomPar[r].z,roomPar[r].w);
 
256
  float ra=roomMod[r].y;
257
  if(ra>0.001) d+=ra*sin(q.x*roomMod[r].z)*sin(q.y*roomMod[r].z)*sin(q.z*roomMod[r].z);
258
- return d/(1.0+abs(tw)*roomPar[r].y*0.6+ra*roomMod[r].z); /* Lipschitz guard */
259
  }
260
  float worldSD(vec3 p){
261
  float d=min(roomSD(p,0),min(roomSD(p,1),roomSD(p,2)));
@@ -271,21 +257,18 @@ vec3 wallNormal(vec3 p){ /* inward-facing */
271
  e.xyy*worldSD(p+e.xyy)+e.yyx*worldSD(p+e.yyx)+
272
  e.yxy*worldSD(p+e.yxy)+e.xxx*worldSD(p+e.xxx));}
273
 
274
- /* ---------- moons: the same twelve shapes, shrunk and rounded into glass.
275
- core-scale, height-scale and rounding radius per shape, from const tables
276
- this keeps the shader small enough for slow driver compilers ---------- */
277
- const float CS[12]=float[12](0.50,1.00,0.60,0.765,0.613,0.52,0.644,0.590,0.740,0.600,0.568,0.55);
278
- const float CH[12]=float[12](0.50,1.00,0.34,0.180,0.613,0.42,0.545,0.590,0.740,0.470,0.280,0.55);
279
- const float CR[12]=float[12](0.26,0.00,0.22,0.16,0.24,0.20,0.00,0.18,0.06,0.14,0.12,0.10);
280
  float moonSD(vec3 p,int k){
281
  vec3 q=p-wells[k].xyz;
282
  float s=wells[k].w;
283
  float bs=length(q)-s*2.2;
284
  if(bs>0.6) return bs;
285
- q.xz=rot2(q.xz,wellPar[k].y);
286
- q.xy=rot2(q.xy,wellPar[k].z);
287
- int i=int(wellPar[k].x+0.5);
288
- return shapeSD(q,wellPar[k].x,s*CS[i],s*CH[i],wellPar[k].w)-s*CR[i];
 
289
  }
290
  vec3 moonNormal(vec3 p,int k){
291
  const vec2 e=vec2(0.012,-0.012);
@@ -314,7 +297,7 @@ void main(){
314
  vec3 gA0=mix(roomPos[0],roomPos[1],0.28), gB0=mix(roomPos[0],roomPos[1],0.72);
315
  vec3 gA1=mix(roomPos[1],roomPos[2],0.28), gB1=mix(roomPos[1],roomPos[2],0.72);
316
 
317
- for(int i=0;i<180;i++){
318
  /* lensing: deflection per path length, dtheta ~ gradPhi * ds */
319
  if(lensK>0.001){
320
  vec3 pull=vec3(0.0);
@@ -366,19 +349,44 @@ void main(){
366
  L+=wells[k].w*mGain/(length(x-wells[k].xyz)+1.0);}
367
  col=tint*eqSample(radial)*(0.55+0.7*min(L,1.4)); done=true; break;
368
  }
369
- /* BOUNDARY & CASCADE: heavy optical crystal.
370
- Fresnel glints off the surface; the ray itself REFRACTS (Snell),
371
- marches the negative-distance chord inside, exits the far side,
372
- and keeps flying — you see the world warped through the glass. */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
373
  glow+=tint*eqSample(reflect(rd,opticN))*F*0.55; /* mirror glint, Fresnel-weighted */
374
- if(mode==2) accum+=tint*eqSample(radial)*F*1.1; /* cascade: crystal wears the face at the rims */
375
- bnc++; if(bnc>MAXB){col=(mode==2)?accum:vec3(0.0);done=true;break;}
376
  const float IOR=1.45;
377
  vec3 d=refract(rd,opticN,1.0/IOR);
378
  if(dot(d,d)<1e-4) d=reflect(rd,opticN); /* entry-grazing guard */
379
  x-=n*0.04; /* step inside the glass */
380
  float chord=0.0;
381
- for(int j=0;j<26;j++){
382
  float sd=moonSD(x,hb);
383
  if(sd>-0.006){ /* reached the far surface */
384
  vec3 ne=moonNormal(x,hb);
@@ -399,11 +407,11 @@ void main(){
399
  tint*=exp(-(vec3(1.0)-wellCol[hb])*chord*0.35)*0.96;
400
  rd=d;
401
  vec3 ne2=moonNormal(x,hb);
402
- for(int j=0;j<10;j++){ if(moonSD(x,hb)>0.05)break; x+=ne2*0.05; }
403
  x+=rd*0.04; trav+=chord+0.08; stp=0.05; continue;
404
  }
405
  if(dw<0.03){
406
- if(mode==0){ /* BOUNDARY: walls are the image */
407
  vec3 wc=wallImg(x);
408
  float L=0.0; for(int k=0;k<nWell;k++){
409
  L+=wells[k].w*mGain/(length(x-wells[k].xyz)+1.0);}
@@ -412,8 +420,8 @@ void main(){
412
  } else { /* PROJECTION / CASCADE: walls mirror */
413
  vec3 n=wallNormal(x);
414
  rd=reflect(rd,n); tint*=0.85; bnc++;
415
- if(bnc>MAXB){ col=(mode==2)?accum:vec3(0.0); done=true; break; }
416
- for(int j=0;j<10;j++){ if(-worldSD(x)>0.05)break; x+=n*0.05; }
417
  x+=rd*0.04; stp=0.05; continue;
418
  }
419
  }
@@ -493,8 +501,17 @@ function rng(i,salt){
493
  t^=t+Math.imul(t^(t>>>7),t|61);
494
  return ((t^(t>>>14))>>>0)/4294967296;
495
  }
496
- const SHAPENAMES=['CUBE','SPHERE','HEX PRISM','STAR','OCTAHEDRON','CYLINDER',
497
- 'CAPSULE VAULT','CUT CRYSTAL','DOME','CROSS CATHEDRAL','GEAR','WHEEL'];
 
 
 
 
 
 
 
 
 
498
  const SYL=['KA','RA','JA','PIN','TA','VUO','LU','MI','SA','TÄH','TI','KI','VI','HEL','ME','NU','SO','LO'];
499
 
500
  /* ------- level geometry: depth(L) rooms per level, cumulative starts ------- */
@@ -514,23 +531,40 @@ function nMoonsOf(L){return Math.min(3+((L-1)>>1),5);}
514
 
515
  function roomDef(i){
516
  const L=levelOfRoom(i);
517
- const pool=Math.min(6+Math.max(L-1,0),12); /* shapes unlock with depth */
518
- const shape=Math.floor(rng(i,1)*pool);
 
 
 
519
  const size=7.0+rng(i,2)*4.0+Math.min(L*0.15,3.0);
520
  const height=4.5+rng(i,3)*2.5;
521
- const starN=5+Math.floor(rng(i,4)*3); /* 5, 6 or 7 points */
 
 
 
 
522
  const hue=rng(i,5);
523
- const turn=(rng(i,6)-0.5)*2.4; /* gate heading vs. entry, +-69 deg */
524
- const twist=(rng(i,8)-0.5)*2.0*Math.min(0.015+L*0.011,0.13);
525
- const ripA=Math.min(Math.max(L-2,0)*0.06,0.45)*rng(i,9);
526
  const ripF=0.5+rng(i,10)*1.2;
 
 
 
 
 
 
 
 
 
 
527
  const massK=Math.min(0.7+rng(i,11)*0.9+L*0.04,2.8);
528
  let name='';const nc=2+Math.floor(rng(i,7)*2);
529
  for(let k=0;k<nc;k++)name+=SYL[Math.floor(rng(i,20+k)*SYL.length)];
530
  const tint=[0.86+0.14*Math.cos(6.283*hue),
531
  0.86+0.14*Math.cos(6.283*(hue+0.33)),
532
  0.86+0.14*Math.cos(6.283*(hue+0.66))];
533
- return {shape,size,height,starN,hue,turn,name,tint,twist,ripA,ripF,massK,level:L};
534
  }
535
  function moonDef(i,m){ /* moon m of room i — re-dreamed identically every visit */
536
  const s=100+m*13;
@@ -566,7 +600,9 @@ function rebuildChain(){
566
  dirOut
567
  };
568
  $('roomV').textContent=dCur.name;
569
- $('roomI').textContent='#'+CI+' · '+(dCur.shape===3?'STAR-'+dCur.starN:SHAPENAMES[dCur.shape]);
 
 
570
  $('massV').textContent=dCur.massK.toFixed(2)+' · '+(dCur.twist>=0?'+':'')+dCur.twist.toFixed(3);
571
  updateLevelHud();
572
  }
@@ -609,70 +645,22 @@ function retreat(){ /* stepped back — re-dream the past */
609
  }
610
 
611
  /* ---------------- JS twins of the SDFs (collision + transitions) ---------- */
612
- function sdBoxJ(px,py,pz,bx,by,bz){
613
- const qx=Math.abs(px)-bx,qy=Math.abs(py)-by,qz=Math.abs(pz)-bz;
614
- return Math.hypot(Math.max(qx,0),Math.max(qy,0),Math.max(qz,0))
615
- +Math.min(Math.max(qx,Math.max(qy,qz)),0);}
616
- function sdHexJ(px,py,pz,h1,h2){
617
- const kx=-0.8660254,ky=0.5,kz=0.57735;
618
- px=Math.abs(px);py=Math.abs(py);pz=Math.abs(pz);
619
- const d0=Math.min(kx*px+ky*py,0)*2;
620
- px-=d0*kx;py-=d0*ky;
621
- const cl=Math.min(Math.max(px,-kz*h1),kz*h1);
622
- const dx=Math.hypot(px-cl,py-h1)*Math.sign(py-h1);
623
- const dy=pz-h2;
624
- return Math.min(Math.max(dx,dy),0)+Math.hypot(Math.max(dx,0),Math.max(dy,0));}
625
- function sdStarNJ(px,py,r,n,m){
626
- const an=Math.PI/n,en=Math.PI/m;
627
- const ax=Math.cos(an),ay=Math.sin(an),ex=Math.cos(en),ey=Math.sin(en);
628
- let bn=Math.atan2(px,py)%(2*an); if(bn<0)bn+=2*an; bn-=an;
629
- const l=Math.hypot(px,py);
630
- px=l*Math.cos(bn); py=l*Math.abs(Math.sin(bn));
631
- px-=r*ax; py-=r*ay;
632
- const cl=Math.min(Math.max(-(px*ex+py*ey),0),r*ay/ey);
633
- px+=ex*cl; py+=ey*cl;
634
- return Math.hypot(px,py)*Math.sign(px);}
635
- function opExtrJ(d2,z,h){const wy=Math.abs(z)-h;
636
- return Math.min(Math.max(d2,wy),0)+Math.hypot(Math.max(d2,0),Math.max(wy,0));}
637
- function sdSegJ(px,py,pz,a,b){
638
- const bax=b[0]-a[0],bay=b[1]-a[1],baz=b[2]-a[2];
639
- const pax=px-a[0],pay=py-a[1],paz=pz-a[2];
640
- const h=Math.min(Math.max((pax*bax+pay*bay+paz*baz)/(bax*bax+bay*bay+baz*baz),0),1);
641
- return Math.hypot(pax-bax*h,pay-bay*h,paz-baz*h);}
642
- function sdSegOJ(px,py,pz,ax,ay,az,bx,by,bz){
643
- const bax=bx-ax,bay=by-ay,baz=bz-az;
644
- const pax=px-ax,pay=py-ay,paz=pz-az;
645
- const h=Math.min(Math.max((pax*bax+pay*bay+paz*baz)/(bax*bax+bay*bay+baz*baz),0),1);
646
- return Math.hypot(pax-bax*h,pay-bay*h,paz-baz*h);}
647
  function shapeSDJ(x,y,z,d){
648
- const s=d.size,h=d.height;
649
- switch(d.shape){
650
- case 0:return sdBoxJ(x,y,z,s,h,s);
651
- case 1:return Math.hypot(x,y,z)-s;
652
- case 2:return sdHexJ(x,z,y,s,h);
653
- case 3:{const m=2.0+(d.starN-2.0)*0.45;
654
- return opExtrJ(sdStarNJ(x,z,s*1.15,d.starN,m),y,h);}
655
- case 4:return (Math.abs(x)+Math.abs(y)+Math.abs(z)-s*1.5)*0.57735;
656
- case 5:return Math.max(Math.hypot(x,z)-s,Math.abs(y)-h);
657
- case 6:{const cy=y-Math.min(Math.max(y,-h*0.55),h*0.55);
658
- return Math.hypot(x,cy,z)-s*0.90;}
659
- case 7:return Math.max(sdBoxJ(x,y,z,s*0.95,s*0.95,s*0.95),
660
- (Math.abs(x)+Math.abs(y)+Math.abs(z)-s*1.45)*0.57735);
661
- case 8:return (Math.hypot(x,y*1.35,z)-s*1.08)*0.72;
662
- case 9:return Math.min(sdBoxJ(x,y,z,s,h*0.55,s*0.38),
663
- Math.min(sdBoxJ(x,y,z,s*0.38,h*0.55,s),
664
- sdBoxJ(x,y,z,s*0.45,h,s*0.45)));
665
- case 10:{const a=Math.atan2(z,x);
666
- const rr=s*(0.88+0.11*Math.cos(a*d.starN*2.0));
667
- return Math.max(Math.hypot(x,z)-rr,Math.abs(y)-h)*0.68;}
668
- default:{const tor=Math.hypot(Math.hypot(x,z)-s*0.72,y)-s*0.40;
669
- const hub=Math.hypot(x,y,z)-s*0.40;
670
- let sp=Math.min(sdSegOJ(x,y,z,0,0,0,s*0.72,0,0),
671
- sdSegOJ(x,y,z,0,0,0,-s*0.72,0,0));
672
- sp=Math.min(sp,Math.min(sdSegOJ(x,y,z,0,0,0,0,0,s*0.72),
673
- sdSegOJ(x,y,z,0,0,0,0,0,-s*0.72)))-s*0.17;
674
- return Math.min(tor,Math.min(hub,sp));}
675
  }
 
676
  }
677
  function roomSDJ(px,py,pz,r){
678
  const d=R.def[r],c=R.pos[r];
@@ -697,7 +685,8 @@ let P={mGain:1.0,lensK:1.0},mode=0,idle=1;
697
  const wellsF=new Float32Array(MAXW*4), colsF=new Float32Array(MAXW*3),
698
  parF=new Float32Array(MAXW*4),
699
  roomPosF=new Float32Array(9), roomParF=new Float32Array(12),
700
- roomModF=new Float32Array(12), roomTintF=new Float32Array(9);
 
701
  function moonCol(h){
702
  return[0.55+0.45*Math.cos(6.283*h),0.55+0.45*Math.cos(6.283*(h+0.33)),0.55+0.45*Math.cos(6.283*(h+0.66))];}
703
  let keyWell=-1, keyPos=null, keyR=0;
@@ -708,8 +697,9 @@ function packWorld(t){
708
  for(let r=0;r<3;r++){
709
  const d=R.def[r],c=R.pos[r];
710
  roomPosF[r*3]=c[0];roomPosF[r*3+1]=c[1];roomPosF[r*3+2]=c[2];
711
- roomParF[r*4]=d.shape;roomParF[r*4+1]=d.size;roomParF[r*4+2]=d.height;roomParF[r*4+3]=d.starN;
712
  roomModF[r*4]=d.twist;roomModF[r*4+1]=d.ripA;roomModF[r*4+2]=d.ripF;roomModF[r*4+3]=d.massK;
 
713
  roomTintF[r*3]=d.tint[0];roomTintF[r*3+1]=d.tint[1];roomTintF[r*3+2]=d.tint[2];
714
  const A=d.size*0.62, Ay=d.height*0.55;
715
  for(let m=0;m<nM && w<MAXW;m++,w++){
@@ -727,7 +717,7 @@ function packWorld(t){
727
  wellsF[w*4+3]=size;
728
  const cc=isKey?[1.0,0.84,0.36]:moonCol(md.hue);
729
  colsF[w*3]=cc[0];colsF[w*3+1]=cc[1];colsF[w*3+2]=cc[2];
730
- parF[w*4]=d.shape;parF[w*4+1]=md.ra*t+md.pa;parF[w*4+2]=md.rb*t+md.pb;parF[w*4+3]=d.starN;
731
  if(isKey){keyWell=w;keyPos=[wellsF[w*4],wellsF[w*4+1],wellsF[w*4+2]];keyR=size;}
732
  }
733
  }
@@ -844,13 +834,14 @@ $('bLoad').onclick=()=>{
844
 
845
  /* ---------------- controls ---------------- */
846
  const MODES=[
847
- {name:'Boundary', note:'the walls are your camera. the moons are optical crystals the room bends and refocuses through every one of them.'},
848
  {name:'Projection', note:'the moons carry your face; every wall is a mirror. the image emanates from the bulk — the holographic swap.'},
849
- {name:'Cascade', note:'heavy glass: Fresnel rims wear your face, the ray refracts through and keeps going, tinted by each crystal it crosses.'},
 
850
  ];
851
  function setMode(mi){
852
  mode=mi;
853
- ['m0','m1','m2'].forEach((id,i)=>$(id).classList.toggle('on',i===mi));
854
  $('modeV').textContent=MODES[mi].name;
855
  $('modeNote').textContent=MODES[mi].note;
856
  }
@@ -862,6 +853,7 @@ bind('v','v_l',v=>{if(audio)audio.volume=v;});
862
  $('m0').onclick=()=>setMode(0);
863
  $('m1').onclick=()=>setMode(1);
864
  $('m2').onclick=()=>setMode(2);
 
865
  $('bSrc').onclick=()=>{useCam=!useCam;setSrcLabel();};
866
  $('bSeed').onclick=()=>{ SEED=(Math.random()*4294967296)>>>0;
867
  LEVEL=1;maxReached=1;keyFound=false;CI=0;
@@ -929,9 +921,14 @@ function compileAndStart(){
929
  }
930
  gl.useProgram(prog);
931
  ['iRes','iTime','camP','camR','camU','camF','nWell','mode','keyWell','mGain','lensK','camTex',
932
- 'wells','wellCol','wellPar','roomPos','roomPar','roomMod','roomTintU','seal']
 
933
  .forEach(k=>U[k]=gl.getUniformLocation(prog,k));
934
  gl.uniform1i(U.camTex,0);
 
 
 
 
935
  $('bootStat').textContent='ready — click to enter';
936
  requestAnimationFrame(loop);
937
  }
@@ -996,6 +993,7 @@ function loop(now){
996
  gl.uniform3fv(U.roomPos,roomPosF);
997
  gl.uniform4fv(U.roomPar,roomParF);
998
  gl.uniform4fv(U.roomMod,roomModF);
 
999
  gl.uniform3fv(U.roomTintU,roomTintF);
1000
  gl.uniform4f(U.seal,sealV[0],sealV[1],sealV[2],sealV[3]);
1001
  gl.drawArrays(gl.TRIANGLES,0,3);
 
88
  the <em>resonance</em> meter runs hot as you near the key. no enemies, no clocks —<br>
89
  only geometry, depth, and the hash. nothing is stored; walk back and every<br>
90
  room is re-dreamed identically. click / drag to enter · mouse look is inverted.<br><br>
91
+ <em>first launch:</em> the GPU driver compiles the shader once, then caches it.<br>
92
+ the world is now ONE branchless shape kernel this should take seconds, not<br>
93
+ minutes. the counter below is honest elapsed time either way.</div>
 
94
  <div id="bootStat">waking up…</div>
95
  </div>
96
 
 
119
  <button id="m0" class="on">Boundary</button>
120
  <button id="m1">Projection</button>
121
  <button id="m2">Cascade</button>
122
+ <button id="m3">Glass</button>
123
  </div>
124
  <div class="field"><label>Mass gain <var id="m_l">1.0</var></label>
125
  <input id="m" type="range" min="0.2" max="2.2" step="0.05" value="1.0"></div>
 
184
  uniform float iTime;
185
  uniform vec3 camP,camR,camU,camF;
186
  uniform int nWell,mode,keyWell;
187
+ /* loop bounds as UNIFORMS: the D3D/ANGLE compiler cannot know their value,
188
+ so it cannot unroll the raymarch 180-wide (x26 for the glass chord) —
189
+ it must compile each loop body ONCE. This is the first-launch fix. */
190
+ uniform int marchN,glassN,escN;
191
  uniform vec4 wells[${MAXW}]; /* xyz pos · w size */
192
  uniform vec3 wellCol[${MAXW}];
193
+ uniform vec4 wellPar[${MAXW}]; /* x spin-xz · y spin-xy · z room index · w free */
194
  uniform vec3 roomPos[3]; /* prev · current · next */
195
+ uniform vec4 roomPar[3]; /* x size · y p-xz · z p-vert · w squash */
196
+ uniform vec4 roomMod[3]; /* x twist · y breath amp · z breath freq · w mass */
197
+ uniform vec4 roomShp[3]; /* x ripple amp · y lobes · z ring blend · w free */
198
  uniform vec3 roomTintU[3];
199
  uniform vec4 seal; /* xyz pos · w radius (<0 = open) */
200
  uniform float mGain,lensK;
 
202
 
203
  /* ---------- primitives ---------- */
204
  vec2 rot2(vec2 v,float a){float c=cos(a),s=sin(a);return vec2(c*v.x-s*v.y,s*v.x+c*v.y);}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
205
  float sdSeg(vec3 p,vec3 a,vec3 b){vec3 pa=p-a,ba=b-a;
206
  float h=clamp(dot(pa,ba)/dot(ba,ba),0.0,1.0);return length(pa-ba*h);}
207
 
208
+ /* ---------- THE ONE SHAPE the Aeon Forge move applied to geometry.
209
+ Shape is a PARAMETER VECTOR drawn from the hash, not a branch:
210
+ px cross-section exponent (2 = circle -> 8 = square)
211
+ pv profile exponent (2 = ball -> 8 = prism)
212
+ hy squash ratio (height / size)
213
+ aa · an angular ripple amp · lobes (gears, stars)
214
+ tw torus blend (0 = solid -> 1 = ring)
215
+ One branchless formula: compiles in a blink, and rooms MORPH
216
+ continuously with depth instead of unlocking discrete shapes.
217
+ Conservative distance: p-norms with p>=2 are 1-Lipschitz, mix of
218
+ 1-Lipschitz fields is 1-Lipschitz, ripple guarded by its own slope. */
219
+ float shapeSD(vec3 q,float s,float px,float pv,float hy,
220
+ float aa,float an,float tw){
221
+ float cx=pow(pow(abs(q.x)+1e-5,px)+pow(abs(q.z)+1e-5,px),1.0/px);
222
+ float lp=pow(pow(cx,pv)+pow(abs(q.y/hy)+1e-5,pv),1.0/pv);
223
+ float d=(lp-s)*min(hy,1.0);
224
+ if(tw>0.001){
225
+ float dR=length(vec2(length(q.xz)-s*0.72,q.y))-s*0.42;
226
+ d=mix(d,dR,tw);
227
+ }
228
+ if(aa>0.001){
229
+ float rr=length(q.xz);
230
+ float w=clamp(rr/(0.35*s),0.0,1.0); /* fade lobes near the axis:
231
+ kills the 1/r slope blow-up */
232
+ d-=s*aa*w*cos(an*atan(q.z,q.x+1e-6));
233
+ }
234
+ return d/(1.0+aa*(an+1.0)*2.9); /* true slope bound of the faded ripple */
 
 
235
  }
236
  float roomSD(vec3 p,int r){
237
  vec3 q=p-roomPos[r];
238
  float tw=roomMod[r].x;
239
  if(abs(tw)>0.0005) q.xz=rot2(q.xz,q.y*tw);
240
+ float d=shapeSD(q,roomPar[r].x,roomPar[r].y,roomPar[r].z,roomPar[r].w,
241
+ roomShp[r].x,roomShp[r].y,roomShp[r].z);
242
  float ra=roomMod[r].y;
243
  if(ra>0.001) d+=ra*sin(q.x*roomMod[r].z)*sin(q.y*roomMod[r].z)*sin(q.z*roomMod[r].z);
244
+ return d/(1.0+abs(tw)*roomPar[r].x*0.6+ra*roomMod[r].z); /* Lipschitz guard */
245
  }
246
  float worldSD(vec3 p){
247
  float d=min(roomSD(p,0),min(roomSD(p,1),roomSD(p,2)));
 
257
  e.xyy*worldSD(p+e.xyy)+e.yyx*worldSD(p+e.yyx)+
258
  e.yxy*worldSD(p+e.yxy)+e.xxx*worldSD(p+e.xxx));}
259
 
260
+ /* ---------- moons wear their room's shape the SAME kernel,
261
+ shrunk and rounded into optical glass. no tables, no branches. ---------- */
 
 
 
 
262
  float moonSD(vec3 p,int k){
263
  vec3 q=p-wells[k].xyz;
264
  float s=wells[k].w;
265
  float bs=length(q)-s*2.2;
266
  if(bs>0.6) return bs;
267
+ q.xz=rot2(q.xz,wellPar[k].x);
268
+ q.xy=rot2(q.xy,wellPar[k].y);
269
+ int ri=int(wellPar[k].z+0.5);
270
+ return shapeSD(q,s*0.62,roomPar[ri].y,roomPar[ri].z,roomPar[ri].w,
271
+ roomShp[ri].x,roomShp[ri].y,roomShp[ri].z)-s*0.10;
272
  }
273
  vec3 moonNormal(vec3 p,int k){
274
  const vec2 e=vec2(0.012,-0.012);
 
297
  vec3 gA0=mix(roomPos[0],roomPos[1],0.28), gB0=mix(roomPos[0],roomPos[1],0.72);
298
  vec3 gA1=mix(roomPos[1],roomPos[2],0.28), gB1=mix(roomPos[1],roomPos[2],0.72);
299
 
300
+ for(int i=0;i<marchN;i++){
301
  /* lensing: deflection per path length, dtheta ~ gradPhi * ds */
302
  if(lensK>0.001){
303
  vec3 pull=vec3(0.0);
 
349
  L+=wells[k].w*mGain/(length(x-wells[k].xyz)+1.0);}
350
  col=tint*eqSample(radial)*(0.55+0.7*min(L,1.4)); done=true; break;
351
  }
352
+ if(mode==2){
353
+ /* CASCADE: the inspiration.html magic, verbatim in spirit.
354
+ The ray NEVER enters the crystal. It deposits a translucent
355
+ layer of the boundary image (hologram accumulation), then
356
+ bounces off the sphere-blended normal and keeps flying —
357
+ moon to wall to moon. Recursion is capped at 4 like the
358
+ original: it LOOKS endless because the lensing fisheye
359
+ compounds every bounce. Cheap, so it can never TDR. */
360
+ accum+=tint*eqSample(radial)*0.5;
361
+ rd=reflect(rd,opticN);
362
+ tint*=0.62; bnc++;
363
+ if(bnc>4){col=accum;done=true;break;}
364
+ for(int j=0;j<escN;j++){ if(moonSD(x,hb)>0.05)break; x+=n*0.05; }
365
+ x+=rd*0.04; trav+=0.08; stp=0.05; continue;
366
+ }
367
+ if(mode==0){
368
+ /* BOUNDARY: the rajapinta_box move — moons are curved mirrors.
369
+ gravity already bent the ray on the way in; the surface just
370
+ reflects the boundary. your face drags where the moon passes. */
371
+ rd=reflect(rd,opticN);
372
+ tint*=0.82; bnc++;
373
+ if(bnc>4){col=vec3(0.0);done=true;break;}
374
+ for(int j=0;j<escN;j++){ if(moonSD(x,hb)>0.05)break; x+=n*0.05; }
375
+ x+=rd*0.04; trav+=0.08; stp=0.05; continue;
376
+ }
377
+ /* GLASS (mode 3): heavy optical crystal — the one bulk-transport
378
+ mode, kept as the honest-physics exhibit. Snell in, marched
379
+ chord, Beer-Lambert tint, Snell out. Affordable ONLY because
380
+ Glass rays terminate at the first (image) wall; in the
381
+ mirror-wall modes this march is what caused the TDR. */
382
  glow+=tint*eqSample(reflect(rd,opticN))*F*0.55; /* mirror glint, Fresnel-weighted */
383
+ bnc++; if(bnc>MAXB){col=vec3(0.0);done=true;break;}
 
384
  const float IOR=1.45;
385
  vec3 d=refract(rd,opticN,1.0/IOR);
386
  if(dot(d,d)<1e-4) d=reflect(rd,opticN); /* entry-grazing guard */
387
  x-=n*0.04; /* step inside the glass */
388
  float chord=0.0;
389
+ for(int j=0;j<glassN;j++){
390
  float sd=moonSD(x,hb);
391
  if(sd>-0.006){ /* reached the far surface */
392
  vec3 ne=moonNormal(x,hb);
 
407
  tint*=exp(-(vec3(1.0)-wellCol[hb])*chord*0.35)*0.96;
408
  rd=d;
409
  vec3 ne2=moonNormal(x,hb);
410
+ for(int j=0;j<escN;j++){ if(moonSD(x,hb)>0.05)break; x+=ne2*0.05; }
411
  x+=rd*0.04; trav+=chord+0.08; stp=0.05; continue;
412
  }
413
  if(dw<0.03){
414
+ if(mode==0||mode==3){ /* BOUNDARY & GLASS: walls are the image */
415
  vec3 wc=wallImg(x);
416
  float L=0.0; for(int k=0;k<nWell;k++){
417
  L+=wells[k].w*mGain/(length(x-wells[k].xyz)+1.0);}
 
420
  } else { /* PROJECTION / CASCADE: walls mirror */
421
  vec3 n=wallNormal(x);
422
  rd=reflect(rd,n); tint*=0.85; bnc++;
423
+ if(bnc>4){ col=(mode==2)?accum:vec3(0.0); done=true; break; } /* inspiration's maxBnc=4 */
424
+ for(int j=0;j<escN;j++){ if(-worldSD(x)>0.05)break; x+=n*0.05; }
425
  x+=rd*0.04; stp=0.05; continue;
426
  }
427
  }
 
501
  t^=t+Math.imul(t^(t>>>7),t|61);
502
  return ((t^(t>>>14))>>>0)/4294967296;
503
  }
504
+ /* shape is continuous now — names are nearest archetypes, for the HUD */
505
+ function shapeName(d){
506
+ if(d.tw>0.30)return 'RING';
507
+ if(d.aa>0.05)return (d.an>=8?'GEAR-':'STAR-')+d.an;
508
+ const bx=d.px>4.5, pr=d.pv>4.5;
509
+ if(bx&&pr)return 'CUBE';
510
+ if(!bx&&pr)return 'CYLINDER';
511
+ if(bx&&!pr)return 'CUSHION';
512
+ if(d.hy<0.62)return 'DOME';
513
+ return 'ORB';
514
+ }
515
  const SYL=['KA','RA','JA','PIN','TA','VUO','LU','MI','SA','TÄH','TI','KI','VI','HEL','ME','NU','SO','LO'];
516
 
517
  /* ------- level geometry: depth(L) rooms per level, cumulative starts ------- */
 
531
 
532
  function roomDef(i){
533
  const L=levelOfRoom(i);
534
+ /* the Aeon Forge move: depth widens the PARAMETER RANGES of one kernel —
535
+ rooms morph continuously with the descent instead of unlocking shapes */
536
+ const spread=Math.min(1.2+L*0.8,6.0);
537
+ const px=2.0+rng(i,1)*spread; /* 2 circle -> 8 square */
538
+ const pv=2.0+rng(i,4)*spread; /* 2 ball -> 8 prism */
539
  const size=7.0+rng(i,2)*4.0+Math.min(L*0.15,3.0);
540
  const height=4.5+rng(i,3)*2.5;
541
+ const hy=Math.min(Math.max(height/size,0.45),1.0);
542
+ const an=4+Math.floor(rng(i,13)*9); /* 4..12 lobes */
543
+ let aa=L>=2?rng(i,12)*rng(i,12)*Math.min(0.02+L*0.013,0.12):0.0; /* lobes amp */
544
+ aa=Math.min(aa,0.5/an); /* slope budget: aa*an <= 0.5 */
545
+ let tw=L>=4?Math.pow(rng(i,14),1.5)*Math.min((L-3)*0.10,0.55):0.0; /* ring blend */
546
  const hue=rng(i,5);
547
+ const turn=(rng(i,6)-0.5)*2.4; /* gate heading vs. entry */
548
+ let twist=(rng(i,8)-0.5)*2.0*Math.min(0.015+L*0.011,0.13);
549
+ let ripA=Math.min(Math.max(L-2,0)*0.06,0.45)*rng(i,9);
550
  const ripF=0.5+rng(i,10)*1.2;
551
+ /* GUARANTEE the room centre is deep interior (transition fires at -1.0).
552
+ Pure function of the drawn values, so still deterministic per (SEED,i).
553
+ Lobes are zero at the axis (faded), so they don't enter the formula. */
554
+ const cDepth=()=>((-(size*hy)*(1-tw)+size*0.30*tw)
555
+ /(1.0+aa*(an+1.0)*2.9)
556
+ /(1.0+Math.abs(twist)*size*0.6+ripA*ripF));
557
+ for(let g=0;g<10 && cDepth()>-1.3;g++){
558
+ tw*=0.7; aa*=0.7;
559
+ if(g>2){twist*=0.75; ripA*=0.75;}
560
+ }
561
  const massK=Math.min(0.7+rng(i,11)*0.9+L*0.04,2.8);
562
  let name='';const nc=2+Math.floor(rng(i,7)*2);
563
  for(let k=0;k<nc;k++)name+=SYL[Math.floor(rng(i,20+k)*SYL.length)];
564
  const tint=[0.86+0.14*Math.cos(6.283*hue),
565
  0.86+0.14*Math.cos(6.283*(hue+0.33)),
566
  0.86+0.14*Math.cos(6.283*(hue+0.66))];
567
+ return {px,pv,hy,aa,an,tw,size,height,hue,turn,name,tint,twist,ripA,ripF,massK,level:L};
568
  }
569
  function moonDef(i,m){ /* moon m of room i — re-dreamed identically every visit */
570
  const s=100+m*13;
 
600
  dirOut
601
  };
602
  $('roomV').textContent=dCur.name;
603
+ $('roomI').textContent='#'+CI+' · '+shapeName(dCur)
604
+ +' · p'+dCur.px.toFixed(1)+'/'+dCur.pv.toFixed(1)
605
+ +(dCur.tw>0.01?' · ring '+dCur.tw.toFixed(2):'');
606
  $('massV').textContent=dCur.massK.toFixed(2)+' · '+(dCur.twist>=0?'+':'')+dCur.twist.toFixed(3);
607
  updateLevelHud();
608
  }
 
645
  }
646
 
647
  /* ---------------- JS twins of the SDFs (collision + transitions) ---------- */
648
+ /* the SAME kernel, mirrored exactly for collision and gate transitions */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
649
  function shapeSDJ(x,y,z,d){
650
+ const s=d.size;
651
+ const cx=Math.pow(Math.pow(Math.abs(x)+1e-5,d.px)+Math.pow(Math.abs(z)+1e-5,d.px),1/d.px);
652
+ const lp=Math.pow(Math.pow(cx,d.pv)+Math.pow(Math.abs(y/d.hy)+1e-5,d.pv),1/d.pv);
653
+ let sd=(lp-s)*Math.min(d.hy,1.0);
654
+ if(d.tw>0.001){
655
+ const dR=Math.hypot(Math.hypot(x,z)-s*0.72,y)-s*0.42;
656
+ sd=sd+(dR-sd)*d.tw;
657
+ }
658
+ if(d.aa>0.001){
659
+ const rr=Math.hypot(x,z);
660
+ const w=Math.min(Math.max(rr/(0.35*s),0),1);
661
+ sd-=s*d.aa*w*Math.cos(d.an*Math.atan2(z,x+1e-6));
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
662
  }
663
+ return sd/(1.0+d.aa*(d.an+1.0)*2.9);
664
  }
665
  function roomSDJ(px,py,pz,r){
666
  const d=R.def[r],c=R.pos[r];
 
685
  const wellsF=new Float32Array(MAXW*4), colsF=new Float32Array(MAXW*3),
686
  parF=new Float32Array(MAXW*4),
687
  roomPosF=new Float32Array(9), roomParF=new Float32Array(12),
688
+ roomModF=new Float32Array(12), roomShpF=new Float32Array(12),
689
+ roomTintF=new Float32Array(9);
690
  function moonCol(h){
691
  return[0.55+0.45*Math.cos(6.283*h),0.55+0.45*Math.cos(6.283*(h+0.33)),0.55+0.45*Math.cos(6.283*(h+0.66))];}
692
  let keyWell=-1, keyPos=null, keyR=0;
 
697
  for(let r=0;r<3;r++){
698
  const d=R.def[r],c=R.pos[r];
699
  roomPosF[r*3]=c[0];roomPosF[r*3+1]=c[1];roomPosF[r*3+2]=c[2];
700
+ roomParF[r*4]=d.size;roomParF[r*4+1]=d.px;roomParF[r*4+2]=d.pv;roomParF[r*4+3]=d.hy;
701
  roomModF[r*4]=d.twist;roomModF[r*4+1]=d.ripA;roomModF[r*4+2]=d.ripF;roomModF[r*4+3]=d.massK;
702
+ roomShpF[r*4]=d.aa;roomShpF[r*4+1]=d.an;roomShpF[r*4+2]=d.tw;roomShpF[r*4+3]=0;
703
  roomTintF[r*3]=d.tint[0];roomTintF[r*3+1]=d.tint[1];roomTintF[r*3+2]=d.tint[2];
704
  const A=d.size*0.62, Ay=d.height*0.55;
705
  for(let m=0;m<nM && w<MAXW;m++,w++){
 
717
  wellsF[w*4+3]=size;
718
  const cc=isKey?[1.0,0.84,0.36]:moonCol(md.hue);
719
  colsF[w*3]=cc[0];colsF[w*3+1]=cc[1];colsF[w*3+2]=cc[2];
720
+ parF[w*4]=md.ra*t+md.pa;parF[w*4+1]=md.rb*t+md.pb;parF[w*4+2]=r;parF[w*4+3]=0;
721
  if(isKey){keyWell=w;keyPos=[wellsF[w*4],wellsF[w*4+1],wellsF[w*4+2]];keyR=size;}
722
  }
723
  }
 
834
 
835
  /* ---------------- controls ---------------- */
836
  const MODES=[
837
+ {name:'Boundary', note:'the walls are your camera; the moons are curved mirrors that lens and reflect them your face drags where a moon passes.'},
838
  {name:'Projection', note:'the moons carry your face; every wall is a mirror. the image emanates from the bulk — the holographic swap.'},
839
+ {name:'Cascade', note:'every crystal wears your face and mirrors its neighbours worlds inside worlds, moon to wall to moon, built entirely from you.'},
840
+ {name:'Glass', note:'heavy optics: the ray refracts through each crystal (Snell + Beer-Lambert) and keeps flying — the honest-physics exhibit.'},
841
  ];
842
  function setMode(mi){
843
  mode=mi;
844
+ ['m0','m1','m2','m3'].forEach((id,i)=>$(id).classList.toggle('on',i===mi));
845
  $('modeV').textContent=MODES[mi].name;
846
  $('modeNote').textContent=MODES[mi].note;
847
  }
 
853
  $('m0').onclick=()=>setMode(0);
854
  $('m1').onclick=()=>setMode(1);
855
  $('m2').onclick=()=>setMode(2);
856
+ $('m3').onclick=()=>setMode(3);
857
  $('bSrc').onclick=()=>{useCam=!useCam;setSrcLabel();};
858
  $('bSeed').onclick=()=>{ SEED=(Math.random()*4294967296)>>>0;
859
  LEVEL=1;maxReached=1;keyFound=false;CI=0;
 
921
  }
922
  gl.useProgram(prog);
923
  ['iRes','iTime','camP','camR','camU','camF','nWell','mode','keyWell','mGain','lensK','camTex',
924
+ 'marchN','glassN','escN',
925
+ 'wells','wellCol','wellPar','roomPos','roomPar','roomMod','roomShp','roomTintU','seal']
926
  .forEach(k=>U[k]=gl.getUniformLocation(prog,k));
927
  gl.uniform1i(U.camTex,0);
928
+ /* loop bounds — same values as the old constants, now opaque to the compiler */
929
+ gl.uniform1i(U.marchN,180);
930
+ gl.uniform1i(U.glassN,26);
931
+ gl.uniform1i(U.escN,10);
932
  $('bootStat').textContent='ready — click to enter';
933
  requestAnimationFrame(loop);
934
  }
 
993
  gl.uniform3fv(U.roomPos,roomPosF);
994
  gl.uniform4fv(U.roomPar,roomParF);
995
  gl.uniform4fv(U.roomMod,roomModF);
996
+ gl.uniform4fv(U.roomShp,roomShpF);
997
  gl.uniform3fv(U.roomTintU,roomTintF);
998
  gl.uniform4f(U.seal,sealV[0],sealV[1],sealV[2],sealV[3]);
999
  gl.drawArrays(gl.TRIANGLES,0,3);