{"asset_map": {"bgm_harbor": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/sounds/music/bgm_harbor_5b5673a2-1c3c-44be-ade0-25796a8f381b.mp3", "type": "audio", "prompt": "Polished seamless-loop instrumental background music for a cheerful cartoon 3D restaurant tycoon set in a snowy farm village. Cozy upbeat kitchen bustle vibe, 105 BPM, C major. Features playful pizzicato strings, warm acoustic guitar, and a light marimba melody. Includes rounded acoustic bass and soft brushed percussion with occasional delicate glockenspiel bells for winter warmth. The composition is catchy but relaxed and unobtrusive for long gameplay sessions; balanced gentle dynamics, no vocals, no sound effects, no dramatic intro or ending. High-quality production, warm mix, suitable for continuous looping.", "description": "Cheerful and cozy background music for a snowy farm village restaurant tycoon.", "subtype": "music", "aspect_ratio": null, "compression_suggestion": "original", "duration_secs": 60, "loop": true, "opus_url": "https://d2oir5eh8rty2e.cloudfront.net/assets/sounds/music/bgm_harbor_5b5673a2-1c3c-44be-ade0-25796a8f381b.ogg", "animation": null}}, "game_code": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no\">\n<title>Giant Chicken Fry</title>\n<style>\nhtml,body{margin:0;height:100%;overflow:hidden;background:#dfe7f4;touch-action:none;-webkit-user-select:none;user-select:none;font-family:\"Arial Rounded MT Bold\",\"Arial Black\",Arial,sans-serif}\ncanvas{display:block}\n#hud{position:fixed;left:10px;top:10px;display:flex;align-items:center;gap:6px;background:rgba(20,40,60,.55);color:#fff;border-radius:18px;padding:4px 12px 4px 6px;font-size:20px;font-weight:900;pointer-events:none;text-shadow:0 2px 0 rgba(0,0,0,.35)}\n#hud .bill{width:26px;height:16px;border-radius:3px;background:#3fcf4a;box-shadow:inset 0 0 0 2px #2a9b33;position:relative}\n#hud .bill:after{content:\"$\";position:absolute;left:8px;top:-3px;font-size:13px;color:#e9ffe9}\n#joy{position:fixed;width:96px;height:96px;margin:-48px 0 0 -48px;border-radius:50%;background:rgba(255,255,255,.18);border:3px solid rgba(255,255,255,.55);display:none;pointer-events:none}\n#knob{position:absolute;left:50%;top:50%;width:44px;height:44px;margin:-22px 0 0 -22px;border-radius:50%;background:rgba(255,255,255,.85)}\n#banner{position:fixed;left:50%;top:27%;transform:translate(-50%,-50%) scale(.6);opacity:0;transition:all .35s cubic-bezier(.2,1.6,.4,1);color:#fff;font-size:28px;font-weight:900;text-align:center;text-shadow:0 3px 0 #1d4f7a,0 0 12px rgba(0,0,0,.25);pointer-events:none;white-space:nowrap}\n#banner.on{opacity:1;transform:translate(-50%,-50%) scale(1)}\n#hint{position:fixed;left:50%;top:70%;transform:translate(-50%,0);color:#fff;font-size:20px;font-weight:900;text-shadow:0 2px 0 rgba(0,0,0,.45);pointer-events:none;transition:opacity .5s;white-space:nowrap;text-align:center}\n#hint .ring{width:110px;height:110px;margin:0 auto 6px;border-radius:50%;background:rgba(255,255,255,.18);border:4px solid rgba(255,255,255,.85);position:relative;box-shadow:0 3px 10px rgba(0,0,0,.25)}\n#hint .dot{position:absolute;left:50%;top:50%;width:44px;height:44px;margin:-22px 0 0 -22px;border-radius:50%;background:#fff;box-shadow:0 2px 6px rgba(0,0,0,.3);animation:hintdrag 1.6s ease-in-out infinite}\n@keyframes hintdrag{0%{transform:translate(0,0)}25%{transform:translate(0,-30px)}50%{transform:translate(28px,-16px)}75%{transform:translate(-28px,-16px)}100%{transform:translate(0,0)}}\n</style>\n</head>\n<body>\n<div id=\"hud\"><div class=\"bill\"></div><span id=\"cash\">0</span></div>\n<div id=\"joy\"><div id=\"knob\"></div></div>\n<div id=\"banner\"></div>\n<div id=\"hint\"><div class=\"ring\"><div class=\"dot\"></div></div>DRAG TO MOVE</div>\n<script src=\"https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js\"></script>\n<script>/* PERFKIT v1 (2026-10-26) \u2014 mobile frame-rate guard, same approach as Tulip Rush (Median FPS: Optimal).\n   Wraps THREE.WebGLRenderer before the game creates it:\n   - touch devices: no MSAA, pixel ratio capped at 1.25, PCF (not soft) shadows;\n   - adaptive: every ~1.2 s of play, if the average frame takes longer than 1/34 s twice in a row, drop one stage:\n     stage 1 = pixel ratio 1.0, stage 2 = pixel ratio 0.85 + shadow map refreshed every 3rd frame. */\n(function(){\n  if(!window.THREE||!THREE.WebGLRenderer||THREE.WebGLRenderer.__pk) return;\n  var Orig=THREE.WebGLRenderer;\n  var TOUCH=!!(window.matchMedia&&window.matchMedia('(pointer: coarse)').matches);\n  var Q=/[?&]pk=([\\d.]+)/.exec(location.search);\n  function PK(p){\n    p=Object.assign({},p||{}); if(TOUCH) p.antialias=false;\n    var r=new Orig(p), setPR=r.setPixelRatio.bind(r), want=window.devicePixelRatio||1;\n    r.__pkCap=Q?parseFloat(Q[1]):(TOUCH?1.25:99); r.__pkStage=0;\n    r.setPixelRatio=function(v){ want=v; return setPR(Math.min(v,r.__pkCap)); };\n    setPR(Math.min(want,r.__pkCap));\n    if(TOUCH){ var sm=r.shadowMap, ty=THREE.PCFShadowMap; Object.defineProperty(sm,'type',{get:function(){return ty;},set:function(v){ ty=(v===THREE.PCFSoftShadowMap)?THREE.PCFShadowMap:v; }}); }\n    var last=0,warm=0,acc=0,n=0,bad=0,fr=0;\n    function tick(t){\n      if(last&&!document.hidden){ var d=(t-last)/1000;\n        if(d>0&&d<0.5){ warm+=d; if(warm>2){ acc+=d; n++; if(acc>=1.2){ var avg=acc/n; acc=0; n=0; bad=avg>1/34?bad+1:0;\n          if(bad>=2&&r.__pkStage<2){ r.__pkStage++; bad=0; r.__pkCap=Math.min(r.__pkCap,r.__pkStage===1?1:0.85); setPR(Math.min(want,r.__pkCap));\n            if(r.__pkStage===2&&r.shadowMap){ r.shadowMap.autoUpdate=false; r.shadowMap.needsUpdate=true; } } } } } }\n      if(r.__pkStage===2&&r.shadowMap&&(++fr%3===0)) r.shadowMap.needsUpdate=true;\n      last=t; requestAnimationFrame(tick); }\n    requestAnimationFrame(tick);\n    window.__perfkit=r; return r;\n  }\n  PK.prototype=Orig.prototype; PK.__pk=1; THREE.WebGLRenderer=PK;\n})();\n</script>\n<script>\n(function(){\n'use strict';\nconst Q=new URLSearchParams(location.search);\nconst BOT=Q.has('bot'); const CAM_YAW=Math.PI*(+(Q.get('yaw')||1.1)); const NOHIRE=Q.has('nohire');\nlet seed=1234567;\nfunction rnd(){ seed|=0; seed=seed+0x6D2B79F5|0; let t=Math.imul(seed^seed>>>15,1|seed); t=t+Math.imul(t^t>>>7,61|t)^t; return ((t^t>>>14)>>>0)/4294967296; }\nconst R=(a,b)=>a+(b-a)*rnd();\nconst clamp=(v,a,b)=>v<a?a:v>b?b:v, lerp=(a,b,t)=>a+(b-a)*t, ease=t=>t<0?0:t>1?1:t*t*(3-2*t);\nconst V3=THREE.Vector3;\nconst near=(a,b,r)=>{ const dx=a.x-b.x, dz=a.z-b.z; return dx*dx+dz*dz<r*r; };\nfunction canvasTex(w,h,draw,repeat){ const c=document.createElement('canvas'); c.width=w; c.height=h; const g=c.getContext('2d'); draw(g,w,h); const t=new THREE.CanvasTexture(c); t.anisotropy=4; if(repeat){ t.wrapS=t.wrapT=THREE.RepeatWrapping; t.repeat.set(repeat[0],repeat[1]); } t._g=g; return t; }\nfunction roundRect(g,x,y,w,h,r){ g.beginPath(); g.moveTo(x+r,y); g.arcTo(x+w,y,x+w,y+h,r); g.arcTo(x+w,y+h,x,y+h,r); g.arcTo(x,y+h,x,y,r); g.arcTo(x,y,x+w,y,r); g.closePath(); }\nconst billTex=canvasTex(128,64,(g,w,h)=>{ g.fillStyle='#38c43f'; g.fillRect(0,0,w,h); g.strokeStyle='#1f8f2a'; g.lineWidth=6; g.strokeRect(5,5,w-10,h-10); g.fillStyle='#7be37f'; g.beginPath(); g.ellipse(64,32,17,17,0,0,7); g.fill(); g.fillStyle='#1f8f2a'; g.font='bold 26px Arial'; g.textAlign='center'; g.textBaseline='middle'; g.fillText('$',64,33); });\nfunction spriteTex(draw){ return canvasTex(128,128,draw); }\nfunction facetStrip(cols,cSide,cTop,topY){ const pos=[], col=[]; const A=new THREE.Vector3(), B=new THREE.Vector3(), C=new THREE.Vector3(), N=new THREE.Vector3(), E1=new THREE.Vector3(), E2=new THREE.Vector3();\n  const tri=(a,b,c)=>{ A.set(...a); B.set(...b); C.set(...c); E1.subVectors(B,A); E2.subVectors(C,A); N.crossVectors(E1,E2).normalize(); if(N.y<0){ N.negate(); pos.push(...a,...c,...b); } else pos.push(...a,...b,...c); const k=N.y>(topY||0.62)?cTop:cSide; for(let i=0;i<3;i++) col.push(k.r,k.g,k.b); };\n  for(let i=0;i<cols.length-1;i++){ const L=cols[i], R2=cols[i+1]; for(let j=0;j<L.length-1;j++){ tri(L[j],R2[j],L[j+1]); tri(R2[j],R2[j+1],L[j+1]); } }\n  const g=new THREE.BufferGeometry(); g.setAttribute('position',new THREE.Float32BufferAttribute(pos,3)); g.setAttribute('color',new THREE.Float32BufferAttribute(col,3)); g.computeVertexNormals(); return g; }\nconst cliffTops=[];\nfunction sprite(tex,s){ const sp=new THREE.Sprite(new THREE.SpriteMaterial({map:tex,depthTest:false,transparent:true})); sp.scale.set(s,s,1); sp.renderOrder=10; sp.visible=false; scene.add(sp); return sp; }\nfunction mergeGeos(list){ const gs=list.map(g=>g.index?g.toNonIndexed():g); const n=gs.reduce((a,g)=>a+g.attributes.position.count,0); const out=new THREE.BufferGeometry(); for(const k of ['position','normal','uv']){ const it=gs[0].attributes[k].itemSize, arr=new Float32Array(n*it); let o=0; for(const g of gs){ arr.set(g.attributes[k].array,o); o+=g.attributes[k].array.length; } out.setAttribute(k,new THREE.BufferAttribute(arr,it)); } return out; }\nfunction makeHuman(opt){ const g=new THREE.Group(); const body=new THREE.Group(); g.add(body); const coat=opt.coat, s=opt.scale||1;\n  const torso=new THREE.Mesh(new THREE.CylinderGeometry(0.3,0.4,0.72,12),coat); torso.position.y=0.78; torso.castShadow=true; body.add(torso);\n  const belly=new THREE.Mesh(new THREE.SphereGeometry(0.38,12,10),coat); belly.scale.y=0.7; belly.position.y=0.55; belly.castShadow=true; body.add(belly);\n  const head=new THREE.Mesh(new THREE.SphereGeometry(0.31,14,12),M.skin); head.position.y=1.36; head.castShadow=true; body.add(head);\n  for(const sx of [-0.1,0.1]){ const e=new THREE.Mesh(new THREE.SphereGeometry(0.036,6,6),M.dark); e.position.set(sx,1.39,0.28); body.add(e); }\n  if(opt.hat==='chef'){ const h1=new THREE.Mesh(new THREE.CylinderGeometry(0.26,0.24,0.32,12),M.white); h1.position.y=1.68; body.add(h1); const h2=new THREE.Mesh(new THREE.SphereGeometry(0.3,12,10),M.white); h2.scale.y=0.75; h2.position.y=1.88; body.add(h2); }\n  if(opt.hat==='hood'){ const h=new THREE.Mesh(new THREE.SphereGeometry(0.37,14,12,0,Math.PI*2,0,Math.PI*0.62),opt.hoodMat||M.white); h.position.set(0,1.37,-0.05); h.rotation.x=-0.35; h.castShadow=true; body.add(h); const fur=new THREE.Mesh(new THREE.TorusGeometry(0.3,0.09,8,16),M.white); fur.position.set(0,1.37,0.12); body.add(fur); }\n  if(opt.hat==='cap'){ const h=new THREE.Mesh(new THREE.SphereGeometry(0.34,12,10,0,Math.PI*2,0,Math.PI*0.5),opt.hoodMat); h.position.y=1.42; body.add(h); const pm=new THREE.Mesh(new THREE.SphereGeometry(0.11,8,6),M.white); pm.position.y=1.78; body.add(pm); }\n  const armGeo=mergeGeos([new THREE.CylinderGeometry(0.095,0.085,0.52,8).translate(0,-0.23,0),new THREE.SphereGeometry(0.1,8,6).translate(0,-0.5,0)]); const arms=[];\n  for(const sx of [-1,1]){ const a=new THREE.Mesh(armGeo,coat); a.position.set(0.37*sx,1.03,0); a.castShadow=true; body.add(a); arms.push(a); }\n  const legGeo=new THREE.CylinderGeometry(0.12,0.11,0.42,8); legGeo.translate(0,-0.2,0); const legs=[]; for(const sx of [-1,1]){ const l=new THREE.Mesh(legGeo,opt.legs||M.dark); l.position.set(0.15*sx,0.42,0); l.castShadow=true; g.add(l); legs.push(l); }\n  { const tmp=new THREE.Group(); body.children.filter(c=>!arms.includes(c)).forEach(c=>tmp.add(c)); body.add(tmp); mergeGroup(tmp); }\n  g.scale.setScalar(s); g.userData={body,arms,legs,phase:0,s}; scene.add(g); return g; }\nfunction makeTrident(){ const g=new THREE.Group(); const shaft=new THREE.Mesh(new THREE.CylinderGeometry(0.04,0.04,1.9,6),M.woodDark); shaft.position.y=0.55; g.add(shaft);\n  const tm=new THREE.MeshLambertMaterial({color:0xff5ab0}); const bar=new THREE.Mesh(new THREE.BoxGeometry(0.34,0.06,0.06),tm); bar.position.y=1.48; g.add(bar);\n  for(const dx of [-0.13,0,0.13]){ const t=new THREE.Mesh(new THREE.ConeGeometry(0.05,0.36,5),tm); t.position.set(dx,1.68,0); g.add(t); }\n  const tip=new THREE.Object3D(); tip.position.y=1.75; g.add(tip); g.userData.tip=tip; return g; }\nfunction animHuman(h,speed,dt,pose){ const u=h.userData; u.phase+=dt*(speed>0.1?11:2.2); const w=clamp(speed/4.5,0,1);\n  u.legs[0].rotation.x=Math.sin(u.phase)*0.75*w; u.legs[1].rotation.x=-Math.sin(u.phase)*0.75*w; u.body.position.y=Math.abs(Math.sin(u.phase))*0.08*w; u.body.rotation.x=0; u.body.rotation.z=Math.sin(u.phase)*0.05*w;\n  if(pose==='overhead'){ u.arms[0].rotation.x=u.arms[1].rotation.x=-2.95+Math.sin(u.phase*2)*0.04; u.arms[0].rotation.z=0.25; u.arms[1].rotation.z=-0.25; }\n  else if(pose==='front'){ u.arms[0].rotation.x=u.arms[1].rotation.x=-1.25; u.arms[0].rotation.z=0.12; u.arms[1].rotation.z=-0.12; }\n  else { u.arms[0].rotation.x=-Math.sin(u.phase)*0.7*w; u.arms[1].rotation.x=Math.sin(u.phase)*0.7*w; u.arms[0].rotation.z=0.12; u.arms[1].rotation.z=-0.12; } }\nfunction tileTex(icon,price,paid,lv){ return canvasTex(160,160,(g)=>drawTile(g,icon,price,paid,lv)); }\nfunction drawTile(g,icon,price,paid,lv){ g.clearRect(0,0,160,160); g.fillStyle='rgba(70,46,40,.82)'; roundRect(g,8,8,144,144,26); g.fill(); g.strokeStyle='#fff'; g.lineWidth=6; roundRect(g,8,8,144,144,26); g.stroke();\n  if(paid>0){ g.save(); roundRect(g,14,14,132,132,22); g.clip(); g.fillStyle='rgba(70,224,90,.55)'; g.fillRect(14,146-132*paid,132,132*paid); g.restore(); }\n  g.fillStyle='#fff'; g.strokeStyle='#fff'; g.lineWidth=6; g.lineCap='round'; g.save(); g.translate(80,price===null?80:62); drawIcon(g,icon); g.restore();\n  if(price!==null){ g.fillStyle='#3fcf4a'; roundRect(g,30,108,100,32,9); g.fill(); g.fillStyle='#fff'; g.font='900 26px Arial'; g.textAlign='center'; g.textBaseline='middle'; g.fillText('$'+price,80,125); }\n  if(lv){ g.fillStyle='#ffb321'; roundRect(g,96,4,60,30,10); g.fill(); g.fillStyle='#fff'; g.font='900 20px Arial'; g.textAlign='center'; g.textBaseline='middle'; g.fillText('Lv'+lv,126,20); } }\nfunction drawIcon(g,icon){\n  if(icon==='fish'){ g.beginPath(); g.ellipse(-6,0,30,15,0,0,7); g.fill(); g.beginPath(); g.moveTo(20,0); g.lineTo(40,-16); g.lineTo(40,16); g.fill(); g.fillStyle='#46302a'; g.beginPath(); g.arc(-22,-3,4,0,7); g.fill(); }\n  else if(icon==='slice'){ g.beginPath(); g.arc(0,0,30,0,7); g.fill(); g.strokeStyle='#46302a'; g.lineWidth=4; for(let j=0;j<3;j++){ g.beginPath(); g.arc(-32,0,14+j*10,-0.9,0.9); g.stroke(); } }\n  else if(icon==='helper'){ g.beginPath(); g.arc(-6,-14,12,0,7); g.fill(); g.fillRect(-22,2,32,26); g.lineWidth=5; g.beginPath(); g.moveTo(22,-28); g.lineTo(22,28); g.stroke(); g.beginPath(); g.moveTo(14,-28); g.lineTo(14,-18); g.lineTo(30,-18); g.lineTo(30,-28); g.stroke(); }\n  else if(icon==='chef'){ g.beginPath(); g.arc(-8,-6,12,0,7); g.fill(); g.fillRect(-24,8,32,22); g.fillRect(-18,-30,20,12); g.fillRect(12,-24,22,28); g.fillStyle='#46302a'; g.fillRect(16,-20,14,4); }\n  else if(icon==='fence'){ for(let k=0;k<5;k++) g.fillRect(-30+k*13,-24,8,46); g.fillRect(-34,-10,68,7); g.fillRect(-34,8,68,7); }\n  else if(icon==='belt'){ g.fillRect(-34,8,68,10); for(let k=0;k<4;k++){ g.beginPath(); g.arc(-26+k*17,24,5,0,7); g.fill(); } g.beginPath(); g.arc(-6,-8,11,0,7); g.fill(); g.beginPath(); g.arc(16,-8,11,0,7); g.fill(); }\n  else if(icon==='grill'){ g.fillRect(-30,10,60,10); g.fillRect(-26,20,6,14); g.fillRect(20,20,6,14); g.beginPath(); g.moveTo(-18,8); g.quadraticCurveTo(-26,-14,-8,-30); g.quadraticCurveTo(-10,-12,0,-6); g.quadraticCurveTo(4,-22,14,-28); g.quadraticCurveTo(26,-8,18,8); g.closePath(); g.fill(); }\n  else if(icon==='giant'){ g.beginPath(); g.ellipse(-4,4,36,18,0,0,7); g.fill(); g.beginPath(); g.moveTo(28,4); g.lineTo(44,-12); g.lineTo(44,20); g.fill(); g.beginPath(); g.moveTo(-26,-14); g.lineTo(-20,-32); g.lineTo(-12,-20); g.lineTo(-4,-34); g.lineTo(4,-20); g.lineTo(12,-32); g.lineTo(16,-14); g.closePath(); g.fill(); }\n  else if(icon==='ship'){ g.beginPath(); g.moveTo(-38,0); g.lineTo(38,0); g.lineTo(28,22); g.lineTo(-30,22); g.closePath(); g.fill(); g.fillRect(-10,-24,26,22); g.fillRect(4,-36,8,12); }\n  else if(icon==='knife'){ g.beginPath(); g.moveTo(-34,8); g.lineTo(22,-26); g.lineTo(26,-20); g.lineTo(-24,18); g.closePath(); g.fill(); g.fillRect(18,-18,22,10); }\n  else if(icon==='harpoon'){ g.lineWidth=7; g.beginPath(); g.moveTo(-32,26); g.lineTo(22,-20); g.stroke(); g.beginPath(); g.moveTo(34,-32); g.lineTo(12,-22); g.lineTo(24,-10); g.closePath(); g.fill(); g.beginPath(); g.arc(-26,20,10,0,7); g.lineWidth=4; g.stroke(); }\n  else if(icon==='fire'){ g.beginPath(); g.moveTo(-20,26); g.quadraticCurveTo(-34,-4,-10,-34); g.quadraticCurveTo(-12,-10,0,-4); g.quadraticCurveTo(4,-24,16,-30); g.quadraticCurveTo(36,2,20,26); g.closePath(); g.fill(); }\n  else if(icon==='lane'){ g.fillRect(-34,-22,68,12); g.fillRect(-34,10,68,12); g.lineWidth=5; g.beginPath(); g.arc(0,0,26,Math.PI,0); g.stroke(); }\n  else if(icon==='boat'){ g.beginPath(); g.moveTo(-38,4); g.lineTo(38,4); g.lineTo(26,24); g.lineTo(-28,24); g.closePath(); g.fill(); g.fillRect(-14,-16,22,18); g.lineWidth=5; g.beginPath(); g.moveTo(14,0); g.lineTo(36,-26); g.stroke(); }\n  else if(icon==='whale'){ g.beginPath(); g.ellipse(-6,4,32,18,0,0,7); g.fill(); g.beginPath(); g.moveTo(22,0); g.lineTo(42,-16); g.lineTo(38,4); g.lineTo(42,22); g.fill(); g.fillStyle='#46302a'; g.beginPath(); g.arc(-24,0,4,0,7); g.fill(); }\n  else if(icon==='coin'){ g.beginPath(); g.arc(0,0,30,0,7); g.fill(); g.fillStyle='#46302a'; g.font='900 40px Arial'; g.textAlign='center'; g.textBaseline='middle'; g.fillText('$',0,2); }\n  else if(icon==='net'){ g.beginPath(); g.moveTo(-38,8); g.lineTo(30,8); g.lineTo(20,26); g.lineTo(-30,26); g.closePath(); g.fill(); g.fillRect(-20,-16,18,22); g.lineWidth=4; g.beginPath(); g.moveTo(10,6); g.lineTo(34,-30); g.stroke(); for(let k=0;k<3;k++){ g.beginPath(); g.moveTo(14+k*7,-8-k*10); g.lineTo(40,-6-k*8); g.stroke(); } }\n  else if(icon==='rail'){ g.fillRect(-36,-6,72,7); g.fillRect(-36,14,72,7); for(let k=0;k<5;k++) g.fillRect(-32+k*15,-10,6,36); g.beginPath(); g.ellipse(0,-20,24,10,0,0,7); g.fill(); }\n  else if(icon==='cargo'){ g.beginPath(); g.moveTo(-40,6); g.lineTo(40,6); g.lineTo(30,26); g.lineTo(-32,26); g.closePath(); g.fill(); for(let k=0;k<3;k++) g.fillRect(-26+k*18,-14,15,17); g.fillRect(-26,-30,15,14); g.fillRect(22,-28,10,34); }\n  else if(icon==='crane'){ g.fillRect(-26,-30,9,62); g.fillRect(-30,-34,62,8); g.lineWidth=3; g.beginPath(); g.moveTo(24,-26); g.lineTo(24,4); g.stroke(); g.fillRect(14,4,20,16); g.fillRect(-36,26,26,8); }\n  else if(icon==='pen'){ g.lineWidth=6; g.strokeRect(-30,-22,60,44); for(let k=0;k<3;k++){ g.beginPath(); g.ellipse(-12+k*12,-4+(k%2)*8,9,4,0.4,0,7); g.fill(); } } }\nconst joy=document.getElementById('joy'), knob=document.getElementById('knob'), hint=document.getElementById('hint');\nconst input={active:false,id:-1,sx:0,sy:0,dx:0,dy:0}; const keys={};\naddEventListener('pointerdown',e=>{ audio(); if(input.active) return; input.active=true; input.id=e.pointerId; input.sx=e.clientX; input.sy=e.clientY; input.dx=input.dy=0; joy.style.display='block'; joy.style.left=e.clientX+'px'; joy.style.top=e.clientY+'px'; knob.style.transform='translate(0,0)'; hint.style.opacity=0; });\naddEventListener('pointermove',e=>{ if(!input.active||e.pointerId!==input.id) return; let dx=e.clientX-input.sx, dy=e.clientY-input.sy; const L=Math.hypot(dx,dy), mx=42; if(L>mx){dx*=mx/L; dy*=mx/L;} input.dx=dx/mx; input.dy=dy/mx; knob.style.transform=`translate(${dx}px,${dy}px)`; });\nfunction endPtr(e){ if(e&&e.pointerId!==undefined&&e.pointerId!==input.id) return; input.active=false; input.dx=input.dy=0; joy.style.display='none'; }\naddEventListener('pointerup',endPtr); addEventListener('pointercancel',endPtr); addEventListener('blur',()=>{ endPtr(); for(const k in keys) keys[k]=false; });\naddEventListener('keydown',e=>{ keys[e.key.toLowerCase()]=true; audio(); hint.style.opacity=0; }); addEventListener('keyup',e=>{ keys[e.key.toLowerCase()]=false; });\nfunction moveInput(){ if(window.__forceMove) return window.__forceMove; let ix=input.dx, iy=input.dy; if(keys['a']||keys['arrowleft']) ix-=1; if(keys['d']||keys['arrowright']) ix+=1; if(keys['w']||keys['arrowup']) iy-=1; if(keys['s']||keys['arrowdown']) iy+=1;\n  if(BOT){ const b=botInput(); ix=b[0]; iy=b[1]; } const L=Math.hypot(ix,iy); if(L>1){ ix/=L; iy/=L; } const fx=-Math.sin(CAM_YAW), fz=-Math.cos(CAM_YAW); return [ix*(-fz)-iy*fx, ix*fx-iy*fz]; } // camera yaw 225deg: screen right=(-x,+z), screen up=(+x,+z)// ======================= GIANT CHICKEN FRY \u00b7 world layout (x = east, z = north; camera looks from south-west, so north is screen up-right) =======================\nconst DECK_Y=0.36;\nconst SPINE=3.0;                                                        // main production axis: coop \u2192 splitter \u2192 plate 1 \u2192 fryer \u2192 packer \u2192 counter\nconst FAC={x0:-6.0,x1:12.0,z0:0.0,z1:19.0};                              // the kitchen yard (wood floor, player area)\nconst COOP={x0:-1.2,x1:7.2,z0:25.0,z1:30.5};                             // coop at the north end of the spine\nconst SPLIT=new V3(SPINE,0,22.0);                                       // chicken diverter (gate) outside the yard\nconst PLATES=[new V3(SPINE,0,16.4),new V3(8.0,0,16.4)];                  // stomp plates (blue spike press)\nconst PAD=new V3(5.35,0,16.0);                                          // the player's stomp button next to plate 1\nconst TRAYS=[{x0:0.0,x1:5.6,z0:9.4,z1:14.0},{x0:5.6,x1:11.2,z0:9.4,z1:14.0}]; // fryer pans (heater bar on each pan's west edge)\nconst PACKER=new V3(-2.6,0,7.6), PACK_IN=new V3(-0.7,0,7.6);            // bucket packer and its drop zone\nconst COLLECT_Z=8.45;                                                    // collector belt along the pans' south edge (auto packer)\nconst COUNTER={x0:-4.6,x1:10.6,z:2.2};                                   // the sales counter along the yard's south edge (customers outside, z<0)\nconst WIN=[new V3(3.0,0,1.2),new V3(8.0,0,1.2)];                         // sales windows; walk-in queues go south from here\nconst CASH_P=new V3(5.6,0,4.4);                                          // the cash pile (behind the counter, inside the yard)\nconst RACK_W={x0:-5.6,x1:-3.6,z0:3.4,z1:6.6}, RACK_E={x0:10.2,x1:11.8,z0:3.4,z1:7.8}; // bucket storage walls (bikes west, trucks east)\nconst BIKE_BAY=new V3(-7.6,0,5.0), TRUCK_BAY=new V3(14.2,0,5.6);\nconst ROAST={x0:13.0,x1:24.0,z0:9.0,z1:24.0}, SPIT=new V3(18.5,0,17.0), CARVE=new V3(18.5,0,12.4), RPAD=new V3(15.6,0,15.6);\nconst STREET={x0:-26.0,x1:32.0,z0:-16.0,z1:-12.0};\nconst HALL={x0:14.0,x1:30.0,z0:-11.0,z1:-3.0};                          // roast restaurant (south-east), its south side opens onto the street\nconst PLAZA={x0:-25.0,x1:-8.0,z0:-31.0,z1:-17.0};                        // winter feast square (south-west), north side on the street\nfunction inR(r,x,z,m){ m=m||0; return x>r.x0-m&&x<r.x1+m&&z>r.z0-m&&z<r.z1+m; }\n\n// ================= RENDERER =================\nconst renderer=new THREE.WebGLRenderer({antialias:true});\nrenderer.setPixelRatio(Math.min(1.5,window.devicePixelRatio||1));\nrenderer.shadowMap.enabled=true; renderer.shadowMap.type=THREE.PCFSoftShadowMap;\ndocument.body.appendChild(renderer.domElement);\nconst scene=new THREE.Scene(); scene.background=new THREE.Color(0xdde8f6); scene.fog=new THREE.Fog(0xdde8f6,48,110);\nconst camera=new THREE.PerspectiveCamera(40,1,0.5,260);\nscene.add(new THREE.HemisphereLight(0xf2f6ff,0x9aa6c8,0.74));\nconst sun=new THREE.DirectionalLight(0xfff0dc,0.84); sun.castShadow=true; sun.shadow.mapSize.set(1024,1024);\n{ const c=sun.shadow.camera; c.left=-16; c.right=16; c.top=16; c.bottom=-16; c.near=1; c.far=80; } sun.shadow.bias=-0.0008; scene.add(sun); scene.add(sun.target);\nfunction resize(){ const w=innerWidth,h=innerHeight; renderer.setSize(w,h); camera.aspect=w/h; camera.updateProjectionMatrix(); }\naddEventListener('resize',resize); resize(); setTimeout(()=>{ if(window.fireMat) fireMat.uniforms.uScale.value=innerHeight/(2*Math.tan(camera.fov*Math.PI/360)); },0);\n\n// ================= MATERIALS =================\nconst plankTex=canvasTex(256,256,(g,w,h)=>{ g.fillStyle='#d29a6c'; g.fillRect(0,0,w,h); for(let i=0;i<8;i++){ const y=i*32; g.fillStyle=i%2?'#c98f60':'#d7a274'; g.fillRect(0,y,w,32); g.fillStyle='rgba(110,60,30,.30)'; g.fillRect(0,y,w,2); const o=(i*97)%256; g.fillRect(o,y,2,32); g.fillRect((o+128)%256,y,2,32); g.fillStyle='rgba(255,235,215,.14)'; g.fillRect(0,y+3,w,3); } },[6,6]);\nconst stoneTex=canvasTex(256,256,(g,w,h)=>{ g.fillStyle='#8f96a3'; g.fillRect(0,0,w,h); for(let y=0;y<8;y++) for(let x=0;x<8;x++){ const o=(y%2)*16; const c=165+((x*37+y*53)%5)*9; g.fillStyle=`rgb(${c},${c+3},${c+10})`; g.fillRect(x*32+o+2,y*32+2,28,28); } },[10,2]);\nconst M={ plank:new THREE.MeshLambertMaterial({map:plankTex}), plankSide:new THREE.MeshLambertMaterial({color:0x9b6347}), wood:new THREE.MeshLambertMaterial({color:0xcf7d43}), woodDark:new THREE.MeshLambertMaterial({color:0x9f5a2f}), woodTop:new THREE.MeshLambertMaterial({color:0xeaa56c}),\n  white:new THREE.MeshLambertMaterial({color:0xf6f7fa}), skin:new THREE.MeshLambertMaterial({color:0xf2c7a0}), dark:new THREE.MeshLambertMaterial({color:0x2e2b33}), iron:new THREE.MeshLambertMaterial({color:0x56606c}), steel:new THREE.MeshLambertMaterial({color:0xdfe5ec}),\n  pine:new THREE.MeshLambertMaterial({color:0x9db6d6}), pineSnow:new THREE.MeshLambertMaterial({color:0xf3f7fd}), blue:new THREE.MeshLambertMaterial({color:0x2f86d8}), blueL:new THREE.MeshLambertMaterial({color:0x6fb8f2}), blueDark:new THREE.MeshLambertMaterial({color:0x1f4f8f}), snow:new THREE.MeshLambertMaterial({color:0xeaf0fb,flatShading:true}),\n  red:new THREE.MeshLambertMaterial({color:0xd8443a}), yellow:new THREE.MeshLambertMaterial({color:0xf2b632,emissive:0x2a1a00}), stone:new THREE.MeshLambertMaterial({map:stoneTex}), orange:new THREE.MeshLambertMaterial({color:0xf07a2a}) };\n\n// ================= TERRAIN: rolling snow, pines, far ice cliffs =================\nconst FLAT=[{x0:FAC.x0-1,x1:FAC.x1+1,z0:FAC.z0-14,z1:COOP.z1+1},ROAST,STREET,HALL,PLAZA,{x0:-34,x1:-6,z0:2,z1:8},{x0:12,x1:60,z0:2,z1:9},{x0:13,x1:31,z0:-4,z1:4},{x0:-14,x1:-6,z0:-18,z1:8}];\nfunction landH(x,z){ let w=0; for(const r of FLAT){ const ox=Math.max(0,r.x0-x,x-r.x1), oz=Math.max(0,r.z0-z,z-r.z1); w=Math.max(w,1-Math.hypot(ox,oz)/4.5); }\n  const h=0.3+Math.max(0,Math.sin(x*0.21+1.3)*Math.cos(z*0.17)*1.4+Math.sin(x*0.07-z*0.05)*1.8+1.0); return lerp(h,0.3,clamp(w,0,1)); }\nfunction pineOK(x,z){ for(const r of FLAT) if(inR(r,x,z,2.0)) return false; return true; }\n{ const g=new THREE.PlaneGeometry(150,130,110,96); g.rotateX(-Math.PI/2); g.translate(4,0,-2); const P2=g.attributes.position;\n  for(let i=0;i<P2.count;i++){ const x=P2.getX(i), z=P2.getZ(i); P2.setY(i,landH(x,z)); }\n  const ng=g.toNonIndexed(); ng.computeVertexNormals(); const m=new THREE.Mesh(ng,M.snow); m.receiveShadow=true; scene.add(m); }\nconst pineGeo=[new THREE.ConeGeometry(1.0,1.2,8),new THREE.ConeGeometry(0.78,1.0,8),new THREE.ConeGeometry(0.55,0.85,8)];\nconst pines=[]; function pine(x,z,s){ if(!pineOK(x,z)) return; pines.push([x,landH(x,z)-0.05,z,s]); }\nfor(let i=0;i<220;i++) pine(R(-50,56),R(-60,60),R(1.0,1.8));\n{ const m4=new THREE.Matrix4(); for(let k=0;k<3;k++){ const a=new THREE.InstancedMesh(pineGeo[k],M.pine,pines.length), b=new THREE.InstancedMesh(pineGeo[k],M.pineSnow,pines.length); a.castShadow=true;\n  pines.forEach(([x,y,z,s],i)=>{ m4.makeScale(s,s,s).setPosition(x,y+(0.8+k*0.62)*s,z); a.setMatrixAt(i,m4); m4.makeScale(s*0.8,s*0.5,s*0.8).setPosition(x,y+(1.05+k*0.62)*s,z); b.setMatrixAt(i,m4); }); scene.add(a); scene.add(b); } }\nfunction slab2(w,h,d,sideM,topM){ const g=new THREE.Group(); const b=new THREE.Mesh(new THREE.BoxGeometry(w,h,d),sideM); b.receiveShadow=true; g.add(b); const t=new THREE.Mesh(new THREE.PlaneGeometry(w,d),topM); t.rotation.x=-Math.PI/2; t.position.y=h/2+0.004; t.receiveShadow=true; g.add(t); return g; }\nconst billGeo=new THREE.BoxGeometry(0.62,0.11,0.36); const billSideM=new THREE.MeshLambertMaterial({color:0x2fae36}), billTopM=new THREE.MeshLambertMaterial({map:billTex});\nconst billMesh=new THREE.InstancedMesh(billGeo,[billSideM,billSideM,billTopM,billTopM,billSideM,billSideM],1700); billMesh.instanceMatrix.setUsage(THREE.DynamicDrawUsage); billMesh.castShadow=true; billMesh.frustumCulled=false; scene.add(billMesh);\nconst chunkMesh=new THREE.InstancedMesh(new THREE.DodecahedronGeometry(0.26,0),new THREE.MeshLambertMaterial({color:0xe8796f,flatShading:true}),900); chunkMesh.instanceMatrix.setUsage(THREE.DynamicDrawUsage); chunkMesh.castShadow=true; chunkMesh.frustumCulled=false; scene.add(chunkMesh);\nconst flying=[]; function fly(kind,a,b,dur,h,onLand,extra){ const f={kind,a:a.clone(),b:b?b.clone():null,t:0,dur,h,onLand,spin:R(-8,8)}; if(extra) Object.assign(f,extra); flying.push(f); return f; }\nfunction hsh(i){ const x=Math.sin(i*127.1+311.7)*43758.5453; return x-Math.floor(x); }\n\n// tiles\nfunction flatTile(icon,x,z,size){ const t=tileTex(icon,null,0); const m=new THREE.Mesh(new THREE.PlaneGeometry(size||1.7,size||1.7),new THREE.MeshBasicMaterial({map:t,transparent:true})); m.rotation.x=-Math.PI/2; m.position.set(x,DECK_Y+0.02,z); scene.add(m); return m; }\nconst ringMat=new THREE.MeshBasicMaterial({color:0x46e05a,transparent:true,opacity:0.95}); function ring(r){ const m=new THREE.Mesh(new THREE.RingGeometry(r*0.86,r,40),ringMat.clone()); m.rotation.x=-Math.PI/2; m.visible=false; scene.add(m); return m; }\n\n// particles / audio / UI helpers\nconst partMesh=new THREE.InstancedMesh(new THREE.SphereGeometry(0.1,6,5),new THREE.MeshBasicMaterial({color:0xffffff}),500); partMesh.frustumCulled=false; scene.add(partMesh); const parts=[];\nfunction splash(x,y,z,n,spd){ for(let i=0;i<n;i++){ if(parts.length>480) parts.shift(); const a=R(0,6.28), s=R(0.4,1)*spd; parts.push({x,y,z,vx:Math.cos(a)*s,vy:R(1.5,4)*spd*0.5+1,vz:Math.sin(a)*s,life:R(0.4,0.8),t:0,s:R(0.6,1.4)}); } }\nconst fireMesh=new THREE.InstancedMesh(new THREE.OctahedronGeometry(0.28,0),new THREE.MeshBasicMaterial({color:0xffffff,vertexColors:false}),360); fireMesh.frustumCulled=false; scene.add(fireMesh); const fires=[];\n{ const c=new THREE.Color(); for(let i=0;i<360;i++){ c.setHSL(0.03+(i%5)*0.025,1,0.55+(i%3)*0.08); fireMesh.setColorAt(i,c); } fireMesh.instanceColor.needsUpdate=true; }\nfunction flame(x,y,z,n,spd,up){ for(let i=0;i<n;i++){ const a=R(0,6.28), s=R(0.2,1)*spd; flame2(x+R(-0.3,0.3),y,z+R(-0.3,0.3),Math.cos(a)*s,R(1.5,3.5)*(up||1),Math.sin(a)*s,R(0.8,1.5),R(0.35,0.7)); } return;  for(let i=0;i<n;i++){ if(fires.length>350) fires.shift(); const a=R(0,6.28), s=R(0.2,1)*spd; fires.push({x:x+R(-0.3,0.3),y,z:z+R(-0.3,0.3),vx:Math.cos(a)*s,vy:R(1.5,3.5)*(up||1),vz:Math.sin(a)*s,life:R(0.35,0.75),t:0,s:R(0.7,1.5)}); } }\nconst sparkMesh=new THREE.InstancedMesh(new THREE.OctahedronGeometry(0.15,0),new THREE.MeshBasicMaterial({color:0xffe14a}),220); sparkMesh.frustumCulled=false; scene.add(sparkMesh); const sparks=[];\nfunction sparkle(x,y,z,n,spd){ for(let i=0;i<n;i++){ if(sparks.length>210) sparks.shift(); const a=R(0,6.28); sparks.push({x,y,z,vx:Math.cos(a)*R(0.8,2.4)*(spd||1),vy:R(1.2,3.6),vz:Math.sin(a)*R(0.8,2.4)*(spd||1),t:0,life:R(0.35,0.7)}); } }\nconst pops=[]; function pop(o){ pops.push({o,t:0,base:o.scale.x||1}); }\nconst rises=[]; function rise(o,from){ const y=o.position.y; o.position.y=y+from; o.visible=true; rises.push({o,y,t:0}); }\nconst banner=document.getElementById('banner'); let bannerT=0; function showBanner(s){ banner.textContent=s; banner.classList.add('on'); bannerT=1.8; }\nlet AC=null; function audio(){ if(!AC){ try{ AC=new (window.AudioContext||window.webkitAudioContext)(); }catch(e){} } if(AC&&AC.state==='suspended') AC.resume().then(startBgm).catch(()=>{}); else startBgm(); }\n// background music (Astrocade asset 'bgm_harbor'); fetched once, decoded after the first gesture, looped quietly under the SFX\nlet bgmBuf=null, bgmSrc=null, bgmReq=null;\ntry{ const a=(typeof lib!=='undefined'&&lib.getAsset)?lib.getAsset('bgm_harbor'):null; if(a&&a.url) bgmReq=fetch(a.url).then(r=>r.arrayBuffer()).catch(()=>null); }catch(e){}\nfunction startBgm(){ if(bgmSrc||!AC||!bgmReq) return; bgmSrc=1; bgmReq.then(ab=>{ if(!ab) return null; return AC.decodeAudioData(ab); }).then(buf=>{ if(!buf){ bgmSrc=null; return; } const src=AC.createBufferSource(); src.buffer=buf; src.loop=true; const g=AC.createGain(); g.gain.value=0.0001; g.gain.exponentialRampToValueAtTime(0.22,AC.currentTime+2.5); src.connect(g); g.connect(AC.destination); src.start(); bgmSrc=src; }).catch(()=>{ bgmSrc=null; }); }\nfunction tone(f,d,type,vol,slide){ if(!AC||AC.state!=='running') return; const t=AC.currentTime; const o=AC.createOscillator(), g=AC.createGain(); o.type=type||'sine'; o.frequency.setValueAtTime(f,t); if(slide) o.frequency.exponentialRampToValueAtTime(f*slide,t+d); g.gain.setValueAtTime(vol||0.08,t); g.gain.exponentialRampToValueAtTime(0.0001,t+d); o.connect(g); g.connect(AC.destination); o.start(t); o.stop(t+d+0.02); }\nfunction noise(d,vol,freq){ if(!AC||AC.state!=='running') return; const t=AC.currentTime; const b=AC.createBuffer(1,Math.floor(AC.sampleRate*d),AC.sampleRate); const a=b.getChannelData(0); for(let i=0;i<a.length;i++) a[i]=(Math.random()*2-1)*(1-i/a.length); const s=AC.createBufferSource(); s.buffer=b; const f=AC.createBiquadFilter(); f.type='lowpass'; f.frequency.value=freq||1200; const g=AC.createGain(); g.gain.value=vol||0.15; s.connect(f); f.connect(g); g.connect(AC.destination); s.start(t); }\nlet lastTick=0; function tick(p){ const now=performance.now(); if(now-lastTick<35) return; lastTick=now; tone(320+(p%12)*18,0.06,'sine',0.045); }\nfunction ching(){ tone(260,0.22,'triangle',0.07,1.9); noise(0.18,0.08,1800); }\nlet shake=0;\n\nconst FIRE_N=1400; const fireGeo=new THREE.BufferGeometry();\nconst fPos=new Float32Array(FIRE_N*3), fSize=new Float32Array(FIRE_N), fLife=new Float32Array(FIRE_N), fSeed=new Float32Array(FIRE_N);\nfireGeo.setAttribute('position',new THREE.BufferAttribute(fPos,3)); fireGeo.setAttribute('aSize',new THREE.BufferAttribute(fSize,1)); fireGeo.setAttribute('aLife',new THREE.BufferAttribute(fLife,1)); fireGeo.setAttribute('aSeed',new THREE.BufferAttribute(fSeed,1));\nconst fireMat=new THREE.ShaderMaterial({ transparent:true, depthWrite:false, blending:THREE.NormalBlending, uniforms:{uScale:{value:500}},\n  vertexShader:`attribute float aSize; attribute float aLife; attribute float aSeed; varying float vL; varying float vS; uniform float uScale; void main(){ vL=aLife; vS=aSeed; vec4 mv=modelViewMatrix*vec4(position,1.); gl_PointSize= aLife>=1.? 0. : aSize*uScale/-mv.z; gl_Position=projectionMatrix*mv; }`,\n  fragmentShader:`varying float vL; varying float vS; void main(){ vec2 p=gl_PointCoord-0.5; float up=clamp(0.5-p.y,0.,1.); p.x*=1.0+up*up*2.2*(1.-vL*0.5); p.y*=0.95; float d=length(p); if(d>0.5) discard; float a=smoothstep(0.5,0.05,d);\n    vec3 c= vL<0.1? mix(vec3(1.,.82,.35),vec3(1.,.6,.12),vL/0.1) : vL<0.4? mix(vec3(1.,.6,.12),vec3(.95,.3,.04),(vL-0.1)/0.3) : vL<0.75? mix(vec3(1.,.55,.12),vec3(.85,.2,.04),(vL-0.4)/0.35) : mix(vec3(.85,.2,.04),vec3(.25,.06,.02),(vL-0.75)/0.25);\n    float fade=(1.-smoothstep(0.35,0.7,vL))*smoothstep(0.,0.05,vL); float core=smoothstep(0.3,0.0,d)*(1.-smoothstep(0.,0.25,vL)); gl_FragColor=vec4(min(vec3(1.),c*(1.05-vL*0.3)+core*0.18),a*fade*(0.62-vL*0.3)); }` });\nwindow.fireMat=fireMat;\n const fireP=new THREE.Points(fireGeo,fireMat); fireP.frustumCulled=false; fireP.renderOrder=20; scene.add(fireP);\nconst flames=[]; for(let i=0;i<FIRE_N;i++){ fLife[i]=1; }\nfunction smoke_unused(x,y,z){ if(parts.length>880) parts.shift(); parts.push({x,y,z,vx:R(0.3,1),vy:R(1.2,2.2),vz:R(-0.3,0.3),life:R(0.9,1.4),t:0,s:R(1.4,2.2),g:-0.4,drag:0.6,dark:1}); }\nlet fireHead=0; function flame2(x,y,z,vx,vy,vz,size,life){ const i=fireHead; fireHead=(fireHead+1)%FIRE_N; flames[i]={x,y,z,vx,vy,vz,size,life,t:0}; }\nfunction updateFire(dt){ for(let i=0;i<FIRE_N;i++){ const f=flames[i]; if(!f){ fLife[i]=1; continue; } f.t+=dt; const l=f.t/f.life; if(l>=1){ flames[i]=null; fLife[i]=1; continue; } f.vy+=dt*2.2; f.vx*=1-dt*0.8; f.vz*=1-dt*0.8; f.x+=f.vx*dt; f.y+=f.vy*dt; f.z+=f.vz*dt; fPos[i*3]=f.x; fPos[i*3+1]=f.y; fPos[i*3+2]=f.z; fSize[i]=f.size*(0.55+l*0.9); fLife[i]=l; fSeed[i]=(i%17)/17; }\n  fireGeo.attributes.position.needsUpdate=true; fireGeo.attributes.aSize.needsUpdate=true; fireGeo.attributes.aLife.needsUpdate=true; }\n\n\n// ================= WHALES =================// floating world text\nconst popTexts=[]; function popText(txt,pos,col,dur,scale){ const t=canvasTex(512,128,(g)=>{ g.font='900 64px Arial'; g.textAlign='center'; g.textBaseline='middle'; g.lineWidth=14; g.strokeStyle='#fff'; g.strokeText(txt,256,64); g.fillStyle=col||'#e8a014'; g.fillText(txt,256,64); }); const sp=new THREE.Sprite(new THREE.SpriteMaterial({map:t,depthTest:false,transparent:true})); const sc=(scale||1)*1.35; sp.scale.set(4.4*sc,1.1*sc,1); sp.renderOrder=14; sp.position.copy(pos); scene.add(sp); popTexts.push({sp,t:0,dur:dur||2.5,y0:pos.y,bx:4.4*sc,by:1.1*sc}); }\nfunction updatePopTexts(dt){ for(let i=popTexts.length-1;i>=0;i--){ const p=popTexts[i]; p.t+=dt; const k=p.t/p.dur; p.sp.position.y=p.y0+k*1.2; p.sp.material.opacity=k<0.8?1:1-(k-0.8)/0.2; const s0=k<0.12?0.6+k/0.12*0.4:1; p.sp.scale.set(p.bx*s0,p.by*s0,1); if(p.t>=p.dur){ scene.remove(p.sp); popTexts.splice(i,1); } } }// ================= shared helpers (from Tuna Soup Pier) =================\nconst logGeo=new THREE.CylinderGeometry(0.2,0.22,1,10);\nconst wallM=new THREE.MeshLambertMaterial({color:0xb36a3c}), wallD=new THREE.MeshLambertMaterial({color:0x7d4527}), roofM=new THREE.MeshLambertMaterial({color:0xa8352a}), snowM=new THREE.MeshLambertMaterial({color:0xf3f7fd}), winM=new THREE.MeshBasicMaterial({color:0xffc865}), beamM=new THREE.MeshLambertMaterial({color:0x5e3a24}), warmLamp=new THREE.MeshBasicMaterial({color:0xffd36a});\n// static-geometry merger: bakes a group's meshes into one mesh per material (keeps draw calls low)\nfunction mergeGroup(g){ g.updateMatrixWorld(true); const inv=new THREE.Matrix4().copy(g.matrixWorld).invert(); const buckets=new Map(); const keep=[];\n  g.traverse(o=>{ if(!o.isMesh||o.userData.keep||Array.isArray(o.material)||o.isInstancedMesh){ if(o.isMesh&&o!==g) keep.push(o); return; } const m=new THREE.Matrix4().multiplyMatrices(inv,o.matrixWorld); let geo=o.geometry.index?o.geometry.toNonIndexed():o.geometry.clone(); geo.applyMatrix4(m); for(const k of Object.keys(geo.attributes)) if(k!=='position'&&k!=='normal'&&k!=='uv') geo.deleteAttribute(k); if(!geo.attributes.uv){ geo.setAttribute('uv',new THREE.BufferAttribute(new Float32Array(geo.attributes.position.count*2),2)); } const key=o.material.uuid; if(!buckets.has(key)) buckets.set(key,{mat:o.material,geos:[],cast:false}); const b=buckets.get(key); b.geos.push(geo); b.cast=b.cast||o.castShadow; });\n  const kids=[...g.children]; for(const k of kids) g.remove(k);\n  for(const b of buckets.values()){ let n=0; for(const q of b.geos) n+=q.attributes.position.count; const pos=new Float32Array(n*3), nor=new Float32Array(n*3), uv=new Float32Array(n*2); let o=0; for(const q of b.geos){ pos.set(q.attributes.position.array,o*3); nor.set(q.attributes.normal.array,o*3); uv.set(q.attributes.uv.array,o*2); o+=q.attributes.position.count; }\n    const geo=new THREE.BufferGeometry(); geo.setAttribute('position',new THREE.BufferAttribute(pos,3)); geo.setAttribute('normal',new THREE.BufferAttribute(nor,3)); geo.setAttribute('uv',new THREE.BufferAttribute(uv,2)); const m=new THREE.Mesh(geo,b.mat); m.castShadow=b.cast; m.receiveShadow=true; g.add(m); }\n  for(const k of keep){ const m=new THREE.Matrix4().multiplyMatrices(inv,k.matrixWorld); k.matrix.copy(m); k.matrix.decompose(k.position,k.quaternion,k.scale); g.add(k); } return g; }\n// log fence helper (instanced, one group per zone so it can rise with the zone)\nfunction logFence(parent,segs,h,y){ const pts=[]; for(const [x0,z0,x1,z1,gap] of segs){ const L=Math.hypot(x1-x0,z1-z0), n=Math.max(1,Math.floor(L/0.42)); for(let i=0;i<=n;i++){ const t=i/n, x=lerp(x0,x1,t), z=lerp(z0,z1,t); if(gap&&gap(x,z)) continue; pts.push([x,z,h*(0.92+0.16*((i*37)%7)/7)]); } }\n  const lm=new THREE.InstancedMesh(logGeo,M.wood,pts.length), tm=new THREE.InstancedMesh(new THREE.CylinderGeometry(0.19,0.19,0.04,10),M.woodTop,pts.length); const m=new THREE.Matrix4();\n  pts.forEach(([x,z,hh],i)=>{ m.makeScale(1,hh,1).setPosition(x,y+hh/2,z); lm.setMatrixAt(i,m); m.makeTranslation(x,y+hh+0.02,z); tm.setMatrixAt(i,m); }); lm.castShadow=true; parent.add(lm); parent.add(tm); }\nfunction deckSlab(parent,r,y,thick){ const d=slab2(r.x1-r.x0,thick||0.5,r.z1-r.z0,M.plankSide,M.plank); d.position.set((r.x0+r.x1)/2,(y??DECK_Y)-(thick||0.5)/2,(r.z0+r.z1)/2); d.receiveShadow=true; d.userData.keep=1; parent.add(d); return d; }\n// rope barriers mark where a future zone will open (the purchase tile sits in front of it)\nfunction ropeGate(x0,z0,x1,z1){ const g=new THREE.Group(); scene.add(g); const n=Math.max(2,Math.round(Math.hypot(x1-x0,z1-z0)/1.4)); for(let i=0;i<=n;i++){ const p=new THREE.Mesh(new THREE.CylinderGeometry(0.08,0.08,0.9,6),M.woodDark); p.position.set(lerp(x0,x1,i/n),DECK_Y+0.45,lerp(z0,z1,i/n)); g.add(p); }\n  const L=Math.hypot(x1-x0,z1-z0); const rp=new THREE.Mesh(new THREE.BoxGeometry(0.06,0.06,L),new THREE.MeshLambertMaterial({color:0xd8443a})); rp.position.set((x0+x1)/2,DECK_Y+0.8,(z0+z1)/2); rp.lookAt(x1,DECK_Y+0.8,z1); g.add(rp); return g; }\n\nfunction boxAt(g,w,h,d,x,y,z,mat,ry){ const m=new THREE.Mesh(new THREE.BoxGeometry(w,h,d),mat); m.position.set(x,y,z); if(ry) m.rotation.y=ry; m.castShadow=h>=1.5; g.add(m); return m; }\nfunction wallSeg(g,ax,az,bx,bz,h,mat,win){ const L=Math.hypot(bx-ax,bz-az), ry=-Math.atan2(bz-az,bx-ax); boxAt(g,L,h,0.35,(ax+bx)/2,DECK_Y+h/2,(az+bz)/2,mat,ry); boxAt(g,L+0.2,0.18,0.5,(ax+bx)/2,DECK_Y+h+0.09,(az+bz)/2,wallD,ry);\n  if(win){ const n=Math.floor(L/2.6); for(let k=0;k<n;k++){ const t=(k+0.5)/n; boxAt(g,1.1,1.3,0.42,lerp(ax,bx,t),DECK_Y+2.1,lerp(az,bz,t),winM,ry); } } }\nfunction signTex(txt,bg,fg,w){ return canvasTex(w||512,124,(g,W)=>{ g.fillStyle=bg; roundRect(g,4,4,W-8,116,22); g.fill(); g.strokeStyle=fg; g.lineWidth=7; roundRect(g,4,4,W-8,116,22); g.stroke(); g.fillStyle=fg; g.font='900 50px Arial'; g.textAlign='center'; g.textBaseline='middle'; g.fillText(txt,W/2,64); }); }\nfunction lantern(g,x,y,z){ const l=new THREE.Mesh(new THREE.SphereGeometry(0.2,8,6),warmLamp); l.position.set(x,y,z); g.add(l); }\nfunction cInst(geo,mat,n){ const m=new THREE.InstancedMesh(geo,mat,n); m.instanceMatrix.setUsage(THREE.DynamicDrawUsage); m.castShadow=true; m.frustumCulled=false; scene.add(m); return m; }\nconst CG={body:new THREE.CylinderGeometry(0.28,0.37,0.74,10).translate(0,0.8,0), belly:new THREE.SphereGeometry(0.36,10,8).scale(1,0.7,1).translate(0,0.55,0), head:new THREE.SphereGeometry(0.27,12,10).translate(0,1.34,0.02),\n  hat:new THREE.SphereGeometry(0.29,12,10,0,Math.PI*2,0,Math.PI*0.5).translate(0,1.4,-0.02), e1:new THREE.SphereGeometry(0.038,6,5).translate(-0.09,1.36,0.25), e2:new THREE.SphereGeometry(0.038,6,5).translate(0.09,1.36,0.25), arm:new THREE.CylinderGeometry(0.085,0.075,0.5,7).translate(0,-0.22,0)};\n// ================= GIANT CHICKENS \u00b7 stomp plates \u00b7 fryer pans \u00b7 drumsticks =================\nconst _p=new V3(), _q=new THREE.Quaternion(), _s=new V3(), _m=new THREE.Matrix4(), _e=new THREE.Euler(), _m2=new THREE.Matrix4();\nconst stat={T:0,sold:0,earned:0,chickens:0,sticks:0,cooked:0,packed:0,buckets:0,soldBy:{},events:[],tr:[]};\nfunction logEv(s){ stat.events.push([+stat.T.toFixed(1),s]); }\nconst owned=new Set(); function has(id){ return owned.has(id); }\nconst showcase={t:0,pos:new V3(),zoom:0,path:null,dur:1};\n\n// ---------- the chicken (instanced parts; one instance per chicken) ----------\nconst CS=1.12; // chicken scale (\u2248 4.4 m tall, about twice the player)\nconst chickBodyM=new THREE.MeshLambertMaterial({color:0xf4cf73}), chickBellyM=new THREE.MeshLambertMaterial({color:0xfde9bd}), chickWingM=new THREE.MeshLambertMaterial({color:0xeebd5a}), chickTailM=new THREE.MeshLambertMaterial({color:0xe6a547});\nconst combM=new THREE.MeshLambertMaterial({color:0xe23b2e}), beakM=new THREE.MeshLambertMaterial({color:0xf5a524}), eyeWM=new THREE.MeshBasicMaterial({color:0xffffff}), pupilM=new THREE.MeshBasicMaterial({color:0x1d1a22}), blushM=new THREE.MeshLambertMaterial({color:0xf59a8e}), legM=new THREE.MeshLambertMaterial({color:0xf08a1e});\nconst CHG={\n  body:mergeGeos([new THREE.SphereGeometry(1.35,22,16).scale(1.0,0.92,1.05).translate(0,1.55,0),new THREE.SphereGeometry(0.8,18,14).translate(0,2.85,0.5)]),\n  belly:new THREE.SphereGeometry(1.0,18,14).scale(1,0.95,0.7).translate(0,1.3,0.72),\n  eyeW:mergeGeos([new THREE.SphereGeometry(0.31,14,12).scale(1,1.12,0.8).translate(-0.33,3.02,1.1),new THREE.SphereGeometry(0.31,14,12).scale(1,1.12,0.8).translate(0.33,3.02,1.1)]),\n  pupil:mergeGeos([new THREE.SphereGeometry(0.17,12,10).translate(-0.34,2.98,1.32),new THREE.SphereGeometry(0.17,12,10).translate(0.34,2.98,1.32),new THREE.SphereGeometry(0.055,6,5).translate(-0.29,3.07,1.46),new THREE.SphereGeometry(0.055,6,5).translate(0.39,3.07,1.46)]),\n  beak:mergeGeos([new THREE.ConeGeometry(0.2,0.42,10).rotateX(Math.PI/2).translate(0,2.72,1.42)]),\n  comb:mergeGeos([new THREE.SphereGeometry(0.22,10,8).translate(0,3.62,0.28),new THREE.SphereGeometry(0.25,10,8).translate(0,3.74,0.55),new THREE.SphereGeometry(0.2,10,8).translate(0,3.6,0.84),new THREE.SphereGeometry(0.15,8,6).scale(1,1.3,1).translate(0,2.4,1.25)]),\n  blush:mergeGeos([new THREE.SphereGeometry(0.17,10,8).scale(1,0.6,0.4).translate(-0.58,2.72,1.0),new THREE.SphereGeometry(0.17,10,8).scale(1,0.6,0.4).translate(0.58,2.72,1.0)]),\n  wingL:new THREE.SphereGeometry(0.5,14,10).scale(0.3,0.9,1.15).translate(0,-0.45,0), wingR:new THREE.SphereGeometry(0.5,14,10).scale(0.3,0.9,1.15).translate(0,-0.45,0),\n  tail:mergeGeos([new THREE.ConeGeometry(0.32,1.0,8).rotateX(-0.9).translate(0,2.1,-1.35),new THREE.ConeGeometry(0.28,0.9,8).rotateX(-0.6).rotateZ(0.35).translate(-0.3,2.0,-1.3),new THREE.ConeGeometry(0.28,0.9,8).rotateX(-0.6).rotateZ(-0.35).translate(0.3,2.0,-1.3)]),\n  legs:mergeGeos([new THREE.CylinderGeometry(0.11,0.13,0.6,8).translate(-0.45,0.3,0.1),new THREE.CylinderGeometry(0.11,0.13,0.6,8).translate(0.45,0.3,0.1),new THREE.BoxGeometry(0.42,0.1,0.55).translate(-0.45,0.05,0.25),new THREE.BoxGeometry(0.42,0.1,0.55).translate(0.45,0.05,0.25)])};\nconst CHN=26;\nconst chM={}; for(const [k,mat] of [['body',chickBodyM],['belly',chickBellyM],['eyeW',eyeWM],['pupil',pupilM],['beak',beakM],['comb',combM],['blush',blushM],['wingL',chickWingM],['wingR',chickWingM],['tail',chickTailM],['legs',legM]]){ chM[k]=cInst(CHG[k],mat,CHN); chM[k].count=0; }\nfor(const k of ['eyeW','pupil','blush','comb','beak']) chM[k].castShadow=false;\nfunction initColors(m,n){ const c0=m.count; m.count=n; const c=new THREE.Color(0xffffff); for(let i=0;i<n;i++) m.setColorAt(i,c); m.instanceColor.needsUpdate=true; m.count=c0; }\ninitColors(chM.body,CHN);\n// ---------- lanes (polylines from the coop door) ----------\nconst COOP_DOOR=new V3(SPINE,0,COOP.z0+0.4);\nfunction mkPath(pts){ const P={pts,len:[0]}; for(let i=1;i<pts.length;i++) P.len.push(P.len[i-1]+pts[i].distanceTo(pts[i-1])); P.L=P.len[P.len.length-1]; return P; }\nfunction pathAt(P,s){ s=clamp(s,0,P.L); for(let i=1;i<P.pts.length;i++) if(s<=P.len[i]+1e-6){ const k=(s-P.len[i-1])/Math.max(1e-6,P.len[i]-P.len[i-1]); const a=P.pts[i-1], b=P.pts[i]; return {x:lerp(a.x,b.x,k),z:lerp(a.z,b.z,k),ry:Math.atan2(b.x-a.x,b.z-a.z)}; } const n=P.pts.length; return {x:P.pts[n-1].x,z:P.pts[n-1].z,ry:Math.atan2(P.pts[n-1].x-P.pts[n-2].x,P.pts[n-1].z-P.pts[n-2].z)}; }\nconst LANES=[ {id:0,path:mkPath([COOP_DOOR,SPLIT.clone(),PLATES[0].clone()]),q:[],cap:4,on:true},\n  {id:1,path:mkPath([COOP_DOOR,SPLIT.clone(),new V3(PLATES[1].x,0,SPLIT.z),PLATES[1].clone()]),q:[],cap:4,on:false},\n  {id:2,path:mkPath([COOP_DOOR,SPLIT.clone(),new V3(SPIT.x,0,SPLIT.z),new V3(SPIT.x,0,ROAST.z1-3.2)]),q:[],cap:2,on:false} ];\nconst TRUNK=COOP_DOOR.z-SPLIT.z; const GAP=3.3;\nconst chickens=[]; let coopT=0, laneRR=0;\nfunction spawnChicken(lane){ const c={lane,s:-R(0.5,1.0),ph:R(0,6),state:'walk',t:0,wing:0,squash:0,x:COOP_DOOR.x,z:COOP_DOOR.z+1,ry:Math.PI,spd:0,big:lane.id===2,mega:lane.id<2&&MEGA.next>0&&(MEGA.next--,true)}; chickens.push(c); lane.q.push(c); stat.chickens++; return c; }\n// ---------- stomp plates + the press ----------\nconst plateM=new THREE.MeshLambertMaterial({color:0x3f9ae6}), plateRimM=new THREE.MeshLambertMaterial({color:0x2166b0}), spikeM=new THREE.MeshLambertMaterial({color:0x2a4f86});\nconst plates=PLATES.map((P,i)=>{ const g=new THREE.Group(); g.position.set(P.x,DECK_Y,P.z); scene.add(g);\n  boxAt(g,3.0,0.36,3.0,0,0.18,0,plateRimM); boxAt(g,2.7,0.08,2.7,0,0.4,0,plateM);\n  for(let a=0;a<4;a++) for(let b=0;b<4;b++){ const sp=new THREE.Mesh(new THREE.ConeGeometry(0.13,0.36,6),spikeM); sp.position.set(-1.0+a*0.66,0.6,-1.0+b*0.66); g.add(sp); }\n  mergeGroup(g); return {i,P,g,cd:0,cur:null,auto:false,press:null,pressY:0}; });\nplates[1].g.visible=false;\nconst padTex=canvasTex(128,128,(g)=>{ g.fillStyle='#3fa8ff'; roundRect(g,8,8,112,112,24); g.fill(); g.strokeStyle='#fff'; g.lineWidth=8; roundRect(g,8,8,112,112,24); g.stroke(); g.fillStyle='#fff'; g.beginPath(); g.moveTo(64,26); g.lineTo(98,62); g.lineTo(78,62); g.lineTo(78,98); g.lineTo(50,98); g.lineTo(50,62); g.lineTo(30,62); g.closePath(); g.fill(); });\nconst padMesh=new THREE.Mesh(new THREE.BoxGeometry(1.5,0.16,1.5),[plateRimM,plateRimM,new THREE.MeshLambertMaterial({map:padTex}),plateRimM,plateRimM,plateRimM]); padMesh.position.set(PAD.x,DECK_Y+0.08,PAD.z); scene.add(padMesh);\nconst padLink=new THREE.Mesh(new THREE.BoxGeometry(PAD.x-PLATES[0].x-1.5,0.1,0.35),new THREE.MeshLambertMaterial({color:0x6fc4ff})); padLink.position.set((PAD.x+PLATES[0].x+1.5)/2-0.75,DECK_Y+0.05,PAD.z); scene.add(padLink);\nconst padRing=ring(1.05); padRing.position.set(PAD.x,DECK_Y+0.18,PAD.z);\n// hydraulic press (auto stomp) \u2014 built over a plate\nfunction makePress(pl){ const g=new THREE.Group(); g.position.set(pl.P.x,DECK_Y,pl.P.z); scene.add(g);\n  for(const sx of [-1.65,1.65]){ boxAt(g,0.34,6.4,0.34,sx,3.2,-1.2,M.yellow); boxAt(g,0.34,6.4,0.34,sx,3.2,1.2,M.yellow); }\n  boxAt(g,3.7,0.5,2.9,0,6.4,0,M.yellow); const cyl=new THREE.Mesh(new THREE.CylinderGeometry(0.35,0.35,1.2,12),M.steel); cyl.position.set(0,5.7,0); g.add(cyl);\n  const stripe=new THREE.MeshLambertMaterial({color:0x222222}); for(let k=0;k<4;k++) boxAt(g,0.36,0.25,0.36,-1.65,1.0+k*1.4,1.2,stripe);\n  mergeGroup(g); const head=new THREE.Group(); const hp=new THREE.Mesh(new THREE.BoxGeometry(2.7,0.4,2.5),M.iron); head.add(hp); const rod=new THREE.Mesh(new THREE.CylinderGeometry(0.18,0.18,2.0,10),M.steel); rod.position.y=1.2; head.add(rod); head.position.set(pl.P.x,DECK_Y+5.4,pl.P.z); scene.add(head); pl.press={g,head}; pl.auto=true; return pl.press; }\n// ---------- fryer pans ----------\nconst panM=new THREE.MeshLambertMaterial({color:0x9aa1ad}), panRimM=new THREE.MeshLambertMaterial({color:0x5d6672}), oilM=new THREE.MeshPhongMaterial({color:0x8a6a32,emissive:0x1a0e00,shininess:90,specular:0xffe0a0});\nconst heatM=new THREE.MeshBasicMaterial({color:0xff7a22}), heatM2=new THREE.MeshBasicMaterial({color:0xffd23a});\nconst pans=TRAYS.map((T,i)=>{ const g=new THREE.Group(); scene.add(g); const w=T.x1-T.x0, d=T.z1-T.z0, cx=(T.x0+T.x1)/2, cz=(T.z0+T.z1)/2;\n  boxAt(g,w+0.2,0.5,d+0.2,cx,DECK_Y+0.25,cz,panRimM); boxAt(g,w-0.3,0.06,d-0.3,cx,DECK_Y+0.53,cz,oilM);\n  for(const [x,z,ww,dd] of [[cx,T.z0,w+0.3,0.25],[cx,T.z1,w+0.3,0.25],[T.x1,cz,0.25,d+0.3]]) boxAt(g,ww,0.36,dd,x,DECK_Y+0.66,z,panRimM);\n  // heater bar on the west edge\n  boxAt(g,0.42,0.7,d+0.2,T.x0-0.12,DECK_Y+0.65,cz,M.red); boxAt(g,0.2,0.3,d-0.2,T.x0+0.08,DECK_Y+0.95,cz,heatM2);\n  for(const z of [T.z0+0.4,T.z1-0.4]) boxAt(g,0.5,1.9,0.5,T.x0-0.12,DECK_Y+0.95,z,M.yellow);\n  mergeGroup(g); const wave=new THREE.Mesh(new THREE.PlaneGeometry(0.9,d-0.4),new THREE.MeshBasicMaterial({color:0xffa040,transparent:true,opacity:0.0,depthWrite:false})); wave.rotation.x=-Math.PI/2; wave.position.set(T.x0,DECK_Y+0.72,cz); scene.add(wave);\n  return {i,T,g,wave,waveX:-1,on:i===0,sticks:0,cap:150}; });\npans[1].g.visible=false;\n// ---------- drumsticks (instanced: meat with per-instance colour + bone) ----------\nconst STN=900;\nconst STICK_MEAT=mergeGeos([new THREE.SphereGeometry(0.22,12,10).scale(1.0,0.82,1.55).translate(0,0,0.12)]);\nconst STICK_BONE=mergeGeos([new THREE.CylinderGeometry(0.055,0.06,0.32,8).rotateX(Math.PI/2).translate(0,0,-0.36),new THREE.SphereGeometry(0.075,8,6).translate(-0.04,0,-0.53),new THREE.SphereGeometry(0.075,8,6).translate(0.04,0,-0.53)]);\nconst meatM=new THREE.MeshLambertMaterial({color:0xffffff}), boneM=new THREE.MeshLambertMaterial({color:0xfff4e4});\nconst stMeat=cInst(STICK_MEAT,meatM,STN), stBone=cInst(STICK_BONE,boneM,STN); stMeat.count=0; stBone.count=0; stBone.castShadow=false; initColors(stMeat,STN);\nconst RAW=new THREE.Color(0xf4a9a0), FRIED=new THREE.Color(0xd8892a), FRIED2=new THREE.Color(0xb8641c), _c=new THREE.Color();\nconst sticks=[]; // {state, x,y,z, rx,ry,rz, cook(0..1), cookAt, pan, ...}\nfunction newStick(x,y,z){ if(sticks.length>=STN) return null; const s={state:'fly',x,y,z,ax:x,ay:y,az:z,bx:x,bz:z,by:DECK_Y+0.72,t:0,dur:0.55,h:1.6,rx:R(-0.3,0.3),ry:R(0,6.28),rz:R(-0.4,0.4),cook:0,cookAt:1e9,crisp:R(0,1),pan:null}; sticks.push(s); return s; }\nfunction killStick(s){ const i=sticks.indexOf(s); if(i>=0) sticks.splice(i,1); }\nfunction panSpot(pan){ const T=pan.T; const x=R(T.x0+0.45,T.x1-0.45), z=T.z1-0.45-Math.pow(rnd(),1.6)*(T.z1-T.z0-0.9); return [x,z]; }\nfunction panHeight(pan,x,z){ let n=0; for(const s of sticks) if(s.pan===pan&&(s.state==='pan'||s.state==='fly')&&Math.abs(s.bx-x)<0.45&&Math.abs(s.bz-z)<0.45) n++; return DECK_Y+0.7+Math.min(n,6)*0.17; }\n// ---------- feathers, tiny chicks ----------\nconst FEN=360; const featherM=new THREE.MeshLambertMaterial({color:0xffffff,side:THREE.DoubleSide});\nconst FEATHER=new THREE.PlaneGeometry(0.22,0.46); const feMesh=cInst(FEATHER,featherM,FEN); initColors(feMesh,FEN); feMesh.count=0; feMesh.castShadow=false; const feathers=[];\nconst featherCols=[new THREE.Color(0xfff6dc),new THREE.Color(0xf4cf73),new THREE.Color(0xffffff),new THREE.Color(0xeebd5a)];\nfunction featherBurst(x,y,z,n){ for(let i=0;i<n;i++){ if(feathers.length>=FEN) feathers.shift(); const a=R(0,6.28), sp=R(2,7); feathers.push({x:x+R(-0.8,0.8),y:y+R(-0.8,1.2),z:z+R(-0.8,0.8),vx:Math.cos(a)*sp,vy:R(3,9),vz:Math.sin(a)*sp,t:0,life:R(1.4,2.4),r1:R(0,6),r2:R(0,6),w:R(4,9),c:featherCols[i%4]}); } }\nconst CKG={body:new THREE.SphereGeometry(0.34,12,10).translate(0,0.36,0),head:new THREE.SphereGeometry(0.24,12,10).translate(0,0.72,0.12),beak:new THREE.ConeGeometry(0.07,0.16,8).rotateX(Math.PI/2).translate(0,0.68,0.38),eyes:mergeGeos([new THREE.SphereGeometry(0.05,6,5).translate(-0.1,0.78,0.31),new THREE.SphereGeometry(0.05,6,5).translate(0.1,0.78,0.31)])};\nconst chickYM=new THREE.MeshLambertMaterial({color:0xffe14a});\nconst ckM=[cInst(CKG.body,chickYM,14),cInst(CKG.head,chickYM,14),cInst(CKG.beak,beakM,14),cInst(CKG.eyes,pupilM,14)]; for(const m of ckM) m.count=0;\nconst chicks=[];\n// ---------- tray stats ----------\nfunction panOf(x,z){ for(const p of pans) if(p.on && inR(p.T,x,z,0.2)) return p; return null; }\n\nfunction stompBurst(pl,c){ const P=pl.P; c.state='gone'; const lane=c.lane; lane.q.shift();\n  featherBurst(P.x,DECK_Y+2.2,P.z,46); sparkle(P.x,DECK_Y+2.2,P.z,14,1.6); shake=Math.max(shake,0.35); noise(0.18,0.2,900); tone(160,0.25,'sine',0.12,0.4); tone(520,0.12,'triangle',0.05,1.6);\n  const pan=pans[pl.i]; pan.batchT=stat.T+0.55; pan.waveX=0; const n=c.mega?72:(c.big?36:20); if(c.mega){ featherBurst(P.x,DECK_Y+3,P.z,90); shake=Math.max(shake,0.8); tone(90,0.4,'sine',0.16,0.5); chicks.push({x:P.x,z:P.z,y:DECK_Y+0.5,t:0,dir:R(-0.6,0.6)-(pl.i?0.8:-0.8),vy:5}); }\n  for(let k=0;k<n;k++){ const s=newStick(P.x+R(-0.8,0.8),DECK_Y+1.6+R(0,1.4),P.z+R(-0.7,0.7)); if(!s) break; const [bx,bz]=panSpot(pan); s.by=panHeight(pan,bx,bz); s.bx=bx; s.bz=bz; s.dur=R(0.45,0.75); s.h=R(1.2,2.6); s.pan=pan; s.t=-k*0.012; pan.sticks++; stat.sticks++; }\n  // the tiny chick is left behind and runs away\n  chicks.push({x:P.x,z:P.z,y:DECK_Y+0.5,t:0,dir:R(-0.6,0.6)+(pl.i?0.8:-0.8),vy:4}); popText('+'+n,new V3(P.x,DECK_Y+4.4,P.z),'#ff9a2a',0.9,0.8);\n  killChicken(c); pl.cur=null; pl.cd=0.35; }\nfunction killChicken(c){ const i=chickens.indexOf(c); if(i>=0) chickens.splice(i,1); }\n\nfunction updateChickens(dt){\n  // coop releases chickens toward lanes that need them (one at a time through the door)\n  coopT-=dt; if(coopT<=0){ const need=LANES.filter(l=>l.on && l.q.length<l.cap); if(need.length){ const last=chickens[chickens.length-1]; if(!last||last.s>2.4||last.state!=='walk'){ need.sort((a,b)=>a.q.length-b.q.length); const L=need[(laneRR++)%Math.min(need.length,1)]; spawnChicken(L); coopT=0.55; } } }\n  for(const L of LANES){ const P=L.path; L.q.forEach((c,idx)=>{ if(c.state!=='walk') return;\n      let lim=P.L; for(let j=1;j<=idx;j++) lim-=GAP+(L.q[j-1].mega?1.9:0)+(L.q[j].mega?1.9:0); // slot in this lane's queue\n      // keep spacing with the chicken ahead on the shared trunk (spawn order)\n      if(c.s<TRUNK+0.2){ const ci=chickens.indexOf(c); for(let j=ci-1;j>=0;j--){ const o=chickens[j]; if(o.state==='walk'&&o.s<TRUNK+GAP){ lim=Math.min(lim,o.s-GAP); break; } } }\n      const spd=2.4; const ns=Math.min(c.s+spd*dt,lim); c.spd=(ns-c.s)/dt; c.s=Math.max(c.s,ns); const a=pathAt(P,c.s); c.x=a.x; c.z=a.z; c.ry=lerp(c.ry,a.ry,clamp(dt*8,0,1)); c.ph+=dt*(c.spd>0.2?9:2.5); }); }\n  // plates\n  for(const pl of plates){ if(!pl.g.visible) continue; const L=LANES[pl.i]; pl.cd-=dt; const front=L.q[0]; const pan=pans[pl.i];\n    const atPlate=front && front.state==='walk' && front.s>=L.path.L-0.05;\n    const trigger=pl.auto || (pl.i===0 && near(player.pos,PAD,0.95));\n    if(atPlate && pl.cd<=0 && trigger && pan.sticks<pan.cap-(front.mega?62:16)){ front.state='stomp'; front.t=0; pl.cur=front; }\n    if(pl.cur){ const c=pl.cur; c.t+=dt; const k=c.t; c.squash=k<0.25?k/0.25:1; c.wing=k; if(pl.press){ pl.pressY=k<0.35?k/0.35:1; } if(k>0.18&&!c.honk){ c.honk=1; tone(820,0.12,'square',0.04,0.6); tone(620,0.16,'square',0.035,0.7); }\n      if(k>=0.42){ stompBurst(pl,c); } }\n    if(pl.press){ if(!pl.cur) pl.pressY=Math.max(0,pl.pressY-dt*2.2); pl.press.head.position.y=DECK_Y+5.4-pl.pressY*2.2; } }\n  // render\n  let i=0; for(const c of chickens){ if(c.state==='gone'||i>=CHN) continue; const bob=c.spd>0.2?Math.abs(Math.sin(c.ph))*0.18:Math.sin(c.ph)*0.03; const roll=c.spd>0.2?Math.sin(c.ph)*0.12:Math.sin(c.ph*0.7)*0.03;\n    let sx=1, sy=1; if(c.state==='stomp'){ const k=c.squash; sy=1-0.22*k+Math.sin(c.t*60)*0.04; sx=1+0.18*k; }\n    const sc=CS*(c.mega?1.75:(c.big?1.18:1)); _e.set(0,c.ry,roll,'YXZ'); _q.setFromEuler(_e); _m.compose(_p.set(c.x,DECK_Y+(c.state==='stomp'?0.45:0)+bob,c.z),_q,_s.set(sc*sx,sc*sy,sc*sx));\n    for(const k of ['body','belly','eyeW','pupil','beak','comb','blush','tail','legs']) chM[k].setMatrixAt(i,_m);\n    const flap=c.state==='stomp'?0.9+Math.sin(c.t*55)*0.7:(c.spd>0.2?0.25+Math.sin(c.ph*2)*0.2:0.12);\n    for(const [k,sd] of [['wingL',-1],['wingR',1]]){ _e.set(0,0,sd*flap,'XYZ'); _q.setFromEuler(_e); _m2.compose(_p.set(sd*1.22,2.05,0.05),_q,_s.set(1,1,1)); _m2.premultiply(_m); chM[k].setMatrixAt(i,_m2); }\n    if(c.mega){ chM.body.setColorAt(i,_c.setHex(0xffb6c8)); } else if(c.big){ chM.body.setColorAt(i,_c.setHex(0xffd84a)); } else chM.body.setColorAt(i,_c.setHex(0xffffff)); i++; }\n  for(const k in chM){ chM[k].count=i; chM[k].instanceMatrix.needsUpdate=true; if(chM[k].instanceColor) chM[k].instanceColor.needsUpdate=true; }\n  // tiny chicks\n  let n=0; for(let k=chicks.length-1;k>=0;k--){ const c=chicks[k]; c.t+=dt; c.vy-=dt*14; c.y=Math.max(DECK_Y,c.y+c.vy*dt); if(c.y<=DECK_Y) c.vy=Math.abs(c.vy)*0.0; const sp=5.5; c.x+=Math.sin(c.dir+Math.PI*0.5)*sp*dt; c.z+=Math.cos(c.dir+Math.PI*0.5)*sp*dt*0.2+dt*2.6;\n    if(c.t>3.2){ chicks.splice(k,1); continue; } if(n>=14) continue; const ry=c.dir+Math.PI*0.5; _e.set(0,ry,Math.sin(c.t*30)*0.25,'YXZ'); _q.setFromEuler(_e); _m.compose(_p.set(c.x,c.y+Math.abs(Math.sin(c.t*26))*0.18,c.z),_q,_s.set(1.2,1.2,1.2)); for(const m of ckM) m.setMatrixAt(n,_m); n++; }\n  for(const m of ckM){ m.count=n; m.instanceMatrix.needsUpdate=true; } }\n\nfunction updateSticks(dt){ let cookedOnPan=0;\n  for(let k=sticks.length-1;k>=0;k--){ const s=sticks[k];\n    if(s.state==='fly'){ s.t+=dt; if(s.t<0) continue; const e=clamp(s.t/s.dur,0,1); s.x=lerp(s.ax,s.bx,e); s.z=lerp(s.az,s.bz,e); s.y=lerp(s.ay,s.by,e)+Math.sin(e*Math.PI)*s.h; s.ry+=dt*9; s.rx+=dt*7;\n      if(e>=1){ s.state='pan'; s.rx=R(-0.25,0.25); s.rz=R(-0.35,0.35); s.cookAt=Math.max(stat.T,(s.pan.batchT||stat.T))+(s.x-s.pan.T.x0)/4.2+0.15; if(rnd()<0.3) tone(300+rnd()*200,0.03,'sine',0.02); } }\n    else if(s.state==='pan'){ if(stat.T>=s.cookAt && s.cook<1){ s.cook=Math.min(1,s.cook+dt/1.1); if(s.cook>=1){ stat.cooked++; } if(rnd()<dt*3) sparkle(s.x,s.y+0.2,s.z,1,0.4); } if(s.cook>=1) cookedOnPan++; }\n    else if(s.state==='hop'){ s.t+=dt; const e=clamp(s.t/s.dur,0,1); const b=s.tgt(); s.x=lerp(s.ax,b.x,e); s.z=lerp(s.az,b.z,e); s.y=lerp(s.ay,b.y,e)+Math.sin(e*Math.PI)*s.h; s.ry+=dt*12; if(e>=1){ s.done(); } }\n    else if(s.state==='carry'){ const b=s.carrier.slot(s.slot); s.x=b.x; s.y=b.y; s.z=b.z; s.ry=b.ry; s.rx=0; s.rz=Math.PI/2; }\n    else if(s.state==='belt'){ /* positioned by the collector belt */ } }\n  // heat waves sweep west \u2192 east over each pan while it has raw sticks\n  for(const p of pans){ if(!p.on){ p.wave.material.opacity=0; continue; } const raw=sticks.some(s=>s.pan===p&&s.state==='pan'&&s.cook<1);\n    if(raw){ const x=p.T.x0+((stat.T-(p.batchT||0))*4.2)%(p.T.x1-p.T.x0+1.5); p.wave.position.x=Math.min(p.T.x1-0.3,x); p.wave.material.opacity=0.42+Math.sin(stat.T*20)*0.08; if(rnd()<dt*16) flame(p.wave.position.x+R(-0.3,0.3),DECK_Y+0.8,R(p.T.z0+0.4,p.T.z1-0.4),1,0.2,0.4); }\n    else p.wave.material.opacity=Math.max(0,p.wave.material.opacity-dt*2); }\n  // render\n  let n=0; for(const s of sticks){ if(n>=STN) break; _e.set(s.rx,s.ry,s.rz||0,'YXZ'); _q.setFromEuler(_e); _m.compose(_p.set(s.x,s.y,s.z),_q,_s.set(1.45,1.45,1.45)); stMeat.setMatrixAt(n,_m); stBone.setMatrixAt(n,_m);\n    const ck=s.cook; if(ck<=0) _c.copy(RAW); else { _c.copy(RAW).lerp(FRIED,Math.min(1,ck*1.15)); if(ck>0.85) _c.lerp(FRIED2,(ck-0.85)/0.15*0.35*s.crisp); } stMeat.setColorAt(n,_c); n++; }\n  stMeat.count=n; stBone.count=n; stMeat.instanceMatrix.needsUpdate=true; stBone.instanceMatrix.needsUpdate=true; if(stMeat.instanceColor) stMeat.instanceColor.needsUpdate=true;\n  // feathers\n  let f=0; for(let k=feathers.length-1;k>=0;k--){ const p=feathers[k]; p.t+=dt; if(p.t>p.life){ feathers.splice(k,1); continue; } p.vy-=dt*(p.vy>-1.2?9:0); p.vx*=1-dt*1.6; p.vz*=1-dt*1.6; p.x+=p.vx*dt+Math.sin(p.t*p.w)*dt*0.8; p.y=Math.max(DECK_Y+0.05,p.y+p.vy*dt); p.z+=p.vz*dt;\n    _e.set(p.r1+p.t*p.w*0.6,p.r2+p.t*2,Math.sin(p.t*p.w)*0.8); _q.setFromEuler(_e); const sc=1-Math.max(0,(p.t-p.life+0.4)/0.4); _m.compose(_p.set(p.x,p.y,p.z),_q,_s.set(sc,sc,sc)); feMesh.setMatrixAt(f,_m); feMesh.setColorAt(f,p.c); f++; }\n  feMesh.count=f; feMesh.instanceMatrix.needsUpdate=true; if(feMesh.instanceColor) feMesh.instanceColor.needsUpdate=true;\n  for(const p of pans) p.cooked=0; for(const s of sticks) if(s.state==='pan'&&s.cook>=1) s.pan.cooked++; }\n// ================= PLAYER STACK \u00b7 packer \u00b7 buckets \u00b7 counter \u00b7 customers \u00b7 cash \u00b7 racks \u00b7 bikes =================\nconst PLAYER_SCALE=1.2;\nconst chef=makeHuman({coat:new THREE.MeshLambertMaterial({color:0x2a9bd6}),hat:'chef',scale:PLAYER_SCALE});\nchef.position.set(PAD.x-1.2,DECK_Y,PAD.z-2.4);\nconst player={pos:chef.position, vel:new V3(), face:Math.PI, bills:0, stack:[], cap:24, moved:0};\n// the drumstick tower on the player's back: each stick lies across, the tower leans with movement\nplayer.lean=new V3(); player.slot=function(i){ const f=this.face; const bx=this.pos.x-Math.sin(f)*0.5, bz=this.pos.z-Math.cos(f)*0.5; const h=i*0.27; const k=(i/24)*(i/24);\n  return {x:bx-this.lean.x*k*2.2, y:DECK_Y+1.35+h, z:bz-this.lean.z*k*2.2, ry:f+Math.PI/2+Math.sin(i*1.7)*0.25}; };\nfunction stackPush(who,s){ s.state='carry'; s.carrier=who; s.slot=who.stack.length; who.stack.push(s); }\n// ---------- packer ----------\nconst packG=new THREE.Group(); scene.add(packG);\n{ boxAt(packG,2.4,1.5,2.0,PACKER.x,DECK_Y+0.75,PACKER.z,M.blue); boxAt(packG,2.6,0.2,2.2,PACKER.x,DECK_Y+1.6,PACKER.z,M.blueDark);\n  const fun=new THREE.Mesh(new THREE.CylinderGeometry(0.9,0.45,0.9,4,1,true),M.steel); fun.rotation.y=Math.PI/4; fun.position.set(PACKER.x+0.3,DECK_Y+2.15,PACKER.z+0.2); packG.add(fun);\n  boxAt(packG,1.0,0.6,0.1,PACKER.x,DECK_Y+0.9,PACKER.z-1.02,M.dark); boxAt(packG,0.9,0.5,0.05,PACKER.x,DECK_Y+0.95,PACKER.z-1.06,new THREE.MeshBasicMaterial({color:0x8fe6ff}));\n  boxAt(packG,0.9,0.12,2.2,PACKER.x,DECK_Y+0.45,PACKER.z-2.0,M.iron);\n  mergeGroup(packG); }\nconst packRing=ring(1.0); packRing.position.set(PACK_IN.x,DECK_Y+0.05,PACK_IN.z);\nconst packer={q:0,t:0,out:[],bump:0};\n// ---------- buckets (instanced) ----------\nconst bucketTex=canvasTex(128,64,(g,w,h)=>{ for(let i=0;i<8;i++){ g.fillStyle=i%2?'#ffffff':'#e0392e'; g.fillRect(i*16,0,16,h); } g.fillStyle='rgba(0,0,0,.08)'; g.fillRect(0,h-6,w,6); });\nconst BUCKET_GEO=new THREE.CylinderGeometry(0.34,0.26,0.62,16,1,true).translate(0,0.31,0);\nconst BUCKET_TOP=mergeGeos([new THREE.SphereGeometry(0.17,8,6).translate(-0.12,0.66,0.05),new THREE.SphereGeometry(0.16,8,6).translate(0.12,0.68,-0.06),new THREE.SphereGeometry(0.15,8,6).translate(0.02,0.74,0.1),new THREE.SphereGeometry(0.14,8,6).translate(0.0,0.64,-0.16),new THREE.CircleGeometry(0.33,16).rotateX(-Math.PI/2).translate(0,0.6,0)]);\nconst BKN=420; const bkMesh=cInst(BUCKET_GEO,new THREE.MeshLambertMaterial({map:bucketTex,side:THREE.DoubleSide}),BKN), bkTop=cInst(BUCKET_TOP,new THREE.MeshLambertMaterial({color:0xd8892a}),BKN); bkMesh.count=0; bkTop.count=0; bkTop.castShadow=false;\nconst buckets=[]; // {state, x,y,z, ...}\nconst stocks={counter:0,rackW:0,rackE:0}; let counterPend=0, rackWPend=0, rackEPend=0;\nconst COUNTER_CAP=28;\nfunction counterSlot(i){ const r=Math.floor(i/14), c=i%14; return new V3(-0.2+c*0.6, DECK_Y+1.02, COUNTER.z-0.2+r*0.5); }\nfunction rackSlot(R2,i){ const cols=Math.floor((R2.z1-R2.z0)/0.62), layer=Math.floor(i/(cols*2)), j=i%(cols*2); const c=j%cols, row=Math.floor(j/cols); return new V3(R2.x0+0.5+row*(R2.x1-R2.x0-1.0), DECK_Y+0.35+layer*0.75, R2.z0+0.32+c*0.62); }\nconst RACK_CAP=36;\nfunction newBucket(x,y,z){ const b={state:'fly',x,y,z,ry:R(0,6.28)}; buckets.push(b); stat.buckets++; return b; }\nfunction killBucket(b){ const i=buckets.indexOf(b); if(i>=0) buckets.splice(i,1); }\nfunction bucketHop(b,to,dur,h,done){ b.state='hop'; b.ax=b.x; b.ay=b.y; b.az=b.z; b.to=to; b.t=0; b.dur=dur; b.h=h; b.done=done; }\n// the roller along the back of the counter: buckets ride east to their slot\nconst roller={items:[]};\nconst rollerG=new THREE.Group(); scene.add(rollerG); { const L=COUNTER.x1+1.2-PACKER.x; boxAt(rollerG,L,0.5,0.6,(PACKER.x+COUNTER.x1+1.2)/2,DECK_Y+0.7,COUNTER.z+0.85,M.iron); boxAt(rollerG,L,0.08,0.5,(PACKER.x+COUNTER.x1+1.2)/2,DECK_Y+0.97,COUNTER.z+0.85,M.dark); for(let x=PACKER.x;x<COUNTER.x1+1.2;x+=1.6) boxAt(rollerG,0.12,0.7,0.12,x,DECK_Y+0.35,COUNTER.z+0.85,M.iron); mergeGroup(rollerG); }\nconst ROLL_Z=COUNTER.z+0.85, ROLL_Y=DECK_Y+1.02;\n// ---------- counter, windows, cashiers ----------\nconst counterG=new THREE.Group(); scene.add(counterG);\n{ const L=COUNTER.x1-COUNTER.x0, cx=(COUNTER.x0+COUNTER.x1)/2; boxAt(counterG,L,0.95,1.0,cx,DECK_Y+0.47,COUNTER.z,M.wood); boxAt(counterG,L+0.2,0.1,1.15,cx,DECK_Y+0.98,COUNTER.z,M.woodTop);\n  for(let x=COUNTER.x0+0.4;x<COUNTER.x1;x+=0.9) boxAt(counterG,0.45,0.85,0.04,x,DECK_Y+0.45,COUNTER.z-0.52,new THREE.MeshLambertMaterial({color:0xe0392e}));\n  mergeGroup(counterG); }\nfunction makeWindow(W,col){ const g=new THREE.Group(); scene.add(g); for(const sx of [-1.1,1.1]) boxAt(g,0.16,2.6,0.16,W.x+sx,DECK_Y+1.3,COUNTER.z-0.3,M.woodDark);\n  const awM=new THREE.MeshLambertMaterial({color:col}); for(let k=0;k<5;k++){ const st=boxAt(g,0.5,0.08,1.4,W.x-1.0+k*0.5,DECK_Y+2.65,COUNTER.z-0.45,k%2?M.white:awM); st.rotation.x=0.28; }\n  const reg=boxAt(g,0.6,0.35,0.45,W.x+0.8,DECK_Y+1.17,COUNTER.z+0.1,M.dark); mergeGroup(g); return g; }\nconst win0G=makeWindow(WIN[0],0xe0392e), win1G=makeWindow(WIN[1],0x2f86d8); win1G.visible=false;\nfunction staff(color,hood,x,z){ const h=makeHuman({coat:new THREE.MeshLambertMaterial({color}),hat:'cap',hoodMat:new THREE.MeshLambertMaterial({color:hood}),scale:1.05}); h.position.set(x,DECK_Y,z); return h; }\nconst cashiers=[staff(0xe0392e,0xffffff,WIN[0].x+0.8,COUNTER.z+0.75)]; cashiers[0].rotation.y=Math.PI;\n// ---------- customers (instanced crowd), one queue per open window ----------\nconst CUN=56; const custCoats=[0x3a7fd0,0x46a35a,0xe08a2a,0x9a4fc4,0xd24b6a,0x2bb3b0];\nconst custM=new THREE.MeshLambertMaterial({color:0xffffff}), custHat=new THREE.MeshLambertMaterial({color:0xf4f6fa});\nconst cms=[cInst(CG.body,custM,CUN),cInst(CG.belly,custM,CUN),cInst(CG.head,M.skin,CUN),cInst(CG.hat,custHat,CUN),cInst(CG.e1,M.dark,CUN),cInst(CG.e2,M.dark,CUN)], carm=cInst(CG.arm,custM,CUN*2);\ninitColors(cms[0],CUN); initColors(cms[1],CUN); initColors(carm,CUN*2);\nconst QSP=0.95, QZ0=WIN[0].z-0.7;\nconst custs=[]; for(let i=0;i<CUN;i++){ const w=i<28?0:1; custs.push({w,x:WIN[w].x,z:QZ0-(i%28)*QSP-R(0,0.3),tx:0,tz:0,state:w===0?'queue':'off',want:1+Math.floor(rnd()*3),got:0,carry:[],ph:R(0,6),col:new THREE.Color(custCoats[i%custCoats.length]),cheer:0,path:null}); }\ncusts.forEach(c=>c.col&&0);\nconst winState=[{t:0,open:true,sold:0},{t:0,open:false,sold:0}];\nconst PRICE_WALK=4;\nconst bubbleTex={}; function bubbleFor(n){ if(bubbleTex[n]) return bubbleTex[n]; return bubbleTex[n]=spriteTex((g)=>{ g.fillStyle='#fff'; g.beginPath(); g.arc(64,56,50,0,7); g.fill(); g.beginPath(); g.moveTo(50,98); g.lineTo(64,122); g.lineTo(78,98); g.fill();\n  for(let k=0;k<8;k++){ g.fillStyle=k%2?'#ffffff':'#e0392e'; g.fillRect(36+k*6,44,6,34); } g.strokeStyle='#b8261e'; g.lineWidth=3; g.strokeRect(36,44,48,34); g.fillStyle='#d8892a'; g.beginPath(); g.ellipse(60,42,26,10,0,0,7); g.fill(); g.fillStyle='#2a2a2a'; g.font='900 30px Arial'; g.textAlign='center'; g.textBaseline='middle'; g.fillText('\u00d7'+n,94,86); }); }\nconst bubbles=[]; for(let i=0;i<8;i++) bubbles.push(sprite(bubbleFor(1),1.05));\n// ---------- cash pile ----------\nconst CASH={pos:CASH_P,n:0,pend:0};\nfunction pileTop(pl,i){ const col=i%16, row=Math.floor(i/16); return new V3(pl.pos.x+((col%4)-1.5)*0.64+(hsh(i)-0.5)*0.1, DECK_Y+0.06+row*0.11, pl.pos.z+(Math.floor(col/4)-1.5)*0.38+(hsh(i+3)-0.5)*0.1); }\nfunction payBill(from,k,pl){ pl=pl||CASH; stat.earned++; const idx=pl.n+pl.pend; pl.pend++; const to=pileTop(pl,Math.min(idx,239)); const d=from.distanceTo(to); fly('bill',from,to,0.45+d*0.025,1.1+d*0.12,()=>{ pl.n++; pl.pend--; },{t:-k*0.05}); }\nfunction backSlot(i){ const f=player.face, sd=(i%2?0.2:-0.2); return new V3(player.pos.x+Math.sin(f)*0.15+Math.cos(f)*sd, player.pos.y+0.9+Math.floor(i/2)*0.11, player.pos.z+Math.cos(f)*0.15-Math.sin(f)*sd); }\n\nfunction updatePacker(dt){ packer.t-=dt; packer.bump=Math.max(0,packer.bump-dt*4);\n  // the player drops the drumstick tower into the packer\n  player.dT=(player.dT||0)-dt;\n  if(player.stack.length && near(player.pos,PACK_IN,1.25)){ while(player.dT<=0 && player.stack.length){ player.dT+=0.035; const s=player.stack.pop(); s.state='hop'; s.ax=s.x; s.ay=s.y; s.az=s.z; s.t=0; s.dur=0.35; s.h=1.0; s.tgt=()=>new V3(PACKER.x+0.3,DECK_Y+2.4,PACKER.z+0.2); s.done=()=>{ killStick(s); packer.q++; packer.bump=1; }; tick(player.stack.length); } }\n  if(player.dT<0) player.dT=0;\n  // 4 drumsticks \u2192 one bucket\n  if(packer.q>=4 && packer.t<=0){ const dest=bucketDest(); if(dest){ packer.q-=4; packer.t=0.22; stat.packed+=4; const b=newBucket(PACKER.x,DECK_Y+0.6,PACKER.z-1.6); dest(b); noise(0.05,0.06,1400); } } }\nlet bdAlt=0;\nfunction bucketDest(){ // where does a new bucket go: the counter first, then the storage walls (during the feast every 2nd bucket goes to the bike wall)\n  const toW=(b)=>{ const idx=stocks.rackW+rackWPend; rackWPend++; bucketHop(b,rackSlot(RACK_W,idx),0.45,1.2,()=>{ stocks.rackW++; rackWPend--; b.state='rack'; b.rack='W'; b.idx=idx; }); };\n  if(FEST.on && !FEST.done && FEST.got.bucket<FEST.need.bucket && RACK_CAP-stocks.rackW-rackWPend>0 && (bdAlt=(bdAlt+1)%2)===0) return toW;\n  if(stocks.counter+counterPend<COUNTER_CAP) return (b)=>{ const idx=stocks.counter+counterPend; counterPend++; roller.items.push({b,idx,x:PACKER.x}); b.state='roll'; b.x=PACKER.x; b.y=ROLL_Y; b.z=ROLL_Z; };\n  if(has('racks')){ const wNeed=has('bikes')?1:0.5, eNeed=has('trucks')?1:0.4;\n    const wRoom=RACK_CAP-stocks.rackW-rackWPend, eRoom=RACK_CAP-stocks.rackE-rackEPend;\n    if(wRoom>0 && (wRoom*wNeed>=eRoom*eNeed || eRoom<=0)) return (b)=>{ const idx=stocks.rackW+rackWPend; rackWPend++; bucketHop(b,rackSlot(RACK_W,idx),0.45,1.2,()=>{ stocks.rackW++; rackWPend--; b.state='rack'; b.rack='W'; b.idx=idx; }); };\n    if(eRoom>0) return (b)=>{ const idx=stocks.rackE+rackEPend; rackEPend++; roller.items.push({b,idx,x:PACKER.x,toRack:true}); b.state='roll'; b.x=PACKER.x; b.y=ROLL_Y; b.z=ROLL_Z; }; }\n  return null; }\nfunction updateBuckets(dt){\n  // roller\n  for(let k=roller.items.length-1;k>=0;k--){ const it=roller.items[k]; const tx=it.toRack?COUNTER.x1+0.8:counterSlot(it.idx).x; it.x=Math.min(tx,it.x+3.4*dt); it.b.x=it.x; it.b.y=ROLL_Y; it.b.z=ROLL_Z;\n    if(it.x>=tx-0.01){ roller.items.splice(k,1); const b=it.b; if(it.toRack){ bucketHop(b,rackSlot(RACK_E,it.idx),0.4,1.0,()=>{ stocks.rackE++; rackEPend--; b.state='rack'; b.rack='E'; b.idx=it.idx; }); } else { bucketHop(b,counterSlot(it.idx),0.22,0.3,()=>{ stocks.counter++; counterPend--; b.state='counter'; b.idx=it.idx; }); } } }\n  for(const b of buckets){ if(b.state==='hop'){ b.t+=dt; const e=clamp(b.t/b.dur,0,1); const T=typeof b.to==='function'?b.to():b.to; b.x=lerp(b.ax,T.x,e); b.z=lerp(b.az,T.z,e); b.y=lerp(b.ay,T.y,e)+Math.sin(e*Math.PI)*b.h; b.ry+=dt*6; if(e>=1){ b.x=T.x; b.y=T.y; b.z=T.z; const d=b.done; b.done=null; b.state='idle'; if(d) d(); } }\n    else if(b.state==='held'){ const c=b.holder; const p=c.handPos(b.hi); b.x=p.x; b.y=p.y; b.z=p.z; } }\n  // keep counter/rack buckets packed at the right slots (when one is taken, the last one fills the gap visually)\n  const cb=buckets.filter(b=>b.state==='counter').sort((a,b)=>a.idx-b.idx); cb.forEach((b,i)=>{ b.idx=i; const p=counterSlot(i); b.x=p.x; b.y=p.y; b.z=p.z; });\n  for(const r of ['W','E']){ const rb=buckets.filter(b=>b.state==='rack'&&b.rack===r).sort((a,b)=>a.idx-b.idx); rb.forEach((b,i)=>{ b.idx=i; const p=rackSlot(r==='W'?RACK_W:RACK_E,i); b.x=p.x; b.y=p.y; b.z=p.z; }); }\n  let n=0; for(const b of buckets){ if(n>=BKN) break; _e.set(0,b.ry,0); _q.setFromEuler(_e); _m.compose(_p.set(b.x,b.y-0.02,b.z),_q,_s.set(1,1,1)); bkMesh.setMatrixAt(n,_m); bkTop.setMatrixAt(n,_m); n++; }\n  bkMesh.count=n; bkTop.count=n; bkMesh.instanceMatrix.needsUpdate=true; bkTop.instanceMatrix.needsUpdate=true; }\nfunction takeCounterBucket(){ const cb=buckets.filter(b=>b.state==='counter'); if(!cb.length) return null; cb.sort((a,b)=>b.idx-a.idx); const b=cb[0]; b.state='idle'; stocks.counter--; return b; }\nfunction takeRackBucket(r){ const rb=buckets.filter(b=>b.state==='rack'&&b.rack===r); if(!rb.length) return null; rb.sort((a,b)=>b.idx-a.idx); const b=rb[0]; b.state='idle'; if(r==='W') stocks.rackW--; else stocks.rackE--; return b; }\n\nfunction custHand(c){ return (i)=>{ const f=c.face||0; return new V3(c.x+Math.sin(f)*0.42+Math.cos(f)*((i%2)-0.5)*0.4, DECK_Y+0.95+Math.floor(i/2)*0.55, c.z+Math.cos(f)*0.42-Math.sin(f)*((i%2)-0.5)*0.4); }; }\nfunction updateCustomers(dt){ let bi=0;\n  for(let w=0;w<2;w++){ const W=WIN[w], ws=winState[w]; if(!ws.open) continue; ws.t-=dt;\n    const line=custs.filter(c=>c.w===w&&c.state==='queue').sort((a,b)=>b.z-a.z); line.forEach((c,k)=>{ c.tx=W.x+(k?Math.sin(k*1.9)*0.18:0); c.tz=QZ0-k*QSP; c.slot=k; });\n    const f=line[0]; if(f && Math.abs(f.z-f.tz)<0.2 && ws.t<=0 && stocks.counter>0 && f.got<f.want){ const b=takeCounterBucket(); if(b){ ws.t=0.78; const hi=f.got; f.got++; b.holder=f; b.hi=hi; f.carry.push(b); f.handPos=custHand(f); bucketHop(b,()=>f.handPos(hi),0.35,0.8,()=>{ b.state='held'; }); if(f.got>=f.want){ f.state='pay'; f.t=0.35; } } }\n    for(const c of line.slice(0,4)){ if(bi<bubbles.length && Math.abs(c.z-c.tz)<0.6){ const sp=bubbles[bi++]; sp.visible=true; sp.material.map=bubbleFor(c.want-c.got); sp.position.set(c.x,DECK_Y+2.5+Math.sin(stat.T*3+c.x)*0.06,c.z); } } }\n  for(;bi<bubbles.length;bi++) bubbles[bi].visible=false;\n  for(const c of custs){ if(c.state==='off') continue; c.ph+=dt*6; c.cheer=Math.max(0,c.cheer-dt);\n    if(c.state==='pay'){ c.t-=dt; if(c.t<=0){ const nb=c.want*PRICE_WALK; for(let k=0;k<nb;k++) payBill(new V3(c.x,DECK_Y+1.6,c.z),k); stat.sold+=c.want; stat.soldBy.walk=(stat.soldBy.walk||0)+c.want; winState[c.w].sold+=c.want; festAdd('bucket',0); c.cheer=0.7; c.state='leave'; const W=WIN[c.w]; c.path=[new V3(W.x+1.2,0,c.z-0.6),new V3(W.x+1.2,0,STREET.z1+0.5),new V3(W.x+(c.w?14:-14),0,(STREET.z0+STREET.z1)/2)]; } }\n    else if(c.state==='leave'){ const p=c.path[0]; const dx=p.x-c.x, dz=p.z-c.z, L=Math.hypot(dx,dz); if(L<0.15){ c.path.shift(); if(!c.path.length){ for(const b of c.carry) killBucket(b); c.carry=[]; c.got=0; c.want=1+Math.floor(rnd()*3); c.state='queue'; c.x=WIN[c.w].x+R(-0.3,0.3); c.z=QZ0-30*QSP; } continue; } const s=Math.min(L,dt*3.6); c.x+=dx/L*s; c.z+=dz/L*s; c.face=Math.atan2(dx,dz); }\n    else { const dx=c.tx-c.x, dz=c.tz-c.z, L=Math.hypot(dx,dz); if(L>0.04){ const s=Math.min(L,dt*3.8); c.x+=dx/L*s; c.z+=dz/L*s; } c.face=Math.PI*0; c.face=0; c.mv=L>0.1; } }\n  let i=0; for(const c of custs){ if(c.state==='off'){ _m.makeScale(1e-4,1e-4,1e-4); for(const m of cms) m.setMatrixAt(i,_m); carm.setMatrixAt(i*2,_m); carm.setMatrixAt(i*2+1,_m); i++; continue; }\n    const mv=c.state==='leave'||c.mv; const y=0.3+(mv?Math.abs(Math.sin(c.ph))*0.1:0)+(c.cheer>0?Math.sin((1-c.cheer/0.7)*Math.PI)*0.4:0); _e.set(0,c.face||0,0,'YXZ'); _q.setFromEuler(_e); _m.compose(_p.set(c.x,y,c.z),_q,_s.set(1,1,1));\n    for(const m of cms) m.setMatrixAt(i,_m); cms[0].setColorAt(i,c.col); cms[1].setColorAt(i,c.col);\n    const carry=c.carry.length>0; const aL=carry?-1.4:(c.slot===0&&c.state==='queue'?-2.4-Math.sin(c.ph)*0.3:0.1+Math.sin(c.ph)*0.3*(mv?1:0)), aR=carry?-1.4:(c.slot===0&&c.state==='queue'?-2.4+Math.sin(c.ph)*0.3:0.1-Math.sin(c.ph)*0.3*(mv?1:0));\n    for(let q=0;q<2;q++){ _e.set(q?aR:aL,0,q?-0.15:0.15,'XYZ'); _q.setFromEuler(_e); _m2.compose(_p.set(q?0.35:-0.35,1.02,0),_q,_s.set(1,1,1)); _m2.premultiply(_m); carm.setMatrixAt(i*2+q,_m2); carm.setColorAt(i*2+q,c.col); } i++; }\n  for(const m of [...cms,carm]){ m.instanceMatrix.needsUpdate=true; if(m.instanceColor) m.instanceColor.needsUpdate=true; } }\n\n// ---------- storage walls (racks) ----------\nfunction makeRack(R2){ const g=new THREE.Group(); g.visible=false; scene.add(g); const cx=(R2.x0+R2.x1)/2, cz=(R2.z0+R2.z1)/2, w=R2.x1-R2.x0, d=R2.z1-R2.z0;\n  for(let l=0;l<3;l++) boxAt(g,w,0.08,d,cx,DECK_Y+0.3+l*0.75,cz,M.woodTop); for(const [x,z] of [[R2.x0,R2.z0],[R2.x1,R2.z0],[R2.x0,R2.z1],[R2.x1,R2.z1]]) boxAt(g,0.12,2.4,0.12,x,DECK_Y+1.2,z,M.woodDark);\n  mergeGroup(g); return g; }\nconst rackWG=makeRack(RACK_W), rackEG=makeRack(RACK_E);\n// ---------- delivery bikes (west road) ----------\nconst roadM=new THREE.MeshLambertMaterial({color:0x4a5060}), lineM=new THREE.MeshBasicMaterial({color:0xf4f6fa});\nconst bikeRoadG=new THREE.Group(); bikeRoadG.visible=false; scene.add(bikeRoadG);\n{ const x0=-40, x1=FAC.x0-0.2, z0=3.4, z1=6.6; boxAt(bikeRoadG,x1-x0,0.06,z1-z0,(x0+x1)/2,0.33,(z0+z1)/2,roadM); for(let x=x0+1;x<x1-1;x+=2.4) boxAt(bikeRoadG,1.2,0.02,0.14,x,0.37,(z0+z1)/2,lineM); boxAt(bikeRoadG,2.4,0.03,2.6,BIKE_BAY.x,0.38,BIKE_BAY.z,new THREE.MeshBasicMaterial({color:0xf2b632})); mergeGroup(bikeRoadG); }\nfunction makeBike(){ const g=new THREE.Group(); scene.add(g); const bodyM=new THREE.MeshLambertMaterial({color:0x2f86d8});\n  boxAt(g,0.7,0.35,1.6,0,0.55,0,bodyM); boxAt(g,0.5,0.8,0.25,0,0.95,0.7,bodyM); boxAt(g,0.9,0.75,0.8,0,1.05,-0.55,new THREE.MeshLambertMaterial({color:0xf2b632}));\n  for(const z of [-0.65,0.65]){ const w=new THREE.Mesh(new THREE.CylinderGeometry(0.3,0.3,0.18,12),M.dark); w.rotation.z=Math.PI/2; w.position.set(0,0.3,z); g.add(w); }\n  const rider=new THREE.Group(); const t=new THREE.Mesh(new THREE.CylinderGeometry(0.25,0.3,0.6,10),new THREE.MeshLambertMaterial({color:0x2a5fa8})); t.position.y=1.25; rider.add(t); const h=new THREE.Mesh(new THREE.SphereGeometry(0.24,12,10),M.skin); h.position.y=1.72; rider.add(h); const hm=new THREE.Mesh(new THREE.SphereGeometry(0.27,12,10,0,6.3,0,1.6),new THREE.MeshLambertMaterial({color:0xf2b632})); hm.position.y=1.76; rider.add(hm); rider.position.z=0.1; g.add(rider);\n  mergeGroup(g); g.visible=false; return g; }\nconst bikes=[]; function addBike(){ const g=makeBike(); const b={g,state:'away',t:R(0.5,2.5)+bikes.length*1.6,x:-44,z:BIKE_BAY.z,load:[],k:bikes.length}; bikes.push(b); return b; }\nconst BIKE_ORDER=3, PRICE_BIKE=5;\nfunction updateBikes(dt){ if(!has('bikes')) return; const bay=BIKE_BAY;\n  const loading=bikes.find(b=>b.state==='load');\n  // bikes waiting in line behind the bay\n  const inLine=bikes.filter(b=>b.state==='in').sort((a,b)=>b.x-a.x);\n  for(const b of bikes){ b.g.visible=b.x>-43;\n    if(b.state==='away'){ b.t-=dt; b.x=-44; if(b.t<=0) b.state='in'; }\n    else if(b.state==='in'){ const k=inLine.indexOf(b); const stop=bay.x-(loading?2.8:0)-k*2.8; b.x=Math.min(stop,b.x+dt*11); b.z=bay.z;\n      if(b.x>=bay.x-0.01 && !bikes.some(o=>o!==b&&o.state==='load')){ b.state='load'; b.t=0.2; if(b.paid){ for(let q=0;q<b.paid;q++) payBill(new V3(b.x,1.6,b.z),q); b.paid=0; } } }\n    else if(b.state==='load'){ b.t-=dt; if(b.ord===undefined){ const fo=festBikeOrder(); b.fest=fo>0; b.ord=fo>0?fo:BIKE_ORDER; } if(b.load.length<b.ord){ if(b.t<=0 && stocks.rackW>0){ const bk=takeRackBucket('W'); if(bk){ b.t=0.22; const hi=b.load.length; b.load.push(bk); bucketHop(bk,()=>bikeSlot(b,hi),0.4,1.0,()=>{ bk.state='bike'; }); tick(hi); } } }\n      else if(b.t<=0){ b.state='out'; if(b.fest) b.path=FEAST_BIKE_PATH.map(p=>p.clone()); } }\n    else if(b.state==='out' && b.fest){ if(festBikeMove(b,dt)){ b.state='drop'; b.t=0.1; } }\n    else if(b.state==='drop'){ b.t-=dt; if(b.load.length){ if(b.t<=0){ b.t=0.13; const bk=b.load.pop(); feastBucket(bk); for(let q=0;q<FEAST_PRICE_B;q++) payBill(new V3(b.x,1.6,b.z),q); stat.sold++; stat.soldBy.feastB=(stat.soldBy.feastB||0)+1; } } else if(b.t<=0){ b.state='back'; b.path=FEAST_BIKE_PATH.slice(0,-1).reverse().map(p=>p.clone()); } }\n    else if(b.state==='back'){ if(festBikeMove(b,dt)){ b.state='in'; b.fest=false; b.ord=undefined; b.z=BIKE_BAY.z; } }\n    else if(b.state==='out'){ b.x-=dt*12; if(b.x<-44){ b.ord=undefined; for(const bk of b.load) killBucket(bk); stat.sold+=b.load.length; stat.soldBy.bike=(stat.soldBy.bike||0)+b.load.length; festAdd('bucket',b.festN||0); b.paid=b.load.length*PRICE_BIKE; b.load=[]; b.state='away'; b.t=4.5+R(0,1.5); } }\n    for(const bk of b.load) if(bk.state==='bike'){ const p=bikeSlot(b,b.load.indexOf(bk)); bk.x=p.x; bk.y=p.y; bk.z=p.z; }\n    if(!b.fest||b.state==='load'||b.state==='in'||b.state==='away') b.ry=(b.state==='out')?-Math.PI/2:Math.PI/2; b.g.position.set(b.x,0.33,b.z); b.g.rotation.y=b.ry; } }\nfunction bikeSlot(b,i){ const ry=b.ry===undefined?Math.PI/2:b.ry, sx=Math.sin(ry), cz=Math.cos(ry), l=((i%2)-0.5)*0.36, bk=0.55+(Math.floor(i/2)%3===2?0.0:0); return new V3(b.x-sx*bk+cz*l,1.55+Math.floor(i/2)*0.55,b.z-cz*bk-sx*l); }\n// ================= STATIC BUILD: yard, fences, coop, chicken lanes, splitter, collector belt =================\ndeckSlab(scene,FAC);\n// customer apron (stone) south of the counter, and the queue lanes down to the street\n{ const g=new THREE.Group(); scene.add(g); const ap=slab2(FAC.x1-FAC.x0+2,0.3,FAC.z0-STREET.z1+1.8,M.stone,M.stone); ap.position.set((FAC.x0+FAC.x1)/2,0.2,(FAC.z0+STREET.z1)/2+0.9); g.add(ap);\n  const st=slab2(STREET.x1-STREET.x0,0.3,STREET.z1-STREET.z0,M.stone,M.stone); st.position.set((STREET.x0+STREET.x1)/2,0.2,(STREET.z0+STREET.z1)/2); g.add(st); mergeGroup(g); }\n// fences around the yard (openings: chicken lanes north, windows south, bike bay west, truck bay east)\nconst fenceG=new THREE.Group(); scene.add(fenceG);\nlogFence(fenceG,[[FAC.x0+0.15,FAC.z1-0.15,FAC.x1-0.15,FAC.z1-0.15,(x)=>Math.abs(x-PLATES[0].x)<1.8||Math.abs(x-PLATES[1].x)<1.8],\n  [FAC.x0+0.15,FAC.z0+0.15,FAC.x0+0.15,FAC.z1-0.15,(x,z)=>z>RACK_W.z0-0.2&&z<RACK_W.z1+0.2],[FAC.x1-0.15,FAC.z0+0.15,FAC.x1-0.15,FAC.z1-0.15,(x,z)=>(z>RACK_E.z0-0.2&&z<RACK_E.z1+0.2)||(z>14.6&&z<17.6)],\n  [FAC.x0+0.15,FAC.z0+0.15,COUNTER.x0,FAC.z0+0.15],[COUNTER.x1,FAC.z0+0.15,FAC.x1-0.15,FAC.z0+0.15]],0.95,DECK_Y);\n// queue lane rails (two lanes south of the windows)\nconst laneFenceG=[new THREE.Group(),new THREE.Group()]; for(let w=0;w<2;w++){ scene.add(laneFenceG[w]); const x=WIN[w].x; logFence(laneFenceG[w],[[x-0.7,FAC.z0-0.6,x-0.7,STREET.z1+0.6],[x+0.7,FAC.z0-0.6,x+0.7,STREET.z1+0.6,(x2,z)=>false]],0.8,0.36); } laneFenceG[1].visible=false;\n// the coop (barn) at the north end of the spine\nconst coopG=new THREE.Group(); scene.add(coopG);\n{ const C=COOP, cx=(C.x0+C.x1)/2, cz=(C.z0+C.z1)/2, w=C.x1-C.x0, d=C.z1-C.z0; const redW=new THREE.MeshLambertMaterial({color:0xc8463a}), trim=new THREE.MeshLambertMaterial({color:0xfaf3e6}), doorM=new THREE.MeshLambertMaterial({color:0x3a2418});\n  boxAt(coopG,w,4.2,d,cx,2.1+0.3,cz,redW); for(const x of [C.x0,C.x1]) boxAt(coopG,0.3,4.3,d+0.1,x,2.15+0.3,cz,trim);\n  for(const s2 of [-1,1]){ const r=boxAt(coopG,w/2+0.9,0.3,d+0.8,cx+s2*(w/4+0.2),5.3,cz,roofM); r.rotation.z=-s2*0.55; const sn=boxAt(coopG,w/2+0.7,0.1,d+0.6,cx+s2*(w/4+0.18),5.5,cz,snowM); sn.rotation.z=-s2*0.55; }\n  boxAt(coopG,3.6,3.4,0.1,SPINE,2.0,C.z0-0.02,doorM); for(const sx of [-1.9,1.9]) boxAt(coopG,0.3,3.6,0.3,SPINE+sx,2.1,C.z0-0.1,trim); boxAt(coopG,4.1,0.3,0.3,SPINE,3.85,C.z0-0.1,trim);\n  const sg=new THREE.Mesh(new THREE.PlaneGeometry(3.6,0.9),new THREE.MeshBasicMaterial({map:signTex('GIANT COOP','#fff3d6','#b8261e',440)})); sg.position.set(SPINE,4.55,C.z0-0.2); sg.rotation.y=Math.PI; sg.userData.keep=1; coopG.add(sg);\n  // a silo + a few hay bales so it reads as a farm building\n  const silo=new THREE.Mesh(new THREE.CylinderGeometry(1.3,1.3,6.5,16),new THREE.MeshLambertMaterial({color:0xdfe5ec})); silo.position.set(C.x1+1.9,3.5,cz+0.8); coopG.add(silo); const dome=new THREE.Mesh(new THREE.SphereGeometry(1.32,16,8,0,6.3,0,1.6),roofM); dome.position.set(C.x1+1.9,6.75,cz+0.8); coopG.add(dome);\n  for(const [x,z] of [[C.x0-1.2,C.z0+0.8],[C.x0-1.0,C.z0+2.1],[C.x0-2.2,C.z0+1.4]]){ const h=new THREE.Mesh(new THREE.CylinderGeometry(0.6,0.6,1.0,12),new THREE.MeshLambertMaterial({color:0xe9c46a})); h.rotation.z=Math.PI/2; h.position.set(x,0.8,z); coopG.add(h); }\n  mergeGroup(coopG); }\n// chicken lanes: wide conveyor strips under the queue\nconst laneTex=canvasTex(64,64,(g)=>{ g.fillStyle='#5a6272'; g.fillRect(0,0,64,64); g.fillStyle='#6f7889'; for(let i=0;i<4;i++) g.fillRect(0,i*16,64,6); }); laneTex.wrapS=laneTex.wrapT=THREE.RepeatWrapping;\nfunction laneStrip(pts,col){ const g=new THREE.Group(); scene.add(g); const t=laneTex.clone(); t.needsUpdate=true; const mat=new THREE.MeshLambertMaterial({map:t}); const W=3.0;\n  for(let i=1;i<pts.length;i++){ const a=pts[i-1], b=pts[i], L=a.distanceTo(b); const ext=i>1?1.5:0; const geo=new THREE.PlaneGeometry(W,L+ext); { const uv=geo.attributes.uv; for(let q=0;q<uv.count;q++) uv.setY(q,uv.getY(q)*(L+ext)/1.2); } geo.rotateX(-Math.PI/2);\n    const m=new THREE.Mesh(geo,mat); const k=new THREE.Group(); k.position.set((a.x+b.x)/2,DECK_Y+0.06,(a.z+b.z)/2); k.lookAt(b.x,DECK_Y+0.06,b.z); m.position.z=-ext/2; k.add(m);\n    for(const sx of [-W/2-0.1,W/2+0.1]) { const r=new THREE.Mesh(new THREE.BoxGeometry(0.22,0.4,L+ext),col); r.position.set(sx,0.1,-ext/2); k.add(r); } g.add(k); }\n  mergeGroup(g); return {g,tex:t}; }\nconst laneVis=[laneStrip([COOP_DOOR.clone().add(new V3(0,0,-0.4)),SPLIT.clone(),PLATES[0].clone().add(new V3(0,0,1.5))],M.yellow),\n  laneStrip([SPLIT.clone(),new V3(PLATES[1].x,0,SPLIT.z),PLATES[1].clone().add(new V3(0,0,1.5))],M.yellow),\n  laneStrip([new V3(PLATES[1].x,0,SPLIT.z),new V3(SPIT.x,0,SPLIT.z),new V3(SPIT.x,0,ROAST.z1-3.0)],M.orange)];\nlaneVis[1].g.visible=false; laneVis[2].g.visible=false;\n// splitter gate: a post with an arm that swings toward the lane it is feeding\nconst splitG=new THREE.Group(); splitG.position.set(SPLIT.x-1.9,DECK_Y,SPLIT.z+1.6); scene.add(splitG); { boxAt(splitG,0.5,1.8,0.5,0,0.9,0,M.yellow); const lamp=new THREE.Mesh(new THREE.SphereGeometry(0.22,10,8),new THREE.MeshBasicMaterial({color:0x5cff6a})); lamp.position.y=1.95; splitG.add(lamp); }\nconst splitArm=new THREE.Mesh(new THREE.BoxGeometry(0.2,0.3,2.8),M.red); splitArm.geometry.translate(0,0,1.4); splitArm.position.set(SPLIT.x-1.9,DECK_Y+1.1,SPLIT.z+1.6); scene.add(splitArm);\n// collector belt (auto packer): runs west along the pans' south edge into the packer\nconst colG=new THREE.Group(); colG.visible=false; scene.add(colG); const colTex=laneTex.clone(); colTex.needsUpdate=true;\n{ const x0=PACK_IN.x-0.2, x1=TRAYS[1].x1, L=x1-x0; const top=new THREE.Mesh(new THREE.PlaneGeometry(L,0.7),new THREE.MeshLambertMaterial({map:colTex})); { const uv=top.geometry.attributes.uv; for(let q=0;q<uv.count;q++) uv.setX(q,uv.getX(q)*L/0.8); } top.rotation.x=-Math.PI/2; top.position.set((x0+x1)/2,DECK_Y+0.62,COLLECT_Z); colG.add(top);\n  boxAt(colG,L,0.5,0.8,(x0+x1)/2,DECK_Y+0.35,COLLECT_Z,M.iron); for(const dz of [-0.42,0.42]) boxAt(colG,L,0.18,0.08,(x0+x1)/2,DECK_Y+0.72,COLLECT_Z+dz,M.yellow);\n  const chute=boxAt(colG,1.6,0.12,0.8,PACK_IN.x-0.9,DECK_Y+1.4,COLLECT_Z-0.3,M.steel); chute.rotation.z=-0.6; mergeGroup(colG); }\nconst rakes=pans.map(p=>{ const g=new THREE.Mesh(new THREE.BoxGeometry(p.T.x1-p.T.x0-0.3,0.18,0.18),M.yellow); g.position.set((p.T.x0+p.T.x1)/2,DECK_Y+0.95,p.T.z1); g.visible=false; scene.add(g); return {p,g,z:p.T.z1,t:0}; });\nconst belt={items:[]};\nfunction updateCollector(dt){ if(!has('autopack')) return; colTex.offset.x+=dt*4.2/0.8;\n  for(const r of rakes){ if(!r.p.on) continue; r.g.visible=true; r.t+=dt; const T=r.p.T; const cyc=2.2, k=(r.t%cyc)/cyc; const z=T.z1-0.2-k*(T.z1-T.z0-0.4); r.g.position.z=z;\n    for(const s of sticks){ if(s.state==='pan' && s.pan===r.p && s.cook>=1 && s.z>z-0.25 && s.z<z+0.35 && belt.items.length<34){ s.state='hop'; s.ax=s.x; s.ay=s.y; s.az=s.z; s.t=0; s.dur=0.4; s.h=0.6; const bx=s.x; s.tgt=()=>new V3(bx,DECK_Y+0.72,COLLECT_Z); s.done=()=>{ s.state='belt'; s.y=DECK_Y+0.72; s.z=COLLECT_Z; belt.items.push(s); s.pan.sticks--; }; } } }\n  belt.items.sort((a,b)=>a.x-b.x); let lim=PACK_IN.x-0.4;\n  for(let i=0;i<belt.items.length;i++){ const s=belt.items[i]; const nx=Math.max(lim,s.x-4.2*dt); s.x=nx; lim=s.x+0.24; s.ry=Math.PI/2; s.rx=0;\n    if(s.x<=PACK_IN.x-0.38 && packer.q<24){ belt.items.splice(i,1); i--; s.state='hop'; s.ax=s.x; s.ay=s.y; s.az=s.z; s.t=0; s.dur=0.32; s.h=0.9; s.tgt=()=>new V3(PACKER.x+0.3,DECK_Y+2.4,PACKER.z+0.2); s.done=()=>{ killStick(s); packer.q++; packer.bump=1; }; } } }\n// ================= FESTIVAL hooks (defined fully later; stubs keep early code simple) =================\nconst FEST={on:false,done:false,need:{bucket:0,plate:0},got:{bucket:0,plate:0}};\nfunction festAdd(kind,n){ }\n// ================= TILES / UNLOCKS =================\nconst tiles=[];\nconst after=(id,s)=>stat.T>(stat['at_'+id]||1e9)+s;\nconst PRICE=Object.assign({press:20,plate2:45,autopack:80,window2:120,racks:160,bikes:200,trucks:280,roast:380,plaza:460,mega:520,hall:640},JSON.parse(Q.get('prices')||'{}'));\nconst TNAME={press:'AUTO PRESS',plate2:'SECOND PLATE',autopack:'AUTO PACKER',window2:'SECOND WINDOW',racks:'BUCKET WALL',bikes:'DELIVERY BIKES',trucks:'PICKUP TRUCKS',roast:'ROAST YARD',plaza:'WINTER FEAST',hall:'ROAST HALL',mega:'MEGA CHICKEN'};\nconst TILE_DEFS=[\n  {id:'press',   icon:'crane', x:6.6, z:13.8+0.0, req:()=>stat.chickens>=2 && stat.T>6},\n  {id:'plate2',  icon:'giant', x:8.0, z:12.2, req:()=>has('press') && after('press',6)},\n  {id:'autopack',icon:'belt',  x:1.6, z:6.6, req:()=>has('plate2') && after('plate2',8)},\n  {id:'window2', icon:'chef',  x:8.0, z:4.0, req:()=>has('autopack') && after('autopack',8)},\n  {id:'racks',   icon:'cargo', x:-3.0, z:4.2+0.6, req:()=>has('window2') && (stocks.counter>=COUNTER_CAP-4 || after('window2',14))},\n  {id:'bikes',   icon:'boat',  x:-4.6, z:9.6, req:()=>has('racks') && (stocks.rackW>=6 || after('racks',10))},\n];\nTILE_DEFS.forEach(d=>d.price=PRICE[d.id]);\nfunction addTile(d){ const tex=tileTex(d.icon,d.price,0,d.lv); const m=new THREE.Mesh(new THREE.PlaneGeometry(1.8,1.8),new THREE.MeshBasicMaterial({map:tex,transparent:true})); m.rotation.x=-Math.PI/2; m.position.set(d.x,(d.y??DECK_Y)+0.03,d.z); m.scale.setScalar(0.01); scene.add(m); const t={...d,mesh:m,tex,paid:0,shown:false,done:false,popT:0,inflight:0}; tiles.push(t); return t; }\nfunction redrawTile(t){ drawTile(t.tex._g,t.icon,Math.max(0,t.price-t.paid),t.paid/t.price,t.lv); t.tex.needsUpdate=true; }\nTILE_DEFS.forEach(addTile);\nfunction tour(a,b,dur,zoom){ showcase.path=[a.clone(),b.clone()]; showcase.dur=dur; showcase.t=dur; showcase.zoom=zoom||0; }\nfunction unlock(t){ if(t.done) return; t.done=true; owned.add(t.id); stat['at_'+t.id]=stat.T; logEv('unlock '+t.id); ching(); t.mesh.visible=false; sparkle(t.x,DECK_Y+0.5,t.z,20,1.5);\n  if(t.id==='press'){ const pr=makePress(plates[0]); rise(pr.g,-6); rise(pr.head,-6); padMesh.visible=false; padLink.visible=false; shake=Math.max(shake,0.4); showcase.pos.set(PLATES[0].x,0,PLATES[0].z-2); showcase.t=2.2; showBanner('AUTO PRESS!'); }\n  if(t.id==='plate2'){ const pl=plates[1]; pl.g.visible=true; rise(pl.g,-3); makePress(pl); rise(pl.press.g,-6); rise(pl.press.head,-6); pans[1].g.visible=true; pans[1].on=true; rise(pans[1].g,-2); laneVis[1].g.visible=true; rise(laneVis[1].g,-1.5); LANES[1].on=true;\n    for(let k=0;k<26;k++) flame(R(PLATES[1].x-1.6,PLATES[1].x+1.6),DECK_Y+0.6,R(PLATES[1].z-1.6,PLATES[1].z+1.6),1,0.5,1.4); featherBurst(PLATES[1].x,DECK_Y+1,PLATES[1].z,20); shake=Math.max(shake,0.5); tour(new V3(SPLIT.x+2,0,SPLIT.z-2),new V3(8,0,12),2.6,1.1); showBanner('SECOND PLATE!  \u00d72'); }\n  if(t.id==='autopack'){ colG.visible=true; rise(colG,-1.2); for(const r of rakes) if(r.p.on){ r.g.visible=true; } showcase.pos.set(4,0,8.4); showcase.t=2.4; showBanner('AUTO PACKER!'); }\n  if(t.id==='window2'){ win1G.visible=true; rise(win1G,-3); laneFenceG[1].visible=true; rise(laneFenceG[1],-1); winState[1].open=true; const c=staff(0x2f86d8,0xffffff,WIN[1].x+0.8,COUNTER.z+0.75); c.rotation.y=Math.PI; cashiers.push(c); pop(c);\n    for(const c2 of custs) if(c2.w===1){ c2.state='queue'; c2.z-=6; } tour(new V3(WIN[1].x,0,3),new V3(WIN[1].x,0,-6),2.6,1.1); showBanner('SECOND WINDOW!'); }\n  if(t.id==='racks'){ rackWG.visible=true; rise(rackWG,-3); rackEG.visible=true; rise(rackEG,-3); showcase.pos.set(-4,0,5); showcase.t=2.2; showBanner('BUCKET WALL!'); }\n  if(t.id==='bikes'){ bikeRoadG.visible=true; rise(bikeRoadG,-0.5); for(let k=0;k<3;k++) addBike(); tour(new V3(-5,0,5),new V3(-18,0,5),2.8,1.15); showBanner('DELIVERY BIKES!'); }\n  afterUnlock(t); }\nconst afterHooks=[]; function afterUnlock(t){ for(const f of afterHooks) f(t); }\nfunction updateTiles(dt){ for(const t of tiles){ if(!t.shown && !t.done && t.req()){ t.shown=true; t.popT=0; logEv('tile '+t.id); sparkle(t.x,DECK_Y+0.3,t.z,10,1); if(showcase.t<=0 && !near(player.pos,t,6)){ showcase.pos.set(t.x,0,t.z); showcase.t=2.2; showcase.path=null; } }\n  if(t.shown && !t.done){ t.popT+=dt; const s=t.popT<0.4? ease(t.popT/0.4)*1.15 : 1+0.08*Math.abs(Math.sin(stat.T*4)); t.mesh.scale.setScalar(s); } } updateGoalUI(); }\n\n// ================= PLAYER =================\nconst WALK={x0:FAC.x0+0.3,x1:FAC.x1-0.3,z0:COUNTER.z+0.75,z1:FAC.z1-0.3};\nconst PILES=[CASH];\nconst WALKRS=()=>{ const L=[WALK]; for(const f of extraWalk) if(f.on()) L.push(f.r); return L; };\nconst extraWalk=[];\nfunction obsBoxes(){ const L=[]; for(const pl of plates) if(pl.g.visible) L.push([pl.P.x,pl.P.z,1.55,1.55]);\n  L.push([PACKER.x,PACKER.z,1.25,1.05],[(COUNTER.x0+COUNTER.x1)/2,COUNTER.z+0.85,(COUNTER.x1-COUNTER.x0)/2+0.6,0.35]);\n  if(has('racks')){ L.push([(RACK_W.x0+RACK_W.x1)/2,(RACK_W.z0+RACK_W.z1)/2,(RACK_W.x1-RACK_W.x0)/2+0.1,(RACK_W.z1-RACK_W.z0)/2+0.1],[(RACK_E.x0+RACK_E.x1)/2,(RACK_E.z0+RACK_E.z1)/2,(RACK_E.x1-RACK_E.x0)/2+0.1,(RACK_E.z1-RACK_E.z0)/2+0.1]); }\n  for(const o of extraObs) if(o.on()) L.push(o.b);\n  return L; }\nconst extraObs=[];\nfunction walkClamp(p){ const m=0.4; let best=null, bd=1e9; for(const r of WALKRS()){ const cx=clamp(p.x,r.x0+m,r.x1-m), cz=clamp(p.z,r.z0+m,r.z1-m); const d=(cx-p.x)**2+(cz-p.z)**2; if(d<bd){ bd=d; best=[cx,cz]; } } p.x=best[0]; p.z=best[1];\n  for(const o of obsBoxes()){ const [cx,cz,hx,hz]=o; if(Math.abs(p.x-cx)<hx && Math.abs(p.z-cz)<hz){ const ox=hx-Math.abs(p.x-cx), oz=hz-Math.abs(p.z-cz); if(ox<oz) p.x+=(p.x>cx?ox:-ox); else p.z+=(p.z>cz?oz:-oz); } } }\nfunction updatePlayer(dt){ let [wx,wz]=moveInput(); const SPD=6.4;\n  player.vel.lerp(new V3(wx*SPD,0,wz*SPD),clamp(dt*14,0,1)); player.pos.x+=player.vel.x*dt; player.pos.z+=player.vel.z*dt; walkClamp(player.pos); player.pos.y=DECK_Y;\n  player.lean.lerp(new V3(player.vel.x/SPD,0,player.vel.z/SPD),clamp(dt*4,0,1));\n  const sp=Math.hypot(player.vel.x,player.vel.z); if(sp>0.3){ const ang=Math.atan2(player.vel.x,player.vel.z); let d=((ang-player.face+Math.PI*3)%(Math.PI*2))-Math.PI; player.face+=d*clamp(dt*14,0,1); if(!player.moved){ player.moved=1; hint.style.opacity=0; } }\n  chef.rotation.y=player.face; animHuman(chef,sp,dt,player.stack.length?'overhead':null); if(player.stack.length){ const u=chef.userData; u.arms[0].rotation.x=u.arms[1].rotation.x=-2.7; }\n  // pick up fried drumsticks by walking over a pan\n  player.pT=(player.pT||0)-dt; const pan=panOf(player.pos.x,player.pos.z);\n  if(pan && player.stack.length<player.cap){ while(player.pT<=0 && player.stack.length<player.cap){ let best=null, bd=1e9; for(const s of sticks){ if(s.state!=='pan'||s.pan!==pan||s.cook<1) continue; const d=(s.x-player.pos.x)**2+(s.z-player.pos.z)**2; if(d<bd){ bd=d; best=s; } } if(!best) break; player.pT+=0.04; best.pan.sticks--; const idx=player.stack.length; best.state='hop'; best.ax=best.x; best.ay=best.y; best.az=best.z; best.t=0; best.dur=0.28; best.h=0.9; best.tgt=()=>player.slot(idx); const bs=best; stackPush(player,bs); bs.state='hop'; bs.done=()=>{ bs.state='carry'; }; tick(idx); } }\n  if(player.pT<0) player.pT=0;\n  // cash pile\n  player.xT=(player.xT||0)-dt;\n  for(const PL of PILES) if(PL.n>0 && near(player.pos,PL.pos,1.7) && player.xT<=0){ const n=Math.min(PL.n,1+Math.floor(PL.n/30)); player.xT=0.03; for(let k=0;k<n;k++){ PL.n--; const from=pileTop(PL,PL.n); const idx=player.bills; player.bills++; fly('bill',from,null,0.22,0.8,null,{toBack:Math.min(idx,89)}); } if(rnd()<0.5) noise(0.03,0.025,3200); }\n  // pay into tiles\n  for(const t of tiles){ if(!t.shown||t.done) continue; if(near(player.pos,t,1.05) && player.bills>0){ t.payT=(t.payT||0)-dt; let nb=0; while(player.bills>0 && t.price-t.paid-t.inflight>0 && t.payT<=0 && nb<3){ nb++; t.rate=Math.min(120,(t.rate||14)*1.12); t.payT+=1/t.rate; player.bills--; t.inflight++; const from=backSlot(Math.min(player.bills,89)); fly('bill',from,new V3(t.x,DECK_Y+0.1,t.z),0.3,1.2,()=>{ t.inflight--; t.paid++; redrawTile(t); if(t.paid%3===0) noise(0.03,0.03,2600); if(t.paid>=t.price) unlock(t); }); } if(t.payT<-0.1) t.payT=0; } else { t.rate=14; t.payT=0; } } }\n\n// ================= GUIDE / BOT =================\nlet guideExtra=()=>null;\nfunction regionOf(p){ if(inR(ROAST,p.x,p.z)) return 'roast'; if(p.x>13.2&&p.z<ROAST.z0&&p.z>HALL.z0) return p.z<TR_Z0-1?'hall':'roast'; if(inR(HALL,p.x,p.z)) return 'hall'; if(inR(PLAZA,p.x,p.z)) return 'plaza'; if(p.z<FAC.z0) return 'street'; return 'yard'; }\nconst gCash={on:false,until:0};\nfunction guideTarget(){ const a=player;\n  const paying=tiles.find(t=>t.shown&&!t.done&&t.paid>0&&a.bills>0&&near(a.pos,t,1.3)); if(paying) return new V3(paying.x,0,paying.z);\n  const aff=tiles.find(t=>t.shown&&!t.done&&a.bills>=t.price-t.paid-t.inflight); if(aff) return new V3(aff.x,0,aff.z);\n  const want=tiles.find(t=>t.shown&&!t.done);\n  { const gx=guideExtra(want); if(gx) return gx; }\n  if(a.stack.length && (a.stack.length>=a.cap || !sticks.some(s=>s.state==='pan'&&s.cook>=1) || has('autopack'))) return PACK_IN.clone();\n  if(gCash.on && (CASH.n<=0 || stat.T>gCash.until)) gCash.on=false;\n  if(!gCash.on && CASH.n>0 && ((want && a.bills+CASH.n>=want.price-want.paid) || CASH.n>=60)){ gCash.on=true; gCash.until=stat.T+6; }\n  if(gCash.on) return CASH.pos.clone().add(new V3(0,0,0.4));\n  if(!has('autopack')){ const ck=sticks.filter(s=>s.state==='pan'&&s.cook>=1); if(ck.length>=(has('press')?6:10) || (ck.length && a.stack.length)){ let best=null,bd=1e9; for(const s of ck){ const d=(s.x-a.pos.x)**2+(s.z-a.pos.z)**2; if(d<bd){ bd=d; best=s; } } return new V3(best.x,0,best.z); } }\n  if(!has('press')) return PAD.clone();\n  if(CASH.n>0) return CASH.pos.clone();\n  return null; }\nconst arrowTex=spriteTex((g)=>{ g.fillStyle='#39d64a'; g.strokeStyle='#fff'; g.lineWidth=10; g.lineJoin='round'; g.beginPath(); g.moveTo(64,118); g.lineTo(14,62); g.lineTo(42,62); g.lineTo(42,10); g.lineTo(86,10); g.lineTo(86,62); g.lineTo(114,62); g.closePath(); g.stroke(); g.fill(); });\nconst arrowSprite=sprite(arrowTex,1.3);\nconst chevGeo=(()=>{ const sh=new THREE.Shape(); sh.moveTo(-0.34,-0.15); sh.lineTo(0,0.17); sh.lineTo(0.34,-0.15); sh.lineTo(0.19,-0.15); sh.lineTo(0,0.03); sh.lineTo(-0.19,-0.15); sh.closePath(); const g=new THREE.ShapeGeometry(sh); g.rotateX(Math.PI/2); g.scale(1.4,1,1.4); return g; })();\nconst chevMesh=new THREE.InstancedMesh(chevGeo,new THREE.MeshBasicMaterial({color:0x2fa8ff,transparent:true,opacity:0.95,depthWrite:false,side:THREE.DoubleSide}),40); chevMesh.frustumCulled=false; chevMesh.renderOrder=5; scene.add(chevMesh);\nconst gNav={path:null,tgt:null,t:0};\nfunction guideStep(T0){ if(BOT&&botNav.path&&botNav.path.length) return botNav.path[0]; gNav.t-=1/60; if(!gNav.path||!gNav.tgt||gNav.tgt.distanceTo(T0)>0.4||gNav.t<=0){ gNav.path=findPath(player.pos,T0); gNav.tgt=T0.clone(); gNav.t=0.5; } while(gNav.path.length>1&&near(player.pos,gNav.path[0],0.6)) gNav.path.shift(); return gNav.path[0]; }\nfunction updateGuide(){ const T0=guideTarget(); const a=player; let n=0; const t=T0?guideStep(T0):null;\n  if(t){ const dx=t.x-a.pos.x, dz=t.z-a.pos.z, L=Math.hypot(dx,dz); if(L>2){ const ux=dx/L, uz=dz/L, ry=Math.atan2(ux,uz), SP=1.1, off=(stat.T*2.6)%SP; for(let d=0.9+off; d<L-0.5 && n<40; d+=SP){ _e.set(0,ry,0); _q.setFromEuler(_e); _m.compose(_p.set(a.pos.x+ux*d,DECK_Y+0.05,a.pos.z+uz*d),_q,_s.set(0.9,1,0.9)); chevMesh.setMatrixAt(n++,_m); } } }\n  chevMesh.count=n; chevMesh.instanceMatrix.needsUpdate=true;\n  const showArrow=T0 && !near(a.pos,T0,1.0); arrowSprite.visible=!!showArrow; if(showArrow) arrowSprite.position.set(T0.x,DECK_Y+1.8+Math.abs(Math.sin(stat.T*5))*0.4,T0.z); }\nconst GR={x0:-27,z0:-33,c:0.5,nx:120,nz:116,key:null,w:null};\nfunction gridKey(){ return [...owned].sort().join(','); }\nfunction buildGrid(){ const g=new Uint8Array(GR.nx*GR.nz), R=WALKRS(), O=obsBoxes(), m=0.38;\n  for(let j=0;j<GR.nz;j++) for(let i=0;i<GR.nx;i++){ const x=GR.x0+(i+0.5)*GR.c, z=GR.z0+(j+0.5)*GR.c; let ok=false; for(const r of R) if(x>r.x0+m&&x<r.x1-m&&z>r.z0+m&&z<r.z1-m){ ok=true; break; } if(ok) for(const o of O) if(Math.abs(x-o[0])<o[2]+0.3&&Math.abs(z-o[1])<o[3]+0.3){ ok=false; break; } g[j*GR.nx+i]=ok?1:0; }\n  GR.w=g; GR.key=gridKey(); }\nconst cellOf=(p)=>[clamp(Math.floor((p.x-GR.x0)/GR.c),0,GR.nx-1),clamp(Math.floor((p.z-GR.z0)/GR.c),0,GR.nz-1)];\nconst cellPos=(i,j)=>new V3(GR.x0+(i+0.5)*GR.c,0,GR.z0+(j+0.5)*GR.c);\nfunction freeNear(i,j){ if(GR.w[j*GR.nx+i]) return [i,j]; for(let r=1;r<12;r++) for(let dj=-r;dj<=r;dj++) for(let di=-r;di<=r;di++){ if(Math.max(Math.abs(di),Math.abs(dj))!==r) continue; const a=i+di,b=j+dj; if(a>=0&&b>=0&&a<GR.nx&&b<GR.nz&&GR.w[b*GR.nx+a]) return [a,b]; } return [i,j]; }\nfunction los(a,b){ const d=a.distanceTo(b), n=Math.ceil(d/0.25); for(let k=1;k<n;k++){ const p=a.clone().lerp(b,k/n); const [i,j]=cellOf(p); if(!GR.w[j*GR.nx+i]) return false; } return true; }\nfunction findPath(a,t){ if(GR.key!==gridKey()) buildGrid(); const [si,sj]=freeNear(...cellOf(a)), [ti,tj]=freeNear(...cellOf(t)); const N=GR.nx*GR.nz, start=sj*GR.nx+si, goal=tj*GR.nx+ti;\n  const gs=new Float32Array(N).fill(1e9), came=new Int32Array(N).fill(-1), closed=new Uint8Array(N); gs[start]=0; const h=(k)=>Math.hypot(k%GR.nx-ti,Math.floor(k/GR.nx)-tj);\n  const heap=[[h(start),start]]; const push=(f,k)=>{ heap.push([f,k]); let i=heap.length-1; while(i>0){ const p=(i-1)>>1; if(heap[p][0]<=heap[i][0]) break; [heap[p],heap[i]]=[heap[i],heap[p]]; i=p; } };\n  const pop=()=>{ const top=heap[0], l=heap.pop(); if(heap.length){ heap[0]=l; let i=0; for(;;){ const a=2*i+1, b=a+1; let m=i; if(a<heap.length&&heap[a][0]<heap[m][0]) m=a; if(b<heap.length&&heap[b][0]<heap[m][0]) m=b; if(m===i) break; [heap[m],heap[i]]=[heap[i],heap[m]]; i=m; } } return top; };\n  while(heap.length){ const [,cur]=pop(); if(closed[cur]) continue; closed[cur]=1; if(cur===goal) break;\n    const ci=cur%GR.nx, cj=Math.floor(cur/GR.nx); for(const [di,dj,c] of [[1,0,1],[-1,0,1],[0,1,1],[0,-1,1],[1,1,1.414],[1,-1,1.414],[-1,1,1.414],[-1,-1,1.414]]){ const ni=ci+di, nj=cj+dj; if(ni<0||nj<0||ni>=GR.nx||nj>=GR.nz) continue; const nk=nj*GR.nx+ni; if(!GR.w[nk]||closed[nk]) continue; if(di&&dj&&(!GR.w[cj*GR.nx+ni]||!GR.w[nj*GR.nx+ci])) continue; const ng=gs[cur]+c; if(ng<gs[nk]){ gs[nk]=ng; came[nk]=cur; push(ng+h(nk),nk); } } }\n  if(came[goal]<0 && goal!==start) return [t.clone()]; const pts=[]; let k=goal; while(k!==start && k>=0){ pts.push(cellPos(k%GR.nx,Math.floor(k/GR.nx))); k=came[k]; } pts.reverse(); pts.push(t.clone());\n  // string-pull\n  const out=[]; let from=a.clone(), idx=0; while(idx<pts.length){ let far=idx; for(let q=pts.length-1;q>idx;q--){ if(los(from,pts[q])){ far=q; break; } } out.push(pts[far]); from=pts[far]; idx=far+1; } return out; }\nconst botNav={path:null,tgt:null,t:0};\nfunction botInput(){ const a=player; if(Q.get('idle')&&stat.T>+Q.get('idle')) return [0,0]; const t=guideTarget(); if(!t) return [0,0]; botNav.t-=1/60;\n  if(!botNav.path||!botNav.tgt||botNav.tgt.distanceTo(t)>0.4||botNav.t<=0){ botNav.path=findPath(a.pos,t); botNav.tgt=t.clone(); botNav.t=0.5; }\n  while(botNav.path.length>1 && near(a.pos,botNav.path[0],0.45)) botNav.path.shift(); const w=botNav.path[0]; const dx=w.x-a.pos.x, dz=w.z-a.pos.z, L=Math.hypot(dx,dz); if(L<0.3) return [0,0]; const wx=dx/L, wz=dz/L; const fx=-Math.sin(CAM_YAW), fz=-Math.cos(CAM_YAW); return [wx*(-fz)+wz*fx,-(wx*fx+wz*fz)]; }\n\n// ================= CAMERA =================\nlet camZoom=1.18, camZoomTarget=1.18; const camT=new V3();\nconst ANCHOR={yard:new V3(4,0,11.5),roast:new V3(18,0,15),hall:new V3(22,0,-5),plaza:new V3(-16,0,-22),street:new V3(2,0,-12)};\nfunction updateCamera(dt,snap){ if(showcase.t<=0) showcase.zoom=0; const zt=showcase.zoom?Math.max(camZoomTarget,showcase.zoom):camZoomTarget; camZoom=lerp(camZoom,zt,clamp(dt*1.2,0,1));\n  const a=player; const focus=new V3(a.pos.x,0,a.pos.z);\n  focus.lerp(ANCHOR[regionOf(a.pos)]||ANCHOR.yard,0.35);\n  focus.z+=(window.__fo??1.6);\n  scene.fog.near=42*camZoom; scene.fog.far=95*camZoom; { const c=sun.shadow.camera, sc=14*Math.max(1,camZoom); if(Math.abs(c.right-sc)>0.6){ c.left=-sc; c.right=sc; c.top=sc; c.bottom=-sc; c.updateProjectionMatrix(); } }\n  if(showcase.t>0){ showcase.t-=dt; if(showcase.path){ const k=ease(1-Math.max(0,showcase.t)/showcase.dur*1.0); focus.copy(showcase.path[0]).lerp(showcase.path[1],clamp(k*1.25,0,1)); } else focus.copy(showcase.pos); if(showcase.t<=0) showcase.path=null; }\n  if(snap) camT.copy(focus); else camT.lerp(focus,clamp(dt*3,0,1));\n  const dist=(window.__cd||38)*camZoom*(camera.aspect<0.6?1.0:0.85); const pitch=(window.__cp||0.9), yaw=CAM_YAW;\n  camera.position.set(camT.x+Math.sin(yaw)*Math.cos(pitch)*dist, Math.sin(pitch)*dist, camT.z+Math.cos(yaw)*Math.cos(pitch)*dist);\n  if(shake>0){ shake-=dt; camera.position.x+=Math.sin(stat.T*61)*shake*0.5; camera.position.y+=Math.sin(stat.T*53)*shake*0.5; }\n  camera.lookAt(camT.x,0,camT.z); sun.position.set(camT.x-9,26,camT.z+5); sun.target.position.set(camT.x,0,camT.z); }\n\n// ================= RENDER STACKS =================\nconst goalUI=document.createElement('div'); goalUI.id='goalui'; goalUI.innerHTML='<div class=\"l\">NEXT</div><div class=\"n\"></div><div class=\"b\"><div class=\"f\"></div></div><div class=\"p\"></div>'; document.body.appendChild(goalUI);\nconst edgeUI=document.createElement('div'); edgeUI.id='edgeui'; edgeUI.textContent='\u27a4'; document.body.appendChild(edgeUI);\n{ const st=document.createElement('style'); st.textContent=`#goalui{position:fixed;left:10px;top:50px;min-width:150px;max-width:56%;background:rgba(20,40,60,.62);border:2px solid #fff;border-radius:12px;padding:4px 10px 6px;color:#fff;font-weight:900;pointer-events:none;display:none;text-shadow:0 2px 0 rgba(0,0,0,.35)}\n#goalui .l{font-size:10px;letter-spacing:1px;color:#ffd36a} #goalui .n{font-size:15px;line-height:17px} #goalui .b{height:8px;border-radius:5px;background:rgba(0,0,0,.35);margin:3px 0 1px;overflow:hidden} #goalui .f{height:100%;width:0;background:linear-gradient(90deg,#46e05a,#b8ff6a)} #goalui .p{font-size:12px}\n#goalui.ok{border-color:#46e05a;box-shadow:0 0 10px #46e05a} #edgeui{position:fixed;width:44px;height:44px;margin:-22px 0 0 -22px;border-radius:50%;background:#46e05a;border:3px solid #fff;color:#fff;font-size:24px;line-height:38px;text-align:center;pointer-events:none;display:none;box-shadow:0 3px 8px rgba(0,0,0,.35)}\n`; document.head.appendChild(st); }\nlet tileArrow=null;\nfunction nextGoal(){ const sh=tiles.filter(t=>t.shown&&!t.done); if(!sh.length) return null; return sh.sort((a,b)=>(a.price-a.paid)-(b.price-b.paid))[0]; }\nfunction updateGoalUI(){ const t=nextGoal(); if(!t){ goalUI.style.display='none'; edgeUI.style.display=\"none\"; if(tileArrow) tileArrow.visible=false; return; }\n  goalUI.style.display='block'; const have=player.bills+t.paid, k=Math.min(1,have/t.price); goalUI.querySelector('.n').textContent=TNAME[t.id]||t.id; goalUI.querySelector('.f').style.width=(k*100)+'%'; goalUI.querySelector('.p').textContent='$'+Math.min(have,t.price)+' / $'+t.price; goalUI.classList.toggle('ok',k>=1);\n  if(!tileArrow) tileArrow=sprite(arrowTex,1.5); tileArrow.visible=true; tileArrow.position.set(t.x,DECK_Y+2.0+Math.abs(Math.sin(stat.T*4))*0.5,t.z);\n  const v=new V3(t.x,DECK_Y+1,t.z).project(camera); const off=Math.abs(v.x)>0.92||Math.abs(v.y)>0.9||v.z>1;\n  if(off){ let x=v.x, y=v.y; if(v.z>1){ x=-x; y=-y; } const m=Math.max(Math.abs(x)/0.86,Math.abs(y)/0.80); x/=m; y/=m; edgeUI.style.display='block'; edgeUI.style.left=((x*0.5+0.5)*innerWidth)+'px'; edgeUI.style.top=((-y*0.5+0.5)*innerHeight)+'px'; edgeUI.style.transform='rotate('+Math.atan2(-y,x)+'rad)'; edgeUI.style.background=k>=1?'#46e05a':'#ff9a2a'; } else edgeUI.style.display='none'; }\n\n\nfunction updateFlying(dt){ for(let k=flying.length-1;k>=0;k--){ const f=flying[k]; f.t+=dt; if(f.t>=f.dur){ flying.splice(k,1); if(f.onLand) f.onLand(); } } }\nfunction updateParts(dt){ updateFire(dt); let n=0; for(let i=parts.length-1;i>=0;i--){ const p=parts[i]; p.t+=dt; if(p.t>p.life){ parts.splice(i,1); continue; } p.vy-=dt*12; p.x+=p.vx*dt; p.y+=p.vy*dt; p.z+=p.vz*dt; const s=p.s*(1-p.t/p.life); _m.compose(_p.set(p.x,p.y,p.z),_q.identity(),_s.set(s,s,s)); partMesh.setMatrixAt(n++,_m); } partMesh.count=n; partMesh.instanceMatrix.needsUpdate=true;\n  n=0; for(let i=sparks.length-1;i>=0;i--){ const p=sparks[i]; p.t+=dt; if(p.t>p.life){ sparks.splice(i,1); continue; } p.vy-=dt*6; p.x+=p.vx*dt; p.y+=p.vy*dt; p.z+=p.vz*dt; const s=1-p.t/p.life; _e.set(p.t*6,p.t*9,0); _q.setFromEuler(_e); _m.compose(_p.set(p.x,p.y,p.z),_q,_s.set(s,s,s)); sparkMesh.setMatrixAt(n++,_m); } sparkMesh.count=n; sparkMesh.instanceMatrix.needsUpdate=true;\n  for(let i=pops.length-1;i>=0;i--){ const p=pops[i]; p.t+=dt; const k=p.t/0.45; const s=k>=1?1:1+Math.sin(k*Math.PI)*0.35*(1-k)-(k<0.15?(0.15-k)*4:0); p.o.scale.setScalar(p.base*s); if(k>=1){ p.o.scale.setScalar(p.base); pops.splice(i,1); } }\n  for(let i=rises.length-1;i>=0;i--){ const r=rises[i]; r.t+=dt; const k=ease(clamp(r.t/0.8,0,1)); r.o.position.y=lerp(r.o.position.y,r.y,k); if(k>=1){ r.o.position.y=r.y; rises.splice(i,1); } } }\n// ================= RENDER STACKS (bills) =================\nfunction renderBills(){ let bi=0;\n  const putBill=(p,ry)=>{ if(bi>=1700) return; _e.set(0,ry||0,0); _q.setFromEuler(_e); _m.compose(p,_q,_s.set(1,1,1)); billMesh.setMatrixAt(bi++,_m); };\n  for(let i=0;i<Math.min(player.bills,28);i++) putBill(backSlot(i).add(new V3(-Math.sin(player.face)*0.25,player.stack.length?0.0:0,-Math.cos(player.face)*0.25)),player.face);\n  for(const PL of PILES) for(let i=0;i<Math.min(PL.n,240);i++) putBill(pileTop(PL,i),(hsh(i)-0.5)*0.5);\n  for(let k=flying.length-1;k>=0;k--){ const f=flying[k]; if(f.t<0||f.kind!=='bill') continue; const e=f.t/f.dur; let b=f.b; if(f.toBack!==undefined) b=backSlot(f.toBack); if(!b) continue;\n    _p.set(lerp(f.a.x,b.x,e),lerp(f.a.y,b.y,e)+Math.sin(e*Math.PI)*f.h,lerp(f.a.z,b.z,e)); putBill(_p.clone(),f.spin*e); }\n  billMesh.count=bi; billMesh.instanceMatrix.needsUpdate=true; }\n// ================= MAIN =================\nconst extraUpdates=[];\nfunction step(dt){ stat.T+=dt; if(Math.floor(stat.T)!==Math.floor(stat.T-dt)){ stat.tr.push({T:Math.round(stat.T),chickens:stat.chickens,sticks:stat.sticks,cooked:stat.cooked,packed:stat.packed,buckets:stat.buckets,counter:stocks.counter,rackW:stocks.rackW,rackE:stocks.rackE,cash:CASH.n,bills:player.bills,sold:stat.sold,earned:stat.earned,walk:stat.soldBy.walk||0,bike:stat.soldBy.bike||0,truck:stat.soldBy.truck||0,stack:player.stack.length,pan0:pans[0].sticks,pan1:pans[1].sticks,pq:packer.q,belt:belt.items.length,reg:regionOf(player.pos)}); }\n  updatePlayer(dt); updateChickens(dt); updateSticks(dt); updateCollector(dt); updatePacker(dt); updateBuckets(dt); updateCustomers(dt); updateBikes(dt); for(const f of extraUpdates) f(dt); updatePopTexts(dt); updateFlying(dt); updateTiles(dt); updateParts(dt); updateCamera(dt,false);\n  for(const L of laneVis) if(L.g.visible) L.tex.offset.y-=dt*0.9;\n  padRing.visible=!has('press')&&!near(player.pos,PAD,0.95); if(padRing.visible) padRing.scale.setScalar(1+Math.sin(stat.T*6)*0.06);\n  packRing.visible=player.stack.length>0; if(packRing.visible) packRing.scale.setScalar(1+Math.sin(stat.T*6)*0.06);\n  splitArm.rotation.y=lerp(splitArm.rotation.y,(LANES[1].on&&(laneRR%2))?-Math.PI*0.5:Math.PI,clamp(dt*4,0,1));\n  if(bannerT>0){ bannerT-=dt; if(bannerT<=0) banner.classList.remove('on'); } }\nfunction render(){ updateGuide(); renderBills(); document.getElementById('cash').textContent=player.bills; renderer.render(scene,camera); }\nlet last=performance.now(), acc=0;\nfunction frame(now){ let dt=Math.min(0.05,(now-last)/1000); last=now; if(window.__PAUSE__){ requestAnimationFrame(frame); return; }\n  acc+=dt; let n=0; while(acc>=1/60 && n<8){ step(1/60); acc-=1/60; n++; } if(n>=8) acc=0;\n  if(hint.style.opacity!=='0'){ const v=player.pos.clone().project(camera); hint.style.top=Math.min(innerHeight-180,(-v.y*0.5+0.5)*innerHeight+60)+'px'; hint.style.left=((v.x*0.5+0.5)*innerWidth)+'px'; }\n  render(); requestAnimationFrame(frame); }\n// ================= LATE GAME: pickup trucks \u00b7 roast yard \u00b7 winter feast \u00b7 roast hall \u00b7 mega chicken =================\n// ---- generic instanced people (runners, waiters, diners, feast guests) ----\nfunction makeCrowd(n,hatMat){ const bodyM=new THREE.MeshLambertMaterial({color:0xffffff}); const ms=[cInst(CG.body,bodyM,n),cInst(CG.belly,bodyM,n),cInst(CG.head,M.skin,n),cInst(CG.hat,hatMat||M.white,n),cInst(CG.e1,M.dark,n),cInst(CG.e2,M.dark,n)], arm=cInst(CG.arm,bodyM,n*2);\n  initColors(ms[0],n); initColors(ms[1],n); initColors(arm,n*2); for(const m of [...ms,arm]){ m.count=0; m.visible=false; } for(let k=1;k<ms.length;k++) ms[k].castShadow=false; arm.castShadow=false; return {ms,arm,n}; }\nfunction renderCrowd(C,people,dt){ let i=0; for(const p of people){ if(i>=C.n) break; if(p.hidden) continue; p.ph=(p.ph||0)+dt*(p.mv?10:2.5);\n    const y=(p.sit?DECK_Y-0.3:DECK_Y)+(p.mv?Math.abs(Math.sin(p.ph))*0.09:0)+(p.cheer>0?Math.sin(p.cheer/0.7*Math.PI)*0.45:0); _e.set(0,p.face||0,0,'YXZ'); _q.setFromEuler(_e); const sc=p.sc||1; _m.compose(_p.set(p.x,y,p.z),_q,_s.set(sc,sc,sc));\n    for(const m of C.ms) m.setMatrixAt(i,_m); C.ms[0].setColorAt(i,p.col); C.ms[1].setColorAt(i,p.col);\n    let aL=0.1+Math.sin(p.ph)*0.35*(p.mv?1:0), aR=0.1-Math.sin(p.ph)*0.35*(p.mv?1:0); if(p.n>0||p.carry){ aL=aR=-1.45; } if(p.eat){ aL=-1.7+Math.sin(p.ph*2.4)*0.35; aR=-0.6; } if(p.cheer>0){ aL=aR=-2.9; }\n    for(let q=0;q<2;q++){ _e.set(q?aR:aL,0,0,'XYZ'); _q.setFromEuler(_e); _m2.compose(_p.set(0,0,0),_q,_s.set(1,1,1)); _m2.premultiply(_m); C.arm.setMatrixAt(i*2+q,_m2); C.arm.setColorAt(i*2+q,p.col); } i++; }\n  for(const m of C.ms){ m.count=i; m.visible=i>0; m.instanceMatrix.needsUpdate=true; if(m.instanceColor) m.instanceColor.needsUpdate=true; } C.arm.count=i*2; C.arm.visible=i>0; C.arm.instanceMatrix.needsUpdate=true; if(C.arm.instanceColor) C.arm.instanceColor.needsUpdate=true; }\nfunction walkP(p,dt,spd){ if(!p.path||!p.path.length){ p.mv=0; return true; } const t=p.path[0]; const dx=t.x-p.x, dz=t.z-p.z, L=Math.hypot(dx,dz); if(L<0.06){ p.path.shift(); if(!p.path.length){ p.mv=0; return true; } return false; } const s=Math.min(L,spd*dt); p.x+=dx/L*s; p.z+=dz/L*s; p.face=Math.atan2(dx,dz); p.mv=1; return false; }\nfunction boxObs(x,z,hx,hz,on){ extraObs.push({on,b:[x,z,hx,hz]}); }\n\n// ================= #7 PICKUP TRUCKS (east road, load from the east bucket wall) =================\nconst TR_Z0=4.0, TR_Z1=7.4, TR_END=54;\nconst truckRoadG=new THREE.Group(); truckRoadG.visible=false; scene.add(truckRoadG);\n{ const x0=FAC.x1+0.2, x1=TR_END+4; boxAt(truckRoadG,x1-x0,0.06,TR_Z1-TR_Z0,(x0+x1)/2,0.33,(TR_Z0+TR_Z1)/2,roadM); for(let x=x0+4;x<x1-1;x+=2.4) if(Math.abs(x-20.2)>2.2) boxAt(truckRoadG,1.2,0.02,0.14,x,0.37,(TR_Z0+TR_Z1)/2,lineM);\n  boxAt(truckRoadG,3.0,0.03,3.0,TRUCK_BAY.x+0.4,0.38,TRUCK_BAY.z,new THREE.MeshBasicMaterial({color:0xf2b632}));\n  for(let k=0;k<6;k++) boxAt(truckRoadG,0.42,0.03,TR_Z1-TR_Z0-0.3,18.3+k*0.76,0.39,(TR_Z0+TR_Z1)/2,lineM); // zebra crossing for the roast runners\n  mergeGroup(truckRoadG); }\nfunction makeTruck(col){ const g=new THREE.Group(); scene.add(g); const body=new THREE.MeshLambertMaterial({color:col}), bed=new THREE.MeshLambertMaterial({color:0x4fc3d9});\n  boxAt(g,2.1,0.7,4.6,0,0.85,0,body); boxAt(g,2.0,0.95,1.7,0,1.65,1.25,body); boxAt(g,1.8,0.5,0.08,0,1.8,2.11,new THREE.MeshBasicMaterial({color:0xbfe9ff})); boxAt(g,2.1,0.12,2.5,0,1.26,-0.95,bed);\n  for(const sx of [-1,1]) boxAt(g,0.12,0.5,2.5,sx*1.0,1.5,-0.95,bed); boxAt(g,2.1,0.5,0.12,0,1.5,-2.2,bed);\n  for(const [x,z] of [[-1.0,1.45],[1.0,1.45],[-1.0,-1.45],[1.0,-1.45]]){ const w=new THREE.Mesh(new THREE.CylinderGeometry(0.45,0.45,0.32,14),M.dark); w.rotation.z=Math.PI/2; w.position.set(x,0.45,z); g.add(w); }\n  mergeGroup(g); g.visible=false; return g; }\nconst trucks=[]; function addTruck(){ const t={g:makeTruck([0xf07a2a,0xe0392e,0x2f86d8][trucks.length%3]),state:'away',t:0.4+trucks.length*3.5,x:TR_END+2,z:TRUCK_BAY.z,load:[],k:trucks.length,ry:-Math.PI/2}; trucks.push(t); return t; }\nconst TRUCK_ORDER=12, PRICE_TRUCK=5;\nfunction truckSlot(t,i){ const col=i%3, row=Math.floor(i/3)%2, lay=Math.floor(i/6); const lx=(col-1)*0.62, lz=-0.5-row*0.85; const c=Math.cos(t.ry), s=Math.sin(t.ry); return new V3(t.x+lx*c+lz*s, 0.33+1.34+lay*0.52, t.z-lx*s+lz*c); }\nfunction playerOnZebra(){ return Math.abs(player.pos.x-RUN_X)<1.6 && player.pos.z>TR_Z0-1.0 && player.pos.z<TR_Z1+1.0; }\nfunction updateTrucks(dt){ if(!has('trucks')) return; const bay=TRUCK_BAY; const loading=trucks.find(t=>t.state==='load'); const inLine=trucks.filter(t=>t.state==='in').sort((a,b)=>a.x-b.x);\n  for(const t of trucks){ t.g.visible=t.x<TR_END+1;\n    if(t.state==='away'){ t.t-=dt; t.x=TR_END+2; if(t.t<=0) t.state='in'; }\n    else if(t.state==='in'){ const k=inLine.indexOf(t); let stop=bay.x+1.6+(loading?5.2:0)+k*5.2; if(stop>RUN_X-4.2&&stop<RUN_X+3.4) stop=RUN_X+3.4+(stop-(RUN_X-4.2)); if(playerOnZebra()&&t.x>RUN_X+3.3&&t.x<RUN_X+5.5){ continue; } t.x=Math.max(stop,t.x-dt*11); if(t.x<=bay.x+1.61 && !loading){ t.state='load'; t.t=0.3; if(t.paid){ for(let q=0;q<t.paid;q++) payBill(new V3(t.x,2,t.z),q); t.paid=0; } } }\n    else if(t.state==='load'){ t.t-=dt; if(t.load.length<TRUCK_ORDER && !(t.load.length>=4 && (t.wait||0)>5)){ if(stocks.rackE<=0) t.wait=(t.wait||0)+dt; if(t.t<=0 && stocks.rackE>0){ const bk=takeRackBucket('E'); if(bk){ t.t=0.15; const hi=t.load.length; t.load.push(bk); bucketHop(bk,()=>truckSlot(t,hi),0.42,1.4,()=>{ bk.state='truck'; }); tick(hi); } } } else if(t.t<=0 && t.load.every(b=>b.state==='truck')){ t.state='out'; t.t=0; t.wait=0; } }\n    else if(t.state==='out'){ if(playerOnZebra()&&t.x<RUN_X-2.0&&t.x>RUN_X-5.8){ t.g.position.set(t.x,0.33,t.z); for(const bk of t.load){ const p=truckSlot(t,t.load.indexOf(bk)); bk.x=p.x; bk.y=p.y; bk.z=p.z; } continue; } t.t+=dt; t.ry=lerp(t.ry,Math.PI/2,clamp(dt*5,0,1)); t.x+=dt*Math.min(12,2+t.t*10); if(t.x>TR_END){ for(const bk of t.load) killBucket(bk); stat.sold+=t.load.length; stat.soldBy.truck=(stat.soldBy.truck||0)+t.load.length; t.paid=t.load.length*PRICE_TRUCK; t.load=[]; t.state='away'; t.t=5+R(0,1.5); t.ry=-Math.PI/2; } }\n    for(const bk of t.load) if(bk.state==='truck'){ const p=truckSlot(t,t.load.indexOf(bk)); bk.x=p.x; bk.y=p.y; bk.z=p.z; }\n    t.g.position.set(t.x,0.33,t.z); t.g.rotation.y=t.ry; } }\n\n// ================= #8 ROAST YARD: golden giants go round the splitter to the spit \u2192 roast \u2192 carve into roast plates =================\nconst roastG=new THREE.Group(); roastG.visible=false; scene.add(roastG);\nconst RUN_X=20.2; // runners' gate (south fence) and the zebra crossing\n{ deckSlab(roastG,ROAST); const R2=ROAST;\n  logFence(roastG,[[R2.x0+0.15,R2.z0+0.15,R2.x0+0.15,R2.z1-0.15,(x,z)=>z>14.6&&z<17.6],[R2.x1-0.15,R2.z0+0.15,R2.x1-0.15,R2.z1-0.15],[R2.x0+0.15,R2.z1-0.15,R2.x1-0.15,R2.z1-0.15],[R2.x0+0.15,R2.z0+0.15,R2.x1-0.15,R2.z0+0.15,(x)=>Math.abs(x-RUN_X)<1.3]],0.95,DECK_Y);\n  const brick=new THREE.MeshLambertMaterial({color:0x9c4f3a}), coal=new THREE.MeshBasicMaterial({color:0xff6a1a});\n  boxAt(roastG,5.4,0.9,3.2,SPIT.x,DECK_Y+0.45,SPIT.z,brick); boxAt(roastG,4.8,0.1,2.6,SPIT.x,DECK_Y+0.92,SPIT.z,coal);\n  for(const sx of [-2.55,2.55]){ boxAt(roastG,0.35,3.4,0.35,SPIT.x+sx,DECK_Y+1.7,SPIT.z,M.iron); boxAt(roastG,0.55,0.4,0.55,SPIT.x+sx,DECK_Y+3.3,SPIT.z,M.steel); }\n  boxAt(roastG,4.6,0.9,1.9,CARVE.x,DECK_Y+0.45,CARVE.z,M.wood); boxAt(roastG,4.8,0.12,2.1,CARVE.x,DECK_Y+0.95,CARVE.z,M.woodTop); boxAt(roastG,2.0,0.06,1.5,CARVE.x-1.1,DECK_Y+1.03,CARVE.z,new THREE.MeshLambertMaterial({color:0xf3e9d2}));\n  const sg=new THREE.Mesh(new THREE.PlaneGeometry(3.6,0.85),new THREE.MeshBasicMaterial({map:signTex('ROAST YARD','#3a1f14','#ffd36a',440)})); sg.position.set(ROAST.x0+2.6,DECK_Y+2.2,ROAST.z0+0.3); { const pst=new THREE.Mesh(new THREE.BoxGeometry(0.2,2.2,0.2),M.iron); pst.position.set(ROAST.x0+1.0,DECK_Y+1.1,ROAST.z0+0.3); roastG.add(pst); const ps2=pst.clone(); ps2.position.x=ROAST.x0+4.2; roastG.add(ps2); } sg.rotation.y=Math.PI; sg.userData.keep=1; roastG.add(sg);\n  mergeGroup(roastG); }\nconst spitWheel=new THREE.Mesh(new THREE.TorusGeometry(0.55,0.09,8,18),M.red); spitWheel.rotation.y=Math.PI/2; spitWheel.position.set(SPIT.x-2.95,DECK_Y+3.3,SPIT.z); spitWheel.visible=false; scene.add(spitWheel);\nconst rpadMesh=new THREE.Mesh(new THREE.BoxGeometry(1.5,0.16,1.5),[plateRimM,plateRimM,new THREE.MeshLambertMaterial({map:padTex}),plateRimM,plateRimM,plateRimM]); rpadMesh.position.set(RPAD.x,DECK_Y+0.08,RPAD.z); rpadMesh.visible=false; scene.add(rpadMesh);\nconst rpadRing=ring(1.1); rpadRing.position.set(RPAD.x,DECK_Y+0.18,RPAD.z); rpadRing.visible=false;\nconst roastSkin=new THREE.MeshPhongMaterial({color:0xf6e2b4,shininess:20,specular:0x222222});\nconst birdG=new THREE.Group(); scene.add(birdG); birdG.visible=false; const birdBody=new THREE.Group(); birdG.add(birdBody);\n{ const b=new THREE.Mesh(new THREE.SphereGeometry(1.25,22,16),roastSkin); b.scale.set(1.0,0.85,1.3); b.castShadow=true; birdBody.add(b);\n  for(const sx of [-1,1]){ const leg=new THREE.Mesh(new THREE.SphereGeometry(0.5,14,10),roastSkin); leg.scale.set(0.8,0.75,1.4); leg.position.set(sx*0.68,0.5,-1.05); birdBody.add(leg); const bn=new THREE.Mesh(new THREE.CylinderGeometry(0.09,0.12,0.55,8),boneM); bn.rotation.x=Math.PI/2.4; bn.position.set(sx*0.68,0.75,-1.75); birdBody.add(bn); const w=new THREE.Mesh(new THREE.SphereGeometry(0.45,12,8),roastSkin); w.scale.set(0.5,0.7,1.1); w.position.set(sx*1.18,0.15,0.25); birdBody.add(w); }\n  const rod=new THREE.Mesh(new THREE.CylinderGeometry(0.08,0.08,5.6,8),M.steel); rod.rotation.z=Math.PI/2; birdG.add(rod); }\nconst ROAST_RAW=new THREE.Color(0xf2c29a), ROAST_DONE=new THREE.Color(0xd27a28), ROAST_DEEP=new THREE.Color(0xa84f18);\nconst roast={state:'empty',k:0,t:0,manualDone:false,birds:0,spin:0,cuts:0,hold:0};\nconst PLATE_GEO=new THREE.CylinderGeometry(0.4,0.34,0.08,18).translate(0,0.04,0), MEAT_GEO=mergeGeos([new THREE.SphereGeometry(0.2,10,8).scale(1.35,0.6,0.9).translate(-0.05,0.13,0),new THREE.SphereGeometry(0.15,10,8).scale(1.2,0.6,0.9).rotateY(0.8).translate(0.14,0.16,0.08),new THREE.CylinderGeometry(0.035,0.035,0.22,6).rotateZ(1.2).translate(0.27,0.2,-0.08)]);\nconst PLN=240; const plMesh=cInst(PLATE_GEO,M.white,PLN), plMeat=cInst(MEAT_GEO,new THREE.MeshPhongMaterial({color:0xc8722a,shininess:60,specular:0x553311}),PLN); plMesh.count=0; plMeat.count=0; plMesh.castShadow=false;\nconst plates2=[]; stocks.carve=0; stocks.pass=0; let carvePend=0, passPend=0; const CARVE_CAP=24;\nfunction carveSlot(i){ const c=i%4, l=Math.floor(i/4); return new V3(CARVE.x+0.55+(c%2)*0.92, DECK_Y+1.02+l*0.1, CARVE.z-0.46+Math.floor(c/2)*0.92); }\nfunction newPlate(p){ const q={state:'idle',x:p.x,y:p.y,z:p.z,ry:R(0,6)}; plates2.push(q); stat.plates=(stat.plates||0)+1; return q; }\nfunction killPlate(q){ const i=plates2.indexOf(q); if(i>=0) plates2.splice(i,1); }\nfunction plateHop(q,to,dur,h,done){ q.state='hop'; q.ax=q.x; q.ay=q.y; q.az=q.z; q.to=to; q.t=0; q.dur=dur; q.h=h; q.done=done; }\nconst roastChef=staff(0xf6f7fa,0xffffff,CARVE.x-1.1,CARVE.z+1.35); roastChef.visible=false; roastChef.rotation.y=Math.PI;\nconst roastBar=(()=>{ const c=document.createElement('canvas'); c.width=256; c.height=48; const t=new THREE.CanvasTexture(c); const s=new THREE.Sprite(new THREE.SpriteMaterial({map:t,depthTest:false,transparent:true})); s.scale.set(3.2,0.6,1); s.renderOrder=9; s.visible=false; scene.add(s); s.userData={g:c.getContext('2d'),t,last:-1}; return s; })();\nfunction drawRoastBar(k,label){ const u=roastBar.userData, q=Math.round(k*40)+(label?label.length*100:0); if(q===u.last) return; u.last=q; const g=u.g; g.clearRect(0,0,256,48); g.fillStyle='rgba(30,20,10,.7)'; roundRect(g,2,2,252,44,20); g.fill(); g.fillStyle='#ffb02e'; roundRect(g,8,8,Math.max(20,240*k),32,15); g.fill(); g.fillStyle='#fff'; g.font='900 22px Arial'; g.textAlign='center'; g.fillText(label||'ROASTING',128,32); u.t.needsUpdate=true; }\nfunction updateRoast(dt){ if(!has('roast')) return; const L=LANES[2]; const front=L.q[0];\n  const needHold=roast.state==='roast'&&!roast.manualDone; rpadRing.visible=needHold&&!near(player.pos,RPAD,0.9); if(rpadRing.visible) rpadRing.scale.setScalar(1+Math.sin(stat.T*6)*0.06);\n  if(roast.state==='empty' && front && front.state==='walk' && front.s>=L.path.L-0.05 && stocks.carve+carvePend<=CARVE_CAP-12){\n    front.state='gone'; L.q.shift(); killChicken(front); featherBurst(SPIT.x,DECK_Y+3,SPIT.z+2.2,50); noise(0.2,0.15,800); tone(700,0.1,'square',0.04,0.6); roast.state='hop'; roast.t=0; birdG.visible=true; birdG.scale.setScalar(1); birdBody.scale.setScalar(1); roastSkin.color.copy(ROAST_RAW); roastSkin.shininess=20; roast.k=0; roast.birds++; logEv('spit '+roast.birds); }\n  if(roast.state==='hop'){ roast.t+=dt; const k=Math.min(1,roast.t/0.45); birdG.position.set(SPIT.x,DECK_Y+3.3+Math.sin(k*Math.PI)*1.5,lerp(SPIT.z+3.4,SPIT.z,k)); birdG.rotation.set(0,0,0); if(k>=1){ roast.state='roast'; shake=Math.max(shake,0.2); } }\n  else if(roast.state==='roast'){ const manual=!roast.manualDone; const turning=manual?near(player.pos,RPAD,0.95):true; roast.hold=turning?1:0;\n    if(turning){ roast.spin+=dt*(manual?5.0:3.0); roast.k=Math.min(1,roast.k+dt/(manual?3.4:4.6)); if(rnd()<dt*34) flame(SPIT.x+R(-2,2),DECK_Y+1.0,SPIT.z+R(-0.9,0.9),1,0.4,1.7); if(rnd()<dt*5) sparkle(SPIT.x+R(-1,1),DECK_Y+3.3,SPIT.z,2,0.6); if(rnd()<dt*7) noise(0.04,0.03,600+rnd()*500); }\n    else if(rnd()<dt*8) flame(SPIT.x+R(-2,2),DECK_Y+1.0,SPIT.z+R(-0.9,0.9),1,0.3,1.0);\n    roastSkin.color.copy(ROAST_RAW).lerp(ROAST_DONE,Math.min(1,roast.k*1.15)); if(roast.k>0.8) roastSkin.color.lerp(ROAST_DEEP,(roast.k-0.8)*1.2); roastSkin.shininess=20+roast.k*90; roastSkin.specular.setScalar(0.1+roast.k*0.45);\n    birdG.position.set(SPIT.x,DECK_Y+3.3,SPIT.z); birdG.rotation.set(roast.spin,0,0); spitWheel.rotation.x=roast.spin;\n    if(manual){ roastBar.visible=true; roastBar.position.set(SPIT.x,DECK_Y+5.6,SPIT.z); drawRoastBar(roast.k,turning?'TURNING\u2026':'STAND TO TURN'); } else roastBar.visible=false;\n    if(roast.k>=1){ roast.state='carry'; roast.t=0; roastBar.visible=false; sparkle(SPIT.x,DECK_Y+3.5,SPIT.z,24,1.4); tone(660,0.15,'triangle',0.06,1.2); tone(990,0.2,'triangle',0.05,1.3); if(manual){ roast.manualDone=true; showBanner('AUTO ROTISSERIE!'); logEv('roast manual done'); } } }\n  else if(roast.state==='carry'){ roast.t+=dt; const k=Math.min(1,roast.t/0.7); birdG.position.set(lerp(SPIT.x,CARVE.x-1.1,k),lerp(DECK_Y+3.3,DECK_Y+1.75,k)+Math.sin(k*Math.PI)*1.4,lerp(SPIT.z,CARVE.z,k)); birdG.rotation.set(0,0,0); if(k>=1){ roast.state='carve'; roast.t=0; roast.cuts=0; shake=Math.max(shake,0.15); } }\n  else if(roast.state==='carve'){ roast.t+=dt;\n    if(roast.t>=(roast.cuts+1)*0.36 && roast.cuts<12){ roast.cuts++; noise(0.05,0.08,1400); const idx=stocks.carve+carvePend; carvePend++; const q=newPlate(new V3(CARVE.x-1.1,DECK_Y+1.6,CARVE.z)); plateHop(q,carveSlot(idx),0.32,0.7,()=>{ stocks.carve++; carvePend--; q.state='carve'; q.idx=idx; }); birdBody.scale.setScalar(Math.max(0.2,1-roast.cuts/12*0.8)); }\n    if(roast.cuts>=12 && roast.t>=12*0.36+0.25){ birdG.visible=false; roast.state='empty'; } }\n  const u=roastChef.userData; if(roast.state==='carve'){ const ph=(roast.t/0.36)%1; animHuman(roastChef,0,dt,null); u.arms[1].rotation.x=ph<0.45?-2.7+ph*4.6:-0.6; u.arms[0].rotation.x=-1.0; } else animHuman(roastChef,0,dt,null); }\nfunction takeCarvePlate(){ let best=null; for(const q of plates2) if(q.state==='carve'&&(!best||q.idx>best.idx)) best=q; if(!best) return null; best.state='idle'; stocks.carve--; return best; }\nfunction updatePlates(dt){ for(const q of plates2){ if(q.state==='hop'){ q.t+=dt; const e=clamp(q.t/q.dur,0,1); const T=typeof q.to==='function'?q.to():q.to; q.x=lerp(q.ax,T.x,e); q.z=lerp(q.az,T.z,e); q.y=lerp(q.ay,T.y,e)+Math.sin(e*Math.PI)*q.h; if(e>=1){ q.x=T.x; q.y=T.y; q.z=T.z; q.state='idle'; const d=q.done; q.done=null; if(d) d(); } } else if(q.state==='held'){ const p=q.holder.handPos(q.hi); q.x=p.x; q.y=p.y; q.z=p.z; } }\n  const cs=plates2.filter(q=>q.state==='carve').sort((a,b)=>a.idx-b.idx); cs.forEach((q,i)=>{ q.idx=i; const p=carveSlot(i); q.x=p.x; q.y=p.y; q.z=p.z; });\n  const ps=plates2.filter(q=>q.state==='pass').sort((a,b)=>a.idx-b.idx); ps.forEach((q,i)=>{ q.idx=i; const p=passSlot(i); q.x=p.x; q.y=p.y; q.z=p.z; });\n  let n=0; for(const q of plates2){ if(n>=PLN) break; _e.set(0,q.ry,0); _q.setFromEuler(_e); _m.compose(_p.set(q.x,q.y,q.z),_q,_s.set(1,1,1)); plMesh.setMatrixAt(n,_m); if(q.eaten) _m.scale(_s.set(1,1-q.eaten*0.9,1)); plMeat.setMatrixAt(n,_m); n++; } plMesh.count=n; plMeat.count=n; plMesh.instanceMatrix.needsUpdate=true; plMeat.instanceMatrix.needsUpdate=true; }\n\n// ================= RUNNERS (roast plates \u2192 feast plaza / roast hall) + WAITERS =================\nconst HPASS=new V3(20.2,0,HALL.z1-0.75);\nfunction passSlot(i){ const c=i%5, l=Math.floor(i/5); return new V3(HPASS.x-1.6+c*0.8, DECK_Y+1.05+l*0.1, HPASS.z); }\nconst PASS_CAP=30;\nconst staffCrowd=makeCrowd(8,new THREE.MeshLambertMaterial({color:0xffffff}));\nconst hstaff=[]; function addStaff(role,k){ const home=role==='run'?new V3(CARVE.x+1.2+k*0.9,0,CARVE.z-1.55):new V3(19.6+k*1.2,0,HPASS.z-0.85); const s={role,k,x:home.x,z:home.z,home,state:'idle',n:0,col:new THREE.Color(role==='run'?(k?0xff8a3a:0xffb02e):0x2b2f3d),path:[],tg:[],face:0,t:0,sc:1.05};\n  s.handPos=(i)=>new V3(s.x+Math.sin(s.face)*0.5,DECK_Y+1.0+i*0.085,s.z+Math.cos(s.face)*0.5); hstaff.push(s); return s; }\nconst PLAZA_RUN=[new V3(RUN_X,0,ROAST.z0-0.5),new V3(RUN_X,0,2.4),new V3(13.5,0,2.4),new V3(13.5,0,-13.9),new V3(PLAZA.x1+1.2,0,-13.9)];\nconst HALL_RUN=[new V3(RUN_X,0,ROAST.z0-0.5),new V3(RUN_X,0,HALL.z1+0.8)];\nfunction updateStaff(dt){ for(const s of hstaff){ s.t-=dt;\n    if(s.role==='run'){\n      if(s.state==='idle'){ s.state='toPick'; s.path=[s.home.clone()]; }\n      if(s.state==='toPick'){ if(walkP(s,dt,4.2)){ s.face=0; const dest=(s.k===0&&festNeedPlates())?'plaza':(has('hall')?'hall':null); const cap=dest==='plaza'?12:8;\n          const room=dest==='plaza'?Math.min(cap,FEST.need.plate-FEST.got.plate-FEST.resP):(dest==='hall'?Math.min(cap,PASS_CAP-stocks.pass-passPend-hstaff.filter(o=>o.role==='run'&&o!==s&&o.state==='toDrop').reduce((a,o)=>a+o.n,0)):0);\n          if(dest && s.n<room && stocks.carve>0){ if(s.t<=0){ const q=takeCarvePlate(); if(q){ s.t=0.07; q.holder=s; q.hi=s.n; s.n++; q.state='held'; noise(0.02,0.03,2400); } } s.wait=0; }\n          else if(dest && s.n>0 && (s.n>=room || (s.wait=(s.wait||0)+dt)>1.2)){ s.dest=dest; if(dest==='plaza') FEST.resP+=s.n; s.state='toDrop'; s.wait=0; s.path=(dest==='plaza'?PLAZA_RUN.concat([FEAST_DROP]):HALL_RUN.concat([new V3(HPASS.x-0.8+s.k*1.6,0,HALL.z1+0.35)])).map(p=>p.clone()); } } }\n      else if(s.state==='toDrop'){ if(walkP(s,dt,s.dest==='plaza'?6.0:4.6)){ s.face=Math.PI; if(s.n>0){ if(s.t<=0){ s.t=0.06; s.n--; const q=plates2.find(p=>p.holder===s&&p.hi===s.n&&p.state==='held'); if(q){ q.holder=null; if(s.dest==='plaza'){ FEST.resP--; feastPlate(q); } else { const idx=stocks.pass+passPend; passPend++; plateHop(q,passSlot(idx),0.28,0.5,()=>{ stocks.pass++; passPend--; q.state='pass'; q.idx=idx; }); } } } }\n          else { s.state='toPick'; const back=(s.dest==='plaza'?PLAZA_RUN:HALL_RUN).slice().reverse(); s.path=back.concat([s.home]).map(p=>p.clone()); } } } }\n    else { // waiters\n      if(s.state==='idle'){ if(!walkP(s,dt,4.0)) continue; s.face=Math.PI; if(stocks.pass>0 && s.t<=0){ const wait=diners.filter(d=>d.state==='sit'&&!d.w); if(wait.length){ const hx=s.x; wait.sort((a,b)=>Math.abs(HSEATS[a.seat].aisle-s.z)*2+Math.abs(HSEATS[a.seat].x-hx)-(Math.abs(HSEATS[b.seat].aisle-s.z)*2+Math.abs(HSEATS[b.seat].x-hx)));\n            const want=Math.min(4,stocks.pass,wait.length); s.tg=[]; for(let k=0;k<want;k++){ let top=null; for(const p of plates2) if(p.state==='pass'&&(!top||p.idx>top.idx)) top=p; if(!top) break; stocks.pass--; top.state='held'; top.holder=s; top.hi=s.n; s.n++; const d=wait[k]; d.w=s; s.tg.push(d); }\n            s.tg.sort((a,b)=>HSEATS[a.seat].aisle-HSEATS[b.seat].aisle||HSEATS[a.seat].x-HSEATS[b.seat].x).reverse(); s.state='serve'; s.path=[]; } } }\n      else if(s.state==='serve'){ if(!s.path.length){ const d=s.tg[0]; if(!d){ s.state='idle'; s.path=[new V3(HALL_AISLE_X,0,s.z),new V3(HALL_AISLE_X,0,s.home.z),s.home.clone()]; continue; } const S=HSEATS[d.seat]; const T=new V3(S.x,0,S.aisle);\n          if(Math.hypot(T.x-s.x,T.z-s.z)<0.1){ s.tg.shift(); s.n--; s.face=S.face+Math.PI; const q=plates2.find(p=>p.holder===s&&p.hi===s.n&&p.state==='held'); if(q){ q.holder=null; plateHop(q,new V3(S.x,DECK_Y+0.9,S.tz),0.26,0.4,()=>{ q.state='table'; d.plate=q; d.state='eat'; d.t=0; d.w=null; }); } else d.w=null; continue; }\n          s.path=Math.abs(s.z-T.z)<0.1?[T]:[new V3(HALL_AISLE_X,0,s.z),new V3(HALL_AISLE_X,0,T.z),T]; } walkP(s,dt,4.0); } } }\n  renderCrowd(staffCrowd,hstaff,dt); }\n\n// ================= #10 ROAST HALL (south-east, door on the street): diners sit, waiters bring roast, they pay =================\nconst hallG=new THREE.Group(); hallG.visible=false; scene.add(hallG);\nconst HPDOOR0=23.4, HPDOOR1=25.8; const HROWS=[-6.0,-9.0], HTABLES=[[15.6,19.4],[21.0,24.8],[25.6,29.2]], HALL_AISLE_X=HALL.x0+0.75, HDOOR_X=20.2;\n{ const H=HALL, cx=(H.x0+H.x1)/2, cz=(H.z0+H.z1)/2; const tileT=canvasTex(128,128,(g)=>{ for(let y=0;y<4;y++) for(let x=0;x<4;x++){ g.fillStyle=(x+y)%2?'#b8553e':'#f1dcc0'; g.fillRect(x*32,y*32,32,32); } },[8,4]); const fl=slab2(H.x1-H.x0,0.5,H.z1-H.z0,M.plankSide,new THREE.MeshLambertMaterial({map:tileT})); fl.position.set(cx,DECK_Y-0.25,cz); hallG.add(fl);\n  wallSeg(hallG,H.x0,H.z1,18.4,H.z1,3.2,wallM,true); wallSeg(hallG,22.0,H.z1,HPDOOR0,H.z1,3.2,wallM,true); wallSeg(hallG,HPDOOR1,H.z1,H.x1,H.z1,3.2,wallM,true); wallSeg(hallG,H.x1,H.z1,H.x1,H.z0,1.0,wallD); wallSeg(hallG,H.x0,H.z1,H.x0,H.z0,1.0,wallD);\n  wallSeg(hallG,H.x0,H.z0,HDOOR_X-1.4,H.z0,1.0,wallD); wallSeg(hallG,HDOOR_X+1.4,H.z0,H.x1,H.z0,1.0,wallD);\n  const aw=boxAt(hallG,H.x1-H.x0+0.6,0.22,1.8,cx,DECK_Y+3.4,H.z1-0.7,roofM); aw.rotation.x=-0.3; const sn=boxAt(hallG,H.x1-H.x0+0.4,0.08,1.6,cx,DECK_Y+3.55,H.z1-0.72,snowM); sn.rotation.x=-0.3;\n  boxAt(hallG,4.4,0.95,0.7,HPASS.x,DECK_Y+0.48,HPASS.z,M.wood); boxAt(hallG,4.6,0.12,0.85,HPASS.x,DECK_Y+0.98,HPASS.z,M.woodTop);\n  for(const z of HROWS) for(const [a,b] of HTABLES){ const L=b-a, tx=(a+b)/2; boxAt(hallG,L,0.7,0.9,tx,DECK_Y+0.45,z,new THREE.MeshLambertMaterial({color:0x6b4a3a})); const cl=new THREE.Mesh(new THREE.BoxGeometry(L+0.06,0.05,1.0),M.red); cl.position.set(tx,DECK_Y+0.82,z); hallG.add(cl); for(const dz of [0.78,-0.78]) boxAt(hallG,L-0.2,0.42,0.42,tx,DECK_Y+0.21,z+dz,M.wood); }\n  const sg=new THREE.Mesh(new THREE.PlaneGeometry(4.0,0.9),new THREE.MeshBasicMaterial({map:signTex('ROAST HALL','#3a1f14','#ffd36a',440)})); sg.position.set(HDOOR_X,DECK_Y+2.9,H.z0-0.25); sg.rotation.y=Math.PI; sg.userData.keep=1; hallG.add(sg);\n  for(const x of [HDOOR_X-1.4,HDOOR_X+1.4]){ boxAt(hallG,0.4,2.6,0.4,x,DECK_Y+1.3,H.z0,wallD); lantern(hallG,x,DECK_Y+2.75,H.z0-0.3); }\n  for(let x=H.x0+1.5;x<H.x1;x+=3.2) lantern(hallG,x,DECK_Y+3.0,H.z1-1.3);\n  { const ap=slab2(31-13.6,0.3,TR_Z0-(H.z1-0.2),M.stone,M.stone); ap.position.set((13.6+31)/2,0.2,(TR_Z0+H.z1-0.2)/2); hallG.add(ap); const pth=slab2(2.6,0.3,ROAST.z0-TR_Z1+0.4,M.stone,M.stone); pth.position.set(RUN_X,0.2,(ROAST.z0+TR_Z1)/2); hallG.add(pth);\n    const mat=new THREE.MeshBasicMaterial({map:signTex('ROAST HALL \u2193','#3a1f14','#ffd36a',440)}); const sg2=new THREE.Mesh(new THREE.PlaneGeometry(2.4,0.55),mat); sg2.position.set((HPDOOR0+HPDOOR1)/2,DECK_Y+2.6,H.z1+0.25); sg2.rotation.y=Math.PI; sg2.userData.keep=1; hallG.add(sg2); }\n  mergeGroup(hallG); }\nconst HSEATS=[]; for(const z of HROWS) for(const [a,b] of HTABLES) for(let x=a+0.55;x<b-0.3;x+=0.9){ HSEATS.push({x,z:z+0.78,face:Math.PI,aisle:z+1.35,tz:z+0.3}); HSEATS.push({x,z:z-0.78,face:0,aisle:z-1.35,tz:z-0.3}); }\nconst hallCrowd=makeCrowd(56,M.white);\nconst diners=[]; const dinerCols=[0xd2453a,0xe08a2a,0x3a7fd0,0x46a35a,0x9a4fc4,0x2bb3a6]; for(let i=0;i<56;i++) diners.push({x:0,z:0,state:'off',col:new THREE.Color(dinerCols[i%6]),seat:-1,ph:R(0,6),path:null,hidden:true});\nconst hseatTaken=[]; const HCASH={pos:new V3(27.6,0,-4.15),n:0,pend:0}; PILES.push(HCASH);\nconst DINE_LINE_Z=STREET.z1-0.9, PRICE_PLATE=12;\nfunction updateHall(dt){ if(!has('hall')) return;\n  const line=diners.filter(d=>d.state==='line').sort((a,b)=>b.x-a.x); line.forEach((d,k)=>{ d.tx=HDOOR_X-1.4-k*0.75; d.tz=DINE_LINE_Z; });\n  if(line.length){ const d=line[0]; if(Math.hypot(d.x-d.tx,d.z-d.tz)<0.3){ let s=-1; for(let i=0;i<HSEATS.length;i++) if(!hseatTaken[i]){ s=i; break; } if(s>=0){ hseatTaken[s]=d; d.seat=s; const S=HSEATS[s]; d.state='walk'; d.path=[new V3(HDOOR_X,0,HALL.z0+0.3),new V3(HDOOR_X,0,S.aisle),new V3(S.x,0,S.aisle),new V3(S.x,0,S.z)]; } } }\n  for(const d of diners){ d.cheer=Math.max(0,(d.cheer||0)-dt); if(d.state==='off'){ d.hidden=true; continue; } d.hidden=false; d.sit=false; d.eat=false;\n    if(d.state==='line'){ const dx=d.tx-d.x, dz=d.tz-d.z, L=Math.hypot(dx,dz); if(L>0.05){ const s=Math.min(L,dt*3.8); d.x+=dx/L*s; d.z+=dz/L*s; d.face=Math.atan2(dx,dz); d.mv=1; } else { d.mv=0; d.face=Math.PI/2; } }\n    else if(d.state==='walk'){ if(walkP(d,dt,4.2)) d.state='sit'; }\n    else if(d.state==='sit'){ d.sit=true; d.face=HSEATS[d.seat].face; d.mv=0; }\n    else if(d.state==='eat'){ d.sit=true; d.eat=true; d.face=HSEATS[d.seat].face; d.t+=dt; if(d.plate && d.t>1.0) d.plate.eaten=Math.min(1,(d.t-1.0)/1.8);\n      if(d.t>2.9){ d.state='pay'; for(let k=0;k<PRICE_PLATE;k++) payBill(new V3(d.x,DECK_Y+1.6,d.z),k,HCASH); stat.sold++; stat.soldBy.hall=(stat.soldBy.hall||0)+1; if(d.plate){ killPlate(d.plate); d.plate=null; } d.cheer=0.7; hseatTaken[d.seat]=null; const S=HSEATS[d.seat]; d.seat=-1;\n        d.path=[new V3(S.x,0,S.aisle),new V3(HDOOR_X+0.4,0,S.aisle),new V3(HDOOR_X+0.4,0,HALL.z0-0.6),new V3(HDOOR_X+0.4,0,STREET.z0+1.2),new V3(STREET.x1-1,0,STREET.z0+1.2)]; } }\n    else if(d.state==='pay'){ if(walkP(d,dt,4.2)){ d.state='line'; d.x=STREET.x0+R(4,10); d.z=DINE_LINE_Z; } } }\n  renderCrowd(hallCrowd,diners,dt); }\n\n// ================= #9 WINTER FEAST (south-west plaza): long tables filled by real buckets (bikes) and roast plates (runner) =================\nconst FEAST_DROP=new V3(PLAZA.x1-1.0,0,PLAZA.z1-1.2);\nconst FEAST_BIKE_PATH=[new V3(-12,0,BIKE_BAY.z),new V3(-12,0,PLAZA.z1-0.6),new V3(-12.0,0,PLAZA.z1-2.2)];\nconst FEAST_PRICE_B=6, FEAST_PRICE_P=10;\nFEST.need={bucket:42,plate:36}; FEST.res=0; FEST.resP=0; FEST.stage=0;\nconst feastRoadG=new THREE.Group(); feastRoadG.visible=false; scene.add(feastRoadG);\n{ boxAt(feastRoadG,3.0,0.06,BIKE_BAY.z+1.6-(STREET.z1),-12,0.33,(BIKE_BAY.z+1.6+STREET.z1)/2,roadM); for(let z=STREET.z1+1.5;z<BIKE_BAY.z-2;z+=2.4) boxAt(feastRoadG,0.14,0.02,1.2,-12,0.37,z,lineM); mergeGroup(feastRoadG); }\nconst feastG=new THREE.Group(); feastG.visible=false; scene.add(feastG);\nconst FT_ROWS=[-20.6,-23.8,-27.0], FT_X0=PLAZA.x0+2.0, FT_X1=PLAZA.x1-3.6;\n{ const P=PLAZA, cx=(P.x0+P.x1)/2, cz=(P.z0+P.z1)/2; const fl=slab2(P.x1-P.x0,0.3,P.z1-P.z0,M.stone,M.stone); fl.position.set(cx,0.2,cz); feastG.add(fl);\n  for(const z of FT_ROWS){ boxAt(feastG,FT_X1-FT_X0,0.7,1.0,(FT_X0+FT_X1)/2,DECK_Y+0.45,z,new THREE.MeshLambertMaterial({color:0x6b4a3a})); const cl=new THREE.Mesh(new THREE.BoxGeometry(FT_X1-FT_X0+0.06,0.05,1.1),M.white); cl.position.set((FT_X0+FT_X1)/2,DECK_Y+0.82,z); feastG.add(cl); const rn=new THREE.Mesh(new THREE.BoxGeometry(FT_X1-FT_X0+0.08,0.06,0.3),M.red); rn.position.set((FT_X0+FT_X1)/2,DECK_Y+0.84,z); feastG.add(rn); for(const dz of [0.85,-0.85]) boxAt(feastG,FT_X1-FT_X0-0.2,0.42,0.42,(FT_X0+FT_X1)/2,DECK_Y+0.21,z+dz,M.wood); }\n  boxAt(feastG,4.6,0.8,3.0,P.x1-2.0,0.75,P.z0+2.4,M.woodDark); for(const sx of [-2.0,2.0]) boxAt(feastG,0.3,4.2,0.3,P.x1-2.0+sx,2.4,P.z0+1.2,M.red); boxAt(feastG,4.6,0.45,0.4,P.x1-2.0,4.5,P.z0+1.2,M.red);\n  const bd=new THREE.Mesh(new THREE.PlaneGeometry(4.0,0.95),new THREE.MeshBasicMaterial({map:signTex('WINTER FEAST','#2f86d8','#ffffff',480)})); bd.position.set(P.x1-2.0,3.8,P.z0+1.0); bd.rotation.y=Math.PI; bd.userData.keep=1; feastG.add(bd);\n  for(let k=0;k<6;k++){ const x=P.x0+1+k*(P.x1-P.x0-2)/5; boxAt(feastG,0.14,2.8,0.14,x,1.75,P.z1-0.4,M.iron); lantern(feastG,x,3.2,P.z1-0.4); }\n  mergeGroup(feastG); }\n// decorations that grow with progress (25 / 50 / 75 / 100 %)\nconst decoG=[0,1,2,3].map(()=>{ const g=new THREE.Group(); g.visible=false; scene.add(g); return g; });\n{ const cols=[M.red,M.yellow,M.blue,new THREE.MeshLambertMaterial({color:0x46c35a})]; for(const z of FT_ROWS){ for(let x=FT_X0;x<FT_X1;x+=0.7){ const f=new THREE.Mesh(new THREE.ConeGeometry(0.18,0.36,3),cols[Math.round((x-FT_X0)/0.7)%4]); f.rotation.x=Math.PI; f.position.set(x,3.0-Math.sin((x-FT_X0)/(FT_X1-FT_X0)*Math.PI)*0.6,z); decoG[0].add(f); } boxAt(decoG[0],0.15,3.2,0.15,FT_X0-0.3,1.9,z,M.iron); boxAt(decoG[0],0.15,3.2,0.15,FT_X1+0.3,1.9,z,M.iron); }\n  const tx=PLAZA.x0+2.0, tz=PLAZA.z0+2.2; boxAt(decoG[1],0.5,1.0,0.5,tx,0.85,tz,M.woodDark); for(let k=0;k<4;k++){ const c=new THREE.Mesh(new THREE.ConeGeometry(2.0-k*0.42,1.9,10),new THREE.MeshLambertMaterial({color:0x2f8a4a})); c.position.set(tx,1.9+k*1.15,tz); decoG[1].add(c); } const star=new THREE.Mesh(new THREE.OctahedronGeometry(0.42),new THREE.MeshBasicMaterial({color:0xffd84a})); star.position.set(tx,6.6,tz); decoG[1].add(star);\n  for(let k=0;k<14;k++){ const a=k*2.4, h=1.6+k*0.33, r=1.75-k*0.1; const b=new THREE.Mesh(new THREE.SphereGeometry(0.14,8,6),cols[k%4]); b.position.set(tx+Math.cos(a)*r,h,tz+Math.sin(a)*r); decoG[1].add(b); }\n  const ax=(PLAZA.x0+PLAZA.x1)/2, az=PLAZA.z1-0.2; for(const sx of [-1,1]) boxAt(decoG[2],0.7,4.6,0.7,ax+sx*3.4,2.6,az,new THREE.MeshLambertMaterial({color:0xbfe4ff})); boxAt(decoG[2],7.6,0.8,0.8,ax,5.0,az,new THREE.MeshLambertMaterial({color:0xbfe4ff})); for(let k=0;k<9;k++) lantern(decoG[2],ax-3.2+k*0.8,4.5,az-0.45);\n  for(const [x,z] of [[PLAZA.x0+4.5,PLAZA.z1-1.6],[PLAZA.x1-1.6,PLAZA.z0+5.6]]){ const b1=new THREE.Mesh(new THREE.SphereGeometry(0.75,12,10),M.pineSnow); b1.position.set(x,0.95,z); decoG[2].add(b1); const b2=new THREE.Mesh(new THREE.SphereGeometry(0.5,12,10),M.pineSnow); b2.position.set(x,2.0,z); decoG[2].add(b2); const nose=new THREE.Mesh(new THREE.ConeGeometry(0.08,0.35,6),M.orange); nose.rotation.x=-Math.PI/2; nose.position.set(x,2.05,z-0.5); decoG[2].add(nose); }\n  const gold=new THREE.MeshPhongMaterial({color:0xffc23a,shininess:90,specular:0x886622}); const st=new THREE.Mesh(new THREE.SphereGeometry(0.9,16,12),gold); st.scale.set(1,0.85,1.25); st.position.set(PLAZA.x1-2.0,2.2,PLAZA.z0+2.4); decoG[3].add(st); const bn=new THREE.Mesh(new THREE.CylinderGeometry(0.14,0.18,1.0,8),boneM); bn.rotation.x=1.1; bn.position.set(PLAZA.x1-2.0,2.8,PLAZA.z0+3.6); decoG[3].add(bn);\n  for(const g of decoG) mergeGroup(g); }\nfunction feastSlot(kind,i){ const row=FT_ROWS[i%3], c=Math.floor(i/3); const n=kind==='bucket'?FEST.need.bucket/3:FEST.need.plate/3; const x=lerp(FT_X0+0.5,FT_X1-0.5,(c+0.5)/n); return new V3(x+(kind==='bucket'?-0.18:0.18), DECK_Y+0.86, row+(kind==='bucket'?0.2:-0.22)); }\nconst guestCrowd=makeCrowd(42,new THREE.MeshLambertMaterial({color:0xc0392b})); const guests=[];\n{ const per=7; let i=0; for(const z of FT_ROWS) for(const side of [1,-1]) for(let c=0;c<per;c++){ const x=lerp(FT_X0+0.6,FT_X1-0.6,(c+0.5)/per); guests.push({sx:x,sz:z+side*0.85,face:side>0?Math.PI:0,x:-12+R(-1,1),z:STREET.z0+R(0,3),state:'off',col:new THREE.Color([0xf2b632,0xe0483c,0x3fae5a,0x2f86d8,0x9a4fc4][i%5]),ph:R(0,6),hidden:true,path:null}); i++; } }\nfunction festNeedPlates(){ return FEST.on && !FEST.done && FEST.got.plate+FEST.resP<FEST.need.plate; }\nfunction festBikeOrder(){ if(!FEST.on||FEST.done) return 0; const left=FEST.need.bucket-FEST.got.bucket-FEST.res; if(left<=0) return 0; const n=Math.min(6,left); FEST.res+=n; return n; }\nfunction festBikeMove(b,dt){ if(!b.path.length) return true; const t=b.path[0]; const dx=t.x-b.x, dz=t.z-b.z, L=Math.hypot(dx,dz); if(L<0.1){ b.path.shift(); return !b.path.length; } const s=Math.min(L,dt*11); b.x+=dx/L*s; b.z+=dz/L*s; const ry=Math.atan2(dx,dz); let d=((ry-b.ry+Math.PI*3)%(Math.PI*2))-Math.PI; b.ry+=d*clamp(dt*10,0,1); return false; }\nfunction cheerNear(x,z){ let best=null,bd=1e9; for(const g of guests) if(g.state==='sit'){ const d=(g.x-x)**2+(g.z-z)**2; if(d<bd&&(g.cheer||0)<=0){ bd=d; best=g; } } if(best) best.cheer=0.7; }\nfunction feastPlate(q){ const i=FEST.got.plate; FEST.got.plate++; const to=feastSlot('plate',i); plateHop(q,to,0.4,0.8,()=>{ q.state='feast'; cheerNear(to.x,to.z); }); for(let k=0;k<FEAST_PRICE_P;k++) payBill(new V3(q.x,DECK_Y+1.4,q.z),k); stat.sold++; stat.soldBy.feastP=(stat.soldBy.feastP||0)+1; festProgress(); }\nfunction feastBucket(b){ FEST.res--; const i=FEST.got.bucket; FEST.got.bucket++; const to=feastSlot('bucket',i); bucketHop(b,to,0.5,1.4,()=>{ b.state='feast'; cheerNear(to.x,to.z); }); tick(i); festProgress(); }\nconst festUI=document.createElement('div'); festUI.id='festui'; festUI.innerHTML='<div class=\"t\">\u2744 WINTER FEAST</div><div class=\"r\"><span class=\"i b\"></span><span class=\"v vb\"></span><span class=\"i p\"></span><span class=\"v vp\"></span></div><div class=\"bar\"><div class=\"f\"></div></div>'; document.body.appendChild(festUI);\n{ const st=document.createElement('style'); st.textContent=`#festui{position:fixed;right:10px;top:10px;min-width:150px;background:rgba(30,70,120,.72);border:2px solid #fff;border-radius:12px;padding:4px 10px 6px;color:#fff;font-weight:900;pointer-events:none;display:none;text-shadow:0 2px 0 rgba(0,0,0,.35);font-size:15px}\n#festui .t{font-size:11px;letter-spacing:1px;color:#bfe4ff} #festui .r{display:flex;align-items:center;gap:5px;margin-top:2px} #festui .i{display:inline-block;width:16px;height:16px;border-radius:4px} #festui .i.b{background:repeating-linear-gradient(90deg,#e33 0 4px,#fff 4px 8px)} #festui .i.p{background:#c8722a;border-radius:50%;box-shadow:0 0 0 3px #fff inset} #festui .v{margin-right:8px}\n#festui .bar{height:8px;border-radius:5px;background:rgba(0,0,0,.35);margin-top:4px;overflow:hidden} #festui .f{height:100%;width:0;background:linear-gradient(90deg,#7fd0ff,#ffffff)} #festui.done{background:rgba(200,140,20,.8)}`; document.head.appendChild(st); }\nfunction festUIUpdate(){ if(!FEST.on){ festUI.style.display='none'; return; } festUI.style.display='block'; festUI.querySelector('.vb').textContent=Math.min(FEST.got.bucket,FEST.need.bucket)+'/'+FEST.need.bucket; festUI.querySelector('.vp').textContent=Math.min(FEST.got.plate,FEST.need.plate)+'/'+FEST.need.plate; festUI.querySelector('.f').style.width=(festK()*100)+'%'; festUI.classList.toggle('done',FEST.done); }\nfunction festK(){ return (Math.min(FEST.got.bucket,FEST.need.bucket)+Math.min(FEST.got.plate,FEST.need.plate))/(FEST.need.bucket+FEST.need.plate); }\nlet fireworks=0;\nfunction festProgress(){ const k=festK(); while(FEST.stage<3 && k>=(FEST.stage+1)*0.25){ const g=decoG[FEST.stage]; g.visible=true; rise(g,-3); sparkle(-16,3,-24,24,2); tone(520+FEST.stage*120,0.18,'triangle',0.06,1.2); FEST.stage++; logEv('feast deco '+FEST.stage); if(showcase.t<=0){ showcase.pos.set(-16,0,-22); showcase.t=1.8; showcase.path=null; } }\n  if(!FEST.done && FEST.got.bucket>=FEST.need.bucket && FEST.got.plate>=FEST.need.plate){ FEST.done=true; FEST.doneT=stat.T; decoG[3].visible=true; rise(decoG[3],-3); fireworks=7; logEv('feast done'); showBanner('WINTER FEAST!  \ud83c\udf86'); tour(new V3(-8,0,-8),new V3((PLAZA.x0+PLAZA.x1)/2,0,(PLAZA.z0+PLAZA.z1)/2),3.4,1.25); for(const g of guests) g.cheer=0.7; for(let k=0;k<60;k++) payBill(new V3(-16,3,-24),k); ching(); } }\nfunction updateFeast(dt){ if(!FEST.on){ festUIUpdate(); return; }\n  for(const g of guests){ g.cheer=Math.max(0,(g.cheer||0)-dt); if(g.state==='off') continue; g.hidden=false;\n    if(g.state==='walk'){ if(walkP(g,dt,4.4)){ g.state='sit'; g.x=g.sx; g.z=g.sz; } } if(g.state==='sit'){ g.sit=true; g.face=g.sface; g.eat=FEST.done&&(g.ph%7<3); if(FEST.done && rnd()<dt*0.25) g.cheer=0.7; } }\n  if(fireworks>0){ fireworks-=dt; if(rnd()<dt*5){ const x=R(PLAZA.x0+2,PLAZA.x1-2), z=R(PLAZA.z0+2,PLAZA.z1-2), y=R(8,12); sparkle(x,y,z,30,3.2); tone(R(300,700),0.08,'sine',0.04,0.3); } }\n  renderCrowd(guestCrowd,guests,dt); festUIUpdate(); }\n\n// ================= #11 MEGA CHICKEN (during the feast) =================\nconst MEGA={on:false,next:0,t:0};\nfunction updateMega(dt){ if(!MEGA.on) return; MEGA.t-=dt; if(MEGA.t<=0 && MEGA.next===0){ MEGA.next=1; MEGA.t=13; } }\n\n// ================= tiles, walk areas, obstacles =================\nObject.assign(TNAME,{trucks:'PICKUP TRUCKS',roast:'ROAST YARD',plaza:'WINTER FEAST',hall:'ROAST HALL',mega:'MEGA CHICKEN'});\nconst LATE_DEFS=[\n  {id:'trucks',icon:'cargo',x:8.6, z:6.7, req:()=>has('bikes') && (stocks.rackE>=10 || after('bikes',8))},\n  {id:'roast', icon:'giant',x:10.6,z:16.0,req:()=>has('trucks') && after('trucks',6)},\n  {id:'plaza', icon:'chef', x:17.0,z:10.6,req:()=>has('roast') && roast.manualDone},\n  {id:'mega',  icon:'giant',x:10.6,z:17.8,req:()=>has('plaza') && after('plaza',4)},\n  {id:'hall',  icon:'chef', x:22.6,z:10.6,req:()=>has('mega') && after('mega',4)} ];\nLATE_DEFS.forEach(d=>{ d.price=PRICE[d.id]; addTile(d); });\nextraWalk.push({on:()=>has('roast'),r:{x0:ROAST.x0+0.3,x1:ROAST.x1-0.3,z0:ROAST.z0+0.3,z1:ROAST.z1-0.3}},{on:()=>has('roast'),r:{x0:FAC.x1-1,x1:ROAST.x0+1,z0:14.8,z1:17.4}});\n{ const onH=()=>has('hall'); extraWalk.push({on:onH,r:{x0:RUN_X-1.1,x1:RUN_X+1.1,z0:TR_Z0-1.4,z1:ROAST.z0+1.8}},{on:onH,r:{x0:13.9,x1:30.8,z0:HALL.z1+0.1,z1:TR_Z0+0.9}},{on:onH,r:{x0:HPDOOR0+0.1,x1:HPDOOR1-0.1,z0:HALL.z1-1.4,z1:HALL.z1+1.2}},{on:onH,r:{x0:HALL.x0+0.25,x1:HALL.x1-0.25,z0:HALL.z0+0.25,z1:HALL.z1-0.2}});\n  for(const z of HROWS) for(const [a,b] of HTABLES) boxObs((a+b)/2,z,(b-a)/2+0.05,1.0,onH); boxObs(HPASS.x,HPASS.z,2.3,0.45,onH); }\nboxObs(SPIT.x,SPIT.z,2.75,1.65,()=>has('roast')); boxObs(CARVE.x,CARVE.z,2.4,1.1,()=>has('roast')); boxObs(15.9,SPLIT.z,3.0,1.8,()=>has('roast')); boxObs(SPIT.x,ROAST.z1-1.6,1.7,1.6,()=>has('roast'));\nafterHooks.push((t)=>{\n  if(t.id==='trucks'){ truckRoadG.visible=true; rise(truckRoadG,-0.5); addTruck(); addTruck(); tour(new V3(11,0,6),new V3(24,0,5.6),2.6,1.15); showBanner('PICKUP TRUCKS!'); }\n  if(t.id==='roast'){ roastG.visible=true; rise(roastG,-3); rpadMesh.visible=true; rise(rpadMesh,-2); spitWheel.visible=true; roastChef.visible=true; pop(roastChef); laneVis[2].g.visible=true; rise(laneVis[2].g,-1.5); LANES[2].on=true;\n    tour(new V3(SPLIT.x+3,0,SPLIT.z-2),new V3(SPIT.x,0,SPIT.z-2),2.8,1.1); showBanner('ROAST YARD!'); }\n  if(t.id==='plaza'){ feastG.visible=true; rise(feastG,-1); feastRoadG.visible=true; FEST.on=true; addStaff('run',0); for(const g of guests){ g.state='walk'; g.hidden=false; g.sface=g.face; g.path=[new V3(-12,0,STREET.z0+1),new V3(g.sx,0,PLAZA.z1-0.8),new V3(g.sx,0,g.sz)]; }\n    tour(new V3(-10,0,-6),new V3(-16,0,-23),3.2,1.25); showBanner('WINTER FEAST ORDERS!'); }\n  if(t.id==='hall'){ hallG.visible=true; rise(hallG,-2); addStaff('run',1); addStaff('wait',0); addStaff('wait',1); diners.forEach((d,i)=>{ d.state='line'; d.z=DINE_LINE_Z; d.x=HDOOR_X-8-i*0.75; });\n    tour(new V3(20,0,4),new V3(22,0,-8),3.0,1.2); showBanner('ROAST HALL!'); }\n  if(t.id==='mega'){ MEGA.on=true; MEGA.next=1; MEGA.t=13; tour(new V3(SPLIT.x,0,SPLIT.z),new V3(5,0,15),2.6,1.15); showBanner('MEGA CHICKEN!'); } });\nguideExtra=(want)=>{ const a=player;\n  if(has('roast') && !roast.manualDone && (roast.state==='roast'||roast.state==='hop'||(LANES[2].q.length&&roast.state==='empty'))) return RPAD.clone();\n  if(HCASH.n>0 && (HCASH.n>=40 || (want && a.bills+HCASH.n>=want.price-want.paid-want.inflight))) return HCASH.pos.clone();\n  return null; };\nextraUpdates.push(updateTrucks,updateRoast,updatePlates,updateStaff,updateHall,updateFeast,updateMega,(dt)=>{ const L=stat.tr[stat.tr.length-1]; if(L&&L.carve===undefined){ Object.assign(L,{carve:stocks.carve,pass:stocks.pass,hcash:HCASH.n,birds:roast.birds,plates:stat.plates||0,hall:stat.soldBy.hall||0,fB:FEST.got.bucket,fP:FEST.got.plate,feastB:stat.soldBy.feastB||0}); } });\nwindow.__late={roast,FEST,trucks,plates2,diners,hstaff,HCASH,MEGA,guests};\nhint.style.opacity=1;\nupdateCamera(0,true);\nwindow.__game={scene,stat,stocks,player,tiles,has,step,render,cam:camera,THREE,CASH,sticks,chickens,buckets,bikes,plates,pans,regionOf,guide:()=>guideTarget(),unlockId:(id)=>{ const t=tiles.find(q=>q.id===id); if(t&&!t.done){ t.shown=true; unlock(t);} },give:(n)=>{ player.bills+=n; },info:()=>({calls:renderer.info.render.calls,tris:renderer.info.render.triangles})};\nrequestAnimationFrame(frame);\n})();\n</script>\n<script>window.run = window.run || function(mode){ void window.gameConfig; };</script>\n</body>\n</html>\n", "game_config": "{}", "game_design_document": "", "persistent_storage_config": {"leaderboard": {"sort_ascending": true}}, "game_metadata": {"game_id": "01M61RJS9Q5N83E3QFT2SF5H7S", "title": "Giant Chicken Fry", "description": "Stomp giant chickens into drumsticks, fry, pack buckets, roast whole birds and fill the winter feast!", "thumbnail_url": "https://d16qq0o35va7kz.cloudfront.net/images/38414350-a061-706a-ab50-c3e0688c7aa8/1793211532127_01M61VS0TZ3GXKXTXK3XCJH90M.jpg", "created_at": 1793208182, "updated_at": 1793266986446.4456, "art_style": null}, "task_plan": null}