diff --git a/dist/game.js b/dist/game.js index 73d86dc..3276d8d 100644 --- a/dist/game.js +++ b/dist/game.js @@ -1,13 +1,17 @@ -var H={version:"v27.6 direct conveyor chick movement",time:{daySeconds:60,farmShutdownGraceSeconds:10},cash:{starting:250},grid:{x:170,y:78,cols:20,rows:10,cell:46,expansion:{colsPerPurchase:10,rowsPerPurchase:10,costBase:1e3,costMultiplier:1.3333333333333333,truncateUnit:100}},map:{blockedRatio:.075},economy:{income:{mixer:5,truck:10},poopFine:10,zundaTax:{exemption:500,legalWorkExemptionBonus:250,maxRateCash:1e4,stepAmount:500,stepRate:.05,maxRate:.95},fairiesTribute:{perDay:10},incomeUpgradeRate:1.05,explosionDamageDivisor:30,maleTruckFinePerHalfDay:30,shredderBonus:{maxCards:30,probabilityPercentDivisor:2}},production:{poopRate:.08,autoScannerCooldown:2.25,autoScannerUpgradeRate:.95,autoScannerMinCooldown:.5,eggSpawnRanges:[[1.95,4.35],[1.55,3.45],[1.25,2.8],[1.05,2.35]]},conveyor:{baseSpeed:52,maxSpeed:300,bearingSpeedMultiplier:1.075},cards:{definitions:[{id:"upgradeEgg",title:"EGG Improvement",rarity:"common",type:"equipmentUpgrade",target:"eggFarm",description:"Choose one EGG FARM on the map and raise it by 1 level.",tags:["EGG","ACTIVE"]},{id:"upgradeAutoScanner",title:"AUTO SCANNER Improvement",rarity:"common",type:"equipmentUpgrade",target:"autoScanner",description:"Choose one AUTO SCANNER on the map and raise it by 1 level.",tags:["SCANNER","ACTIVE"]},{id:"upgradeMixer",title:"MIXER Improvement",rarity:"common",type:"equipmentUpgrade",target:"mixer",description:"Choose MIXER on the map and raise it by 1 level.",tags:["ECONOMY","ACTIVE"]},{id:"upgradeTruck",title:"TRUCK Improvement",rarity:"common",type:"equipmentUpgrade",target:"truck",description:"Choose TRUCK on the map and raise it by 1 level.",tags:["ECONOMY","ACTIVE"]},{id:"upgradeTrash",title:"SHREDDER Improvement",rarity:"common",type:"equipmentUpgrade",target:"trash",description:"Shred poops for a little money. Max 30 upgrades.",tags:["POOP","ACTIVE"]},{id:"crowdedFarming",title:"Crowded Farming Contract",rarity:"common",type:"instant",description:"EGG production interval -5%. Equipment deterioration +4%. Max 10 cards.",tags:["EGG","RISK","PASSIVE"]},{id:"hatchingFeed",title:"Hatching Feed",rarity:"common",type:"instant",description:"EGG production interval -4%. Poop rate +3%. No card limit.",tags:["EGG","POOP","PASSIVE"]},{id:"safetyCover",title:"Safety Cover",rarity:"common",type:"instant",description:"Explosion damage -5%. No card limit.",tags:["RISK","PASSIVE"]},{id:"preventiveMaintenance",title:"Preventive Maintenance Manual",rarity:"common",type:"instant",description:"All equipment deterioration speed -5%. No card limit.",tags:["MAINTENANCE","PASSIVE"]},{id:"dudFilter",title:"DUD Filter",rarity:"common",type:"instant",description:"DUD chance -5 percentage points. Stacks until DUD chance reaches 0%.",tags:["CARD","PASSIVE"]},{id:"durabilityCoating",title:"Durability Coating",rarity:"common",type:"instant",description:"All equipment maximum durability +3%. No card limit.",tags:["MAINTENANCE","PASSIVE"]},{id:"bearing",title:"High-Quality Bearing",rarity:"common",type:"instant",description:"Conveyor speed +7.5%.",tags:["CONVEYOR","PASSIVE"]},{id:"extraEggOutlet",title:"Extra Egg Outlet",rarity:"rare",type:"equipmentUpgrade",target:"eggOutlet",description:"Choose one EGG FARM and add +1 output port to that EGG. Max +3 per EGG.",tags:["EGG","RARE","ACTIVE"]},{id:"recyclingSubsidy",title:"Recycling Subsidy",rarity:"rare",type:"instant",description:"SELL refund rate +10 points. Max 3 cards; normal SELL can reach 80%.",tags:["ECONOMY","RARE","PASSIVE"]},{id:"dynamite",title:"Dynamite",rarity:"rare",type:"cellAction",target:"blockedCell",description:"Remove any 3 blocked cells with a burst effect.",tags:["RISK","RARE","ONE-SHOT"]},{id:"usedMachine",title:"Used Machines",rarity:"rare",type:"instant",description:"Future equipment costs half. New purchases have no refund and 60% durability. New Machines cancels it.",tags:["ECONOMY","RISK","RARE"]},{id:"newMachine",title:"New Machines",rarity:"common",type:"instant",description:"Cancel Used Machines and restore normal prices, refunds, and durability.",tags:["ECONOMY","ONE-SHOT"]},{id:"flattery",title:"Flattery",rarity:"rare",type:"instant",description:"Next Fairies tribute -50%.",tags:["ECONOMY","RARE","ONE-SHOT"]},{id:"loan",title:"Loan",rarity:"rare",type:"instant",description:"Gain JPY 300 per elapsed day now. Pay JPY 400 per day for that many days starting tomorrow.",tags:["ECONOMY","RISK","RARE"]},{id:"extraCards",title:"Extra Cards",rarity:"rare",type:"instant",description:"Draw 2 cards now and choose both.",tags:["RARE","ACTIVE"]},{id:"legalWork",title:"Legal Work",rarity:"rare",type:"instant",description:"ZUNDA TAX exemption +\uFFE5250. The 95% cap point also moves up by \uFFE5250.",tags:["ECONOMY","RARE","PASSIVE"]},{id:"chemicalWeaponSubsidy",title:"Chemical Weapons Subsidy",rarity:"ultraRare",type:"instant",description:"Each day, gain JPY 2000 per 100 poop shipped by truck. No card limit.",tags:["POOP","ULTRA RARE","PASSIVE"]},{id:"laborExploitation",title:"Motivational Exploitation",rarity:"ultraRare",type:"instant",description:"Repairman labor cost -30%. The repairman smiles with bloodshot eyes. One time only.",tags:["MAINTENANCE","ULTRA RARE","PASSIVE"]},{id:"rescueLoan",title:"Emergency Loan",rarity:"ultraRare",type:"instant",description:"If cash is below JPY 0 at day-end settlement, gain JPY 500. Consumed on use.",tags:["ECONOMY","ULTRA RARE","ONE-SHOT"]}],defaultEffects:{bearing:0,legalWork:0,fairiesFlatteryNext:0,crowdedFarming:0,extraEggOutlet:0,hatchingFeed:0,safetyCover:0,recyclingSubsidy:0,chemicalWeaponSubsidy:0,preventiveMaintenance:0,dudFilter:0,durabilityCoating:0,laborExploitation:0,usedMachineActive:!1,rescueLoanCharges:0,loans:[]},commonWeight:8,rareWeight:2,ultraRareWeight:.45,baseDraftSize:3,maxDudsPerDraft:2,dudChancePerCard:.3,dudChanceReductionPerFilter:.05,extraCardsAddedPicks:2,rerollCost:{dayDivisor:2,multiplier:100}},contracts:{eventChance:.7,firstTurn:8},maintenance:{fullPerformanceUntilWear:.5,minimumPerformance:.5,durability:{conveyor:1300,eggFarm:475,autoScanner:850,mixer:775,trash:950},processingDelayMaxSeconds:{mixer:1.2,trash:1},repairman:{dailyCost:100,secondsPerOnePercent:.5,walkSpeed:90}},facilities:{conveyor:{id:"conveyor",type:"conveyor",name:"Conveyor",shortName:"Belt",price:30,buildable:!0,upgradeable:!1},eggFarm:{id:"eggFarm",type:"eggFarm",name:"Egg Farm",shortName:"EGG",price:250,buildable:!0,upgradeable:!0,maxLevel:4,upgradeCosts:[null,300,720,1600]},manualScanner:{id:"manualScanner",type:"scanner",kind:"manual",name:"Manual Scanner",shortName:"Manual Scanner",price:260,buildable:!0,upgradeable:!1},autoScanner:{id:"autoScanner",type:"scanner",kind:"auto",name:"Auto Scanner",shortName:"Auto Scanner",price:360,buildable:!0,upgradeable:!1},mixer:{id:"mixer",type:"facility",name:"Mixer",shortName:"MIXER",price:450,buildable:!0,upgradeable:!0,incomeKey:"mixer",body:{w:164,h:126}},trash:{id:"trash",type:"facility",name:"Waste Shredder",shortName:"SHREDDER",price:240,buildable:!0,upgradeable:!0,body:{w:180,h:100}},truck:{id:"truck",type:"facility",name:"Truck",shortName:"TRUCK",price:450,buildable:!0,upgradeable:!0,incomeKey:"truck",body:{w:176,h:138}}}};var ln=H.version,At=H.time.daySeconds,zt=H.cash.starting,fe=H.economy,Go=H.time.farmShutdownGraceSeconds,an=H.production.autoScannerCooldown,Uo=H.production.autoScannerUpgradeRate,Nn=H.production.autoScannerMinCooldown,Wo=H.contracts.eventChance,bt=H.contracts.firstTurn,_o=H.production.poopRate,cn=H.conveyor.baseSpeed,Dt=H.conveyor.maxSpeed,un=H.conveyor.bearingSpeedMultiplier,ke=H.cards,Ko=H.cards.definitions,Gn=H.cards.defaultEffects,nt=H.production.eggSpawnRanges,y=H.grid,pe=H.facilities,Un=["conveyor","eggFarm","autoScanner","manualScanner","mixer","trash","truck"],ot=["mixer","trash","truck"],Ho=["mixer","truck"],kt=Object.fromEntries(Object.entries(pe).map(([e,t])=>[e,t.price])),ge=[{name:"right",dc:1,dr:0,angle:0,opposite:"left"},{name:"left",dc:-1,dr:0,angle:Math.PI,opposite:"right"},{name:"down",dc:0,dr:1,angle:Math.PI/2,opposite:"up"},{name:"up",dc:0,dr:-1,angle:-Math.PI/2,opposite:"down"}],d={bg:"#eefbea",ink:"#102015",muted:"#526456",green:"#22b94f",greenSoft:"#b7f5c6",mixerBlue:"#2477ff",truckPink:"#ff6aa8",wasteGreen:"#22b94f",danger:"#c42424",warn:"#f0a020",white:"#ffffff",poop:"#7b4a23",male:"#62a9ff",female:"#ff8bc6"},we={ambient:0,important:1,critical:2};function S(e){let t=Number(e)||0;return`${t<0?"-":""}\uFFE5${Math.ceil(Math.abs(t)).toLocaleString("ja-JP")}`}function F(e,t){return`${e},${t}`}function ee(e){let[t,n]=String(e).split(",").map(Number);return{col:t,row:n}}function Vt(e,t,n){return Math.max(t,Math.min(n,e))}function O(e,t){return e+Math.random()*(t-e)}function Ee(e,t){return e>=0&&e=0&&tMath.round(Vt(r,0,255)).toString(16).padStart(2,"0");return`#${o(e)}${o(t)}${o(n)}`}function _n(e,t,n){let o=qo(e),r=qo(t);return is(Wn(o.r,r.r,n),Wn(o.g,r.g,n),Wn(o.b,r.b,n))}var te={appTitle:"Chick Sorter",versionTitle:`Chick Sorter ${ln}`,currency:"\uFFE5",phases:{title:"Title",build:"Build",running:"Sorting",clearing:"Clearing",loanDecision:"Emergency Loan",gameover:"Game Over"},actions:{startGame:"Start Game",nextDay:"Next Day",buildPhase:"Build Phase",restart:"Restart",close:"Close",remove:"Remove",refund:"Refund",autoMenu:"Auto Menu"},status:{build:e=>`BUILD: ${(e==="erase"?"SELL":e||"SELECT").toUpperCase()} | Right-drag pan | Hover equipment for info`,sorting:"Sorting: use scanner keys. Build after clearing the day.",allPortsConnected:"Start condition met: one EGG route reaches an exit.",equipmentPanelEmpty:"Hover equipment in Build phase.",eventPanelRunning:"Forced events appear in Build phase."},routeLabels:{mixer:"MIXER",truck:"TRUCK",trash:"WASTE",scanner:"NEXT","scanner-role-1":"NEXT",input:"IN"},fail:{outOfGrid:"Out of grid",cellOccupied:"Cell occupied",notEnoughCash:"Not enough cash",facilityExists:"Facility already exists",facilityOverlap:"Facility overlap",nothingToErase:"Nothing to sell",noUpgrade:"No upgrade available"}};function Ft(e){return pe[e]?.name||String(e||"").replace(/^[a-z]/,t=>t.toUpperCase())}function ss(e){let t=pe[e];return t?e==="conveyor"?`${S(t.price)} / tile`:e==="autoScanner"?`${S(t.price)} / ${an.toFixed(1)}s cooldown`:e==="trash"?S(t.price):S(t.price):""}function ls(e){return{conveyor:"A narrow green belt. Routes decide whether chicks live, ship, or become invoices.",eggFarm:"A tiny gatehouse producing questionable eggs on schedule.",autoScanner:"An automated judge. Faster than hands, still very sure of itself.",manualScanner:"A manual checkpoint. The operator is the algorithm.",mixer:"Male chicks become revenue here. Do not feed it poop.",trash:"A polite shredder for poop and other regrets.",truck:"Ships the target cargo. Wrong cargo still leaves a paper trail."}[e]||""}function zo(e){let t=ls(e);return`${Ft(e)}${ss(e)}${t?`${t}`:""}`}function Vo(e){return te.routeLabels[e]||String(e).toUpperCase()}function Xo(e){let t=2166136261;for(let n=0;n>>0}function Jo(e){let t=e>>>0;return()=>{t+=1831565813;let n=Math.imul(t^t>>>15,1|t);return n^=n+Math.imul(n^n>>>7,61|n),((n^n>>>14)>>>0)/4294967296}}function fn(){return`RUN-${Date.now().toString(36)}-${Math.floor(Math.random()*1e6).toString(36)}`.toUpperCase()}function Qo({seed:e,targetRatio:t=.2,reserved:n=[]}={}){let o=Jo(Xo(e||fn())),r=new Set(n.map(f=>typeof f=="string"?f:F(f.col,f.row))),i=[];for(let f=0;f0;f-=1){let w=Math.floor(o()*(f+1));[i[f],i[w]]=[i[w],i[f]]}let l=Math.round(y.cols*y.rows*t);return new Set(i.slice(0,Math.min(l,i.length)).map(f=>f.k))}function Zo({seed:e,colStart:t=0,colEnd:n=y.cols,rowStart:o=0,rowEnd:r=y.rows,targetRatio:i=.2,reserved:l=[]}={}){let f=Jo(Xo(e||fn())),w=new Set(l.map(z=>typeof z=="string"?z:F(z.col,z.row))),M=[],R=Math.max(0,Math.floor(t)),D=Math.max(R,Math.floor(n)),I=Math.max(0,Math.floor(o)),j=Math.max(I,Math.floor(r));for(let z=I;z0;z-=1){let X=Math.floor(f()*(z+1));[M[z],M[X]]=[M[X],M[z]]}let se=Math.round((D-R)*(j-I)*i);return new Set(M.slice(0,Math.min(se,M.length)).map(z=>z.k))}var It=10,jo=2,er=1;function Lt(e,t){return`${e},${t}`}function as(e){let[t,n]=String(e).split(",").map(Number);return{chunkX:t,chunkY:n}}function pn(e){let t=H.grid.expansion||{},n=Math.max(1,(e.gridExpansionPurchases||0)+1),o=(t.costBase||1e3)*n*(t.costMultiplier||4/3),r=Math.max(1,t.truncateUnit||100);return Math.floor(o/r)*r}function tr(e){e.ownedCells=new Set,e.ownedChunks=new Set,e.gridChunkOriginY=0,e.gridExpansionPurchases=0,e.gridExpansionPurchasesByDirection={right:0,up:0};for(let t=0;t=0&&t=0&&n0)continue;let M=Lt(w.chunkX,w.chunkY);t.has(M)||n.has(M)||n.set(M,Kn(e,w.chunkX,w.chunkY,w.direction))}}return[...n.values()].sort((o,r)=>{let i=o.direction===r.direction?0:o.direction==="right"?-1:1;return i||(o.chunkY!==r.chunkY?o.chunkY-r.chunkY:o.chunkX-r.chunkX)})}function rr(e,t="right"){let n=vt(e);return n.find(o=>o.direction===t)||n[0]||null}function Ct(e){return{x:y.x+e.colStart*y.cell,y:y.y+e.rowStart*y.cell,w:e.cols*y.cell,h:e.rows*y.cell}}function Hn(e){let t=Ct(e);return{x:t.x+t.w/2-116,y:t.y+t.h/2-36,w:232,h:72,cx:t.x+t.w/2,cy:t.y+t.h/2}}function ir(e,t){let n=Hn(e);return t.x>=n.x&&t.x<=n.x+n.w&&t.y>=n.y&&t.y<=n.y+n.h}function sr(e){let t=[];for(let n=e.rowStart;nEe(t.col,t.row))}function ms(e){return[{col:e.col,row:e.row+1},{col:e.col-1,row:e.row},{col:e.col+1,row:e.row},{col:e.col,row:e.row-1}].filter(t=>Ee(t.col,t.row))}function fr(e){let t=new Set([...e.conveyorTiles]),n=o=>{o&&Ee(o.col,o.row)&&t.add(F(o.col,o.row))};for(let o of e.scanners){n(o);for(let r of hs(o))n(r)}for(let o of e.eggFarms){n(o);for(let r of ms(o))n(r)}for(let o of Object.values(e.facilities))n(o.entry);return t}function ur(e){let t=ds.map(([n,o])=>({col:n,row:o}));for(let{col:n,row:o}of t)dr(e,n,o);for(let n=0;n({...n,keys:n.keys?{left:{...n.keys.left},right:{...n.keys.right}}:null,queue:[]})),e.eggFarms=ps.map(n=>{let o=St(n);return{...n,nextSpawn:o,lastInterval:o}});let t=fr(e);e.blockedCells=Qo({seed:e.runSeed,targetRatio:H.map.blockedRatio,reserved:[...t]}),xs(e),ur(e),e.routingVersion=(e.routingVersion||0)+1,e.factoryGraph=null,e.factoryGraphCommittedVersion=-1}function le(e){return P(e.col,e.row)}function Ot(e,t,n){return e.eggFarms.find(o=>o.col===t&&o.row===n)||null}function Nt(e,t,n){return e.scanners.find(o=>o.col===t&&o.row===n)||null}function it(e,t){return e.scanners.find(n=>n.id===t)||null}function Xn(e,t){return e.scanners.find(n=>n.kind==="manual"&&n.slot===t)||null}function ve(e,t){return{inputA:{col:e.col,row:e.row-1},inputB:null,left:{col:e.col-1,row:e.row},right:{col:e.col+1,row:e.row}}[t]}function hr(e){return e?ge.map(t=>({col:e.col+t.dc,row:e.row+t.dr})).filter(t=>Ee(t.col,t.row)):[]}function Jn(e,t,n=[],o=[]){if(!t||!Ee(t.col,t.row))return[];let r=[],i=l=>{!l||!Ee(l.col,l.row)||!e.conveyorTiles.has(F(l.col,l.row))||n.some(f=>f&&dn(l,f))||r.some(f=>dn(f,l))||r.push({...l,viaTolerance:!dn(l,t)})};i(t);for(let l of o)i(l);for(let l of hr(t))i(l);return r}function pr(e,t,n=[],o=[]){let r=Jn(e,t,n,o),i=r.filter(l=>!l.viaTolerance);return i.length?i:r}function ws(e,t){let n=ve(t,"inputA");return Jn(e,n,[{col:t.col,row:t.row}])}function bs(e,t,n){let o=ve(t,n);return Jn(e,o,[{col:t.col,row:t.row}])}function ks(e,t){let n=e.facilities[t];return n?n.entry?P(n.entry.col,n.entry.row):t==="mixer"?{x:n.x+n.w,y:n.y+n.h*.52}:t==="truck"?{x:n.x,y:n.y+n.h*.52}:t==="trash"?{x:n.x+n.w/2,y:n.y+8}:{x:n.x+n.w/2,y:n.y+n.h/2}:null}function mr(e,t,n){return n==="inputA"?ws(e,t).length>0:bs(e,t,n).length>0}function he(e){e.branchCounters?.clear?.(),e.routeCache=null,e.routingVersion=(e.routingVersion||0)+1,e.factoryGraph=null,e.factoryGraphCommittedVersion=-1}function Pe(e,t){return F(e,t)}function Bt(e){return ee(e)}function vs(e,t){let n=e.conveyorMeta?.get?.(t)||{},o=[];return Array.isArray(n.outDirs)&&o.push(...n.outDirs),n.dir&&o.push(n.dir),[...new Set(o)].filter(r=>ge.some(i=>i.name===r))}function Cs(e){return{version:e.routingVersion||0,cells:new Set,adjacency:new Map,components:new Map,cellToComponent:new Map,scannerInputs:new Map,scannerPorts:new Map,facilityPorts:new Map,farmOutputs:new Map,pathCache:new Map,metrics:{conveyorCells:0,edges:0,components:0,deadEnds:0,crosses:0,scannerPorts:0,connectedScannerPorts:0,facilityPorts:0,connectedFacilityPorts:0,farmOutputs:0},issues:[]}}function Ss(e){let t=Cs(e);for(let o of e.conveyorTiles)t.cells.add(o),t.adjacency.set(o,[]);for(let o of t.cells){let r=Bt(o),i=vs(e,o),l=i.length?ge.filter(f=>i.includes(f.name)):ge;for(let f of l){let w=Pe(r.col+f.dc,r.row+f.dr);t.cells.has(w)&&t.adjacency.get(o).push({key:w,dir:f.name})}}let n=0;for(let[o,r]of t.adjacency.entries()){n+=r.length;let i=new Set(r.map(l=>l.dir));r.length===1&&(t.metrics.deadEnds+=1),r.length===4&&i.has("left")&&i.has("right")&&i.has("up")&&i.has("down")&&(t.metrics.crosses+=1)}return t.metrics.conveyorCells=t.cells.size,t.metrics.edges=Math.floor(n/2),Ms(t),Ts(e,t),t.issues=Ns(e,t,{includeRoutes:!1}),t}function Ms(e){let t=1;for(let n of e.cells){if(e.cellToComponent.has(n))continue;let o=t++,r=[n],i=[];for(e.cellToComponent.set(n,o);r.length;){let l=r.shift();i.push(l);for(let f of e.adjacency.get(l)||[])e.cellToComponent.has(f.key)||(e.cellToComponent.set(f.key,o),r.push(f.key))}e.components.set(o,{id:o,cells:i,capacity:Math.max(1,i.length),count:0,ratio:0})}e.metrics.components=e.components.size}function Ts(e,t){for(let n of e.scanners){let o={};for(let r of["inputA","left","right"]){let i=ve(n,r),l=pr(e,i,[{col:n.col,row:n.row}]).filter(M=>t.cells.has(Pe(M.col,M.row))),f=l.map(M=>Pe(M.col,M.row)),w=f.length>0;if(o[r]={type:r,cell:i,cells:l,keys:f,key:f[0]||null,connected:w},t.metrics.scannerPorts+=1,w&&(t.metrics.connectedScannerPorts+=1),r==="inputA")for(let M of f)t.scannerInputs.set(M,n.id)}t.scannerPorts.set(n.id,o)}for(let[n,o]of Object.entries(e.facilities)){if(!o.entry)continue;let r=pr(e,o.entry).filter(f=>t.cells.has(Pe(f.col,f.row))),i=r.map(f=>Pe(f.col,f.row)),l=i.length>0;t.facilityPorts.set(n,{id:n,cell:{...o.entry},cells:r,keys:i,key:i[0]||null,connected:l}),t.metrics.facilityPorts+=1,l&&(t.metrics.connectedFacilityPorts+=1)}for(let n of e.eggFarms){let o=hr({col:n.col,row:n.row}).map(r=>Pe(r.col,r.row)).filter(r=>t.cells.has(r));t.farmOutputs.set(n.id,o),t.metrics.farmOutputs+=o.length}}function Ce(e){let t=e.routingVersion||0;return(!e.factoryGraph||e.factoryGraph.version!==t)&&(e.factoryGraph=Ss(e)),e.factoryGraph}function xr(e){let t=Ce(e);return e.factoryGraphCommittedVersion=t.version,e.factoryGraphSnapshot=gs(t),t}function gs(e){return e?{version:e.version,conveyorCells:e.metrics.conveyorCells,edges:e.metrics.edges,components:e.metrics.components,deadEnds:e.metrics.deadEnds,crosses:e.metrics.crosses,scannerPorts:`${e.metrics.connectedScannerPorts}/${e.metrics.scannerPorts}`,facilityPorts:`${e.metrics.connectedFacilityPorts}/${e.metrics.facilityPorts}`,farmOutputs:e.metrics.farmOutputs,issues:[...e.issues||[]]}:null}function wr(e,t,n){let o=Ce(e),r=Pe(t,n);return(o.adjacency.get(r)||[]).map(i=>({...Bt(i.key),dir:ge.find(l=>l.name===i.dir)||{name:i.dir,dc:0,dr:0}}))}function Es(e,t){let n=t.col-e.col,o=t.row-e.row;return ge.find(r=>r.dc===n&&r.dr===o)?.name||null}function Ps(e,t){let n=e.adjacency.get(t)||[],o=new Set(n.map(r=>r.dir));return n.length===4&&o.has("left")&&o.has("right")&&o.has("up")&&o.has("down")}function Qn(e,t,n){let o=Ce(e),r=Pe(t.col,t.row);if(!o.cells.has(r))return[];let i=`${r}|${n.cacheKey||"dynamic"}`;if(n.cacheKey&&o.pathCache.has(i))return o.pathCache.get(i).map(g=>g.map(W=>({...W})));let l=`${r}|none`,f=[{key:r,col:t.col,row:t.row,incoming:"none",depth:0}],w=new Map([[l,0]]),M=new Map,R=[],D=1/0,I=8,j=24,se=80;for(;f.length;){let g=f.shift();if(g.depth>D)break;let W=`${g.key}|${g.incoming}`;if(n(g)){if(R.push(W),D=g.depth,R.length>=j)break;continue}for(let _ of o.adjacency.get(g.key)||[]){let ae=Bt(_.key),Z=Es(g,ae);if(g.incoming!=="none"&&Ps(o,g.key)&&Z!==g.incoming)continue;let me=g.depth+1;if(me>D)continue;let E=`${_.key}|${Z}`,A=w.get(E);if(A==null)w.set(E,me),M.set(E,[W]),f.push({key:_.key,col:ae.col,row:ae.row,incoming:Z,depth:me});else if(A===me){let x=M.get(E)||[];x.length{if(z.length>=se)return;let[_]=g.split("|"),ae=[Bt(_),...W];if(g===l||_===r){z.push(ae);return}for(let Z of M.get(g)||[])X(Z,ae)};for(let g of R)X(g,[]);let ue=[],ie=new Set;for(let g of z){let W=g.map(_=>Pe(_.col,_.row)).join(">");ie.has(W)||(ie.add(W),ue.push(g))}return n.cacheKey&&o.pathCache.set(i,ue.map(g=>g.map(W=>({...W})))),ue}function br(e){let t=Ce(e),n=new Map;for(let[o,r]of t.components.entries())n.set(o,{id:o,cells:[...r.cells],capacity:r.capacity,count:0,ratio:0});return{components:n,cellToComponent:new Map(t.cellToComponent)}}function kr(e,t,n){let o=Ce(e),r=null,i=1/0;for(let l of o.cells){let f=ee(l),w=P(f.col,f.row),M=Math.hypot(w.x-t,w.y-n);MP(o.col,o.row));return t&&n.push(t),n}function rt(e,t){t&&(!e.length||Yo(e[e.length-1],t)>1)&&e.push(t)}function Rs(e,t,n,o=null){let r=[{x:e.x,y:e.y}];rt(r,P(t.col,t.row));for(let i of xn(n,o))rt(r,i);return r}function $s(e,t){let n=xn(e),o=ve(t,"inputA");return o&&rt(n,P(o.col,o.row)),rt(n,le(t)),n}function As(e,t,n,o){let r=[{x:e.x,y:e.y}];rt(r,P(t.col,t.row));for(let l of xn(n))rt(r,l);let i=ve(o,"inputA");return i&&rt(r,P(i.col,i.row)),rt(r,le(o)),r}function Ds(e,t,n=0){let o=P(t.col,t.row),r=0;for(let i of e.chicks||[]){if(i.stage==="flying"||i.stage==="queued")continue;Math.hypot(i.x-o.x,i.y-o.y){let D=M.roleBias??0,I=R.roleBias??0;if(D!==I)return D-I;let j=M.cells?.length??M.route?.length??Number.POSITIVE_INFINITY,se=R.cells?.length??R.route?.length??Number.POSITIVE_INFINITY;if(j!==se)return j-se;let z=M.key||JSON.stringify(M.route||M.cells||M),X=R.key||JSON.stringify(R.route||R.cells||R);return z.localeCompare(X)});if(!o||r.length===1)return r[0];let i=r[0].roleBias??0,l=r.filter(M=>(M.roleBias??0)===i),f=Math.min(...l.map(M=>yr(e,M)));l=l.filter(M=>yr(e,M)===f);let w=e.branchCounters.get(t)||0;return e.branchCounters.set(t,w+1),l[Math.floor(Math.random()*l.length)]}function vr(e){return e.role===0?{left:{match:"male",destination:"mixer"},right:{match:"default",destination:"scanner-role-1"}}:{left:{match:"poop",destination:"trash"},right:{match:"default",destination:"truck"}}}function Zn(e){let t=vr(e);return{left:t.left.destination,right:t.right.destination}}function jn(e,t){return vr(e).left.match===t.sex?"left":"right"}function eo(e){return Vo(e)}function to(e){return{mixer:"#2477ff",truck:"#ff6aa8",trash:"#22b94f","scanner-role-1":"#526456",scanner:"#526456",input:"#526456"}[e]||"#102015"}function Ye(e,t,n=!1){let o=Ce(e),r=(o.farmOutputs.get(t.id)||[]).map(Bt),i=[];for(let w of r){let M=D=>o.scannerInputs.has(Pe(D.col,D.row));M.cacheKey=`farmInput:${w.col},${w.row}:v${o.version}`;let R=Qn(e,w,M);for(let D of R){let I=D[D.length-1],j=o.scannerInputs.get(Pe(I.col,I.row)),se=it(e,j);se&&i.push({key:`${se.id}:${I.col},${I.row}:${D.length}`,scanner:se,cells:D})}}let l=mn(e,`farm:${t.id}`,i,n);if(l)return{scannerId:l.scanner.id,route:$s(l.cells,l.scanner)};let f=mn(e,`farm:${t.id}:output`,r.map(w=>({key:F(w.col,w.row),cells:[w]})),n);return f?{scannerId:null,route:xn(f.cells)}:null}function Qt(e,t,n,o,r=!1){let i=it(e,o);if(!i)return null;let f=Ce(e).scannerPorts.get(i.id)?.[t],w=ve(i,t),M=f?.connected?(f.keys||[f.key]).filter(Boolean).map(Bt):[];if(!w||!M.length)return null;let R=Zn(i)[t];return R==="scanner-role-1"?Fs(e,i,t,n,w,M,r):Is(e,i,t,n,w,M,R,r)}function Fs(e,t,n,o,r,i,l){let f=Ce(e),w=e.scanners.filter(D=>D.id!==t.id&&f.scannerPorts.get(D.id)?.inputA?.connected),M=[];for(let D of i)for(let I of w){let j=f.scannerPorts.get(I.id)?.inputA?.key;if(!j)continue;let se=(f.scannerPorts.get(I.id)?.inputA?.keys||[j]).filter(Boolean),z=ie=>se.includes(Pe(ie.col,ie.row));z.cacheKey=`toScanner:${D.col},${D.row}:${I.id}:v${f.version}`;let X=Qn(e,D,z),ue=I.role===1?0:1;for(let ie of X)M.push({key:`${ue}:start${F(D.col,D.row)}:s${I.id}:${ie.length}:${F(ie[ie.length-1].col,ie[ie.length-1].row)}`,target:I,cells:ie,roleBias:ue})}let R=mn(e,`scanner:${t.id}:${n}:toRole1`,M,l);return R?{destination:"scanner",nextScannerId:R.target.id,route:As(o,r,R.cells,R.target)}:null}function Is(e,t,n,o,r,i,l,f){let w=Ce(e),M=w.facilityPorts.get(l);if(!M?.connected)return null;let R=[];for(let I of i){let j=(M.keys||[M.key]).filter(Boolean);if(!j.length)continue;let se=X=>j.includes(Pe(X.col,X.row));se.cacheKey=`toFacility:${I.col},${I.row}:${l}:v${w.version}`;let z=Qn(e,I,se);for(let X of z)R.push({key:`start${F(I.col,I.row)}:${l}:${X.length}:${F(X[X.length-1].col,X[X.length-1].row)}`,cells:X})}let D=mn(e,`scanner:${t.id}:${n}:${l}`,R,f);return D?{destination:l,route:Rs(o,r,D.cells,ks(e,l))}:null}function Cr(e,t,n=new Set){if(!t||n.has(t.id))return!1;n.add(t.id);for(let o of["left","right"]){let r=Qt(e,o,le(t),t.id,!1);if(r){if(["mixer","trash","truck"].includes(r.destination))return!0;if(r.destination==="scanner"){let i=it(e,r.nextScannerId);if(Cr(e,i,n))return!0}}}return!1}function Sr(e,t,n,o=new Set){if(!t||o.has(t.id))return!1;o.add(t.id);for(let r of["left","right"]){let i=Qt(e,r,le(t),t.id,!1);if(i){if(i.destination===n)return!0;if(i.destination==="scanner"){let l=it(e,i.nextScannerId);if(Sr(e,l,n,o))return!0}}}return!1}function Ls(e,t){return!!Ye(e,t,!1)?.scannerId}function Bs(e,t){return Ce(e).facilityPorts.get(t)?.connected?e.scanners.some(o=>Sr(e,o,t)):!1}function wn(e){if(e.phase!=="build")return[];let t=[];for(let n of e.eggFarms||[])Ls(e,n)||t.push({type:"eggFarm",id:n.id,ref:n,message:"This Egg will not produce chicks"});for(let n of ot){let o=e.facilities?.[n];o&&!Bs(e,n)&&t.push({type:"facility",id:n,ref:o,message:"This facility cannot receive items"})}return t}function Os(e){let t=Ce(e);for(let r of e.eggFarms||[]){let i=Ye(e,r,!1);if(!i?.scannerId)continue;let l=it(e,i.scannerId);if(Cr(e,l))return[]}let n=e.eggFarms?.length||0,o=["mixer","trash","truck"].filter(r=>t.facilityPorts.get(r)?.connected).length;return n?t.metrics.farmOutputs?e.scanners?.length?o?["Connect at least one EGG through a scanner to any valid exit."]:["Connect at least one receiver exit: Mixer, Shredder, or Truck."]:["Build at least one scanner and connect it to an EGG route."]:["Connect at least one EGG to a conveyor."]:["Build at least one EGG before starting the next day."]}function hn(e){return e.kind==="manual"?`S${(e.slot??0)+1}`:`Auto Scanner #${e.id}`}function Ns(e,t=Ce(e),n={}){let o=n.includeRoutes!==!1,r=[],i={mixer:"Mixer",trash:"Shredder",truck:"Truck"};for(let l of ot){if(!e.facilities[l]){r.push(`${i[l]||l} is missing`);continue}t.facilityPorts.get(l)?.connected||r.push(`${i[l]||l} receiver has no conveyor`)}for(let l of e.scanners){let f=t.scannerPorts.get(l.id)||{};if(f.inputA?.connected||r.push(`${hn(l)} input has no conveyor`),f.left?.connected||r.push(`${hn(l)} left output has no conveyor`),f.right?.connected||r.push(`${hn(l)} right output has no conveyor`),!!o)for(let w of["left","right"])f[w]?.connected&&!Qt(e,w,le(l),l.id)&&r.push(`${hn(l)} ${w} route is incomplete`)}if(o)for(let l of e.eggFarms)Ye(e,l)?.scannerId||r.push(`Egg Farm #${l.id} has no scanner route`);else for(let l of e.eggFarms)(t.farmOutputs.get(l.id)||[]).length||r.push(`Egg Farm #${l.id} has no output conveyor`);return[...new Set(r)]}function Zt(e){return Os(e)}function Mr(e){return Zt(e).length===0}var Tr=[{target:"female",title:"Premium Female Shipment",route:"Female -> Truck",note:"Only female chicks are valid truck cargo this turn."},{target:"male",title:"Male Export Trial",route:"Male -> Truck",note:"Male chicks are temporarily valid truck cargo. Male truck fines are suspended for valid male cargo."},{target:"poop",title:"Contaminant Sample Export",route:"Poop -> Truck",note:"Poop is temporarily valid truck cargo. Shipped poop does not soil this contract shipment."}];function no(e){return[...e.eggFarms.map(t=>t.price||0),...e.scanners.map(t=>t.price||0),...Object.values(e.facilities).map(t=>t.price||0),...[...e.conveyorMeta.values()].map(t=>t.price||0)].reduce((t,n)=>t+n,0)}function Gs(e,t=1){return e.eggFarms.reduce((n,o)=>{let[r,i]=Jt(o),l=Math.max(.1,(r+i)/2);return n+At/l*t},0)}function gr(e){if(e.turn+1Wo)return null;let n=Tr[Math.floor(Math.random()*Tr.length)],o=2,r=3,i=Gs(e,o),l=Math.max(1,Math.ceil(i*.9)),f=Math.round((.08+Math.random()*.08)*100)/100,w=Math.floor(no(e)*f),M=Math.floor(Math.max(0,e.cash)*.25),R=Math.max(w,M);return{id:`contract-${e.turn}-${Date.now()}-${Math.floor(Math.random()*9999)}`,...n,forced:!0,productionMultiplier:o,poopMultiplier:r,targetAmount:l,estimatedOutput:Math.round(i),rewardRate:f,reward:R,rewardByFactory:w,rewardByCash:M,createdTurn:e.turn,oneTurnOnly:!0}}function Er(e){e.contractActive=e.contractOffer?{...e.contractOffer}:null,e.contractOffer=null}function Pr(e){e.contractActive=null}function Rr(e){return e.contractActive?.productionMultiplier||1}function $r(e){let t=Math.max(0,Number(e?.cardEffects?.hatchingFeed)||0),n=Math.pow(1.03,t);return Math.min(.95,_o*(e.contractActive?.poopMultiplier||1)*n)}function ze(e){return e.contractActive?.target||"female"}function oo(e,t){return t==="male"?e.truckMale||0:t==="poop"?e.poopTruck||0:e.truckFemale||0}function Ar(e,t){return ze(e)===t}function Dr(e){return ze(e)!=="male"}function Fr(e){let t=e.contractActive;if(!t)return null;let n=oo(e.stats,t.target),o=n>=t.targetAmount,r=o?t.reward:0;r>0?(Ae(e,r),e.stats.contractBonus=r,e.totals.contractBonus+=r,e.totals.contractSuccess+=1):(e.stats.contractBonus=0,e.totals.contractFailed+=1);let i={title:t.title,target:t.target,route:t.route,targetAmount:t.targetAmount,count:n,success:o,bonus:r,reward:t.reward,productionMultiplier:t.productionMultiplier,poopMultiplier:t.poopMultiplier};return e.stats.contractResult=i,i}function ro(e,t){return Math.max(0,Number(e?.cardEffects?.[t])||0)}function Us(e){return!!e?.cardEffects?.usedMachineActive}function Ws(e,t){return t?1:Math.min(.8,.5+.1*Math.min(3,ro(e,"recyclingSubsidy")))}function _s(e){return Math.pow(.95,ro(e,"safetyCover"))}function io(e){let t=e?.ref||e||{},n=e?.type||t.type;return n==="conveyor"?pe.conveyor.price:n==="eggFarm"?t.price||pe.eggFarm.price:n==="scanner"?t.price||(t.kind==="auto"?pe.autoScanner.price:pe.manualScanner.price):n==="facility"?t.price||pe[t.id]?.price||0:pe[t.id]?.price||0}function Re(e,t=null){let n=pe[e]?.price||0;return Us(t)?Math.ceil(n*.5):n}function Ks(e,t){return Math.max(0,(e.facilities?.[t]?.level||1)-1)}function st(e,t){return Ho.includes(t)?Math.pow(fe.incomeUpgradeRate,Ks(e,t)):1}function lt(e){return Math.ceil(fe.income.mixer*st(e,"mixer"))}function Ie(e){return Math.ceil(fe.income.truck*st(e,"truck"))}function jt(e){return Math.ceil(fe.poopFine*st(e,"mixer"))}function en(e){return Math.ceil(fe.poopFine*st(e,"truck"))}function so(e){let t=fe.maleTruckFinePerHalfDay*(e.turn/2);return Math.ceil(t*st(e,"truck"))}function Mt(e){let t=Math.max(0,(e.facilities?.trash?.level||1)-1);return Math.min(fe.shredderBonus.maxCards,t)}function Ir(e){return Math.max(0,(e.facilities?.trash?.level||1)-1)}function Gt(){return fe.shredderBonus.maxCards}function at(e){let t=typeof e=="number"?e:Mt(e),n=Math.min(fe.shredderBonus.maxCards,Math.max(0,t)),o=Math.sqrt(n)/fe.shredderBonus.probabilityPercentDivisor*n;return Math.min(1,o/100)}function Hs(e){let t=Mt(e),n=at(t),o=t>0&&Math.random()0&&(e.stats.shredderBonus=(e.stats.shredderBonus||0)+t.amount,e.totals.shredderBonus=(e.totals.shredderBonus||0)+t.amount,Ae(e,t.amount)),t}function bn(e,t=null){let n=fe.zundaTax,o=Math.max(0,t?.cardEffects?.legalWork||0),r=n.exemption+o*n.legalWorkExemptionBonus,i=n.maxRateCash+o*n.legalWorkExemptionBonus,l=Math.max(0,Math.floor(Number(e)||0)),f=Math.max(0,l-r);if(f<=0)return{profit:l,exemption:r,maxRateCash:i,taxable:0,rate:0,ratePercent:0,tax:0,step:0,legalWork:o};let w=Math.max(1,Math.ceil(f/n.stepAmount)),M=Math.min(n.maxRate,w*n.stepRate),R=Math.ceil(f*M);return{profit:l,exemption:r,maxRateCash:i,taxable:f,rate:M,ratePercent:Math.round(M*100),tax:R,step:w,legalWork:o}}function lo(e,t=e.turn){let n=Math.max(1,Math.floor(Number(t)||1)),o=Math.ceil(n*fe.fairiesTribute.perDay),r=Math.max(0,e?.cardEffects?.fairiesFlatteryNext||e?.cardEffects?.flattery||0),i=r>0?Math.pow(.5,r):1,l=Math.max(0,Math.ceil(o*i)),f=Math.max(0,o-l);return{day:n,base:o,flatteryCount:r,perCardReduction:Math.ceil(o*.5),reduction:f,amount:l}}function Tt(e,t){let n=e?.ref||e||{},o=e?.type==="conveyor"?t?.conveyorMeta?.get(e.oldKey||n.id):n,r=o?.price||io(e||n),i=o?.builtSession===t?.buildSession;if(o?.noRefund||n?.noRefund)return{amount:0,sameBuild:i,price:r,rate:0,noRefund:!0};let l=Ws(t,i);return{amount:Math.max(0,Math.ceil(r*l)),sameBuild:i,price:r,rate:l,noRefund:!1}}function qs(e,t=null){let n=Math.max(1,Math.ceil((Number(e)||0)/fe.explosionDamageDivisor));return Math.max(1,Math.ceil(n*_s(t)))}function Ys(e,t){return Math.max(0,Math.ceil(t))}function Br(e,t){let n=e.cash;e.cash+=t,e.stats.profit+=t,e.totals.profit+=t,t>=0?(e.stats.revenue+=t,e.totals.revenue+=t):(e.stats.penalty+=Math.abs(t),e.totals.penalty+=Math.abs(t)),e.totals.maxCash=Math.max(e.totals.maxCash,e.cash)}function Ae(e,t){let n=Math.max(0,Math.ceil(t));return n>0&&Br(e,n),n}function Ve(e,t){let n=Math.max(0,Math.ceil(t));return n>0&&Br(e,-n),n}function ct(e,t){Ve(e,t)}function Or(e,t){if(t<=0)return;let n=e.cash;e.cash+=t,e.stats.profit+=t,e.totals.profit+=t,e.totals.maxCash=Math.max(e.totals.maxCash,e.cash)}function Nr(e){let t=lt(e);return e.stats.mixerRevenue+=t,Ae(e,t),t}function Gr(e){let t=jt(e);return e.stats.mixerPoopFine+=t,e.totals.mixerPoopFine+=t,Ve(e,t),t}function Ur(e){let t=en(e);return e.stats.truckPoopFine+=t,e.totals.truckPoopFine+=t,Ve(e,t),t}function Wr(e){let t=so(e);return e.stats.maleTruckFine+=t,e.totals.maleTruckFine+=t,Ve(e,t),t}function _r(e,t){let n=qs(t,e);return e.stats.explosionDamage+=n,e.totals.explosionDamage+=n,Ve(e,n),n}function Kr(e){let t=ze(e),n=oo(e.stats,t),o=Ie(e),r=n*o,i=Ys(e,r);return e.stats.pendingTruckRevenue=i,e.stats.truckRevenue=i,t==="poop"?e.stats.poopShipmentIncome=i:e.stats.chickShipmentIncome=i,i>0&&Ae(e,i),{base:r,adjusted:i,target:t,targetCount:n,unitPrice:o}}function Hr(e,t=0){let n=bn(t,e);return n.tax<=0||(e.totals.zundaTax=(e.totals.zundaTax||0)+n.tax,e.totals.penalty=(e.totals.penalty||0)+n.tax,e.totals.profit=(e.totals.profit||0)-n.tax,e.cash-=n.tax),n}function qr(e,t=e.turn){let n=lo(e,t);return e.cardEffects&&(e.cardEffects.fairiesFlatteryNext=0,e.cardEffects.flattery!=null&&delete e.cardEffects.flattery),n.amount<=0||(e.stats.fairiesTribute=(e.stats.fairiesTribute||0)+n.amount,e.totals.fairiesTribute=(e.totals.fairiesTribute||0)+n.amount,Ve(e,n.amount)),n}function Yr(e){let t=ro(e,"chemicalWeaponSubsidy"),n=Math.max(0,e?.stats?.poopTruck||0),o=Math.floor(n/100),r=t>0&&o>0?o*2e3*t:0;return r>0&&(e.stats.chemicalWeaponSubsidy=(e.stats.chemicalWeaponSubsidy||0)+r,e.totals.chemicalWeaponSubsidy=(e.totals.chemicalWeaponSubsidy||0)+r,Ae(e,r)),{cards:t,poopTruck:n,blocks:o,amount:r}}function zr(e){let t=e?.cardEffects;if(!t||!Array.isArray(t.loans)||!t.loans.length)return{amount:0,paidLoans:0,remainingLoans:0};let n=0,o=0,r=[];for(let i of t.loans){if((i.daysRemaining||0)<=0)continue;let l=Math.max(0,Math.ceil(i.dailyPayment||400));n+=l,o+=1;let f=Math.max(0,(i.daysRemaining||0)-1);f>0&&r.push({...i,daysRemaining:f})}return t.loans=r,n>0&&(e.stats.loanRepayment=(e.stats.loanRepayment||0)+n,e.totals.loanRepayment=(e.totals.loanRepayment||0)+n,Ve(e,n)),{amount:n,paidLoans:o,remainingLoans:r.length}}function Vr(e){let t=no(e),n=Math.max(1,e.totals.turnsCompleted||e.turn||1),o=Math.max(0,e.cash+e.totals.revenue+t*.5+e.totals.contractBonus*.5),r=Math.ceil(o/n),i=Math.floor(n*35+Math.max(0,n-5)*15),l=e.totals.correct*5,f=e.totals.explosionDamage;return{score:Math.max(0,Math.floor(o+r+l-e.totals.penalty-i)),factoryValue:t,base:o,dailyEarned:r,dayPenalty:i,correctBonus:l,explosionPenalty:f,days:n}}function zs(e){return{...e,keys:e.keys?{left:{...e.keys.left},right:{...e.keys.right}}:null,queue:[],cooldown:e.cooldown||0}}function tn(e){return JSON.stringify({cash:e.cash,stats:e.stats,totals:e.totals,nextId:e.nextId,gridX:y.x,gridY:y.y,gridCols:e.gridCols||y.cols,gridRows:e.gridRows||y.rows,gridExpansionPurchases:e.gridExpansionPurchases||0,gridExpansionPurchasesByDirection:e.gridExpansionPurchasesByDirection||{right:0,up:0},gridChunkOriginY:e.gridChunkOriginY||0,ownedCells:[...e.ownedCells||[]],ownedChunks:[...e.ownedChunks||[]],blockedCells:[...e.blockedCells||[]],selected:e.selected,multiSelected:e.multiSelected||[],buildTool:e.buildTool,view:e.view,facilities:e.facilities,eggFarms:e.eggFarms,scanners:e.scanners.map(zs),conveyorTiles:[...e.conveyorTiles],conveyorMeta:[...e.conveyorMeta.entries()],branchCounters:[...e.branchCounters.entries()],cardEffects:e.cardEffects,cardDraft:e.cardDraft,cardTargetPick:e.cardTargetPick})}function Xr(e,t){let n=JSON.parse(t);e.cash=n.cash,e.stats=n.stats,e.totals=n.totals,e.nextId=n.nextId,y.x=n.gridX??H.grid.x,y.y=n.gridY??H.grid.y,y.cols=n.gridCols||H.grid.cols,y.rows=n.gridRows||H.grid.rows,e.gridCols=y.cols,e.gridRows=y.rows,e.gridExpansionPurchases=n.gridExpansionPurchases||0,e.gridExpansionPurchasesByDirection=n.gridExpansionPurchasesByDirection||{right:0,up:0},e.gridChunkOriginY=n.gridChunkOriginY||0,e.ownedCells=new Set(n.ownedCells||[]),e.ownedChunks=new Set(n.ownedChunks||[]),e.gridOwnedCells=e.ownedCells.size||y.cols*y.rows,e.blockedCells=new Set(n.blockedCells||[]),e.selected=n.selected,e.multiSelected=n.multiSelected||[],e.buildTool=n.buildTool,e.view=n.view||{x:0,y:0,scale:1},e.view.scale==null&&(e.view.scale=1),e.facilities=n.facilities||{},e.eggFarms=n.eggFarms||[],e.scanners=(n.scanners||[]).map(o=>({...o,queue:[],cooldown:o.cooldown||0})),e.conveyorTiles=new Set(n.conveyorTiles||[]),e.conveyorMeta=new Map(n.conveyorMeta||[]),e.branchCounters=new Map(n.branchCounters||[]),e.cardEffects=n.cardEffects||{bearing:0,legalWork:0,flattery:0},e.cardEffects.drawBonus!=null&&delete e.cardEffects.drawBonus,e.cardDraft=n.cardDraft||{pending:!1,choices:[],rerolls:0,picksRemaining:0},e.cardDraft.picksRemaining==null&&(e.cardDraft.picksRemaining=e.cardDraft.pending?1:0),e.cardTargetPick=n.cardTargetPick||null,e.groupDrag=null,e.selectionBox=null,e.pan=null,e.routingVersion=(e.routingVersion||0)+1,e.factoryGraph=null,e.factoryGraphCommittedVersion=-1}function Le(e){e.phase==="build"&&(e.undoStack.push(tn(e)),e.undoStack.length>80&&e.undoStack.shift(),e.redoStack=[])}function ao(e){return e.phase!=="build"||!e.undoStack.length?!1:(e.redoStack.push(tn(e)),Xr(e,e.undoStack.pop()),!0)}function co(e){return e.phase!=="build"||!e.redoStack.length?!1:(e.undoStack.push(tn(e)),Xr(e,e.redoStack.pop()),!0)}function G(e,t,n,o,r=d.ink){e.floatingTexts.push({x:t,y:n,text:o,color:r,life:1,maxLife:1})}function gt(e,t,n){e.shake.strength=Math.max(e.shake.strength,t),e.shake.time=Math.max(e.shake.time,n)}function Jr(e,t,n){e.effects.push({type:"spawn",priority:we.ambient,x:t,y:n,life:.42,maxLife:.42,size:12})}function Qr(e,t,n,o=d.green){e.effects.push({type:"scannerPulse",priority:we.important,x:t,y:n,color:o,life:.42,maxLife:.42,size:24})}function Zr(e,t,n){for(let o=0;o<12;o+=1)e.effects.push({type:"meat",priority:we.important,x:t+O(-18,18),y:n+O(-12,16),vx:O(-60,60),vy:O(-90,-25),life:O(.45,.85),maxLife:.85,size:O(4,8)})}function jr(e,t,n){for(let o=0;o<16;o+=1)e.effects.push({type:"sludge",priority:we.important,x:t+O(-18,18),y:n+O(-12,16),vx:O(-46,46),vy:O(-70,-20),life:O(.45,.9),maxLife:.9,size:O(4,8)})}function ei(e,t,n,o){let r=o==="poop"?d.poop:o==="male"?d.male:d.female;for(let i=0;i<22;i+=1)e.effects.push({type:"shred",priority:we.important,x:t+O(-16,16),y:n+O(-8,8),vx:O(-70,70),vy:O(40,160),color:i%3===0?d.white:r,life:O(.45,.95),maxLife:.95,w:O(3,7),h:O(10,24),rot:O(-Math.PI,Math.PI)});Xe(e,t,n,d.green,34,we.important)}function ti(e,t,n,o){let r=o==="poop"?d.poop:o==="male"?d.danger:d.green;for(let i=0;i<8;i+=1)e.effects.push({type:"load",priority:we.important,x:t+O(-18,18),y:n+O(-18,18),vx:O(-42,42),vy:O(-90,-25),color:r,life:O(.35,.65),maxLife:.65,size:8})}function Xe(e,t,n,o=d.danger,r=52,i=we.critical){e.effects.push({type:"shockwave",priority:i,x:t,y:n,color:o,life:.55,maxLife:.55,size:8,maxSize:r})}function ut(e,t,n,o=12,r=d.danger,i=we.critical){for(let l=0;l0&&(e.shake.time=Math.max(0,e.shake.time-t),e.shake.time<=0&&(e.shake.strength=0));for(let r of e.effects)if(r.life-=t,["meat","sludge","spark","smoke","shred","load","rage"].includes(r.type))r.x+=(r.vx||0)*t,r.y+=(r.vy||0)*t,r.vy=(r.vy||0)+150*t;else if(r.type==="flyingChick"){r.x+=r.vx*t,r.y+=r.vy*t,r.vy+=360*t,r.noDamage||Vs(e,r,o);let i=r.x+e.view.x,l=r.y+e.view.y;(i<-100||l<-100||i>n.width+100||l>n.height+100)&&(r.life=0)}e.effects=e.effects.filter(r=>r.life>0);for(let r of e.floatingTexts)r.life-=t,r.y-=34*t;e.floatingTexts=e.floatingTexts.filter(r=>r.life>0)}function Vs(e,t,n){t.hit||(t.hit=new Set);for(let o of n()){if(t.hit.has(o.id)||t.xo.x+o.w||t.yo.y+o.h)continue;t.hit.add(o.id);let r=_r(e,o.price);G(e,t.x,t.y-10,`-${S(r)}`,d.danger),Xe(e,t.x,t.y,d.danger,30),ut(e,t.x,t.y,5,d.danger),gt(e,5,.18)}}var Se=H.maintenance;function fo(e,t){return Math.max(0,Number(e?.cardEffects?.[t])||0)}function Cn(e){let t=Math.min(1.4,1+.04*Math.min(10,fo(e,"crowdedFarming"))),n=Math.pow(.95,fo(e,"preventiveMaintenance"));return t*n}function Xs(e){return Math.pow(1.03,fo(e,"durabilityCoating"))}function Sn(e,t,n=1){let o=Se.durability[t]||1e3;return Math.max(1,Math.ceil(o*Math.max(.1,n||1)*Xs(e)))}function mo(e){let t=Se.repairman.dailyCost;return e?.cardEffects?.laborExploitation?Math.ceil(t*.7):t}function ii(e){return Math.max(.1,Number(e?.durabilityBaseMultiplier)||1)}function de(e){e.repairman||(e.repairman={hiredForNextDay:!1,active:!1,x:y.x+y.cell*.5,y:y.y+y.rows*y.cell+84,target:null,repairedToday:0});for(let[t,n]of e.conveyorMeta||[]){n.uses==null&&(n.uses=0),n.maintenanceType==null&&(n.maintenanceType="conveyor");let o=Sn(e,"conveyor",ii(n));(n.durability==null||n.durability0&&(o.processingCooldown=Math.max(0,o.processingCooldown-t))}}function So(e){de(e);let t=[];for(let[n,o]of e.conveyorMeta||[])t.push({type:"conveyor",key:n,meta:o,label:`Belt ${n}`,center:P(ee(n).col,ee(n).row)});for(let n of e.eggFarms||[])t.push({type:"eggFarm",ref:n,label:`EGG #${n.id}`,center:P(n.col,n.row)});for(let n of(e.scanners||[]).filter(o=>o.kind==="auto"))t.push({type:"autoScanner",ref:n,label:`AUTO #${n.id}`,center:P(n.col,n.row)});for(let n of["mixer","trash"]){let o=e.facilities?.[n];o&&t.push({type:n,ref:o,label:o.shortName||o.name||n,center:{x:o.x+o.w/2,y:o.y+o.h/2}})}return t}function vn(e){return e.meta?Ut(e):Ut(e.ref)}function ho(e){return e.meta?po(e):po(e.ref)}function ri(e){return e.meta?yo(e):yo(e.ref)}function Zs(e,t){Js(e.meta?e:e.ref,Math.max(0,ho(e)-t))}function ui(e){return e.type==="conveyor"?`conveyor:${e.key}`:`${e.type}:${e.ref?.id||e.ref?.type||e.type}`}function di(e){if(de(e),e.repairman.hiredForNextDay)return{ok:!1,reason:"Repairman already hired."};let t=mo(e);return e.cashui(o)===t.key)||null}function el(e){let t=null;for(let n of So(e)){let o=vn(n);o<=.001||(!t||o>vn(t))&&(t=n)}return t}function yi(e,t){de(e);let n=e.repairman;if(!n.active||e.phase!=="running")return;let o=js(e,n.target)||el(e);if(!o){n.target=null;return}n.target={key:ui(o),label:o.label};let r=Se.repairman.walkSpeed,i=o.center.x-n.x,l=o.center.y-n.y,f=Math.hypot(i,l);if(f>8){let I=Math.min(f,r*t);n.x+=i/f*I,n.y+=l/f*I;return}let w=t/(Se.repairman.secondsPerOnePercent*100),M=ho(o),R=w*ri(o);Zs(o,R);let D=Math.max(0,(M-ho(o))/ri(o)*100);n.repairedToday=(n.repairedToday||0)+D,D>0&&Math.random()<.02&&G(e,n.x,n.y-28,"REPAIR",d.green),vn(o)<=.001&&(n.target=null)}function tl(e){let t=0,n="none";for(let o of So(e)){let r=vn(o);r>t&&(t=r,n=o.label)}return{ratio:t,percent:Math.round(t*100),label:n}}function Mo(e){let t=tl(e),n=wo(e);return{worst:t,avgBeltPerformance:n,repairman:e.repairman||null}}var nl=ke.commonWeight,ol=ke.rareWeight,rl=ke.ultraRareWeight||.45,Wt=ke.baseDraftSize,_t=Ko;function hi(e,t){let n=Me(e);return Math.max(0,Number(n[t])||0)}function Me(e){e.cardEffects||(e.cardEffects={...Gn,loans:[]});for(let[t,n]of Object.entries(Gn))e.cardEffects[t]==null&&(e.cardEffects[t]=Array.isArray(n)?[]:n);return e.cardEffects.flattery!=null&&(e.cardEffects.fairiesFlatteryNext=Math.max(0,e.cardEffects.fairiesFlatteryNext||0)+Math.max(0,e.cardEffects.flattery||0),delete e.cardEffects.flattery),Array.isArray(e.cardEffects.loans)||(e.cardEffects.loans=[]),e.cardEffects.drawBonus!=null&&delete e.cardEffects.drawBonus,e.cardDraft||(e.cardDraft={pending:!1,choices:[],rerolls:0,picksRemaining:0}),e.cardDraft.picksRemaining==null&&(e.cardDraft.picksRemaining=e.cardDraft.pending?1:0),e.cardEffects}function ki(e){let t=Math.max(.5,1-.05*Math.min(10,hi(e,"crowdedFarming"))),n=Math.pow(.96,hi(e,"hatchingFeed"));return t*n}function vi(e,t=null){return Math.min(3,Math.max(0,Number(t?.extraEggOutlet)||0))}function $n(e,t=null){let n=Me(e),o=cn*Math.pow(un,Math.max(0,n.bearing||0)),r=t?1:wo(e);return Math.min(Dt,o*r)}function Je(e,t=null){let n=Math.max(0,(e?.level||1)-1),o=Math.max(Nn,an*Math.pow(Uo,n));return t?Math.max(Nn,o*Tn(e)):o}function mi(e){let t=e.cardDraft||{rerolls:0},n=Math.max(1,(t.rerolls||0)+1);return Math.ceil(Math.max(1,e.turn||1)/ke.rerollCost.dayDivisor*n*ke.rerollCost.multiplier)}function go(e){return _t.find(t=>t.id===e)||null}function Eo(e,t){let n=typeof t=="string"?go(t):t;if(Me(e),!n||n.type==="dud")return{ok:!1,reason:"Card not found."};if(n.id==="extraCards")return e.cardDraft=e.cardDraft||{pending:!1,choices:[],rerolls:0,picksRemaining:0},e.cardDraft.choices.push(...Pn(e,2)),{ok:!0,reason:`${n.title} added draft cards.`};if(n.type==="instant")return Si(e,n),{ok:!0,reason:`${n.title} granted.`};if(n.type==="equipmentUpgrade"){let o=Ue(e,n)[0];return o?(Ci(e,o),{ok:!0,reason:`${n.title} applied.`}):{ok:!1,reason:`No valid target for ${n.title}.`}}if(n.type==="cellAction"&&n.target==="blockedCell"){let o=Ue(e,n).slice(0,3);if(!o.length)return{ok:!1,reason:"No blocked cells to remove."};for(let r of o){e.blockedCells.delete(F(r.col,r.row));let i=P(r.col,r.row);dt(e,i.x,i.y)}return he(e),{ok:!0,reason:`${n.title} removed ${o.length} blocked cells.`}}return{ok:!1,reason:`${n.title} cannot be debug-granted.`}}function il(e,t){if(t.type==="blockedCell")return`Blocked cell ${t.col},${t.row}`;if(t.type==="eggFarm")return`EGG #${t.id} L${t.level} -> L${t.level+1}`;if(t.type==="eggOutlet"){let n=Math.max(0,Number(t.farm?.extraEggOutlet)||0);return`EGG #${t.id} OUTLET ${1+n} -> ${2+n}`}if(t.type==="scanner")return`AUTO #${t.id} L${t.level} -> L${t.level+1} | CD ${Je(t,e).toFixed(1)}s -> ${Je({...t,level:t.level+1},e).toFixed(1)}s`;if(t.type==="facility"&&t.id==="mixer"){let n=t.level,o=lt(e);t.level=n+1;let r=lt(e);return t.level=n,`MIXER L${t.level} -> L${t.level+1} | ${S(o)} -> ${S(r)}`}if(t.type==="facility"&&t.id==="truck"){let n=t.level,o=Ie(e);t.level=n+1;let r=Ie(e);return t.level=n,`TRUCK L${t.level} -> L${t.level+1} | ${S(o)} -> ${S(r)}`}if(t.type==="facility"&&t.id==="trash"){let n=Mt(e),o=Math.min(Gt(),n+1),r=(at(n)*100).toFixed(2),i=(at(o)*100).toFixed(2);return`SHREDDER L${t.level||1} -> L${(t.level||1)+1} | ${r}% -> ${i}% per item`}return`${t.name||t.id} L${t.level||1} -> L${(t.level||1)+1}`}function sl(e){return[...e.blockedCells||[]].map(t=>{let[n,o]=String(t).split(",").map(Number);return{type:"blockedCell",id:t,col:n,row:o}}).filter(t=>Number.isFinite(t.col)&&Number.isFinite(t.row))}function Ue(e,t){let n=typeof t=="string"?go(t):t;return Me(e),n?n.target==="blockedCell"?sl(e):n.target==="eggFarm"?e.eggFarms.filter(o=>(o.level||1)<4):n.target==="eggOutlet"?e.eggFarms.filter(o=>Math.max(0,Number(o.extraEggOutlet)||0)<3).map(o=>({type:"eggOutlet",id:o.id,col:o.col,row:o.row,farm:o})):n.target==="autoScanner"?e.scanners.filter(o=>o.kind==="auto"):n.target==="mixer"?e.facilities.mixer?[e.facilities.mixer]:[]:n.target==="truck"?e.facilities.truck?[e.facilities.truck]:[]:n.target==="trash"?e.facilities.trash&&Ir(e)=10;if(t.id==="recyclingSubsidy")return(n.recyclingSubsidy||0)>=3;if(t.id==="dudFilter"){let o=Math.max(0,Math.min(1,ke.dudChancePerCard||0)),r=Math.max(1e-4,Math.min(1,ke.dudChanceReductionPerFilter||.05));return(n.dudFilter||0)>=Math.ceil(o/r)}return t.id==="laborExploitation"?!!n.laborExploitation:t.id==="usedMachine"?!!n.usedMachineActive:t.id==="newMachine"?!n.usedMachineActive:!1}function al(e){let t=e.repairman||{};return!!(t.active||t.hiredForNextDay||e.pendingRepairWorkerWages>0)}function cl(e){return _t.filter(t=>!(t.id==="laborExploitation"&&!al(e)||ll(e,t)||(t.type==="equipmentUpgrade"||t.type==="cellAction")&&Ue(e,t).length<=0))}function xi(e){return e.rarity==="ultraRare"?rl:e.rarity==="rare"?ol:nl}function ul(e){let t=e.reduce((o,r)=>o+xi(r),0),n=Math.random()*t;for(let o of e)if(n-=xi(o),n<=0)return o;return e[e.length-1]}var wi=0;function dl(){return wi+=1,{id:`dud:${Date.now()}:${wi}`,title:"Dud",rarity:"dud",type:"dud",description:"Click to flick this dud away. It does not consume a pick."}}function fl(e){let t=Me(e),n=Math.max(0,Math.min(1,ke.dudChancePerCard||0)),o=Math.max(0,Math.min(1,ke.dudChanceReductionPerFilter||0));return Math.max(0,n-o*Math.max(0,t.dudFilter||0))}function pl(e,t){let n=[...t],o=0,r=Math.max(0,ke.maxDudsPerDraft||0),i=fl(e);for(let l=0;lf.id===i.id);l>=0&&r.splice(l,1)}return pl(e,o)}function Rn(e){return e?e.type==="blockedCell"?`blockedCell:${e.id}`:e.type==="eggFarm"?`eggFarm:${e.id}`:e.type==="eggOutlet"?`eggOutlet:${e.id}`:e.type==="scanner"?`scanner:${e.id}`:e.type==="facility"?`facility:${e.id}`:`${e.type}:${e.id}`:""}function yl(e){if(!e)return null;if(e.type==="blockedCell"){let t=P(e.col,e.row);return{x:t.x-y.cell*.48,y:t.y-y.cell*.48,w:y.cell*.96,h:y.cell*.96,cx:t.x,cy:t.y}}if(e.type==="eggFarm"||e.type==="eggOutlet"){let t=P(e.col,e.row);return{x:t.x-34,y:t.y-34,w:68,h:68,cx:t.x,cy:t.y}}if(e.type==="scanner"){let t=le(e);return{x:t.x-58,y:t.y-42,w:116,h:84,cx:t.x,cy:t.y}}return e.type==="facility"?{x:e.x,y:e.y,w:e.w,h:e.h,cx:e.x+e.w/2,cy:e.y+e.h/2}:null}function Po(e){if(!e.cardTargetPick?.pending)return[];let t=new Set(e.cardTargetPick.targetKeys||[]);return Ue(e,e.cardTargetPick.cardId).filter(n=>t.has(Rn(n))).map(n=>({key:Rn(n),label:il(e,n),target:n,bounds:yl(n)})).filter(n=>n.bounds)}function hl(e,t){let n=Po(e);for(let o of n){let r=o.bounds;if(t.x>=r.x&&t.x<=r.x+r.w&&t.y>=r.y&&t.y<=r.y+r.h)return o.target}return null}function Ci(e,t){if(t.type==="eggOutlet"){let n=t.farm||e.eggFarms.find(r=>r.id===t.id);if(!n)return;n.extraEggOutlet=Math.min(3,Math.max(0,Number(n.extraEggOutlet)||0)+1);let o=P(n.col,n.row);G(e,o.x,o.y-30,`OUTLET ${1+n.extraEggOutlet}`,d.green),he(e);return}if(t.level=(t.level||1)+1,t.type==="eggFarm"){t.nextSpawn=St(t),t.lastInterval=t.nextSpawn;let n=P(t.col,t.row);G(e,n.x,n.y-30,`LV ${t.level}`,d.green);return}if(t.type==="scanner"){let n=le(t);G(e,n.x,n.y-34,`LV ${t.level}`,d.green);return}t.type==="facility"&&G(e,t.x+t.w/2,t.y+18,`LV ${t.level}`,d.green)}function Si(e,t){let n=Me(e),o=r=>{n[r]=Math.max(0,n[r]||0)+1};if(t.id==="bearing"&&o("bearing"),t.id==="legalWork"&&o("legalWork"),t.id==="crowdedFarming"&&(n.crowdedFarming=Math.min(10,(n.crowdedFarming||0)+1)),t.id==="hatchingFeed"&&o("hatchingFeed"),t.id==="safetyCover"&&o("safetyCover"),t.id==="recyclingSubsidy"&&(n.recyclingSubsidy=Math.min(3,(n.recyclingSubsidy||0)+1)),t.id==="chemicalWeaponSubsidy"&&o("chemicalWeaponSubsidy"),t.id==="preventiveMaintenance"&&o("preventiveMaintenance"),t.id==="dudFilter"&&o("dudFilter"),t.id==="durabilityCoating"&&(o("durabilityCoating"),de(e)),t.id==="flattery"&&o("fairiesFlatteryNext"),t.id==="usedMachine"&&(n.usedMachineActive=!0),t.id==="newMachine"&&(n.usedMachineActive=!1),t.id==="laborExploitation"&&(n.laborExploitation=1),t.id==="rescueLoan"&&(o("rescueLoanCharges"),G(e,y.x+y.cols*y.cell/2,y.y-30,"RESCUE HELD",d.green)),t.id==="loan"){let r=Math.max(1,Math.floor(Number(e.turn)||1)),i=300*r;Ae(e,i),n.loans.push({daysRemaining:r,dailyPayment:400,borrowedOnTurn:e.turn||1}),G(e,y.x+y.cols*y.cell/2,y.y-30,`LOAN +${S(i)}`,d.green)}}function Be(e){return`${Math.round(Number(e||0)*100)}%`}function bi(e){let t=nt[Math.max(0,Math.min(nt.length-1,e-1))]||nt[0]||[0,0];return`${t[0].toFixed(2)}-${t[1].toFixed(2)}s`}function En(e,t){return Math.ceil(e*Math.pow(fe.incomeUpgradeRate,Math.max(0,t-1)))}function ml(e,t){let n=Me(e);if(t.id==="upgradeEgg"){let o=Ue(e,t),r=Math.min(3,Math.max(1,o[0]?.level||1));return`Choose one EGG. Level +1. Production interval ${bi(r)} -> ${bi(r+1)}.`}if(t.id==="upgradeAutoScanner"){let o=Ue(e,t)[0]||{level:1},r=Je(o,e),i=Je({...o,level:(o.level||1)+1},e);return`Choose one AUTO SCANNER. Level +1. Cooldown ${r.toFixed(2)}s -> ${i.toFixed(2)}s.`}if(t.id==="upgradeMixer"){let o=e.facilities?.mixer?.level||1;return`MIXER level +1. Male-chick income ${S(En(fe.income.mixer,o))} -> ${S(En(fe.income.mixer,o+1))}.`}if(t.id==="upgradeTruck"){let o=e.facilities?.truck?.level||1;return`TRUCK level +1. Correct shipment income ${S(En(fe.income.truck,o))} -> ${S(En(fe.income.truck,o+1))}.`}if(t.id==="upgradeTrash"){let o=Mt(e),r=Math.min(Gt(),o+1);return`SHREDDER level +1. Bonus chance per item ${Be(at(o))} -> ${Be(at(r))}.`}if(t.id==="bearing"){let o=Math.min(Dt,cn*Math.pow(un,Math.max(0,n.bearing||0))),r=Math.min(Dt,cn*Math.pow(un,Math.max(0,(n.bearing||0)+1)));return`Conveyor speed +7.5%. After pick: ${o.toFixed(1)} -> ${r.toFixed(1)} px/s.`}if(t.id==="legalWork"){let o=Math.max(0,n.legalWork||0);return`ZUNDA TAX exemption +${S(250)}. After pick: exemption bonus ${S(250*(o+1))}.`}if(t.id==="crowdedFarming"){let o=Math.min(10,(n.crowdedFarming||0)+1);return`Held ${n.crowdedFarming||0}/10. After pick: EGG interval ${Be(Math.max(.5,1-.05*o))}, deterioration ${Be(Math.min(1.4,1+.04*o))}.`}if(t.id==="extraEggOutlet"){let o=Ue(e,t),r=Math.max(0,Number(o[0]?.farm?.extraEggOutlet)||0);return`Choose one EGG. Output ports ${1+r} -> ${2+r}. Max 4 ports per EGG.`}if(t.id==="hatchingFeed")return`Held ${n.hatchingFeed||0}. After pick: EGG interval ${Be(Math.pow(.96,(n.hatchingFeed||0)+1))}, poop rate ${Be(Math.pow(1.03,(n.hatchingFeed||0)+1))}.`;if(t.id==="safetyCover")return`Held ${n.safetyCover||0}. After pick: explosion damage ${Be(Math.pow(.95,(n.safetyCover||0)+1))}.`;if(t.id==="recyclingSubsidy")return`Held ${n.recyclingSubsidy||0}/3. After pick: normal SELL refund ${Math.round(Math.min(.8,.5+.1*((n.recyclingSubsidy||0)+1))*100)}%.`;if(t.id==="chemicalWeaponSubsidy")return`Held ${n.chemicalWeaponSubsidy||0}. After pick: ${S(2e3*((n.chemicalWeaponSubsidy||0)+1))} per 100 truck-shipped poop each day.`;if(t.id==="preventiveMaintenance")return`Held ${n.preventiveMaintenance||0}. After pick: deterioration speed ${Be(Math.pow(.95,(n.preventiveMaintenance||0)+1))}.`;if(t.id==="dudFilter"){let o=Math.max(0,Math.min(1,ke.dudChancePerCard||0)),r=Math.max(0,Math.min(1,ke.dudChanceReductionPerFilter||.05)),i=Math.max(0,n.dudFilter||0),l=Math.max(0,o-r*i),f=Math.max(0,o-r*(i+1));return`Held ${i}. DUD chance per card ${Be(l)} -> ${Be(f)}.`}if(t.id==="durabilityCoating")return`Held ${n.durabilityCoating||0}. After pick: maximum durability ${Be(Math.pow(1.03,(n.durabilityCoating||0)+1))}.`;if(t.id==="flattery")return`Next Fairies tribute -50%. Held for next tribute: ${n.fairiesFlatteryNext||0}.`;if(t.id==="usedMachine")return"Enable Used Machines: future equipment costs 50%, has no SELL refund, and starts with 60% durability.";if(t.id==="newMachine")return"Cancel Used Machines. Future equipment returns to normal price, refund, and durability.";if(t.id==="laborExploitation")return"Repairman labor cost -30% permanently. One time only.";if(t.id==="rescueLoan")return`At day-end bankruptcy check, gain ${S(500)} if cash is below ${S(0)}. Held: ${n.rescueLoanCharges||0}.`;if(t.id==="loan"){let o=Math.max(1,Math.floor(Number(e.turn)||1));return`Gain ${S(300*o)} now. Starting tomorrow, pay ${S(400)} per day for ${o} days.`}return t.description}function To(e,t){let n=e.cardDraft?.choices||[],o=n.findIndex(r=>r.id===t.id);o>=0&&n.splice(o,1)}function Mi({game:e,ui:t,onUpdatePanels:n}){function o(){n&&n()}function r(){Me(e),e.cardTargetPick=null,e.cardDraft={pending:!0,choices:Pn(e,Wt),rerolls:0,picksRemaining:1}}function i(){Me(e),e.cardDraft.pending=!1,e.cardDraft.choices=[],e.cardDraft.picksRemaining=0,e.cardTargetPick=null,t.modal.classList.remove("visible","equipment-popover"),o()}function l(g=Wt){Me(e),e.cardDraft.choices=Pn(e,g)}function f(){Me(e),!e.cardDraft.choices.length&&e.cardDraft.picksRemaining>0&&l(Wt)}function w(g){To(e,g),e.cardDraft.picksRemaining=Math.max(0,(e.cardDraft.picksRemaining||1)-1),e.cardTargetPick=null,f(),e.cardDraft.picksRemaining<=0?i():ie()}function M(g){To(e,g),e.cardDraft.picksRemaining=Math.max(0,(e.cardDraft.picksRemaining||1)-1)+ke.extraCardsAddedPicks,e.cardDraft.choices.push(...Pn(e,2)),ie()}function R(g,W){gt(e,34,.62),document.body?.animate&&document.body.animate([{transform:"translate(0, 0)"},{transform:"translate(-10px, 7px)"},{transform:"translate(12px, -8px)"},{transform:"translate(-8px, -5px)"},{transform:"translate(9px, 6px)"},{transform:"translate(0, 0)"}],{duration:260,easing:"steps(5, end)"}),W&&(W.style.setProperty("--fling-x",`${260+Math.random()*260}px`),W.style.setProperty("--fling-y",`${-260-Math.random()*170}px`),W.style.setProperty("--fling-r",`${(Math.random()<.5?-1:1)*(52+Math.random()*38)}deg`),W.classList.add("flung")),window.setTimeout(()=>{To(e,g),f(),ie()},340)}function D(g,W=null){if(g.type==="dud")return R(g,W);if(g.id==="extraCards")return M(g);if(g.type==="equipmentUpgrade"||g.type==="cellAction")return I(g);Si(e,g),w(g)}function I(g){let W=Ue(e,g);W.length&&(e.cardTargetPick={pending:!0,cardId:g.id,mode:g.target==="blockedCell"?"blockedCell":"equipment",targetKeys:W.map(Rn),remaining:g.id==="dynamite"?Math.min(3,W.length):1},t.modal.classList.remove("visible","equipment-popover"),o())}function j(g){if(!g||g.type!=="blockedCell")return!1;let W=F(g.col,g.row);if(!e.blockedCells?.has(W))return!1;e.blockedCells.delete(W);let _=P(g.col,g.row);return dt(e,_.x,_.y),Xe(e,_.x,_.y,d.danger,62),ut(e,_.x,_.y,18,d.warn),kn(e,_.x,_.y,8),gt(e,16,.35),G(e,_.x,_.y-18,"BOOM",d.danger),he(e),e.cardTargetPick.remaining=Math.max(0,(e.cardTargetPick.remaining||1)-1),e.cardTargetPick.targetKeys=Ue(e,"dynamite").map(Rn),o(),!0}function se(g){if(!e.cardTargetPick?.pending)return!1;let W=go(e.cardTargetPick.cardId),_=hl(e,g);if(!W||!_){let ae=e.cardTargetPick.mode==="blockedCell"?"SELECT BLOCKED CELL":"SELECT UPGRADE TARGET";return G(e,g.x,g.y-18,ae,d.danger),!0}return W.id==="dynamite"?(j(_)&&((e.cardTargetPick.remaining||0)<=0||!Ue(e,"dynamite").length)&&w(W),!0):(Ci(e,_),w(W),!0)}function z(){e.cardTargetPick?.pending&&(e.cardTargetPick=null,ie())}function X(){let g=mi(e);e.cashE!==Z)].slice(0,5);return _.innerHTML=`${g.title}
${me.map(E=>`${E}`).join("")}
${ml(e,g)}`,_.addEventListener("click",()=>D(g,_)),_.dataset.index=String(W),_}function ie(){Me(e),e.cardTargetPick=null,e.cardDraft?.pending||r(),e.cardDraft.choices?.length||l(Wt);let g=e.cardDraft.choices||[],W=mi(e),_=Math.max(1,e.cardDraft.picksRemaining||1);t.modalTitle.textContent="Choose Upgrade Card",t.modalBody.innerHTML=`

Choose ${_} card${_===1?"":"s"} before Build phase. Dud cards can be flicked away without consuming a pick. Equipment cards target machines; Dynamite targets blocked cells.

`;let ae=t.modalBody.querySelector("#cardChoices");g.forEach((me,E)=>ae.appendChild(ue(me,E))),t.modalActions.innerHTML="";let Z=document.createElement("button");Z.type="button",Z.className="facility-action warn reroll-button",Z.textContent=`Reroll ${S(W)}`,Z.disabled=e.cash{n.loaded=!1},(xl||e==="poop")&&t&&(n.onload=()=>{n.loaded=!0},n.src=t),We[e]=n}function Ri(e,t,n,o){e.clearRect(0,0,t.width,t.height),vl(e,t),e.save();let r=n.shake.time>0?O(-n.shake.strength,n.shake.strength):0,i=n.shake.time>0?O(-n.shake.strength,n.shake.strength):0,l=n.view?.scale||1;e.translate((n.view?.x||0)+r,(n.view?.y||0)+i),e.scale(l,l),Sl(e,n),Ml(e,n),Tl(e,n),$l(e,n),Bl(e,n),Al(e,n),Fl(e,n),kl(e,n),Ul(e,n,o.activeQueuedChick),Xl(e,n),ql(e,t,n),Hl(e,n,o.selectedObject,o.selectedTitle),Yl(e,n),zl(e,n),Vl(e,n),e.restore(),Jl(e,n,t)}function bl(e){if(e.type==="eggFarm"){let n=P(e.ref.col,e.ref.row);return{x:n.x-34,y:n.y-34,w:68,h:68,cx:n.x,cy:n.y}}let t=e.ref;return{x:t.x,y:t.y,w:t.w,h:t.h,cx:t.x+t.w/2,cy:t.y+t.h/2}}function kl(e,t){if(t.phase!=="build")return;let n=wn(t);for(let o of n){let r=bl(o);e.save(),e.fillStyle="rgba(196,36,36,.22)",e.strokeStyle=d.danger,e.lineWidth=5,e.setLineDash([10,6]),Q(e,r.x,r.y,r.w,r.h,!0,!0),e.setLineDash([]),e.fillStyle=d.danger,e.strokeStyle=d.white,e.lineWidth=4,e.font="900 16px ui-monospace, monospace",e.textAlign="center",e.strokeText("!",r.cx,r.y+24),e.fillText("!",r.cx,r.y+24),e.font="900 11px ui-monospace, monospace",e.strokeText("NO ROUTE",r.cx,r.y+r.h-8),e.fillText("NO ROUTE",r.cx,r.y+r.h-8),e.restore()}}function Q(e,t,n,o,r,i=!0,l=!0){e.beginPath(),e.rect(t,n,o,r),i&&e.fill(),l&&e.stroke()}function Ao(e,t,n,o,r=d.ink){e.save(),e.font="900 12px ui-monospace, monospace",e.textAlign="center",e.lineWidth=4,e.strokeStyle=d.white,e.fillStyle=r,e.strokeText(o,t,n),e.fillText(o,t,n),e.restore()}function Et(e,t,n,o,r,i){return t?.loaded?(e.drawImage(t,n,o,r,i),!0):!1}function vl(e,t){e.fillStyle=d.bg,e.fillRect(0,0,t.width,t.height),e.save(),e.strokeStyle="rgba(16, 32, 21, 0.035)",e.lineWidth=1;for(let n=0;n=n;for(let r of vt(t)){let i=Ct(r);e.save(),e.fillStyle=o?"rgba(183,245,198,.34)":"rgba(255,255,255,.22)",e.strokeStyle=o?d.green:d.muted,e.lineWidth=5,e.setLineDash([16,10]),Q(e,i.x,i.y,i.w,i.h,!0,!0),e.setLineDash([]);let l=Hn(r),f=l.cx,w=l.cy;e.fillStyle="rgba(255,255,255,.94)",e.strokeStyle=d.ink,e.lineWidth=4,Q(e,l.x,l.y,l.w,l.h,!0,!0),e.fillStyle=o?d.green:d.danger,e.font="900 16px ui-monospace, monospace",e.textAlign="center",e.fillText(r.direction==="right"?"BUY RIGHT LOT":"BUY UPPER LOT",f,w-12),e.fillStyle=d.ink,e.font="900 16px ui-monospace, monospace",e.fillText(`${S(n)} / 10\xD710`,f,w+15),e.fillStyle=d.muted,e.font="900 11px ui-monospace, monospace",e.fillText("blocked tiles included",f,w+30),e.restore()}}function Qe(e,t,n,o,r,i,l=0){let f=Ut(i);f<.5||(e.save(),e.globalAlpha=Math.min(.62,.12+f*.54),e.fillStyle="#6b4a23",l>0?(e.beginPath(),e.roundRect?.(t,n,o,r,l),e.roundRect?e.fill():Q(e,t,n,o,r,!0,!1)):Q(e,t,n,o,r,!0,!1),e.globalAlpha=Math.min(.9,.25+f*.6),e.strokeStyle="#3a2a16",e.lineWidth=Math.max(2,Math.floor(2+f*5)),e.setLineDash([5,6]),Q(e,t+3,n+3,o-6,r-6,!1,!0),e.restore())}function Ro(e,t){let n=e.componentLookup?.get(t);return(n?e.congestion.get(n):null)?.ratio||0}function Tl(e,t){let n=[],o=new Set;for(let i of t.conveyorTiles){let{col:l,row:f}=ee(i);for(let w of wr(t,l,f)){let M=F(w.col,w.row),R=[i,M].sort().join("|");if(o.has(R))continue;o.add(R);let D=Math.max(Ro(t,i),Ro(t,M));n.push({a:P(l,f),b:P(w.col,w.row),ratio:D})}}e.save(),e.lineCap="round",e.lineJoin="round";for(let i of[{width:33,color:d.ink,alpha:1},{width:25,color:d.white,alpha:1},{width:17,color:d.greenSoft,alpha:1}]){e.globalAlpha=i.alpha,e.strokeStyle=i.color,e.lineWidth=i.width;for(let l of n)e.beginPath(),e.moveTo(l.a.x,l.a.y),e.lineTo(l.b.x,l.b.y),e.stroke()}for(let i of n){let l=Math.max(0,(i.ratio-.5)/.5);e.strokeStyle=_n(d.green,d.danger,l),e.globalAlpha=i.ratio>.5?.35+l*.55:.8,e.lineWidth=6,e.beginPath(),e.moveTo(i.a.x,i.a.y),e.lineTo(i.b.x,i.b.y),e.stroke()}let r=El(t);for(let i of t.conveyorTiles){let l=P(...Object.values(ee(i))),f=Ro(t,i);e.fillStyle=f>.5?_n(d.white,"#ffd6d6",Math.max(0,(f-.5)/.5)):d.white,e.strokeStyle=d.ink,e.lineWidth=2,Q(e,l.x-8,l.y-8,16,16,!0,!0),Qe(e,l.x-14,l.y-14,28,28,{meta:t.conveyorMeta.get(i)}),Oe(e,t,"conveyor",i,l.x,l.y,38,38),Pl(e,l,r.get(i)||[]),f>.5&&Ao(e,l.x,l.y-15,`${Math.floor(f*100)}%`,d.danger)}e.restore()}function Ti(e,t){let n=$e(t.x,t.y);if(!n)return null;let o=P(n.col,n.row);if(Math.hypot(o.x-t.x,o.y-t.y)>y.cell*.38)return null;let r=F(n.col,n.row);return e.conveyorTiles.has(r)?r:null}function gi(e,t,n){if(!(!n||n.length<2))for(let o=0;oMath.abs(Math.sin((se-I)/2))<.01)||j.push(I)}}function gl(e){if(!e)return[];let t=[];return Array.isArray(e.outDirs)&&t.push(...e.outDirs),e.dir&&t.push(e.dir),[...new Set(t)].map(n=>ge.find(o=>o.name===n)?.angle).filter(n=>Number.isFinite(n))}function El(e){let t=new Map;for(let[n,o]of e.conveyorMeta||[]){let r=gl(o);r.length&&t.set(n,r)}for(let n of e.eggFarms)gi(e,t,Ye(e,n)?.route);for(let n of e.scanners)for(let o of["left","right"])gi(e,t,Qt(e,o,le(n),n.id)?.route);return t}function Pl(e,t,n){let o=n.slice(0,3);o.length&&(e.save(),e.fillStyle=d.ink,e.globalAlpha=.86,o.forEach((r,i)=>{let l=(i-(o.length-1)/2)*4,f=Math.cos(r+Math.PI/2),w=Math.sin(r+Math.PI/2);Rl(e,t.x+f*l,t.y+w*l,r,6,5)}),e.restore())}function Rl(e,t,n,o,r=11,i=8){e.save(),e.translate(t,n),e.rotate(o),e.beginPath(),e.moveTo(r,0),e.lineTo(-r*.72,-i),e.lineTo(-r*.72,i),e.closePath(),e.fill(),e.restore()}function $l(e,t){e.save(),e.lineWidth=2,e.font="900 12px ui-monospace, monospace",e.textAlign="center";for(let n of t.scanners){let o=Zn(n),r=[{type:"inputA",p:ve(n,"inputA"),label:"IN",color:d.green},{type:"left",p:ve(n,"left"),label:eo(o.left),color:to(o.left)},{type:"right",p:ve(n,"right"),label:eo(o.right),color:to(o.right)}];for(let i of r){if(!i.p)continue;let l=P(i.p.col,i.p.row),f=mr(t,n,i.type||(i.label==="IN"?"inputA":null))||t.conveyorTiles.has(F(i.p.col,i.p.row));e.fillStyle=f?i.color:"#f4c2c2",e.strokeStyle=d.ink,Q(e,l.x-18,l.y-12,36,24,!0,!0),e.fillStyle=f&&i.color===d.white?d.ink:d.white,e.fillText(i.label,l.x,l.y+4)}}e.restore()}function Al(e,t){for(let n of t.eggFarms)Dl(e,n,t)}function Dl(e,t,n){let o=P(t.col,t.row);if(e.save(),Et(e,We.eggFarm,o.x-29,o.y-29,58,58)){Qe(e,o.x-29,o.y-29,58,58,t);let l=1+Math.max(0,Number(t.extraEggOutlet)||0);e.fillStyle="rgba(255,255,255,.92)",e.strokeStyle=d.ink,e.lineWidth=2,Q(e,o.x+9,o.y+12,30,18,!0,!0),e.fillStyle=d.ink,e.font="900 10px ui-monospace, monospace",e.textAlign="center",e.fillText(`O${l}`,o.x+24,o.y+24),Oe(e,n,"eggFarm",t.id,o.x,o.y,68,68),e.restore();return}e.fillStyle=d.white,e.strokeStyle=d.ink,e.lineWidth=4,Q(e,o.x-28,o.y-28,56,56,!0,!0),e.fillStyle=d.green,e.font="900 19px ui-monospace, monospace",e.textAlign="center",e.fillText("EGG",o.x,o.y-4);let r=1+Math.max(0,Number(t.extraEggOutlet)||0);e.font="900 11px ui-monospace, monospace",e.fillStyle=d.ink,e.fillText(`L${t.level} O${r}`,o.x,o.y+13),e.fillText(`${t.nextSpawn.toFixed(1)}s`,o.x,o.y+26);let i=Math.max(0,Math.min(1,t.shutterProgress||0));if(i>0){let l=56*i;e.fillStyle=d.ink,Q(e,o.x-28,o.y-28,56,l,!0,!1),e.strokeStyle=d.white,e.lineWidth=2;for(let f=o.y-24;f=1&&(e.fillStyle=d.white,e.font="900 12px ui-monospace, monospace",e.fillText("SHUT",o.x,o.y+4))}Qe(e,o.x-28,o.y-28,56,56,t),Oe(e,n,"eggFarm",t.id,o.x,o.y,68,68),e.restore()}function Fl(e,t){for(let n of t.scanners)Il(e,n,t)}function Il(e,t,n){let o=le(t);if(e.save(),t.kind==="manual"&&(n.manualCombo?.count||0)>=10){let w=n.manualCombo.count;e.save(),e.globalAlpha=Math.min(.55,.16+w/120),e.strokeStyle=w>=30?d.warn:d.green,e.lineWidth=Math.min(14,5+Math.floor(w/10)),e.shadowColor=e.strokeStyle,e.shadowBlur=Math.min(28,8+w),Q(e,o.x-60,o.y-44,120,88,!1,!0),e.restore()}let r=t.kind==="auto"?We.scannerAuto:We.scannerManual;if(Et(e,r,o.x-54,o.y-38,108,76)){t.kind==="auto"?Qe(e,o.x-54,o.y-38,108,76,t):Ei(e,t,o),Oe(e,n,"scanner",t.id,o.x,o.y,116,84),e.restore();return}e.fillStyle=t.kind==="auto"?"#d8ffe2":d.white,e.strokeStyle=d.ink,e.lineWidth=4,Q(e,o.x-52,o.y-36,104,72,!0,!0),e.fillStyle=t.kind==="auto"?d.green:d.ink,e.font="900 19px ui-monospace, monospace",e.textAlign="center",e.fillText(`${t.kind==="auto"?"AUTO":`S${(t.slot??0)+1}`}`,o.x,o.y-14),e.font="900 11px ui-monospace, monospace";let i=t.keys?.left?.label||(t.slot===0?"A":"Left"),l=t.keys?.right?.label||(t.slot===0?"D":"Right");e.fillText(t.role===0?`${i}:M ${l}:NEXT`:`${i}:WASTE ${l}:TRUCK`,o.x,o.y+3);let f=t.queue.length;t.kind==="manual"?(Ei(e,t,o),e.fillStyle=f>0?d.green:d.muted,e.font="900 10px ui-monospace, monospace",e.fillText(`Q:${f}`,o.x,o.y+32)):(e.fillStyle=f>0?d.green:d.muted,e.font="900 12px ui-monospace, monospace",e.fillText(`Q:${f} CD:${t.cooldown.toFixed(1)}`,o.x,o.y+25),Qe(e,o.x-52,o.y-36,104,72,t)),Oe(e,n,"scanner",t.id,o.x,o.y,116,84),e.restore()}function Ei(e,t,n){if(t.kind!=="manual")return;let r=(t.slot??0)===0?[{side:"left",text:"A"},{side:"right",text:"D"}]:[{side:"left",text:"\u2190"},{side:"right",text:"\u2192"}];r[0].text=t.keys?.left?.label||r[0].text,r[1].text=t.keys?.right?.label||r[1].text;let i=t.keyPressTime>0?t.keyPressSide:null,l=n.x-30,f=n.y+8;e.save(),e.lineWidth=3,e.fillStyle="#f7fff5",e.strokeStyle=d.ink,Q(e,l-4,f-4,68,25,!0,!0),e.font="900 19px ui-monospace, monospace",e.textAlign="center";for(let w=0;w.8,i=t.y+Math.sin(t.bob)*(r?5:1.5);if(e.save(),n&&(e.strokeStyle=d.green,e.lineWidth=5,e.beginPath(),e.arc(t.x,i,t.radius+8,0,Math.PI*2),e.stroke()),r&&(e.strokeStyle=d.danger,e.lineWidth=3,e.beginPath(),e.arc(t.x,i,t.radius+12+Math.sin(t.bob*2)*4,0,Math.PI*2),e.stroke()),t.sex==="poop")Lo(e,t.x,i,t.radius);else{let l=t.sex==="male"?We.chickMale:We.chickFemale;Et(e,l,t.x-t.radius,i-t.radius,t.radius*2,t.radius*2)||(e.fillStyle=t.sex==="male"?d.male:d.female,e.strokeStyle=d.ink,e.lineWidth=5,e.beginPath(),e.arc(t.x,i,t.radius,0,Math.PI*2),e.fill(),e.stroke(),e.fillStyle=d.ink,e.beginPath(),e.arc(t.x-6,i-5,2.5,0,Math.PI*2),e.arc(t.x+6,i-5,2.5,0,Math.PI*2),e.fill(),e.fillStyle="#ffaa2e",e.beginPath(),e.moveTo(t.x,i+2),e.lineTo(t.x+9,i+6),e.lineTo(t.x,i+10),e.closePath(),e.fill())}e.restore()}function _l(e,t){let n=null,o=1/0;for(let[r,i]of e.componentLookup.entries()){let l=ee(r),f=P(l.col,l.row),w=Math.hypot(t.x-f.x,t.y-f.y);wPi(o.type,o.id)===Pi(t,n))}function Oe(e,t,n,o,r,i,l,f){Kl(t,n,o)&&(e.save(),e.fillStyle="rgba(34,185,79,.18)",e.strokeStyle=d.green,e.lineWidth=(t.multiSelected||[]).length>1?6:4,e.setLineDash((t.multiSelected||[]).length>1?[]:[8,5]),Q(e,r-l/2,i-f/2,l,f,!0,!0),e.restore())}function Hl(e,t,n,o){if(t.phase==="build"&&(t.multiSelected||[]).length>1){e.save(),e.fillStyle="rgba(255,255,255,.96)",e.strokeStyle=d.ink,e.lineWidth=3,Q(e,54-t.view.x,66-t.view.y,214,42,!0,!0),e.fillStyle=d.ink,e.font="900 11px ui-monospace, monospace",e.fillText(`${t.multiSelected.length} ITEMS SELECTED`,65-t.view.x,83-t.view.y),e.fillStyle=d.muted,e.font="900 11px ui-monospace, monospace",e.fillText("drag any selected item to move group",65-t.view.x,99-t.view.y),e.restore();return}}function ql(e,t,n){if(!n.cardTargetPick?.pending)return;let o=Po(n);e.save(),e.fillStyle="rgba(0,0,0,.66)",e.beginPath();let r=n.view?.scale||1;e.rect(-(n.view?.x||0)/r,-(n.view?.y||0)/r,t.width/r,t.height/r);for(let w of o){let M=w.bounds,R=8;e.rect(M.x-R,M.y-R,M.w+R*2,M.h+R*2)}e.fill("evenodd");let i=n.cardTargetPick?.mode==="blockedCell";if(!o.length){e.fillStyle="rgba(255,255,255,.95)",e.strokeStyle=d.ink,e.lineWidth=3,Q(e,(260-(n.view?.x||0))/r,(96-(n.view?.y||0))/r,420/r,42/r,!0,!0),e.fillStyle=d.danger,e.font="900 12px ui-monospace, monospace",e.textAlign="center",e.fillText(i?"NO BLOCKED CELL. PRESS ESC.":"NO VALID UPGRADE TARGET. PRESS ESC.",(470-(n.view?.x||0))/r,(122-(n.view?.y||0))/r),e.restore();return}e.fillStyle="rgba(255,255,255,.95)",e.strokeStyle=d.ink,e.lineWidth=3;let l=Math.max(1,n.cardTargetPick?.remaining||1),f=i?`CLICK BLOCKED CELL: ${l} LEFT. PRESS ESC TO CANCEL.`:"CLICK AN UPGRADEABLE MACHINE. PRESS ESC TO CANCEL.";Q(e,(216-(n.view?.x||0))/r,(96-(n.view?.y||0))/r,560/r,42/r,!0,!0),e.fillStyle=d.ink,e.font="900 12px ui-monospace, monospace",e.textAlign="center",e.fillText(f,(496-(n.view?.x||0))/r,(122-(n.view?.y||0))/r),e.restore()}function Yl(e,t){if(t.phase!=="build"||!t.selectionBox)return;let n=t.selectionBox,o=Math.min(n.x1,n.x2),r=Math.min(n.y1,n.y2),i=Math.abs(n.x2-n.x1),l=Math.abs(n.y2-n.y1);e.save(),e.fillStyle="rgba(34,185,79,.12)",e.strokeStyle=d.green,e.lineWidth=2,e.setLineDash([7,4]),Q(e,o,r,i,l,!0,!0),e.restore()}function zl(e,t){let n=[...t.effects].sort((o,r)=>(o.priority??we.ambient)-(r.priority??we.ambient));for(let o of n){let r=Math.max(0,o.life/o.maxLife);e.save(),e.globalAlpha=r,o.type==="meat"?(e.fillStyle=d.green,Q(e,o.x-o.size/2,o.y-o.size/2,o.size*1.5,o.size,!0,!1)):o.type==="sludge"?(e.fillStyle=d.poop,Q(e,o.x-o.size/2,o.y-o.size/2,o.size*1.4,o.size,!0,!1)):o.type==="spawn"?(e.strokeStyle=d.green,e.lineWidth=3,e.beginPath(),e.arc(o.x,o.y,o.size+(1-r)*20,0,Math.PI*2),e.stroke()):o.type==="spark"?(e.fillStyle=o.color||d.danger,Q(e,o.x-o.size/2,o.y-o.size/2,o.size,o.size,!0,!1)):o.type==="smoke"?(e.fillStyle="rgba(16,32,21,.24)",e.beginPath(),e.arc(o.x,o.y,o.size*(1.2-r*.2),0,Math.PI*2),e.fill()):o.type==="shockwave"?(e.strokeStyle=o.color||d.danger,e.lineWidth=4,e.beginPath(),e.arc(o.x,o.y,o.size+(1-r)*(o.maxSize||44),0,Math.PI*2),e.stroke()):o.type==="scannerPulse"?(e.strokeStyle=o.color||d.green,e.lineWidth=5,e.setLineDash([8,6]),e.beginPath(),e.arc(o.x,o.y,o.size+(1-r)*38,0,Math.PI*2),e.stroke()):o.type==="shred"?(e.fillStyle=o.color||d.ink,e.save(),e.translate(o.x,o.y),e.rotate(o.rot||0),Q(e,-o.w/2,-o.h/2,o.w,o.h,!0,!1),e.restore()):o.type==="load"?(e.strokeStyle=o.color||d.green,e.lineWidth=3,e.beginPath(),e.moveTo(o.x-14,o.y),e.lineTo(o.x+14,o.y),e.moveTo(o.x,o.y-14),e.lineTo(o.x,o.y+14),e.stroke()):o.type==="erase"?(e.strokeStyle=d.ink,e.lineWidth=3,e.setLineDash([4,4]),Q(e,o.x-o.size/2,o.y-o.size/2,o.size,o.size,!1,!0)):o.type==="rage"?(e.fillStyle=o.color||d.danger,e.font="900 16px ui-monospace, monospace",e.textAlign="center",e.fillText("!",o.x,o.y)):o.type==="flyingChick"&&(o.sex==="poop"?Lo(e,o.x,o.y,o.radius||11):Bo(e,o.x,o.y,o.sex,o.sex==="male")),e.restore()}}function Vl(e,t){e.save(),e.font="900 19px ui-monospace, monospace",e.textAlign="center";for(let n of t.floatingTexts){let o=Math.max(0,n.life/n.maxLife);e.globalAlpha=o,e.lineWidth=5,e.strokeStyle=d.white,e.fillStyle=n.color,e.strokeText(n.text,n.x,n.y),e.fillText(n.text,n.x,n.y)}e.restore()}function Xl(e,t){let n=t.repairman;if(!(!!n?.active||t.phase==="build"&&!!n?.hiredForNextDay))return;e.save(),e.lineWidth=3,e.fillStyle="#fff0d1",e.strokeStyle=d.ink,e.beginPath(),e.arc(n.x,n.y,17,0,Math.PI*2),e.fill(),e.stroke(),e.fillStyle="#ffd6e7",e.beginPath(),e.arc(n.x-6,n.y-2,3.2,0,Math.PI*2),e.fill(),e.beginPath(),e.arc(n.x+6,n.y-2,3.2,0,Math.PI*2),e.fill();let r=!!t.cardEffects?.laborExploitation;e.fillStyle=r?d.danger:d.ink,e.beginPath(),e.arc(n.x-5,n.y-5,r?2.8:2.2,0,Math.PI*2),e.fill(),e.beginPath(),e.arc(n.x+5,n.y-5,r?2.8:2.2,0,Math.PI*2),e.fill(),e.strokeStyle=d.ink,e.lineWidth=r?3:2,e.beginPath(),e.arc(n.x,n.y+(r?0:1),r?8:7,.1*Math.PI,.9*Math.PI),e.stroke(),e.fillStyle=d.green,Q(e,n.x-9,n.y+14,18,8,!0,!0),e.strokeStyle=d.ink,e.lineWidth=3,e.beginPath(),e.moveTo(n.x-11,n.y+18),e.lineTo(n.x-20,n.y+24),e.stroke(),e.beginPath(),e.moveTo(n.x+11,n.y+18),e.lineTo(n.x+20,n.y+24),e.stroke(),e.fillStyle=d.ink,e.font="900 11px ui-monospace, monospace",e.textAlign="center",e.fillText(n.active?"FIX":"HIRED",n.x,n.y+33),n.target?.label?Ao(e,n.x,n.y-24,n.target.label,d.green):!n.active&&n.hiredForNextDay&&Ao(e,n.x,n.y-24,"NEXT DAY",d.green),e.restore()}function Jl(e,t,n){e.save(),e.font="900 19px ui-monospace, monospace",e.textAlign="left",e.fillStyle=d.ink,e.fillStyle="rgba(255,255,255,.92)",e.strokeStyle=d.ink,e.lineWidth=3,Q(e,n.width-284,18,260,34,!0,!0),e.fillStyle=d.green,e.font="900 12px ui-monospace, monospace",e.fillText(ln,n.width-270,40),e.font="900 19px ui-monospace, monospace",e.fillStyle=d.ink;let o=[];t.phase==="running"&&o.push(t.timeLeft<=0?"TIME UP: FARMS SHUT. CLEAR LINE.":"A/D: S1 \u2190/\u2192: S2"),t.phase==="build"&&o.push(`BUILD: ${t.buildTool||"SELECT"} | connect 1 EGG route to any exit`);let r=76;for(let i of o)e.strokeStyle=d.white,e.lineWidth=4,e.strokeText(i,24,r),e.fillText(i,24,r),r+=20;e.restore()}var Di=[[{key:"a",code:"KeyA",label:"A"},{key:"d",code:"KeyD",label:"D"}],[{key:"arrowleft",code:"ArrowLeft",label:"Left"},{key:"arrowright",code:"ArrowRight",label:"Right"}],[{key:"j",code:"KeyJ",label:"J"},{key:"l",code:"KeyL",label:"L"}],[{key:"f",code:"KeyF",label:"F"},{key:"h",code:"KeyH",label:"H"}],[{key:"q",code:"KeyQ",label:"Q"},{key:"e",code:"KeyE",label:"E"}],[{key:"z",code:"KeyZ",label:"Z"},{key:"x",code:"KeyX",label:"X"}],[{key:"u",code:"KeyU",label:"U"},{key:"o",code:"KeyO",label:"O"}],[{key:"n",code:"KeyN",label:"N"},{key:"m",code:"KeyM",label:"M"}]];function Ql(e){let t=Di[e]||Di[0];return{left:{...t[0]},right:{...t[1]}}}function Fi(e,t,n){let o={type:"eggFarm",id:e.nextId++,col:t,row:n,level:1,nextSpawn:2,lastInterval:2,extraEggOutlet:0,price:kt.eggFarm,builtSession:e.buildSession};return o.nextSpawn=St(o),o.lastInterval=o.nextSpawn,o}function Ii(e,t,n,o){let r=o==="auto"?pe.autoScanner.price:pe.manualScanner.price,i=e.scanners.filter(l=>l.kind==="manual").length;return{type:"scanner",id:e.nextId++,kind:o,slot:o==="manual"?i:null,role:i%2,col:t,row:n,level:1,queue:[],cooldown:0,price:r,builtSession:e.buildSession,autoMode:"standard",keys:o==="manual"?Ql(i):null}}function An(e){let t=(e.x-y.x)/y.cell,n=(e.y-y.y)/y.cell,o=Math.max(0,Math.min(y.cols-1,Math.round(t-.5))),r=Math.max(0,Math.min(y.rows-1,Math.round(n-.5))),i=Math.abs(e.x-y.x),l=Math.abs(e.x-(y.x+y.cols*y.cell)),f=Math.abs(e.y-y.y),w=Math.abs(e.y-(y.y+y.rows*y.cell)),M=[["left",i],["right",l],["top",f],["bottom",w]].sort((R,D)=>R[1]-D[1])[0][0];return M==="left"?{side:M,entry:{col:0,row:r}}:M==="right"?{side:M,entry:{col:y.cols-1,row:r}}:M==="top"?{side:M,entry:{col:o,row:0}}:{side:M,entry:{col:o,row:y.rows-1}}}function Dn(e,t,n){let o={x:y.x+t.col*y.cell+y.cell/2,y:y.y+t.row*y.cell+y.cell/2},r={left:y.x,right:y.x+y.cols*y.cell,top:y.y,bottom:y.y+y.rows*y.cell};return e.entry={...t},e.side=n,n==="left"?(e.x=r.left-e.w-18,e.y=o.y-e.h/2):n==="right"?(e.x=r.right+18,e.y=o.y-e.h/2):n==="top"?(e.x=o.x-e.w/2,e.y=r.top-e.h-18):(e.x=o.x-e.w/2,e.y=r.bottom+18),e}function Li(e,t,n,o){let{entry:r,side:i}=An(n),l=pe[t],f=l?{name:l.name,w:l.body?.w||168,h:l.body?.h||110,price:l.price}:{name:t[0].toUpperCase()+t.slice(1),w:168,h:110,price:o},w={type:"facility",id:t,name:f.name,x:0,y:0,w:f.w,h:f.h,level:1,price:o||f.price,builtSession:e.buildSession};return Dn(w,r,i)}function Zl(e){return Math.random()<$r(e)?"poop":Math.random()<.5?"male":"female"}function Bi(e,t){let n=Zl(e),o=(t.route||[]).map(i=>({x:i.x,y:i.y})),r=o[0];return{id:e.nextId++,sex:n,x:r.x,y:r.y,route:o,targetIndex:o.length>1?1:o.length,stage:t.stage||"belt",scannerId:t.scannerId??null,nextScannerId:null,pendingScannerId:null,radius:n==="poop"?15:17,bob:Math.random()*Math.PI*2,queueIndex:-1,queueRoute:null}}function Oi({game:e,canvasPoint:t,updatePanels:n,equipmentAtPoint:o,fail:r,pointInGrid:i,rectOfFacility:l,rectsOverlap:f}){function w(x){return x?x.type==="conveyor"?`conveyor:${x.id||x.oldKey}`:x.type==="facility"?`facility:${x.id||x.ref?.id}`:`${x.type}:${x.id||x.ref?.id}`:""}function M(x){return x?x.type==="conveyor"?{type:"conveyor",id:x.oldKey}:{type:x.type,id:x.ref.id}:null}function R(x){let m=w(M(x));return(e.multiSelected||[]).some(U=>w(U)===m)}function D(x){return x?x.type==="eggFarm"?e.eggFarms.find(m=>m.id===x.id)||null:x.type==="scanner"?e.scanners.find(m=>m.id===x.id)||null:x.type==="facility"?e.facilities[x.id]||null:x.type==="conveyor"&&e.conveyorTiles.has(x.id)?{type:"conveyor",id:x.id}:null:null}function I(){let x=[];for(let m of e.conveyorTiles){let U=ee(m),K=P(U.col,U.row);x.push({type:"conveyor",id:m,center:K,cell:U})}for(let m of e.eggFarms)x.push({type:"eggFarm",id:m.id,ref:m,center:P(m.col,m.row),cell:{col:m.col,row:m.row}});for(let m of e.scanners)x.push({type:"scanner",id:m.id,ref:m,center:le(m),cell:{col:m.col,row:m.row}});for(let m of Object.values(e.facilities)){let U={x:m.x+m.w/2,y:m.y+m.h/2},K=m.entry?P(m.entry.col,m.entry.row):U;x.push({type:"facility",id:m.id,ref:m,center:U,portCenter:K})}return x}function j(x,m){let U=Math.min(m.x1,m.x2),K=Math.max(m.x1,m.x2),J=Math.min(m.y1,m.y2),oe=Math.max(m.y1,m.y2);return x.x>=U&&x.x<=K&&x.y>=J&&x.y<=oe}function se(x){let m=[];for(let U of I())(j(U.center,x)||U.portCenter&&j(U.portCenter,x))&&m.push({type:U.type,id:U.id});return e.multiSelected=m,e.selected=m[0]||null,n(),m.length}function z(){e.multiSelected=[],e.selected=null,n()}function X(x){let m=t(x);return e.selectionBox={x1:m.x,y1:m.y,x2:m.x,y2:m.y},e.multiSelected=[],e.selected=null,n(),!0}function ue(x){if(!e.selectionBox)return;let m=t(x);e.selectionBox.x2=m.x,e.selectionBox.y2=m.y}function ie(){if(!e.selectionBox)return 0;let x=e.selectionBox,m=Math.abs(x.x2-x.x1),U=Math.abs(x.y2-x.y1);return e.selectionBox=null,m<6&&U<6?(z(),0):se(x)}function g(x){let m=M(x);return m?(e.multiSelected||[]).length&&R(x)?[...e.multiSelected]:[m]:[]}function W(x){let m=D(x);if(!m)return null;if(x.type==="conveyor"){let U=ee(x.id);return{...x,obj:m,oldKey:x.id,currentKey:x.id,meta:e.conveyorMeta.get(x.id)||{price:Re("conveyor"),builtSession:null},col:U.col,row:U.row}}return x.type==="eggFarm"||x.type==="scanner"?{...x,obj:m,col:m.col,row:m.row}:x.type==="facility"?{...x,obj:m,x:m.x,y:m.y,w:m.w,h:m.h,entry:m.entry?{...m.entry}:null,side:m.side,center:{x:m.x+m.w/2,y:m.y+m.h/2}}:null}function _(x){let m=t(x),U=o(m);if(!U)return!1;let K=g(U),J=K.map(W).filter(Boolean);return J.length?(e.groupDrag={start:m,selections:K,origins:J,historySnapshot:tn(e),committed:!1,lastDCol:0,lastDRow:0},e.multiSelected=K,e.selected=K[0]||null,n(),!0):!1}function ae(){!e.groupDrag||e.groupDrag.committed||(e.undoStack.push(e.groupDrag.historySnapshot),e.redoStack=[],e.groupDrag.committed=!0)}function Z(x,m,U){let K=F(x,m),J=Ot(e,x,m);if(J&&!U.has(`eggFarm:${J.id}`))return!0;let oe=Nt(e,x,m);return!!(oe&&!U.has(`scanner:${oe.id}`)||e.conveyorTiles.has(K)&&!U.has(`conveyor:${K}`))}function me(x,m,U){let K=[];for(let J of e.groupDrag.origins){if(J.type!=="facility")continue;let oe={x:J.center.x+x,y:J.center.y+m},{entry:ye,side:Ne}=An(oe);if(!i(ye.col,ye.row)||e.blockedCells?.has?.(F(ye.col,ye.row)))return!0;let _e={...J.obj,entry:{...ye},side:Ne};Dn(_e,ye,Ne),K.push({id:J.id,rect:l(_e)})}for(let J of K)for(let oe of Object.values(e.facilities))if(!U.has(`facility:${oe.id}`)&&f(J.rect,l(oe),12))return!0;for(let J=0;JV.type==="conveyor");for(let V of _e){let xe=V.currentKey||V.oldKey;e.conveyorTiles.delete(xe),e.conveyorMeta.delete(xe)}for(let V of _e){let xe=F(V.col+J,V.row+oe);e.conveyorTiles.add(xe),e.conveyorMeta.set(xe,V.meta||{price:Re("conveyor"),builtSession:null,uses:0}),V.currentKey=xe}for(let V of e.groupDrag.origins)if(V.type==="eggFarm")V.obj.col=V.col+J,V.obj.row=V.row+oe;else if(V.type==="scanner")V.obj.col=V.col+J,V.obj.row=V.row+oe;else if(V.type==="facility"){let xe={x:V.center.x+U,y:V.center.y+K},{entry:De,side:je}=An(xe);Dn(V.obj,De,je)}e.groupDrag.selections=e.groupDrag.origins.map(V=>V.type==="conveyor"?{type:"conveyor",id:V.currentKey||V.oldKey}:{type:V.type,id:V.id}),e.multiSelected=[...e.groupDrag.selections],e.selected=e.multiSelected[0]||null,he(e),n()}function A(){e.groupDrag&&(he(e),e.groupDrag=null,n())}return{selectionFromHit:M,startSelectionBox:X,updateSelectionBox:ue,finishSelectionBox:ie,startGroupDrag:_,updateGroupDrag:E,finishGroupDrag:A}}function Ni({game:e,ui:t,canvas:n,canvasPoint:o,onUpdatePanels:r}){function i(){r&&r()}let l=null,f=!1;function w(){return pn(e)}function M(){e.gridCols=y.cols,e.gridRows=y.rows,e.gridOwnedCells=or(e)}function R(s){if(s){y.y-=s*y.cell,y.rows+=s,e.gridChunkOriginY=(e.gridChunkOriginY||0)-Math.floor(s/10),e.ownedCells=yn(e.ownedCells,s),e.blockedCells=yn(e.blockedCells,s),e.conveyorTiles=yn(e.conveyorTiles,s),e.conveyorMeta=lr(e.conveyorMeta,s);for(let u of e.eggFarms||[])u.row+=s;for(let u of e.scanners||[])u.row+=s;for(let u of Object.values(e.facilities||{}))u.entry&&(u.entry.row+=s)}}function D(s){e.ownedCells=e.ownedCells||new Set;for(let u of sr(s))e.ownedCells.add(F(u.col,u.row))}function I(s){return e.phase!=="build"||e.cardDraft?.pending||e.cardTargetPick?.pending?null:vt(e).find(u=>u&&ir(u,s))||null}function j(s="right"){if(s&&typeof s=="object"&&Number.isFinite(s.chunkX)&&Number.isFinite(s.chunkY))return s;let u=["right","up"].includes(s)?s:"right";return rr(e,u)}function se(s="right"){let u=j(s);if(e.phase!=="build"||e.cardDraft?.pending||e.cardTargetPick?.pending)return{ok:!1,reason:"Cannot expand now."};if(!u)return{ok:!1,reason:"Invalid expansion lot."};let b=u.direction||"right",k=w();if(e.cashy.cols&&(y.cols=B.colStart+B.cols),B.rowStart+B.rows>y.rows&&(y.rows=B.rowStart+B.rows),D(B),ar(e,B.chunkX,B.chunkY),e.gridExpansionPurchases=(e.gridExpansionPurchases||0)+1,e.gridExpansionPurchasesByDirection[b]=(e.gridExpansionPurchasesByDirection[b]||0)+1,M();let ce=Zo({seed:`${e.runSeed}:grid-expansion:${B.chunkX},${B.chunkY}:${e.gridExpansionPurchases}`,colStart:B.colStart,colEnd:B.colStart+B.cols,rowStart:B.rowStart,rowEnd:B.rowStart+B.rows,targetRatio:H.map.blockedRatio});for(let $t of ce)e.blockedCells.add($t);he(e);let Ke=B.cols*B.rows,He=Ct(B);return G(e,He.x+He.w/2,He.y+He.h/2,`${b.toUpperCase()} +${Ke} CELLS -${S(k)}`,d.green),i(),{ok:!0,direction:b,cost:k,addedCells:Ke,blocked:ce.size,chunkX:B.chunkX,chunkY:B.chunkY}}function z(s,u){return s>=0&&s=0&&u!u.has(k.id)&&ie(b,ue(k),12))}function W(s,u,b=null){if(!z(s,u))return!1;if(X(s,u))return!0;let k=F(s,u),$=Ot(e,s,u);if($&&!(b?.type==="eggFarm"&&b.ref?.id===$.id))return!0;let B=Nt(e,s,u);return!!(B&&!(b?.type==="scanner"&&b.ref?.id===B.id)||e.conveyorTiles.has(k)&&!(b?.type==="conveyor"&&b.oldKey===k))}function _(){return e.selected?e.selected.type==="eggFarm"?e.eggFarms.find(s=>s.id===e.selected.id)||null:e.selected.type==="scanner"?e.scanners.find(s=>s.id===e.selected.id)||null:e.selected.type==="conveyor"?e.conveyorTiles.has(e.selected.id)?{type:"conveyor",id:e.selected.id}:null:e.selected.type==="facility"&&e.facilities[e.selected.id]||null:null}function ae(s){return s?s.type==="eggFarm"?`${Ft("eggFarm")} #${s.id}`:s.type==="scanner"?`${s.kind==="auto"?Ft("autoScanner"):Ft("manualScanner")} #${s.id}`:s.type==="conveyor"?Ft("conveyor"):s.type==="facility"?s.name:"Equipment":"Equipment"}function Z(s){return io(s)}function me(){return!!e.cardEffects?.usedMachineActive}function E(){return me()?{usedMachine:!0,noRefund:!0,durabilityBaseMultiplier:.6}:{usedMachine:!1,noRefund:!1,durabilityBaseMultiplier:1}}function A(s){let u=E();return Object.assign(s,u),s}function x(s){let u=s?.ref||s||{};return s?.type==="eggFarm"||u.type==="eggFarm"?e.eggFarms.length<=1?"Cannot sell the last EGG FARM.":"":(s?.type==="facility"||u.type==="facility")&&["mixer","trash","truck"].includes(u.id)&&Object.values(e.facilities).filter(k=>k.id===u.id).length<=1?`Cannot sell the last ${u.shortName||u.name||u.id}.`:""}function m(s){t.buildStatus.textContent=s,G(e,n.width/2-e.view.x,86-e.view.y,s.toUpperCase(),d.danger)}function U(s){if(!s)return null;let u=F(s.col,s.row);return e.conveyorMeta.get(u)||null}function K(s){return s?(Array.isArray(s.outDirs)||(s.outDirs=[]),s.outDirs=[...new Set(s.outDirs.filter(u=>["right","left","down","up"].includes(u)))],s.outDirs):[]}function J(s,u){let b=Xt(s,u);if(!b)return!1;let k=F(s.col,s.row),$=F(u.col,u.row),B=e.conveyorMeta.get(k),ce=e.conveyorMeta.get($);if(!B||!ce)return!1;let Ke=K(B),He=!Ke.includes(b)||B.dir!==b||!ce.dir;return Ke.includes(b)||Ke.push(b),B.dir=b,ce.dir||(ce.dir=b),He}function oe(s,u=null){if(!s)return{ok:!1,reason:te.fail.outOfGrid};let{col:b,row:k}=s,$=F(b,k);if(e.conveyorTiles.has($))return{ok:!1,exists:!0};if(X(b,k))return{ok:!1,reason:"Cannot build on blocked ground."};if(Ot(e,b,k)||Nt(e,b,k))return{ok:!1,reason:te.fail.cellOccupied};let B=Re("conveyor",e);if(e.cash=Math.abs(k)?{col:s.col+Math.sign(b),row:s.row}:{col:s.col,row:s.row+Math.sign(k)}}function _e(s){f=!1,l=null,ye(s)}function V(s){if(!s)return;if(!l)return ye(s);let u=l,b=0;for(;(u.col!==s.col||u.row!==s.row)&&b<64;){let k=Ne(u,s);if(ye(k),u=l||k,b+=1,!l)break}}function xe(){l=null,f=!1}function De(s){if(!s)return m(te.fail.outOfGrid);let{col:u,row:b}=s;if(e.buildTool==="conveyor"){let k=oe(s,null);if(!k.ok)return m(k.reason||te.fail.cellOccupied);he(e);return}if(X(u,b))return m("Cannot build on blocked ground.");if(W(u,b))return m(te.fail.cellOccupied);e.buildTool==="eggFarm"?rn(u,b):e.buildTool==="manualScanner"?ht(u,b,"manual"):e.buildTool==="autoScanner"&&ht(u,b,"auto")}function je(s){let u=$e(s.x,s.y);if(["conveyor","eggFarm","manualScanner","autoScanner"].includes(e.buildTool))return De(u);if(ot.includes(e.buildTool))return On(s,e.buildTool)}function rn(s,u){let b=Re("eggFarm",e);if(e.cashB.kind==="manual").length>=8)return m("Manual Scanner limit reached (8 max).");let k=b==="auto"?Re("autoScanner",e):Re("manualScanner",e);if(e.cash$.id!==u.ref.id),dt(e,k.x,k.y),he(e)}if(u.type==="scanner"){let k=le(u.ref);Pt(u,k.x,k.y),e.scanners=e.scanners.filter($=>$.id!==u.ref.id),dt(e,k.x,k.y),he(e)}if(u.type==="facility"){let k=u.ref;Pt(u,k.x+k.w/2,k.y+k.h/2),delete e.facilities[k.id],dt(e,k.x+k.w/2,k.y+k.h/2),he(e)}e.selected=null,e.multiSelected=[],i()}function et(s){let u=Object.values(e.facilities).find(ce=>{if(s.x>=ce.x&&s.x<=ce.x+ce.w&&s.y>=ce.y&&s.y<=ce.y+ce.h)return!0;if(!ce.entry)return!1;let He=P(ce.entry.col,ce.entry.row);return Math.abs(s.x-He.x)<=y.cell*.46&&Math.abs(s.y-He.y)<=y.cell*.46});if(u)return{type:"facility",ref:u};let b=$e(s.x,s.y);if(!b)return null;let k=Ot(e,b.col,b.row);if(k)return{type:"eggFarm",ref:k};let $=Nt(e,b.col,b.row);if($)return{type:"scanner",ref:$};let B=F(b.col,b.row);return e.conveyorTiles.has(B)?{type:"conveyor",oldKey:B,ref:{type:"conveyor",id:B}}:null}let tt=Oi({game:e,canvasPoint:o,updatePanels:i,equipmentAtPoint:et,fail:m,pointInGrid:z,rectOfFacility:ue,rectsOverlap:ie});function sn(s){return null}function Rt(){let s=_();s&&(s.type==="conveyor"?mt(P(...Object.values(ee(s.id)))):s.type==="eggFarm"?mt(P(s.col,s.row)):s.type==="scanner"?mt(le(s)):s.type==="facility"&&mt({x:s.x+s.w/2,y:s.y+s.h/2}))}function Fe(s,u,b="primary-button"){let k=document.createElement("button");return k.type="button",k.className=b,k.textContent=s,k.addEventListener("click",u),k}function xt(){t.modal.classList.remove("visible","equipment-popover"),t.modal.style.removeProperty("--popover-x"),t.modal.style.removeProperty("--popover-y")}function Yt(s){t.modalTitle.textContent="Auto Scanner",t.modalBody.innerHTML=`

Standard auto scanner.

  • Role 0: male left, others right.
  • Role 1: poop left, others right.
  • Cooldown: ${Je(s).toFixed(1)}s.
`,t.modalActions.innerHTML="";let u=Fe("Set Role 0",()=>{Le(e),s.role=0,xt(),i()}),b=Fe("Set Role 1",()=>{Le(e),s.role=1,xt(),i()});t.modalActions.append(u,b),Te(s),t.modal.classList.add("visible","equipment-popover")}function c(s){return s?.label||s?.key||"?"}function p(s){let u=s.key===" "?"Space":s.key.length===1?s.key.toUpperCase():s.key.replace(/^Arrow/,"");return{key:s.key.toLowerCase(),code:s.code,label:u}}function h(s,u,b){for(let k of e.scanners.filter($=>$.kind==="manual"))for(let $ of["left","right"]){if(k.id===s.id&&$===u)continue;let B=k.keys?.[$];if(B&&(B.code===b.code||B.key===b.key))return!0}return!1}function v(s,u=""){if(!s||s.kind!=="manual")return;t.modalTitle.textContent=`Manual Scanner #${s.id}`;let b=c(s.keys?.left),k=c(s.keys?.right),$=C(s);t.modalBody.innerHTML=` +var H={version:"v27.6 direct conveyor chick movement",time:{daySeconds:60,farmShutdownGraceSeconds:10},cash:{starting:250},grid:{x:170,y:78,cols:20,rows:10,cell:46,expansion:{colsPerPurchase:10,rowsPerPurchase:10,costBase:1e3,costMultiplier:1.3333333333333333,truncateUnit:100}},map:{blockedRatio:.075},economy:{income:{mixer:5,truck:10},poopFine:10,zundaTax:{exemption:500,legalWorkExemptionBonus:250,maxRateCash:1e4,stepAmount:500,stepRate:.05,maxRate:.95},fairiesTribute:{perDay:10},incomeUpgradeRate:1.05,explosionDamageDivisor:30,maleTruckFinePerHalfDay:30,shredderBonus:{maxCards:30,probabilityPercentDivisor:2}},production:{poopRate:.08,autoScannerCooldown:3.5,autoScannerUpgradeRate:.95,autoScannerMinCooldown:.5,eggSpawnRanges:[[1.95,4.35],[1.55,3.45],[1.25,2.8],[1.05,2.35]]},conveyor:{baseSpeed:52,maxSpeed:300,bearingSpeedMultiplier:1.075},cards:{definitions:[{id:"upgradeEgg",title:"EGG Improvement",rarity:"common",type:"equipmentUpgrade",target:"eggFarm",description:"Choose one EGG FARM on the map and raise it by 1 level.",tags:["EGG","ACTIVE"]},{id:"upgradeAutoScanner",title:"AUTO SCANNER Improvement",rarity:"common",type:"equipmentUpgrade",target:"autoScanner",description:"Choose one AUTO SCANNER on the map and raise it by 1 level.",tags:["SCANNER","ACTIVE"]},{id:"upgradeMixer",title:"MIXER Improvement",rarity:"common",type:"equipmentUpgrade",target:"mixer",description:"Choose MIXER on the map and raise it by 1 level.",tags:["ECONOMY","ACTIVE"]},{id:"upgradeTruck",title:"TRUCK Improvement",rarity:"common",type:"equipmentUpgrade",target:"truck",description:"Choose TRUCK on the map and raise it by 1 level.",tags:["ECONOMY","ACTIVE"]},{id:"upgradeTrash",title:"SHREDDER Improvement",rarity:"common",type:"equipmentUpgrade",target:"trash",description:"Shred poops for a little money. Max 30 upgrades.",tags:["POOP","ACTIVE"]},{id:"crowdedFarming",title:"Crowded Farming Contract",rarity:"common",type:"instant",description:"EGG production interval -5%. Equipment deterioration +4%. Max 10 cards.",tags:["EGG","RISK","PASSIVE"]},{id:"hatchingFeed",title:"Hatching Feed",rarity:"common",type:"instant",description:"EGG production interval -4%. Poop rate +3%. No card limit.",tags:["EGG","POOP","PASSIVE"]},{id:"safetyCover",title:"Safety Cover",rarity:"common",type:"instant",description:"Explosion damage -5%. No card limit.",tags:["RISK","PASSIVE"]},{id:"preventiveMaintenance",title:"Preventive Maintenance Manual",rarity:"common",type:"instant",description:"All equipment deterioration speed -5%. No card limit.",tags:["MAINTENANCE","PASSIVE"]},{id:"dudFilter",title:"DUD Filter",rarity:"common",type:"instant",description:"DUD chance -5 percentage points. Stacks until DUD chance reaches 0%.",tags:["CARD","PASSIVE"]},{id:"durabilityCoating",title:"Durability Coating",rarity:"common",type:"instant",description:"All equipment maximum durability +3%. No card limit.",tags:["MAINTENANCE","PASSIVE"]},{id:"bearing",title:"High-Quality Bearing",rarity:"common",type:"instant",description:"Conveyor speed +7.5%.",tags:["CONVEYOR","PASSIVE"]},{id:"sparePartsBin",title:"SPARE PARTS BIN",rarity:"common",type:"instant",description:"Restores 2% durability to every machine. A box of almost-compatible parts.",tags:["MAINTENANCE","ACTIVE"],weightBase:100,weightLossPerCopy:2},{id:"dudRefund",title:"DUD REFUND",rarity:"common",type:"instant",description:"DUDs now come with paperwork. Gain JPY 50 per copy when you pick a DUD.",tags:["CARD","ECONOMY","PASSIVE"]},{id:"freeReroll",title:"FREE REROLL",rarity:"common",type:"instant",description:"The first bad ideas are free.",tags:["CARD","PASSIVE"]},{id:"composter",title:"COMPOSTER",rarity:"common",type:"instant",description:"Turns shredder waste into suspicious fertilizer. Gain JPY 3 per copy when SHREDDER processes poop.",tags:["POOP","ECONOMY","PASSIVE"]},{id:"extraEggOutlet",title:"Extra Egg Outlet",rarity:"rare",type:"equipmentUpgrade",target:"eggOutlet",description:"Choose one EGG FARM and add +1 output port to that EGG. Max +3 per EGG.",tags:["EGG","RARE","ACTIVE"]},{id:"recyclingSubsidy",title:"Recycling Subsidy",rarity:"rare",type:"instant",description:"SELL refund rate +10 points. Max 3 cards; normal SELL can reach 80%.",tags:["ECONOMY","RARE","PASSIVE"]},{id:"dynamite",title:"Dynamite",rarity:"rare",type:"cellAction",target:"blockedCell",description:"Remove any 3 unbuildable cells",tags:["RISK","RARE","ONE-SHOT"]},{id:"usedMachine",title:"Used Machines",rarity:"rare",type:"instant",description:"Future equipment costs half. New purchases have no refund and 60% durability. New Machines cancels it.",tags:["ECONOMY","RISK","RARE"]},{id:"newMachine",title:"New Machines",rarity:"common",type:"instant",description:"Cancel Used Machines and restore normal prices, refunds, and durability.",tags:["ECONOMY","ONE-SHOT"]},{id:"flattery",title:"Flattery",rarity:"rare",type:"instant",description:"Next Fairies tribute -50%.",tags:["ECONOMY","RARE","ONE-SHOT"]},{id:"loan",title:"Loan",rarity:"rare",type:"instant",description:"Gain JPY 300 per elapsed day now. Pay JPY 400 per day for that many days starting tomorrow.",tags:["ECONOMY","RISK","RARE"]},{id:"extraCards",title:"Extra Cards",rarity:"rare",type:"instant",description:"Draw 2 cards now and choose both.",tags:["RARE","ACTIVE"]},{id:"legalWork",title:"Legal Work",rarity:"rare",type:"instant",description:"ZUNDA TAX exemption +\uFFE5250. The 95% cap point also moves up by \uFFE5250.",tags:["ECONOMY","RARE","PASSIVE"]},{id:"chemicalWeaponSubsidy",title:"Chemical Weapons Subsidy",rarity:"ultraRare",type:"instant",description:"Each day, gain JPY 2000 per 100 poop shipped by truck. No card limit.",tags:["POOP","ULTRA RARE","PASSIVE"]},{id:"laborExploitation",title:"Motivational Exploitation",rarity:"ultraRare",type:"instant",description:"Repairman labor cost -30%. The repairman smiles with bloodshot eyes. One time only.",tags:["MAINTENANCE","ULTRA RARE","PASSIVE"]},{id:"rescueLoan",title:"Emergency Loan",rarity:"ultraRare",type:"instant",description:"If cash is below JPY 0 at day-end settlement, gain JPY 500. Consumed on use.",tags:["ECONOMY","ULTRA RARE","ONE-SHOT"]}],defaultEffects:{bearing:0,legalWork:0,fairiesFlatteryNext:0,crowdedFarming:0,extraEggOutlet:0,hatchingFeed:0,safetyCover:0,recyclingSubsidy:0,chemicalWeaponSubsidy:0,preventiveMaintenance:0,dudFilter:0,durabilityCoating:0,sparePartsBin:0,dudRefund:0,freeReroll:0,composter:0,laborExploitation:0,usedMachineActive:!1,rescueLoanCharges:0,loans:[]},commonWeight:8,rareWeight:2,ultraRareWeight:.45,baseDraftSize:3,maxDudsPerDraft:2,dudChancePerCard:.3,dudChanceReductionPerFilter:.05,extraCardsAddedPicks:2,rerollCost:{dayDivisor:2,multiplier:100}},contracts:{eventChance:.7,firstTurn:8},maintenance:{fullPerformanceUntilWear:.5,minimumPerformance:.5,durability:{conveyor:1300,eggFarm:475,autoScanner:850,mixer:775,trash:950},processingDelayMaxSeconds:{mixer:1.2,trash:1},repairman:{dailyCost:100,secondsPerOnePercent:.5,walkSpeed:90}},facilities:{conveyor:{id:"conveyor",type:"conveyor",name:"Conveyor",shortName:"Belt",price:30,buildable:!0,upgradeable:!1},boostConveyor:{id:"boostConveyor",type:"conveyor",name:"Boost Conveyor",shortName:"BOOST BELT",price:50,buildable:!0,upgradeable:!1,speedMultiplier:2},eggFarm:{id:"eggFarm",type:"eggFarm",name:"Egg Farm",shortName:"EGG",price:250,buildable:!0,upgradeable:!0,maxLevel:4,upgradeCosts:[null,300,720,1600]},manualScanner:{id:"manualScanner",type:"scanner",kind:"manual",name:"Manual Scanner",shortName:"Manual Scanner",price:260,buildable:!0,upgradeable:!1},autoScanner:{id:"autoScanner",type:"scanner",kind:"auto",name:"Auto Scanner",shortName:"Auto Scanner",price:360,buildable:!0,upgradeable:!1},mixer:{id:"mixer",type:"facility",name:"Mixer",shortName:"MIXER",price:450,buildable:!0,upgradeable:!0,incomeKey:"mixer",body:{w:164,h:126}},trash:{id:"trash",type:"facility",name:"Waste Shredder",shortName:"SHREDDER",price:240,buildable:!0,upgradeable:!0,body:{w:180,h:100}},truck:{id:"truck",type:"facility",name:"Truck",shortName:"TRUCK",price:450,buildable:!0,upgradeable:!0,incomeKey:"truck",body:{w:176,h:138}}}};var fn=H.version,Ft=H.time.daySeconds,Xt=H.cash.starting,we=H.economy,nr=H.time.farmShutdownGraceSeconds,pn=H.production.autoScannerCooldown,or=H.production.autoScannerUpgradeRate,eo=H.production.autoScannerMinCooldown,rr=H.contracts.eventChance,yt=H.contracts.firstTurn,ir=H.production.poopRate,yn=H.conveyor.baseSpeed,Bt=H.conveyor.maxSpeed,hn=H.conveyor.bearingSpeedMultiplier,ge=H.cards,sr=H.cards.definitions,to=H.cards.defaultEffects,tt=H.production.eggSpawnRanges,y=H.grid,ve=H.facilities,no=["conveyor","boostConveyor","eggFarm","autoScanner","manualScanner","mixer","trash","truck"],Jt=["mixer","trash","truck"],lr=["mixer","truck"],ht=Object.fromEntries(Object.entries(ve).map(([e,t])=>[e,t.price])),pe=[{name:"right",dc:1,dr:0,angle:0,opposite:"left"},{name:"left",dc:-1,dr:0,angle:Math.PI,opposite:"right"},{name:"down",dc:0,dr:1,angle:Math.PI/2,opposite:"up"},{name:"up",dc:0,dr:-1,angle:-Math.PI/2,opposite:"down"}],f={bg:"#eefbea",ink:"#102015",muted:"#526456",green:"#22b94f",greenSoft:"#b7f5c6",mixerBlue:"#2477ff",truckPink:"#ff6aa8",wasteGreen:"#22b94f",danger:"#c42424",warn:"#f0a020",white:"#ffffff",poop:"#7b4a23",male:"#62a9ff",female:"#ff8bc6"},Ce={ambient:0,important:1,critical:2};function S(e){let t=Number(e)||0;return`${t<0?"-":""}\uFFE5${Math.ceil(Math.abs(t)).toLocaleString("ja-JP")}`}function I(e,t){return`${e},${t}`}function Q(e){let[t,n]=String(e).split(",").map(Number);return{col:t,row:n}}function Qt(e,t,n){return Math.max(t,Math.min(n,e))}function W(e,t){return e+Math.random()*(t-e)}function Pe(e,t){return e>=0&&e=0&&tMath.round(Qt(r,0,255)).toString(16).padStart(2,"0");return`#${o(e)}${o(t)}${o(n)}`}function ro(e,t,n){let o=ar(e),r=ar(t);return Is(oo(o.r,r.r,n),oo(o.g,r.g,n),oo(o.b,r.b,n))}var oe={appTitle:"Chick Sorter",versionTitle:`Chick Sorter ${fn}`,currency:"\uFFE5",phases:{title:"Title",build:"Build",running:"Sorting",clearing:"Clearing",loanDecision:"Emergency Loan",gameover:"Game Over"},actions:{startGame:"Start Game",nextDay:"Next Day",buildPhase:"Build Phase",restart:"Restart",close:"Close",remove:"Remove",refund:"Refund",autoMenu:"Auto Menu"},status:{build:e=>`BUILD: ${(e==="erase"?"SELL":e||"SELECT").toUpperCase()} | Right-drag pan | Hover equipment for info`,sorting:"Sorting: use scanner keys. Build after clearing the day.",allPortsConnected:"Start condition met: one EGG route reaches an exit.",equipmentPanelEmpty:"Hover equipment in Build phase.",eventPanelRunning:"Forced events appear in Build phase."},routeLabels:{mixer:"MIXER",truck:"TRUCK",trash:"WASTE",scanner:"NEXT","scanner-role-1":"NEXT",input:"IN"},fail:{outOfGrid:"Out of grid",cellOccupied:"Cell occupied",notEnoughCash:"Not enough cash",facilityExists:"Facility already exists",facilityOverlap:"Facility overlap",nothingToErase:"Nothing to sell",noUpgrade:"No upgrade available"}};function bt(e){return ve[e]?.name||String(e||"").replace(/^[a-z]/,t=>t.toUpperCase())}function Fs(e){let t=ve[e];return t?e==="conveyor"||e==="boostConveyor"?`${S(t.price)} / tile`:e==="autoScanner"?`${S(t.price)} / ${pn.toFixed(1)}s cooldown`:e==="trash"?S(t.price):S(t.price):""}function Bs(e){return{conveyor:"A narrow green belt. Routes decide whether chicks live, ship, or become invoices.",boostConveyor:"Moves chicks twice as fast. Wears down like a normal belt.",eggFarm:"A tiny gatehouse producing questionable eggs on schedule.",autoScanner:"Slower than hands without upgrades.",manualScanner:"A manual checkpoint. The operator is the algorithm.",mixer:"Male chicks become revenue here. Do not feed it poop.",trash:"A polite shredder for poop and other regrets.",truck:"Ships the target cargo. Wrong cargo still leaves a paper trail."}[e]||""}function ur(e){let t=Bs(e);return`${bt(e)}${Fs(e)}${t?`${t}`:""}`}function dr(e){return oe.routeLabels[e]||String(e).toUpperCase()}function fr(e){let t=2166136261;for(let n=0;n>>0}function pr(e){let t=e>>>0;return()=>{t+=1831565813;let n=Math.imul(t^t>>>15,1|t);return n^=n+Math.imul(n^n>>>7,61|n),((n^n>>>14)>>>0)/4294967296}}function bn(){return`RUN-${Date.now().toString(36)}-${Math.floor(Math.random()*1e6).toString(36)}`.toUpperCase()}function yr({seed:e,targetRatio:t=.2,reserved:n=[]}={}){let o=pr(fr(e||bn())),r=new Set(n.map(d=>typeof d=="string"?d:I(d.col,d.row))),s=[];for(let d=0;d0;d-=1){let w=Math.floor(o()*(d+1));[s[d],s[w]]=[s[w],s[d]]}let l=Math.round(y.cols*y.rows*t);return new Set(s.slice(0,Math.min(l,s.length)).map(d=>d.k))}function hr({seed:e,colStart:t=0,colEnd:n=y.cols,rowStart:o=0,rowEnd:r=y.rows,targetRatio:s=.2,reserved:l=[]}={}){let d=pr(fr(e||bn())),w=new Set(l.map(V=>typeof V=="string"?V:I(V.col,V.row))),M=[],$=Math.max(0,Math.floor(t)),F=Math.max($,Math.floor(n)),B=Math.max(0,Math.floor(o)),ee=Math.max(B,Math.floor(r));for(let V=B;V0;V-=1){let ce=Math.floor(d()*(V+1));[M[V],M[ce]]=[M[ce],M[V]]}let ae=Math.round((F-$)*(ee-B)*s);return new Set(M.slice(0,Math.min(ae,M.length)).map(V=>V.k))}var Lt=10,mr=2,br=1;function Ot(e,t){return`${e},${t}`}function Ls(e){let[t,n]=String(e).split(",").map(Number);return{chunkX:t,chunkY:n}}function wn(e){let t=H.grid.expansion||{},n=Math.max(1,(e.gridExpansionPurchases||0)+1),o=(t.costBase||1e3)*n*(t.costMultiplier||4/3),r=Math.max(1,t.truncateUnit||100);return Math.floor(o/r)*r}function wr(e){e.ownedCells=new Set,e.ownedChunks=new Set,e.gridChunkOriginY=0,e.gridExpansionPurchases=0,e.gridExpansionPurchasesByDirection={right:0,up:0};for(let t=0;t=0&&t=0&&n0)continue;let M=Ot(w.chunkX,w.chunkY);t.has(M)||n.has(M)||n.set(M,io(e,w.chunkX,w.chunkY,w.direction))}}return[...n.values()].sort((o,r)=>{let s=o.direction===r.direction?0:o.direction==="right"?-1:1;return s||(o.chunkY!==r.chunkY?o.chunkY-r.chunkY:o.chunkX-r.chunkX)})}function kr(e,t="right"){let n=wt(e);return n.find(o=>o.direction===t)||n[0]||null}function xt(e){return{x:y.x+e.colStart*y.cell,y:y.y+e.rowStart*y.cell,w:e.cols*y.cell,h:e.rows*y.cell}}function so(e){let t=xt(e);return{x:t.x+t.w/2-116,y:t.y+t.h/2-36,w:232,h:72,cx:t.x+t.w/2,cy:t.y+t.h/2}}function Cr(e,t){let n=so(e);return t.x>=n.x&&t.x<=n.x+n.w&&t.y>=n.y&&t.y<=n.y+n.h}function Sr(e){let t=[];for(let n=e.rowStart;nPe(t.col,t.row))}function Hs(e){return[{col:e.col,row:e.row-1},{col:e.col+1,row:e.row-1},{col:e.col,row:e.row},{col:e.col+1,row:e.row},{col:e.col,row:e.row+1},{col:e.col+1,row:e.row+1}].filter(t=>Pe(t.col,t.row))}function qs(e){return[{col:e.col,row:e.row+1},{col:e.col-1,row:e.row},{col:e.col+1,row:e.row},{col:e.col,row:e.row-1}].filter(t=>Pe(t.col,t.row))}function Rr(e){let t=new Set([...e.conveyorTiles]),n=o=>{o&&Pe(o.col,o.row)&&t.add(I(o.col,o.row))};for(let o of e.scanners){for(let r of Hs(o))n(r);for(let r of Ks(o))n(r)}for(let o of e.eggFarms){n(o);for(let r of qs(o))n(r)}for(let o of Object.values(e.facilities))n(o.entry);return t}function Er(e){let t=Gs.map(([n,o])=>({col:n,row:o}));for(let{col:n,row:o}of t)Pr(e,n,o);for(let n=0;n({...n,keys:n.keys?{left:{...n.keys.left},right:{...n.keys.right}}:null,queue:[]})),e.eggFarms=Ws.map(n=>{let o=vt(n);return{...n,nextSpawn:o,lastInterval:o}});let t=Rr(e);e.blockedCells=yr({seed:e.runSeed,targetRatio:H.map.blockedRatio,reserved:[...t]}),Ys(e),Er(e),e.routingVersion=(e.routingVersion||0)+1,e.factoryGraph=null,e.factoryGraphCommittedVersion=-1}function ot(e){return e?[{col:e.col,row:e.row-1},{col:e.col+1,row:e.row-1},{col:e.col,row:e.row},{col:e.col+1,row:e.row},{col:e.col,row:e.row+1},{col:e.col+1,row:e.row+1}]:[]}function de(e){let t=A(e.col,e.row);return{x:t.x+y.cell/2,y:t.y}}function Ct(e,t,n){return e.eggFarms.find(o=>o.col===t&&o.row===n)||null}function St(e,t,n){return e.scanners.find(o=>ot(o).some(r=>r.col===t&&r.row===n))||null}function Mt(e,t){return e.scanners.find(n=>n.id===t)||null}function fo(e,t){return e.scanners.find(n=>n.kind==="manual"&&n.slot===t)||null}function Cn(e){return e?.baseId||e?.id||""}function po(e,t){return Object.entries(e.facilities||{}).filter(([,n])=>Cn(n)===t)}function Re(e,t){return{inputA:{col:e.col,row:e.row-2},inputB:null,left:{col:e.col-1,row:e.row},right:{col:e.col+2,row:e.row}}[t]}function Ir(e){return e?pe.map(t=>({col:e.col+t.dc,row:e.row+t.dr})).filter(t=>Pe(t.col,t.row)):[]}function yo(e,t,n=[],o=[]){if(!t||!Pe(t.col,t.row))return[];let r=[],s=l=>{!l||!Pe(l.col,l.row)||!e.conveyorTiles.has(I(l.col,l.row))||n.some(d=>d&&mn(l,d))||r.some(d=>mn(d,l))||r.push({...l,viaTolerance:!mn(l,t)})};s(t);for(let l of o)s(l);for(let l of Ir(t))s(l);return r}function Dr(e,t,n=[],o=[]){let r=yo(e,t,n,o),s=r.filter(l=>!l.viaTolerance);return s.length?s:r}function zs(e,t){let n=Re(t,"inputA");return yo(e,n,ot(t))}function Vs(e,t,n){let o=Re(t,n);return yo(e,o,ot(t))}function Xs(e,t){let n=e.facilities[t]||po(e,t)[0]?.[1];if(!n)return null;if(n.entry)return A(n.entry.col,n.entry.row);let o=Cn(n)||t;return o==="mixer"?{x:n.x+n.w,y:n.y+n.h*.52}:o==="truck"?{x:n.x,y:n.y+n.h*.52}:o==="trash"?{x:n.x+n.w/2,y:n.y+8}:{x:n.x+n.w/2,y:n.y+n.h/2}}function Fr(e,t,n){return n==="inputA"?zs(e,t).length>0:Vs(e,t,n).length>0}function xe(e){e.branchCounters?.clear?.(),e.routeCache=null,e.routingVersion=(e.routingVersion||0)+1,e.factoryGraph=null,e.factoryGraphCommittedVersion=-1}function Ee(e,t){return I(e,t)}function kt(e){return Q(e)}function Js(e,t){let n=e.conveyorMeta?.get?.(t)||{},o=[];return Array.isArray(n.outDirs)&&o.push(...n.outDirs),n.dir&&o.push(n.dir),[...new Set(o)].filter(r=>pe.some(s=>s.name===r))}function Ar(e,t,n){let o=pe.find(l=>l.name===n);if(!o)return!1;let r=e.conveyorMeta?.get?.(Ee(t.col+o.dc,t.row+o.dr));return r?[...new Set([...Array.isArray(r.outDirs)?r.outDirs:[],r.dir].filter(Boolean))].includes(o.opposite):!1}function Qs(e,t){let n=kt(t),o=pe.filter(l=>e.conveyorTiles.has(Ee(n.col+l.dc,n.row+l.dr))).map(l=>l.name),r=Js(e,t).filter(l=>o.includes(l)&&!Ar(e,n,l)),s=o.filter(l=>!Ar(e,n,l));return r.length?r:s}function Zs(e){return{version:e.routingVersion||0,cells:new Set,adjacency:new Map,components:new Map,cellToComponent:new Map,scannerInputs:new Map,scannerPorts:new Map,facilityPorts:new Map,farmOutputs:new Map,pathCache:new Map,metrics:{conveyorCells:0,edges:0,components:0,deadEnds:0,crosses:0,scannerPorts:0,connectedScannerPorts:0,facilityPorts:0,connectedFacilityPorts:0,farmOutputs:0},issues:[]}}function js(e){let t=Zs(e);for(let o of e.conveyorTiles)t.cells.add(o),t.adjacency.set(o,[]);for(let o of t.cells){let r=kt(o),s=Qs(e,o),l=pe.filter(d=>s.includes(d.name));for(let d of l){let w=Ee(r.col+d.dc,r.row+d.dr);t.cells.has(w)&&t.adjacency.get(o).push({key:w,dir:d.name})}}let n=0;for(let[o,r]of t.adjacency.entries()){n+=r.length;let s=new Set(r.map(l=>l.dir));r.length===1&&(t.metrics.deadEnds+=1),r.length===4&&s.has("left")&&s.has("right")&&s.has("up")&&s.has("down")&&(t.metrics.crosses+=1)}return t.metrics.conveyorCells=t.cells.size,t.metrics.edges=Math.floor(n/2),el(t),tl(e,t),t.issues=pl(e,t,{includeRoutes:!1}),t}function el(e){let t=1;for(let n of e.cells){if(e.cellToComponent.has(n))continue;let o=t++,r=[n],s=[];for(e.cellToComponent.set(n,o);r.length;){let l=r.shift();s.push(l);for(let d of e.adjacency.get(l)||[])e.cellToComponent.has(d.key)||(e.cellToComponent.set(d.key,o),r.push(d.key))}e.components.set(o,{id:o,cells:s,capacity:Math.max(1,s.length),count:0,ratio:0})}e.metrics.components=e.components.size}function tl(e,t){for(let n of e.scanners){let o={};for(let r of["inputA","left","right"]){let s=Re(n,r),l=Dr(e,s,ot(n)).filter(M=>t.cells.has(Ee(M.col,M.row))),d=l.map(M=>Ee(M.col,M.row)),w=d.length>0;if(o[r]={type:r,cell:s,cells:l,keys:d,key:d[0]||null,connected:w},t.metrics.scannerPorts+=1,w&&(t.metrics.connectedScannerPorts+=1),r==="inputA")for(let M of d)t.scannerInputs.set(M,n.id)}t.scannerPorts.set(n.id,o)}for(let[n,o]of Object.entries(e.facilities)){if(!o.entry)continue;let r=Dr(e,o.entry).filter(d=>t.cells.has(Ee(d.col,d.row))),s=r.map(d=>Ee(d.col,d.row)),l=s.length>0;t.facilityPorts.set(n,{id:n,cell:{...o.entry},cells:r,keys:s,key:s[0]||null,connected:l}),t.metrics.facilityPorts+=1,l&&(t.metrics.connectedFacilityPorts+=1)}for(let n of e.eggFarms){let o=Ir({col:n.col,row:n.row}).map(r=>Ee(r.col,r.row)).filter(r=>t.cells.has(r));t.farmOutputs.set(n.id,o),t.metrics.farmOutputs+=o.length}}function De(e){let t=e.routingVersion||0;return(!e.factoryGraph||e.factoryGraph.version!==t)&&(e.factoryGraph=js(e)),e.factoryGraph}function Br(e){let t=De(e);return e.factoryGraphCommittedVersion=t.version,e.factoryGraphSnapshot=nl(t),t}function nl(e){return e?{version:e.version,conveyorCells:e.metrics.conveyorCells,edges:e.metrics.edges,components:e.metrics.components,deadEnds:e.metrics.deadEnds,crosses:e.metrics.crosses,scannerPorts:`${e.metrics.connectedScannerPorts}/${e.metrics.scannerPorts}`,facilityPorts:`${e.metrics.connectedFacilityPorts}/${e.metrics.facilityPorts}`,farmOutputs:e.metrics.farmOutputs,issues:[...e.issues||[]]}:null}function Lr(e,t,n){let o=De(e),r=Ee(t,n);return(o.adjacency.get(r)||[]).map(s=>({...kt(s.key),dir:pe.find(l=>l.name===s.dir)||{name:s.dir,dc:0,dr:0}}))}function ol(e,t){let n=t.col-e.col,o=t.row-e.row;return pe.find(r=>r.dc===n&&r.dr===o)?.name||null}function rl(e,t,n){if(e.conveyorMeta?.get?.(n)?.branchMode!=="pass")return!1;let o=t.adjacency.get(n)||[],r=new Set(o.map(s=>s.dir));return o.length===4&&r.has("left")&&r.has("right")&&r.has("up")&&r.has("down")}function ho(e,t,n){let o=De(e),r=Ee(t.col,t.row);if(!o.cells.has(r))return[];let s=`${r}|${n.cacheKey||"dynamic"}`;if(n.cacheKey&&o.pathCache.has(s))return o.pathCache.get(s).map(g=>g.map(_=>({..._})));let l=`${r}|none`,d=[{key:r,col:t.col,row:t.row,incoming:"none",depth:0}],w=new Map([[l,0]]),M=new Map,$=[],F=1/0,B=8,ee=24,ae=80;for(;d.length;){let g=d.shift();if(g.depth>F)break;let _=`${g.key}|${g.incoming}`;if(n(g)){if($.push(_),F=g.depth,$.length>=ee)break;continue}for(let U of o.adjacency.get(g.key)||[]){let ie=kt(U.key),me=ol(g,ie);if(g.incoming!=="none"&&rl(e,o,g.key)&&me!==g.incoming)continue;let fe=g.depth+1;if(fe>F)continue;let E=`${U.key}|${me}`,G=w.get(E);if(G==null)w.set(E,fe),M.set(E,[_]),d.push({key:U.key,col:ie.col,row:ie.row,incoming:me,depth:fe});else if(G===fe){let x=M.get(E)||[];x.length{if(V.length>=ae)return;let[U]=g.split("|"),ie=[kt(U),..._];if(g===l||U===r){V.push(ie);return}for(let me of M.get(g)||[])ce(me,ie)};for(let g of $)ce(g,[]);let he=[],Z=new Set;for(let g of V){let _=g.map(U=>Ee(U.col,U.row)).join(">");Z.has(_)||(Z.add(_),he.push(g))}return n.cacheKey&&o.pathCache.set(s,he.map(g=>g.map(_=>({..._})))),he}function Or(e){let t=De(e),n=new Map;for(let[o,r]of t.components.entries())n.set(o,{id:o,cells:[...r.cells],capacity:r.capacity,count:0,ratio:0});return{components:n,cellToComponent:new Map(t.cellToComponent)}}function Nr(e,t,n){let o=De(e),r=null,s=1/0;for(let l of o.cells){let d=Q(l),w=A(d.col,d.row),M=Math.hypot(w.x-t,w.y-n);MA(o.col,o.row));return t&&n.push(t),n}function nt(e,t){t&&(!e.length||cr(e[e.length-1],t)>1)&&e.push(t)}function il(e,t,n,o=null){let r=[{x:e.x,y:e.y}];nt(r,A(t.col,t.row));for(let s of Sn(n,o))nt(r,s);return r}function sl(e,t){let n=Sn(e),o=Re(t,"inputA");return o&&nt(n,A(o.col,o.row)),nt(n,de(t)),n}function ll(e,t,n,o){let r=[{x:e.x,y:e.y}];nt(r,A(t.col,t.row));for(let l of Sn(n))nt(r,l);let s=Re(o,"inputA");return s&&nt(r,A(s.col,s.row)),nt(r,de(o)),r}function al(e,t,n=0){let o=A(t.col,t.row),r=0;for(let s of e.chicks||[]){if(s.stage==="flying"||s.stage==="queued")continue;Math.hypot(s.x-o.x,s.y-o.y){let F=M.roleBias??0,B=$.roleBias??0;if(F!==B)return F-B;let ee=M.cells?.length??M.route?.length??Number.POSITIVE_INFINITY,ae=$.cells?.length??$.route?.length??Number.POSITIVE_INFINITY;if(ee!==ae)return ee-ae;let V=M.key||JSON.stringify(M.route||M.cells||M),ce=$.key||JSON.stringify($.route||$.cells||$);return V.localeCompare(ce)});if(!o||r.length===1)return r[0];let s=r[0].roleBias??0,l=r.filter(M=>(M.roleBias??0)===s),d=Math.min(...l.map(M=>$r(e,M)));l=l.filter(M=>$r(e,M)===d);let w=e.branchCounters.get(t)||0;return e.branchCounters.set(t,w+1),l[Math.floor(Math.random()*l.length)]}function Gr(e){return e.role===0?{left:{match:"male",destination:"mixer"},right:{match:"default",destination:"scanner-role-1"}}:{left:{match:"poop",destination:"trash"},right:{match:"default",destination:"truck"}}}function mo(e){let t=Gr(e);return{left:t.left.destination,right:t.right.destination}}function bo(e,t){return Gr(e).left.match===t.sex?"left":"right"}function wo(e){return dr(e)}function xo(e){return{mixer:"#2477ff",truck:"#ff6aa8",trash:"#22b94f","scanner-role-1":"#526456",scanner:"#526456",input:"#526456"}[e]||"#102015"}function ze(e,t,n=!1){let o=De(e),r=(o.farmOutputs.get(t.id)||[]).map(kt),s=[];for(let w of r){let M=F=>o.scannerInputs.has(Ee(F.col,F.row));M.cacheKey=`farmInput:${w.col},${w.row}:v${o.version}`;let $=ho(e,w,M);for(let F of $){let B=F[F.length-1],ee=o.scannerInputs.get(Ee(B.col,B.row)),ae=Mt(e,ee);ae&&s.push({key:`${ae.id}:${B.col},${B.row}:${F.length}`,scanner:ae,cells:F})}}let l=kn(e,`farm:${t.id}`,s,n);if(l)return{scannerId:l.scanner.id,route:sl(l.cells,l.scanner)};let d=kn(e,`farm:${t.id}:output`,r.map(w=>({key:I(w.col,w.row),cells:[w]})),n);return d?{scannerId:null,route:Sn(d.cells)}:null}function Mn(e,t,n,o,r=!1){let s=Mt(e,o);if(!s)return null;let d=De(e).scannerPorts.get(s.id)?.[t],w=Re(s,t),M=d?.connected?(d.keys||[d.key]).filter(Boolean).map(kt):[];if(!w||!M.length)return null;let $=mo(s)[t];return $==="scanner-role-1"?cl(e,s,t,n,w,M,r):ul(e,s,t,n,w,M,$,r)}function cl(e,t,n,o,r,s,l){let d=De(e),w=e.scanners.filter(F=>F.id!==t.id&&d.scannerPorts.get(F.id)?.inputA?.connected),M=[];for(let F of s)for(let B of w){let ee=d.scannerPorts.get(B.id)?.inputA?.key;if(!ee)continue;let ae=(d.scannerPorts.get(B.id)?.inputA?.keys||[ee]).filter(Boolean),V=Z=>ae.includes(Ee(Z.col,Z.row));V.cacheKey=`toScanner:${F.col},${F.row}:${B.id}:v${d.version}`;let ce=ho(e,F,V),he=B.role===1?0:1;for(let Z of ce)M.push({key:`${he}:start${I(F.col,F.row)}:s${B.id}:${Z.length}:${I(Z[Z.length-1].col,Z[Z.length-1].row)}`,target:B,cells:Z,roleBias:he})}let $=kn(e,`scanner:${t.id}:${n}:toRole1`,M,l);return $?{destination:"scanner",nextScannerId:$.target.id,route:ll(o,r,$.cells,$.target)}:null}function ul(e,t,n,o,r,s,l,d){let w=De(e),M=[...w.facilityPorts.entries()].filter(([B,ee])=>ee?.connected&&Cn(e.facilities?.[B])===l);if(!M.length)return null;let $=[];for(let B of s)for(let[ee,ae]of M){let V=(ae.keys||[ae.key]).filter(Boolean);if(!V.length)continue;let ce=Z=>V.includes(Ee(Z.col,Z.row));ce.cacheKey=`toFacility:${B.col},${B.row}:${ee}:v${w.version}`;let he=ho(e,B,ce);for(let Z of he)$.push({key:`start${I(B.col,B.row)}:${ee}:${Z.length}:${I(Z[Z.length-1].col,Z[Z.length-1].row)}`,cells:Z,facilityId:ee})}let F=kn(e,`scanner:${t.id}:${n}:${l}`,$,d);return F?{destination:l,facilityId:F.facilityId,route:il(o,r,F.cells,Xs(e,F.facilityId))}:null}function Ur(e,t,n=new Set){if(!t||n.has(t.id))return!1;n.add(t.id);for(let o of["left","right"]){let r=Mn(e,o,de(t),t.id,!1);if(r){if(["mixer","trash","truck"].includes(r.destination))return!0;if(r.destination==="scanner"){let s=Mt(e,r.nextScannerId);if(Ur(e,s,n))return!0}}}return!1}function dl(e,t){return!!ze(e,t,!1)?.scannerId}function Tn(e){if(e.phase!=="build")return[];let t=[];for(let n of e.eggFarms||[])dl(e,n)||t.push({type:"eggFarm",id:n.id,ref:n,message:"This Egg will not produce chicks"});for(let n of Jt)for(let[o,r]of po(e,n))De(e).facilityPorts.get(o)?.connected||t.push({type:"facility",id:o,ref:r,message:"This facility cannot receive items"});return t}function fl(e){let t=De(e);for(let r of e.eggFarms||[]){let s=ze(e,r,!1);if(!s?.scannerId)continue;let l=Mt(e,s.scannerId);if(Ur(e,l))return[]}let n=e.eggFarms?.length||0,o=[...t.facilityPorts.entries()].filter(([r,s])=>s?.connected&&["mixer","trash","truck"].includes(Cn(e.facilities?.[r]))).length;return n?t.metrics.farmOutputs?e.scanners?.length?o?["Connect at least one EGG through a scanner to any valid exit."]:["Connect at least one receiver exit: Mixer, Shredder, or Truck."]:["Build at least one scanner and connect it to an EGG route."]:["Connect at least one EGG to a conveyor."]:["Build at least one EGG before starting the next day."]}function vn(e){return e.kind==="manual"?`S${(e.slot??0)+1}`:`Auto Scanner #${e.id}`}function pl(e,t=De(e),n={}){let o=n.includeRoutes!==!1,r=[],s={mixer:"Mixer",trash:"Shredder",truck:"Truck"};for(let l of Jt){let d=po(e,l);if(!d.length){r.push(`${s[l]||l} is missing`);continue}for(let[w]of d)t.facilityPorts.get(w)?.connected||r.push(`${s[l]||l} receiver has no conveyor`)}for(let l of e.scanners){let d=t.scannerPorts.get(l.id)||{};if(d.inputA?.connected||r.push(`${vn(l)} input has no conveyor`),d.left?.connected||r.push(`${vn(l)} left output has no conveyor`),d.right?.connected||r.push(`${vn(l)} right output has no conveyor`),!!o)for(let w of["left","right"])d[w]?.connected&&!Mn(e,w,de(l),l.id)&&r.push(`${vn(l)} ${w} route is incomplete`)}if(o)for(let l of e.eggFarms)ze(e,l)?.scannerId||r.push(`Egg Farm #${l.id} has no scanner route`);else for(let l of e.eggFarms)(t.farmOutputs.get(l.id)||[]).length||r.push(`Egg Farm #${l.id} has no output conveyor`);return[...new Set(r)]}function jt(e){return fl(e)}function Wr(e){return jt(e).length===0}var _r=[{target:"female",title:"Premium Female Shipment",route:"Female -> Truck",note:"Only female chicks are valid truck cargo this turn."},{target:"male",title:"Male Export Trial",route:"Male -> Truck",note:"Male chicks are temporarily valid truck cargo. Male truck fines are suspended for valid male cargo."},{target:"poop",title:"Contaminant Sample Export",route:"Poop -> Truck",note:"Poop is temporarily valid truck cargo. Shipped poop does not soil this contract shipment."}];function vo(e){return[...e.eggFarms.map(t=>t.price||0),...e.scanners.map(t=>t.price||0),...Object.values(e.facilities).map(t=>t.price||0),...[...e.conveyorMeta.values()].map(t=>t.price||0)].reduce((t,n)=>t+n,0)}function yl(e,t=1){return e.eggFarms.reduce((n,o)=>{let[r,s]=Zt(o),l=Math.max(.1,(r+s)/2);return n+Ft/l*t},0)}function Kr(e){if(e.turn+1rr)return null;let n=_r[Math.floor(Math.random()*_r.length)],o=2,r=3,s=yl(e,o),l=Math.max(1,Math.ceil(s*.9)),d=Math.round((.08+Math.random()*.08)*100)/100,w=Math.floor(vo(e)*d),M=Math.floor(Math.max(0,e.cash)*.25),$=Math.max(w,M);return{id:`contract-${e.turn}-${Date.now()}-${Math.floor(Math.random()*9999)}`,...n,forced:!0,productionMultiplier:o,poopMultiplier:r,targetAmount:l,estimatedOutput:Math.round(s),rewardRate:d,reward:$,rewardByFactory:w,rewardByCash:M,createdTurn:e.turn,oneTurnOnly:!0}}function Hr(e){e.contractActive=e.contractOffer?{...e.contractOffer}:null,e.contractOffer=null}function qr(e){e.contractActive=null}function Yr(e){return e.contractActive?.productionMultiplier||1}function zr(e){let t=Math.max(0,Number(e?.cardEffects?.hatchingFeed)||0),n=Math.pow(1.03,t);return Math.min(.95,ir*(e.contractActive?.poopMultiplier||1)*n)}function Ve(e){return e.contractActive?.target||"female"}function ko(e,t){return t==="male"?e.truckMale||0:t==="poop"?e.poopTruck||0:e.truckFemale||0}function Vr(e,t){return Ve(e)===t}function Xr(e){return Ve(e)!=="male"}function Jr(e){let t=e.contractActive;if(!t)return null;let n=ko(e.stats,t.target),o=n>=t.targetAmount,r=o?t.reward:0;r>0?(Ae(e,r),e.stats.contractBonus=r,e.totals.contractBonus+=r,e.totals.contractSuccess+=1):(e.stats.contractBonus=0,e.totals.contractFailed+=1);let s={title:t.title,target:t.target,route:t.route,targetAmount:t.targetAmount,count:n,success:o,bonus:r,reward:t.reward,productionMultiplier:t.productionMultiplier,poopMultiplier:t.poopMultiplier};return e.stats.contractResult=s,s}function Co(e,t){return Math.max(0,Number(e?.cardEffects?.[t])||0)}function hl(e){return!!e?.cardEffects?.usedMachineActive}function ml(e,t){return t?1:Math.min(.8,.5+.1*Math.min(3,Co(e,"recyclingSubsidy")))}function bl(e){return Math.pow(.95,Co(e,"safetyCover"))}function gn(e){let t=e?.ref||e||{},n=e?.type||t.type;return n==="conveyor"?t.price||ve[t.kind]?.price||ve.conveyor.price:n==="eggFarm"?t.price||ve.eggFarm.price:n==="scanner"?t.price||(t.kind==="auto"?ve.autoScanner.price:ve.manualScanner.price):n==="facility"?t.price||ve[t.id]?.price||0:ve[t.id]?.price||0}function Be(e,t=null){let n=ve[e]?.price||0;return hl(t)?Math.ceil(n*.5):n}function wl(e,t){return Math.max(0,(e.facilities?.[t]?.level||1)-1)}function rt(e,t){return lr.includes(t)?Math.pow(we.incomeUpgradeRate,wl(e,t)):1}function it(e){return Math.ceil(we.income.mixer*rt(e,"mixer"))}function Ne(e){return Math.ceil(we.income.truck*rt(e,"truck"))}function en(e){return Math.ceil(we.poopFine*rt(e,"mixer"))}function tn(e){return Math.ceil(we.poopFine*rt(e,"truck"))}function So(e){let t=we.maleTruckFinePerHalfDay*(e.turn/2);return Math.ceil(t*rt(e,"truck"))}function Tt(e){let t=Math.max(0,(e.facilities?.trash?.level||1)-1);return Math.min(we.shredderBonus.maxCards,t)}function Qr(e){return Math.max(0,(e.facilities?.trash?.level||1)-1)}function Nt(){return we.shredderBonus.maxCards}function st(e){let t=typeof e=="number"?e:Tt(e),n=Math.min(we.shredderBonus.maxCards,Math.max(0,t)),o=Math.sqrt(n)/we.shredderBonus.probabilityPercentDivisor*n;return Math.min(1,o/100)}function xl(e){let t=Tt(e),n=st(t),o=t>0&&Math.random()0&&(e.stats.shredderBonus=(e.stats.shredderBonus||0)+t.amount,e.totals.shredderBonus=(e.totals.shredderBonus||0)+t.amount,Ae(e,t.amount)),t}function En(e,t=null){let n=we.zundaTax,o=Math.max(0,t?.cardEffects?.legalWork||0),r=n.exemption+o*n.legalWorkExemptionBonus,s=n.maxRateCash+o*n.legalWorkExemptionBonus,l=Math.max(0,Math.floor(Number(e)||0)),d=Math.max(0,l-r);if(d<=0)return{profit:l,exemption:r,maxRateCash:s,taxable:0,rate:0,ratePercent:0,tax:0,step:0,legalWork:o};let w=Math.max(1,Math.ceil(d/n.stepAmount)),M=Math.min(n.maxRate,w*n.stepRate),$=Math.ceil(d*M);return{profit:l,exemption:r,maxRateCash:s,taxable:d,rate:M,ratePercent:Math.round(M*100),tax:$,step:w,legalWork:o}}function Mo(e,t=e.turn){let n=Math.max(1,Math.floor(Number(t)||1)),o=Math.ceil(n*we.fairiesTribute.perDay),r=Math.max(0,e?.cardEffects?.fairiesFlatteryNext||e?.cardEffects?.flattery||0),s=r>0?Math.pow(.5,r):1,l=Math.max(0,Math.ceil(o*s)),d=Math.max(0,o-l);return{day:n,base:o,flatteryCount:r,perCardReduction:Math.ceil(o*.5),reduction:d,amount:l}}function gt(e,t){let n=e?.ref||e||{},o=e?.type==="conveyor"?t?.conveyorMeta?.get(e.oldKey||n.id):n,r=o?.price||gn(e||n),s=o?.builtSession===t?.buildSession;if(o?.noRefund||n?.noRefund)return{amount:0,sameBuild:s,price:r,rate:0,noRefund:!0};let l=ml(t,s);return{amount:Math.max(0,Math.ceil(r*l)),sameBuild:s,price:r,rate:l,noRefund:!1}}function vl(e,t=null){let n=Math.max(1,Math.ceil((Number(e)||0)/we.explosionDamageDivisor));return Math.max(1,Math.ceil(n*bl(t)))}function kl(e,t){return Math.max(0,Math.ceil(t))}function jr(e,t){let n=e.cash;e.cash+=t,e.stats.profit+=t,e.totals.profit+=t,t>=0?(e.stats.revenue+=t,e.totals.revenue+=t):(e.stats.penalty+=Math.abs(t),e.totals.penalty+=Math.abs(t)),e.totals.maxCash=Math.max(e.totals.maxCash,e.cash)}function Ae(e,t){let n=Math.max(0,Math.ceil(t));return n>0&&jr(e,n),n}function Xe(e,t){let n=Math.max(0,Math.ceil(t));return n>0&&jr(e,-n),n}function lt(e,t){Xe(e,t)}function ei(e,t){if(t<=0)return;let n=e.cash;e.cash+=t,e.stats.profit+=t,e.totals.profit+=t,e.totals.maxCash=Math.max(e.totals.maxCash,e.cash)}function ti(e){let t=it(e);return e.stats.mixerRevenue+=t,Ae(e,t),t}function ni(e){let t=en(e);return e.stats.mixerPoopFine+=t,e.totals.mixerPoopFine+=t,Xe(e,t),t}function oi(e){let t=tn(e);return e.stats.truckPoopFine+=t,e.totals.truckPoopFine+=t,Xe(e,t),t}function ri(e){let t=So(e);return e.stats.maleTruckFine+=t,e.totals.maleTruckFine+=t,Xe(e,t),t}function ii(e,t){let n=vl(t,e);return e.stats.explosionDamage+=n,e.totals.explosionDamage+=n,Xe(e,n),n}function si(e){let t=Ve(e),n=ko(e.stats,t),o=Ne(e),r=n*o,s=kl(e,r);return e.stats.pendingTruckRevenue=s,e.stats.truckRevenue=s,t==="poop"?e.stats.poopShipmentIncome=s:e.stats.chickShipmentIncome=s,s>0&&Ae(e,s),{base:r,adjusted:s,target:t,targetCount:n,unitPrice:o}}function li(e,t=0){let n=En(t,e);return n.tax<=0||(e.totals.zundaTax=(e.totals.zundaTax||0)+n.tax,e.totals.penalty=(e.totals.penalty||0)+n.tax,e.totals.profit=(e.totals.profit||0)-n.tax,e.cash-=n.tax),n}function ai(e,t=e.turn){let n=Mo(e,t);return e.cardEffects&&(e.cardEffects.fairiesFlatteryNext=0,e.cardEffects.flattery!=null&&delete e.cardEffects.flattery),n.amount<=0||(e.stats.fairiesTribute=(e.stats.fairiesTribute||0)+n.amount,e.totals.fairiesTribute=(e.totals.fairiesTribute||0)+n.amount,Xe(e,n.amount)),n}function ci(e){let t=Co(e,"chemicalWeaponSubsidy"),n=Math.max(0,e?.stats?.poopTruck||0),o=Math.floor(n/100),r=t>0&&o>0?o*2e3*t:0;return r>0&&(e.stats.chemicalWeaponSubsidy=(e.stats.chemicalWeaponSubsidy||0)+r,e.totals.chemicalWeaponSubsidy=(e.totals.chemicalWeaponSubsidy||0)+r,Ae(e,r)),{cards:t,poopTruck:n,blocks:o,amount:r}}function ui(e){let t=e?.cardEffects;if(!t||!Array.isArray(t.loans)||!t.loans.length)return{amount:0,paidLoans:0,remainingLoans:0};let n=0,o=0,r=[];for(let s of t.loans){if((s.daysRemaining||0)<=0)continue;let l=Math.max(0,Math.ceil(s.dailyPayment||400));n+=l,o+=1;let d=Math.max(0,(s.daysRemaining||0)-1);d>0&&r.push({...s,daysRemaining:d})}return t.loans=r,n>0&&(e.stats.loanRepayment=(e.stats.loanRepayment||0)+n,e.totals.loanRepayment=(e.totals.loanRepayment||0)+n,Xe(e,n)),{amount:n,paidLoans:o,remainingLoans:r.length}}function di(e){let t=vo(e),n=Math.max(1,e.totals.turnsCompleted||e.turn||1),o=Math.max(0,e.cash+e.totals.revenue+t*.5+e.totals.contractBonus*.5),r=Math.ceil(o/n),s=Math.floor(n*35+Math.max(0,n-5)*15),l=e.totals.correct*5,d=e.totals.explosionDamage;return{score:Math.max(0,Math.floor(o+r+l-e.totals.penalty-s)),factoryValue:t,base:o,dailyEarned:r,dayPenalty:s,correctBonus:l,explosionPenalty:d,days:n}}function Cl(e){return{...e,keys:e.keys?{left:{...e.keys.left},right:{...e.keys.right}}:null,queue:[],cooldown:e.cooldown||0}}function nn(e){return JSON.stringify({cash:e.cash,stats:e.stats,totals:e.totals,nextId:e.nextId,gridX:y.x,gridY:y.y,gridCols:e.gridCols||y.cols,gridRows:e.gridRows||y.rows,gridExpansionPurchases:e.gridExpansionPurchases||0,gridExpansionPurchasesByDirection:e.gridExpansionPurchasesByDirection||{right:0,up:0},gridChunkOriginY:e.gridChunkOriginY||0,ownedCells:[...e.ownedCells||[]],ownedChunks:[...e.ownedChunks||[]],blockedCells:[...e.blockedCells||[]],selected:e.selected,multiSelected:e.multiSelected||[],buildTool:e.buildTool,view:e.view,facilities:e.facilities,eggFarms:e.eggFarms,scanners:e.scanners.map(Cl),conveyorTiles:[...e.conveyorTiles],conveyorMeta:[...e.conveyorMeta.entries()],branchCounters:[...e.branchCounters.entries()],cardEffects:e.cardEffects,cardDraft:e.cardDraft,cardTargetPick:e.cardTargetPick})}function fi(e,t){let n=JSON.parse(t);e.cash=n.cash,e.stats=n.stats,e.totals=n.totals,e.nextId=n.nextId,y.x=n.gridX??H.grid.x,y.y=n.gridY??H.grid.y,y.cols=n.gridCols||H.grid.cols,y.rows=n.gridRows||H.grid.rows,e.gridCols=y.cols,e.gridRows=y.rows,e.gridExpansionPurchases=n.gridExpansionPurchases||0,e.gridExpansionPurchasesByDirection=n.gridExpansionPurchasesByDirection||{right:0,up:0},e.gridChunkOriginY=n.gridChunkOriginY||0,e.ownedCells=new Set(n.ownedCells||[]),e.ownedChunks=new Set(n.ownedChunks||[]),e.gridOwnedCells=e.ownedCells.size||y.cols*y.rows,e.blockedCells=new Set(n.blockedCells||[]),e.selected=n.selected,e.multiSelected=n.multiSelected||[],e.buildTool=n.buildTool,e.view=n.view||{x:0,y:0,scale:1},e.view.scale==null&&(e.view.scale=1),e.facilities=n.facilities||{},e.eggFarms=n.eggFarms||[],e.scanners=(n.scanners||[]).map(o=>({...o,queue:[],cooldown:o.cooldown||0})),e.conveyorTiles=new Set(n.conveyorTiles||[]),e.conveyorMeta=new Map(n.conveyorMeta||[]),e.branchCounters=new Map(n.branchCounters||[]),e.cardEffects=n.cardEffects||{bearing:0,legalWork:0,flattery:0},e.cardEffects.drawBonus!=null&&delete e.cardEffects.drawBonus,e.cardDraft=n.cardDraft||{pending:!1,choices:[],rerolls:0,picksRemaining:0},e.cardDraft.picksRemaining==null&&(e.cardDraft.picksRemaining=e.cardDraft.pending?1:0),e.cardDraft.freeRerolls==null&&(e.cardDraft.freeRerolls=0),e.cardTargetPick=n.cardTargetPick||null,e.groupDrag=null,e.selectionBox=null,e.pan=null,e.routingVersion=(e.routingVersion||0)+1,e.factoryGraph=null,e.factoryGraphCommittedVersion=-1}function Le(e){e.phase==="build"&&(e.undoStack.push(nn(e)),e.undoStack.length>80&&e.undoStack.shift(),e.redoStack=[])}function To(e){return e.phase!=="build"||!e.undoStack.length?!1:(e.redoStack.push(nn(e)),fi(e,e.undoStack.pop()),!0)}function go(e){return e.phase!=="build"||!e.redoStack.length?!1:(e.undoStack.push(nn(e)),fi(e,e.redoStack.pop()),!0)}function N(e,t,n,o,r=f.ink){e.floatingTexts.push({x:t,y:n,text:o,color:r,life:1,maxLife:1})}function Et(e,t,n){e.shake.strength=Math.max(e.shake.strength,t),e.shake.time=Math.max(e.shake.time,n)}function pi(e,t,n){e.effects.push({type:"spawn",priority:Ce.ambient,x:t,y:n,life:.42,maxLife:.42,size:12})}function yi(e,t,n,o=f.green){e.effects.push({type:"scannerPulse",priority:Ce.important,x:t,y:n,color:o,life:.42,maxLife:.42,size:24})}function hi(e,t,n){for(let o=0;o<12;o+=1)e.effects.push({type:"meat",priority:Ce.important,x:t+W(-18,18),y:n+W(-12,16),vx:W(-60,60),vy:W(-90,-25),life:W(.45,.85),maxLife:.85,size:W(4,8)})}function mi(e,t,n){for(let o=0;o<16;o+=1)e.effects.push({type:"sludge",priority:Ce.important,x:t+W(-18,18),y:n+W(-12,16),vx:W(-46,46),vy:W(-70,-20),life:W(.45,.9),maxLife:.9,size:W(4,8)})}function bi(e,t,n,o){let r=o==="poop"?f.poop:o==="male"?f.male:f.female;for(let s=0;s<22;s+=1)e.effects.push({type:"shred",priority:Ce.important,x:t+W(-16,16),y:n+W(-8,8),vx:W(-70,70),vy:W(40,160),color:s%3===0?f.white:r,life:W(.45,.95),maxLife:.95,w:W(3,7),h:W(10,24),rot:W(-Math.PI,Math.PI)});Je(e,t,n,f.green,34,Ce.important)}function wi(e,t,n,o){let r=o==="poop"?f.poop:o==="male"?f.danger:f.green;for(let s=0;s<8;s+=1)e.effects.push({type:"load",priority:Ce.important,x:t+W(-18,18),y:n+W(-18,18),vx:W(-42,42),vy:W(-90,-25),color:r,life:W(.35,.65),maxLife:.65,size:8})}function Je(e,t,n,o=f.danger,r=52,s=Ce.critical){e.effects.push({type:"shockwave",priority:s,x:t,y:n,color:o,life:.55,maxLife:.55,size:8,maxSize:r})}function at(e,t,n,o=12,r=f.danger,s=Ce.critical){for(let l=0;l0&&(e.shake.time=Math.max(0,e.shake.time-t),e.shake.time<=0&&(e.shake.strength=0));for(let r of e.effects)if(r.life-=t,["meat","sludge","spark","smoke","shred","load","rage"].includes(r.type))r.x+=(r.vx||0)*t,r.y+=(r.vy||0)*t,r.vy=(r.vy||0)+150*t;else if(r.type==="flyingChick"){r.x+=r.vx*t,r.y+=r.vy*t,r.vy+=360*t,r.noDamage||Sl(e,r,o);let s=r.x+e.view.x,l=r.y+e.view.y;(s<-100||l<-100||s>n.width+100||l>n.height+100)&&(r.life=0)}e.effects=e.effects.filter(r=>r.life>0);for(let r of e.floatingTexts)r.life-=t,r.y-=34*t;e.floatingTexts=e.floatingTexts.filter(r=>r.life>0)}function Sl(e,t,n){t.hit||(t.hit=new Set);for(let o of n()){if(t.hit.has(o.id)||t.xo.x+o.w||t.yo.y+o.h)continue;t.hit.add(o.id);let r=ii(e,o.price);N(e,t.x,t.y-10,`-${S(r)}`,f.danger),Je(e,t.x,t.y,f.danger,30),at(e,t.x,t.y,5,f.danger),Et(e,5,.18)}}var $e=H.maintenance;function Po(e,t){return Math.max(0,Number(e?.cardEffects?.[t])||0)}function An(e){let t=Math.min(1.4,1+.04*Math.min(10,Po(e,"crowdedFarming"))),n=Math.pow(.95,Po(e,"preventiveMaintenance"));return t*n}function Ml(e){return Math.pow(1.03,Po(e,"durabilityCoating"))}function $n(e,t,n=1){let o=$e.durability[t]||1e3;return Math.max(1,Math.ceil(o*Math.max(.1,n||1)*Ml(e)))}function Ao(e){let t=$e.repairman.dailyCost;return e?.cardEffects?.laborExploitation?Math.ceil(t*.7):t}function ki(e){return Math.max(.1,Number(e?.durabilityBaseMultiplier)||1)}function ye(e){e.repairman||(e.repairman={hiredForNextDay:!1,active:!1,x:y.x+y.cell*.5,y:y.y+y.rows*y.cell+84,target:null,repairedToday:0});for(let[t,n]of e.conveyorMeta||[]){n.uses==null&&(n.uses=0),n.maintenanceType==null&&(n.maintenanceType="conveyor"),n.kind||(n.kind="conveyor"),n.speedMultiplier||(n.speedMultiplier=n.kind==="boostConveyor"?2:1),n.branchMode||(n.branchMode="random");let o=$n(e,"conveyor",ki(n));(n.durability==null||n.durability0&&(n.processingCooldown=Math.max(0,n.processingCooldown-t))}function Ln(e){ye(e);let t=[];for(let[n,o]of e.conveyorMeta||[])t.push({type:"conveyor",key:n,meta:o,label:`${o.kind==="boostConveyor"?"Boost Belt":"Belt"} ${n}`,center:A(Q(n).col,Q(n).row)});for(let n of e.eggFarms||[])t.push({type:"eggFarm",ref:n,label:`EGG #${n.id}`,center:A(n.col,n.row)});for(let n of(e.scanners||[]).filter(o=>o.kind==="auto"))t.push({type:"autoScanner",ref:n,label:`AUTO #${n.id}`,center:A(n.col,n.row)});for(let n of["mixer","trash"]){let o=e.facilities?.[n];o&&t.push({type:n,ref:o,label:o.shortName||o.name||n,center:{x:o.x+o.w/2,y:o.y+o.h/2}})}return t}function Rn(e){return e.meta?Gt(e):Gt(e.ref)}function on(e){return e.meta?Ro(e):Ro(e.ref)}function Dn(e){return e.meta?Do(e):Do(e.ref)}function Ei(e,t){Tl(e.meta?e:e.ref,Math.max(0,on(e)-t))}function Pi(e){return e.type==="conveyor"?`conveyor:${e.key}`:`${e.type}:${e.ref?.id||e.ref?.type||e.type}`}function Ri(e,t=.02){ye(e);let n=Math.max(0,Number(t)||0),o=0;for(let r of Ln(e)){let s=on(r);Ei(r,Dn(r)*n),o+=Math.max(0,s-on(r))/Dn(r)*100}return o}function Di(e){if(ye(e),e.repairman.hiredForNextDay)return{ok:!1,reason:"Repairman already hired."};let t=Ao(e);return e.cashPi(o)===t.key)||null}function Pl(e){let t=null;for(let n of Ln(e)){let o=Rn(n);o<=.001||(!t||o>Rn(t))&&(t=n)}return t}function Ii(e,t){ye(e);let n=e.repairman;if(!n.active||e.phase!=="running")return;let o=El(e,n.target)||Pl(e);if(!o){n.target=null;return}n.target={key:Pi(o),label:o.label};let r=$e.repairman.walkSpeed,s=o.center.x-n.x,l=o.center.y-n.y,d=Math.hypot(s,l);if(d>8){let B=Math.min(d,r*t);n.x+=s/d*B,n.y+=l/d*B;return}let w=t/($e.repairman.secondsPerOnePercent*100),M=on(o),$=w*Dn(o);Ei(o,$);let F=Math.max(0,(M-on(o))/Dn(o)*100);n.repairedToday=(n.repairedToday||0)+F,F>0&&Math.random()<.02&&N(e,n.x,n.y-28,"REPAIR",f.green),Rn(o)<=.001&&(n.target=null)}function Rl(e){let t=0,n="none";for(let o of Ln(e)){let r=Rn(o);r>t&&(t=r,n=o.label)}return{ratio:t,percent:Math.round(t*100),label:n}}function Go(e){let t=Rl(e),n=Io(e);return{worst:t,avgBeltPerformance:n,repairman:e.repairman||null}}var Dl=ge.commonWeight,Al=ge.rareWeight,$l=ge.ultraRareWeight||.45,Pt=ge.baseDraftSize,Wt=sr;function sn(e,t){let n=Ie(e);return Math.max(0,Number(n[t])||0)}function Ie(e){e.cardEffects||(e.cardEffects={...to,loans:[]});for(let[t,n]of Object.entries(to))e.cardEffects[t]==null&&(e.cardEffects[t]=Array.isArray(n)?[]:n);return e.cardEffects.flattery!=null&&(e.cardEffects.fairiesFlatteryNext=Math.max(0,e.cardEffects.fairiesFlatteryNext||0)+Math.max(0,e.cardEffects.flattery||0),delete e.cardEffects.flattery),Array.isArray(e.cardEffects.loans)||(e.cardEffects.loans=[]),e.cardEffects.drawBonus!=null&&delete e.cardEffects.drawBonus,e.cardDraft||(e.cardDraft={pending:!1,choices:[],rerolls:0,picksRemaining:0}),e.cardDraft.picksRemaining==null&&(e.cardDraft.picksRemaining=e.cardDraft.pending?1:0),e.cardDraft.freeRerolls==null&&(e.cardDraft.freeRerolls=0),e.cardEffects}function Gi(e){let t=Math.max(.5,1-.05*Math.min(10,sn(e,"crowdedFarming"))),n=Math.pow(.96,sn(e,"hatchingFeed"));return t*n}function Ui(e,t=null){return Math.min(3,Math.max(0,Number(t?.extraEggOutlet)||0))}function Gn(e,t=null){let n=Ie(e),o=yn*Math.pow(hn,Math.max(0,n.bearing||0)),r=t?Math.max(.1,Number(e?.conveyorMeta?.get?.(t)?.speedMultiplier)||1):1,s=t?1:Io(e);return Math.min(Bt,o*s)*r}function Qe(e,t=null){let n=Math.max(0,(e?.level||1)-1),o=Math.max(eo,pn*Math.pow(or,n));return t?Math.max(eo,o*Fn(e)):o}function Fi(e){let t=e.cardDraft||{rerolls:0},n=Math.max(1,(t.rerolls||0)+1);return Math.ceil(Math.max(1,e.turn||1)/ge.rerollCost.dayDivisor*n*ge.rerollCost.multiplier)}function Wo(e){return Wt.find(t=>t.id===e)||null}function _o(e,t){let n=typeof t=="string"?Wo(t):t;if(Ie(e),!n||n.type==="dud")return{ok:!1,reason:"Card not found."};if(n.id==="extraCards")return e.cardDraft=e.cardDraft||{pending:!1,choices:[],rerolls:0,picksRemaining:0},e.cardDraft.choices.push(...Nn(e,2)),{ok:!0,reason:`${n.title} added draft cards.`};if(n.type==="instant")return _i(e,n),{ok:!0,reason:`${n.title} granted.`};if(n.type==="equipmentUpgrade"){let o=Ke(e,n)[0];return o?(Wi(e,o),{ok:!0,reason:`${n.title} applied.`}):{ok:!1,reason:`No valid target for ${n.title}.`}}if(n.type==="cellAction"&&n.target==="blockedCell"){let o=Ke(e,n).slice(0,3);if(!o.length)return{ok:!1,reason:"No blocked cells to remove."};for(let r of o){e.blockedCells.delete(I(r.col,r.row));let s=A(r.col,r.row);ct(e,s.x,s.y)}return xe(e),{ok:!0,reason:`${n.title} removed ${o.length} blocked cells.`}}return{ok:!1,reason:`${n.title} cannot be debug-granted.`}}function Il(e,t){if(t.type==="blockedCell")return`Blocked cell ${t.col},${t.row}`;if(t.type==="eggFarm")return`EGG #${t.id} L${t.level} -> L${t.level+1}`;if(t.type==="eggOutlet"){let n=Math.max(0,Number(t.farm?.extraEggOutlet)||0);return`EGG #${t.id} OUTLET ${1+n} -> ${2+n}`}if(t.type==="scanner")return`AUTO #${t.id} L${t.level} -> L${t.level+1} | CD ${Qe(t,e).toFixed(1)}s -> ${Qe({...t,level:t.level+1},e).toFixed(1)}s`;if(t.type==="facility"&&t.id==="mixer"){let n=t.level,o=it(e);t.level=n+1;let r=it(e);return t.level=n,`MIXER L${t.level} -> L${t.level+1} | ${S(o)} -> ${S(r)}`}if(t.type==="facility"&&t.id==="truck"){let n=t.level,o=Ne(e);t.level=n+1;let r=Ne(e);return t.level=n,`TRUCK L${t.level} -> L${t.level+1} | ${S(o)} -> ${S(r)}`}if(t.type==="facility"&&t.id==="trash"){let n=Tt(e),o=Math.min(Nt(),n+1),r=(st(n)*100).toFixed(2),s=(st(o)*100).toFixed(2);return`SHREDDER L${t.level||1} -> L${(t.level||1)+1} | ${r}% -> ${s}% per item`}return`${t.name||t.id} L${t.level||1} -> L${(t.level||1)+1}`}function Fl(e){return[...e.blockedCells||[]].map(t=>{let[n,o]=String(t).split(",").map(Number);return{type:"blockedCell",id:t,col:n,row:o}}).filter(t=>Number.isFinite(t.col)&&Number.isFinite(t.row))}function Ke(e,t){let n=typeof t=="string"?Wo(t):t;return Ie(e),n?n.target==="blockedCell"?Fl(e):n.target==="eggFarm"?e.eggFarms.filter(o=>(o.level||1)<4):n.target==="eggOutlet"?e.eggFarms.filter(o=>Math.max(0,Number(o.extraEggOutlet)||0)<3).map(o=>({type:"eggOutlet",id:o.id,col:o.col,row:o.row,farm:o})):n.target==="autoScanner"?e.scanners.filter(o=>o.kind==="auto"):n.target==="mixer"?Object.values(e.facilities||{}).filter(o=>(o.baseId||o.id)==="mixer"):n.target==="truck"?Object.values(e.facilities||{}).filter(o=>(o.baseId||o.id)==="truck"):n.target==="trash"?Qr(e)(o.baseId||o.id)==="trash"):[]:[]:[]}function Bl(e,t){let n=Ie(e);if(t.id==="crowdedFarming")return(n.crowdedFarming||0)>=10;if(t.id==="recyclingSubsidy")return(n.recyclingSubsidy||0)>=3;if(t.id==="dudFilter"){let o=Math.max(0,Math.min(1,ge.dudChancePerCard||0)),r=Math.max(1e-4,Math.min(1,ge.dudChanceReductionPerFilter||.05));return(n.dudFilter||0)>=Math.ceil(o/r)}return t.id==="laborExploitation"?!!n.laborExploitation:t.id==="usedMachine"?!!n.usedMachineActive:t.id==="newMachine"?!n.usedMachineActive:!1}function Ll(e){let t=e.repairman||{};return!!(t.active||t.hiredForNextDay||e.pendingRepairWorkerWages>0)}function Ol(e){return Wt.filter(t=>!(t.id==="laborExploitation"&&!Ll(e)||Bl(e,t)||(t.type==="equipmentUpgrade"||t.type==="cellAction")&&Ke(e,t).length<=0))}function Bi(e,t){if(t.weightBase!=null){let n=sn(e,t.id);return Math.max(0,Number(t.weightBase)-Math.max(0,Number(t.weightLossPerCopy)||0)*n)}return t.rarity==="ultraRare"?$l:t.rarity==="rare"?Al:Dl}function Nl(e,t){let n=t.reduce((r,s)=>r+Bi(e,s),0);if(n<=0)return t[Math.floor(Math.random()*t.length)];let o=Math.random()*n;for(let r of t)if(o-=Bi(e,r),o<=0)return r;return t[t.length-1]}var Li=0;function Gl(){return Li+=1,{id:`dud:${Date.now()}:${Li}`,title:"Dud",rarity:"dud",type:"dud",description:"Click to flick this dud away. It does not consume a pick."}}function Ul(e){let t=Ie(e),n=Math.max(0,Math.min(1,ge.dudChancePerCard||0)),o=Math.max(0,Math.min(1,ge.dudChanceReductionPerFilter||0));return Math.max(0,n-o*Math.max(0,t.dudFilter||0))}function Wl(e,t){let n=[...t],o=0,r=Math.max(0,ge.maxDudsPerDraft||0),s=Ul(e);for(let l=0;ld.id===s.id);l>=0&&r.splice(l,1)}return Wl(e,o)}function Ut(e){return e?e.type==="blockedCell"?`blockedCell:${e.id}`:e.type==="eggFarm"?`eggFarm:${e.id}`:e.type==="eggOutlet"?`eggOutlet:${e.id}`:e.type==="scanner"?`scanner:${e.id}`:e.type==="facility"?`facility:${e.id}`:`${e.type}:${e.id}`:""}function Oi(e){if(!e)return null;if(e.type==="blockedCell"){let t=A(e.col,e.row);return{x:t.x-y.cell*.48,y:t.y-y.cell*.48,w:y.cell*.96,h:y.cell*.96,cx:t.x,cy:t.y}}if(e.type==="eggFarm"||e.type==="eggOutlet"){let t=A(e.col,e.row);return{x:t.x-34,y:t.y-34,w:68,h:68,cx:t.x,cy:t.y}}if(e.type==="scanner"){let t=de(e);return{x:t.x-54,y:t.y-77,w:108,h:154,cx:t.x,cy:t.y}}return e.type==="facility"?{x:e.x,y:e.y,w:e.w,h:e.h,cx:e.x+e.w/2,cy:e.y+e.h/2}:null}function Ko(e){if(!e.cardTargetPick?.pending)return[];if(e.cardTargetPick.mode==="autoScannerMenu"){let n=new Set(e.cardTargetPick.targetKeys||[]);return e.scanners.filter(o=>o.kind==="auto"&&n.has(Ut(o))).map(o=>({key:Ut(o),label:"AUTO ROUTE SETTINGS",target:o,bounds:Oi(o)})).filter(o=>o.bounds)}let t=new Set(e.cardTargetPick.targetKeys||[]);return Ke(e,e.cardTargetPick.cardId).filter(n=>t.has(Ut(n))).map(n=>({key:Ut(n),label:Il(e,n),target:n,bounds:Oi(n)})).filter(n=>n.bounds)}function _l(e,t){let n=Ko(e);for(let o of n){let r=o.bounds;if(t.x>=r.x&&t.x<=r.x+r.w&&t.y>=r.y&&t.y<=r.y+r.h)return o.target}return null}function Wi(e,t){if(t.type==="eggOutlet"){let n=t.farm||e.eggFarms.find(r=>r.id===t.id);if(!n)return;n.extraEggOutlet=Math.min(3,Math.max(0,Number(n.extraEggOutlet)||0)+1);let o=A(n.col,n.row);N(e,o.x,o.y-30,`OUTLET ${1+n.extraEggOutlet}`,f.green),xe(e);return}if(t.level=(t.level||1)+1,t.type==="eggFarm"){t.nextSpawn=vt(t),t.lastInterval=t.nextSpawn;let n=A(t.col,t.row);N(e,n.x,n.y-30,`LV ${t.level}`,f.green);return}if(t.type==="scanner"){let n=de(t);N(e,n.x,n.y-34,`LV ${t.level}`,f.green);return}t.type==="facility"&&N(e,t.x+t.w/2,t.y+18,`LV ${t.level}`,f.green)}function _i(e,t){let n=Ie(e),o=r=>{n[r]=Math.max(0,n[r]||0)+1};if(t.id==="bearing"&&o("bearing"),t.id==="legalWork"&&o("legalWork"),t.id==="crowdedFarming"&&(n.crowdedFarming=Math.min(10,(n.crowdedFarming||0)+1)),t.id==="hatchingFeed"&&o("hatchingFeed"),t.id==="safetyCover"&&o("safetyCover"),t.id==="recyclingSubsidy"&&(n.recyclingSubsidy=Math.min(3,(n.recyclingSubsidy||0)+1)),t.id==="chemicalWeaponSubsidy"&&o("chemicalWeaponSubsidy"),t.id==="preventiveMaintenance"&&o("preventiveMaintenance"),t.id==="dudFilter"&&o("dudFilter"),t.id==="durabilityCoating"&&(o("durabilityCoating"),ye(e)),t.id==="sparePartsBin"){o("sparePartsBin");let r=Ri(e,.02);e.stats.sparePartsRepair=(e.stats.sparePartsRepair||0)+r,e.totals.sparePartsRepair=(e.totals.sparePartsRepair||0)+r,N(e,y.x+y.cols*y.cell/2,y.y-30,"REPAIR +2%",f.green)}if(t.id==="dudRefund"&&o("dudRefund"),t.id==="freeReroll"&&o("freeReroll"),t.id==="composter"&&o("composter"),t.id==="flattery"&&o("fairiesFlatteryNext"),t.id==="usedMachine"&&(n.usedMachineActive=!0),t.id==="newMachine"&&(n.usedMachineActive=!1),t.id==="laborExploitation"&&(n.laborExploitation=1),t.id==="rescueLoan"&&(o("rescueLoanCharges"),N(e,y.x+y.cols*y.cell/2,y.y-30,"RESCUE HELD",f.green)),t.id==="loan"){let r=Math.max(1,Math.floor(Number(e.turn)||1)),s=300*r;Ae(e,s),n.loans.push({daysRemaining:r,dailyPayment:400,borrowedOnTurn:e.turn||1}),N(e,y.x+y.cols*y.cell/2,y.y-30,`LOAN +${S(s)}`,f.green)}}function Ge(e){return`${Math.round(Number(e||0)*100)}%`}function Ni(e){let t=tt[Math.max(0,Math.min(tt.length-1,e-1))]||tt[0]||[0,0];return`${t[0].toFixed(2)}-${t[1].toFixed(2)}s`}function On(e,t){return Math.ceil(e*Math.pow(we.incomeUpgradeRate,Math.max(0,t-1)))}function Kl(e,t){let n=Ie(e);if(t.id==="upgradeEgg"){let o=Ke(e,t),r=Math.min(3,Math.max(1,o[0]?.level||1));return`Choose one EGG. Level +1. Production interval ${Ni(r)} -> ${Ni(r+1)}.`}if(t.id==="upgradeAutoScanner"){let o=Ke(e,t)[0]||{level:1},r=Qe(o,e),s=Qe({...o,level:(o.level||1)+1},e);return`Choose one AUTO SCANNER. Level +1. Cooldown ${r.toFixed(2)}s -> ${s.toFixed(2)}s.`}if(t.id==="upgradeMixer"){let o=e.facilities?.mixer?.level||1;return`MIXER level +1. Male-chick income ${S(On(we.income.mixer,o))} -> ${S(On(we.income.mixer,o+1))}.`}if(t.id==="upgradeTruck"){let o=e.facilities?.truck?.level||1;return`TRUCK level +1. Correct shipment income ${S(On(we.income.truck,o))} -> ${S(On(we.income.truck,o+1))}.`}if(t.id==="upgradeTrash"){let o=Tt(e),r=Math.min(Nt(),o+1);return`SHREDDER level +1. Bonus chance per item ${Ge(st(o))} -> ${Ge(st(r))}.`}if(t.id==="bearing"){let o=Math.min(Bt,yn*Math.pow(hn,Math.max(0,n.bearing||0))),r=Math.min(Bt,yn*Math.pow(hn,Math.max(0,(n.bearing||0)+1)));return`Conveyor speed +7.5%. After pick: ${o.toFixed(1)} -> ${r.toFixed(1)} px/s.`}if(t.id==="legalWork"){let o=Math.max(0,n.legalWork||0);return`ZUNDA TAX exemption +${S(250)}. After pick: exemption bonus ${S(250*(o+1))}.`}if(t.id==="crowdedFarming"){let o=Math.min(10,(n.crowdedFarming||0)+1);return`Held ${n.crowdedFarming||0}/10. After pick: EGG interval ${Ge(Math.max(.5,1-.05*o))}, deterioration ${Ge(Math.min(1.4,1+.04*o))}.`}if(t.id==="extraEggOutlet"){let o=Ke(e,t),r=Math.max(0,Number(o[0]?.farm?.extraEggOutlet)||0);return`Choose one EGG. Output ports ${1+r} -> ${2+r}. Max 4 ports per EGG.`}if(t.id==="hatchingFeed")return`Held ${n.hatchingFeed||0}. After pick: EGG interval ${Ge(Math.pow(.96,(n.hatchingFeed||0)+1))}, poop rate ${Ge(Math.pow(1.03,(n.hatchingFeed||0)+1))}.`;if(t.id==="safetyCover")return`Held ${n.safetyCover||0}. After pick: explosion damage ${Ge(Math.pow(.95,(n.safetyCover||0)+1))}.`;if(t.id==="recyclingSubsidy")return`Held ${n.recyclingSubsidy||0}/3. After pick: normal SELL refund ${Math.round(Math.min(.8,.5+.1*((n.recyclingSubsidy||0)+1))*100)}%.`;if(t.id==="chemicalWeaponSubsidy")return`Held ${n.chemicalWeaponSubsidy||0}. After pick: ${S(2e3*((n.chemicalWeaponSubsidy||0)+1))} per 100 truck-shipped poop each day.`;if(t.id==="preventiveMaintenance")return`Held ${n.preventiveMaintenance||0}. After pick: deterioration speed ${Ge(Math.pow(.95,(n.preventiveMaintenance||0)+1))}.`;if(t.id==="dudFilter"){let o=Math.max(0,Math.min(1,ge.dudChancePerCard||0)),r=Math.max(0,Math.min(1,ge.dudChanceReductionPerFilter||.05)),s=Math.max(0,n.dudFilter||0),l=Math.max(0,o-r*s),d=Math.max(0,o-r*(s+1));return`Held ${s}. DUD chance per card ${Ge(l)} -> ${Ge(d)}.`}if(t.id==="durabilityCoating")return`Held ${n.durabilityCoating||0}. After pick: maximum durability ${Ge(Math.pow(1.03,(n.durabilityCoating||0)+1))}.`;if(t.id==="sparePartsBin")return`Held ${n.sparePartsBin||0}. Immediately restores 2% durability to every maintained machine. Offer weight: ${Math.max(0,100-2*Math.max(0,n.sparePartsBin||0))}.`;if(t.id==="dudRefund")return`Held ${n.dudRefund||0}. After pick: DUD clicks award ${S(50*((n.dudRefund||0)+1))}.`;if(t.id==="freeReroll")return`Held ${n.freeReroll||0}. After pick: each card screen starts with ${(n.freeReroll||0)+1} free reroll${(n.freeReroll||0)+1===1?"":"s"}.`;if(t.id==="composter")return`Held ${n.composter||0}. After pick: SHREDDER poop grants ${S(3*((n.composter||0)+1))}.`;if(t.id==="flattery")return`Next Fairies tribute -50%. Held for next tribute: ${n.fairiesFlatteryNext||0}.`;if(t.id==="usedMachine")return"Enable Used Machines: future equipment costs 50%, has no SELL refund, and starts with 60% durability.";if(t.id==="newMachine")return"Cancel Used Machines. Future equipment returns to normal price, refund, and durability.";if(t.id==="laborExploitation")return"Repairman labor cost -30% permanently. One time only.";if(t.id==="rescueLoan")return`At day-end bankruptcy check, gain ${S(500)} if cash is below ${S(0)}. Held: ${n.rescueLoanCharges||0}.`;if(t.id==="loan"){let o=Math.max(1,Math.floor(Number(e.turn)||1));return`Gain ${S(300*o)} now. Starting tomorrow, pay ${S(400)} per day for ${o} days.`}return t.description}function Uo(e,t){let n=e.cardDraft?.choices||[],o=n.findIndex(r=>r.id===t.id);o>=0&&n.splice(o,1)}function Ki({game:e,ui:t,onUpdatePanels:n}){function o(){n&&n()}function r(){Ie(e),e.cardTargetPick=null,e.cardDraft={pending:!0,choices:Nn(e,Pt),rerolls:0,freeRerolls:sn(e,"freeReroll"),picksRemaining:1}}function s(){Ie(e),e.cardDraft.pending=!1,e.cardDraft.choices=[],e.cardDraft.picksRemaining=0,e.cardTargetPick=null,t.modal.classList.remove("visible","equipment-popover"),o()}function l(g=Pt){Ie(e),e.cardDraft.choices=Nn(e,g)}function d(){Ie(e),!e.cardDraft.choices.length&&e.cardDraft.picksRemaining>0&&l(Pt)}function w(g){Uo(e,g),e.cardDraft.picksRemaining=Math.max(0,(e.cardDraft.picksRemaining||1)-1),e.cardTargetPick=null,d(),e.cardDraft.picksRemaining<=0?s():Z()}function M(g){Uo(e,g),e.cardDraft.picksRemaining=Math.max(0,(e.cardDraft.picksRemaining||1)-1)+ge.extraCardsAddedPicks,e.cardDraft.choices.push(...Nn(e,2)),Z()}function $(g,_){let U=sn(e,"dudRefund")*50;if(U>0){let ie=Ae(e,U);e.stats.dudRefundIncome=(e.stats.dudRefundIncome||0)+ie,e.totals.dudRefundIncome=(e.totals.dudRefundIncome||0)+ie,N(e,y.x+y.cols*y.cell/2,y.y-30,`DUD REFUND +${S(ie)}`,f.green)}Et(e,34,.62),document.body?.animate&&document.body.animate([{transform:"translate(0, 0)"},{transform:"translate(-10px, 7px)"},{transform:"translate(12px, -8px)"},{transform:"translate(-8px, -5px)"},{transform:"translate(9px, 6px)"},{transform:"translate(0, 0)"}],{duration:260,easing:"steps(5, end)"}),_&&(_.style.setProperty("--fling-x",`${260+Math.random()*260}px`),_.style.setProperty("--fling-y",`${-260-Math.random()*170}px`),_.style.setProperty("--fling-r",`${(Math.random()<.5?-1:1)*(52+Math.random()*38)}deg`),_.classList.add("flung")),window.setTimeout(()=>{Uo(e,g),d(),Z()},340)}function F(g,_=null){if(g.type==="dud")return $(g,_);if(g.id==="extraCards")return M(g);if(g.type==="equipmentUpgrade"||g.type==="cellAction")return B(g);_i(e,g),w(g)}function B(g){let _=Ke(e,g);_.length&&(e.cardTargetPick={pending:!0,cardId:g.id,mode:g.target==="blockedCell"?"blockedCell":"equipment",targetKeys:_.map(Ut),remaining:g.id==="dynamite"?Math.min(3,_.length):1},t.modal.classList.remove("visible","equipment-popover"),o())}function ee(g){if(!g||g.type!=="blockedCell")return!1;let _=I(g.col,g.row);if(!e.blockedCells?.has(_))return!1;e.blockedCells.delete(_);let U=A(g.col,g.row);return ct(e,U.x,U.y),Je(e,U.x,U.y,f.danger,62),at(e,U.x,U.y,18,f.warn),Pn(e,U.x,U.y,8),Et(e,16,.35),N(e,U.x,U.y-18,"BOOM",f.danger),xe(e),e.cardTargetPick.remaining=Math.max(0,(e.cardTargetPick.remaining||1)-1),e.cardTargetPick.targetKeys=Ke(e,"dynamite").map(Ut),o(),!0}function ae(g){if(!e.cardTargetPick?.pending)return!1;let _=Wo(e.cardTargetPick.cardId),U=_l(e,g);return!_||!U?(V(),!0):_.id==="dynamite"?(ee(U)&&((e.cardTargetPick.remaining||0)<=0||!Ke(e,"dynamite").length)&&w(_),!0):(Wi(e,U),w(_),!0)}function V(){e.cardTargetPick?.pending&&(e.cardTargetPick=null,Z())}function ce(){let g=Math.max(0,e.cardDraft?.freeRerolls||0);if(g>0){e.cardDraft.freeRerolls=g-1,e.cardDraft.rerolls+=1,l(Pt),Z();return}let _=Fi(e);e.cash<_||(Xe(e,_),e.stats.cardRerollCost=(e.stats.cardRerollCost||0)+_,e.totals.cardRerollCost=(e.totals.cardRerollCost||0)+_,e.cardDraft.rerolls+=1,l(Pt),Z())}function he(g,_){let U=document.createElement("button");U.type="button";let ie=g.type==="dud"?"dud":g.rarity==="ultraRare"?"ultra-rare":g.rarity==="rare"?"rare":"common";U.className=`card-choice ${ie}`;let me=g.type==="dud"?"MISS":g.rarity==="ultraRare"?"ULTRA RARE":g.rarity==="rare"?"RARE":"COMMON",fe=[me,...(g.tags||[]).filter(E=>E!==me)].slice(0,5);return U.innerHTML=`${g.title}
${fe.map(E=>`${E}`).join("")}
${Kl(e,g)}`,U.addEventListener("click",()=>F(g,U)),U.dataset.index=String(_),U}function Z(){Ie(e),e.cardTargetPick=null,e.cardDraft?.pending||r(),e.cardDraft.choices?.length||l(Pt);let g=e.cardDraft.choices||[],_=Fi(e),U=Math.max(0,e.cardDraft?.freeRerolls||0),ie=Math.max(1,e.cardDraft.picksRemaining||1);t.modalTitle.textContent="Choose Upgrade Card",t.modalBody.innerHTML=`

Choose ${ie} card${ie===1?"":"s"} before Build phase. Dud cards can be flicked away without consuming a pick. Equipment cards target machines; Dynamite targets blocked cells.

`;let me=t.modalBody.querySelector("#cardChoices");g.forEach((G,x)=>me.appendChild(he(G,x))),t.modalActions.innerHTML="";let fe=document.createElement("button");fe.type="button",fe.className="facility-action warn reroll-button",fe.textContent=U>0?`Reroll (FREE x${U})`:`Reroll ${S(_)}`,fe.disabled=U<=0&&e.cash<_,fe.title=U>0?`${U} free reroll${U===1?"":"s"} remaining.`:fe.disabled?"Not enough cash":"",fe.addEventListener("click",ce),t.modalActions.appendChild(fe);let E=document.createElement("button");E.type="button",E.className="facility-action",E.textContent="No Card",E.title="Skip this upgrade pick and enter Build phase.",E.addEventListener("click",s),t.modalActions.appendChild(E),t.modal.classList.remove("equipment-popover"),t.modal.classList.add("visible")}return{prepareDraft:r,showDraft:Z,finishDraft:s,chooseTargetAtPoint:ae,cancelTargetPick:V}}var Hl=!1,ql={chickMale:"./assets/images/chick_male.png",chickFemale:"./assets/images/chick_female.png",poop:new URL("../../assets/images/poop.png",import.meta.url).href,conveyor:"./assets/images/conveyor.png",scannerManual:"./assets/images/scanner_manual.png",scannerAuto:"./assets/images/scanner_auto.png",eggFarm:"./assets/images/egg_farm.png",mixer:"./assets/images/mixer.png",shredder:"./assets/images/shredder.png",truck:"./assets/images/truck.png"},He={};for(let[e,t]of Object.entries(ql)){let n=new Image;n.loaded=!1,n.onerror=()=>{n.loaded=!1},(Hl||e==="poop")&&t&&(n.onload=()=>{n.loaded=!0},n.src=t),He[e]=n}function Vi(e,t,n,o){e.clearRect(0,0,t.width,t.height),Vl(e,t),e.save();let r=n.shake.time>0?W(-n.shake.strength,n.shake.strength):0,s=n.shake.time>0?W(-n.shake.strength,n.shake.strength):0,l=n.view?.scale||1;e.translate((n.view?.x||0)+r,(n.view?.y||0)+s),e.scale(l,l),Jl(e,n),Ql(e,n),Zl(e,n),la(e,n),ya(e,n),aa(e,n),ua(e,n),zl(e,n),wa(e,n,o.activeQueuedChick),Ea(e,n),Sa(e,t,n),Ca(e,n,o.selectedObject,o.selectedTitle),Ma(e,n),Ta(e,n),ga(e,n),e.restore(),Pa(e,n,t)}function Yl(e){if(e.type==="eggFarm"){let n=A(e.ref.col,e.ref.row);return{x:n.x-34,y:n.y-34,w:68,h:68,cx:n.x,cy:n.y}}let t=e.ref;return{x:t.x,y:t.y,w:t.w,h:t.h,cx:t.x+t.w/2,cy:t.y+t.h/2}}function zl(e,t){if(t.phase!=="build")return;let n=Tn(t);for(let o of n){let r=Yl(o);e.save(),e.fillStyle="rgba(196,36,36,.22)",e.strokeStyle=f.danger,e.lineWidth=5,e.setLineDash([10,6]),j(e,r.x,r.y,r.w,r.h,!0,!0),e.setLineDash([]),e.fillStyle=f.danger,e.strokeStyle=f.white,e.lineWidth=4,e.font="900 16px ui-monospace, monospace",e.textAlign="center",e.strokeText("!",r.cx,r.y+24),e.fillText("!",r.cx,r.y+24),e.font="900 11px ui-monospace, monospace",e.strokeText("NO ROUTE",r.cx,r.y+r.h-8),e.fillText("NO ROUTE",r.cx,r.y+r.h-8),e.restore()}}function j(e,t,n,o,r,s=!0,l=!0){e.beginPath(),e.rect(t,n,o,r),s&&e.fill(),l&&e.stroke()}function qo(e,t,n,o,r=f.ink){e.save(),e.font="900 12px ui-monospace, monospace",e.textAlign="center",e.lineWidth=4,e.strokeStyle=f.white,e.fillStyle=r,e.strokeText(o,t,n),e.fillText(o,t,n),e.restore()}function Rt(e,t,n,o,r,s){return t?.loaded?(e.drawImage(t,n,o,r,s),!0):!1}function Vl(e,t){e.fillStyle=f.bg,e.fillRect(0,0,t.width,t.height),e.save(),e.strokeStyle="rgba(16, 32, 21, 0.035)",e.lineWidth=1;for(let n=0;n=n;for(let r of wt(t)){let s=xt(r);e.save(),e.fillStyle=o?"rgba(183,245,198,.34)":"rgba(255,255,255,.22)",e.strokeStyle=o?f.green:f.muted,e.lineWidth=5,e.setLineDash([16,10]),j(e,s.x,s.y,s.w,s.h,!0,!0),e.setLineDash([]);let l=so(r),d=l.cx,w=l.cy;e.fillStyle="rgba(255,255,255,.94)",e.strokeStyle=f.ink,e.lineWidth=4,j(e,l.x,l.y,l.w,l.h,!0,!0),e.fillStyle=o?f.green:f.danger,e.font="900 16px ui-monospace, monospace",e.textAlign="center",e.fillText(r.direction==="right"?"BUY RIGHT LOT":"BUY UPPER LOT",d,w-12),e.fillStyle=f.ink,e.font="900 16px ui-monospace, monospace",e.fillText(`${S(n)} / 10\xD710`,d,w+15),e.fillStyle=f.muted,e.font="900 11px ui-monospace, monospace",e.fillText("blocked tiles included",d,w+30),e.restore()}}function Ze(e,t,n,o,r,s,l=0){let d=Gt(s);d<.5||(e.save(),e.globalAlpha=Math.min(.62,.12+d*.54),e.fillStyle="#6b4a23",l>0?(e.beginPath(),e.roundRect?.(t,n,o,r,l),e.roundRect?e.fill():j(e,t,n,o,r,!0,!1)):j(e,t,n,o,r,!0,!1),e.globalAlpha=Math.min(.9,.25+d*.6),e.strokeStyle="#3a2a16",e.lineWidth=Math.max(2,Math.floor(2+d*5)),e.setLineDash([5,6]),j(e,t+3,n+3,o-6,r-6,!1,!0),e.restore())}function Ho(e,t){let n=e.componentLookup?.get(t);return(n?e.congestion.get(n):null)?.ratio||0}function Zl(e,t){let n=[],o=new Set;for(let s of t.conveyorTiles){let{col:l,row:d}=Q(s);for(let w of Lr(t,l,d)){let M=I(w.col,w.row),$=[s,M].sort().join("|");if(o.has($))continue;o.add($);let F=Math.max(Ho(t,s),Ho(t,M));n.push({a:A(l,d),b:A(w.col,w.row),ratio:F})}}e.save(),e.lineCap="round",e.lineJoin="round";for(let s of[{width:33,color:f.ink,alpha:1},{width:25,color:f.white,alpha:1},{width:17,color:f.greenSoft,alpha:1}]){e.globalAlpha=s.alpha,e.strokeStyle=s.color,e.lineWidth=s.width;for(let l of n)e.beginPath(),e.moveTo(l.a.x,l.a.y),e.lineTo(l.b.x,l.b.y),e.stroke()}for(let s of n){let l=Math.max(0,(s.ratio-.5)/.5);e.strokeStyle=ro(f.green,f.danger,l),e.globalAlpha=s.ratio>.5?.35+l*.55:.8,e.lineWidth=6,e.beginPath(),e.moveTo(s.a.x,s.a.y),e.lineTo(s.b.x,s.b.y),e.stroke()}let r=ra(t);for(let s of t.conveyorTiles){let l=A(...Object.values(Q(s))),d=t.conveyorMeta.get(s)||{},w=Ho(t,s),M=d.kind==="boostConveyor"?"#fff2b8":f.white;e.fillStyle=w>.5?ro(M,"#ffd6d6",Math.max(0,(w-.5)/.5)):M,e.strokeStyle=f.ink,e.lineWidth=2,j(e,l.x-8,l.y-8,16,16,!0,!0),d.kind==="boostConveyor"&&(e.save(),e.strokeStyle=f.warn,e.lineWidth=3,e.beginPath(),e.moveTo(l.x-15,l.y+13),e.lineTo(l.x-3,l.y-13),e.lineTo(l.x+2,l.y-2),e.lineTo(l.x+15,l.y-13),e.stroke(),e.restore()),Ze(e,l.x-14,l.y-14,28,28,{meta:d}),Ue(e,t,"conveyor",s,l.x,l.y,38,38),ia(e,l,r.get(s)||[]),na(e,l,d,ea(t,s,d)),w>.5&&qo(e,l.x,l.y-15,`${Math.floor(w*100)}%`,f.danger)}e.restore()}function jl(e){return{random:"RND",pass:"PASS",up:"UP",right:"RT",down:"DN",left:"LF"}[e||"random"]||"RND"}function Un(e,t,n){let o=pe.find(l=>l.name===n);if(!o)return!1;let r=e.conveyorMeta?.get?.(I(t.col+o.dc,t.row+o.dr));return r?[...new Set([...Array.isArray(r.outDirs)?r.outDirs:[],r.dir].filter(Boolean))].includes(o.opposite):!1}function ea(e,t,n){let o=Q(t),r=pe.filter(d=>e.conveyorTiles.has(I(o.col+d.dc,o.row+d.dr))).map(d=>d.name),s=[...new Set([...Array.isArray(n.outDirs)?n.outDirs:[],n.dir].filter(Boolean))].filter(d=>r.includes(d)&&!Un(e,o,d)),l=r.filter(d=>!Un(e,o,d));return s.length?s:l}function ta(e,t,n){let o=Q(t),r=pe.filter(d=>e.conveyorTiles.has(I(o.col+d.dc,o.row+d.dr))).map(d=>d.name),s=[...new Set([...Array.isArray(n.outDirs)?n.outDirs:[],n.dir].filter(Boolean))].filter(d=>r.includes(d)&&!Un(e,o,d)),l=r.filter(d=>!Un(e,o,d));return s.length?s:l}function na(e,t,n,o){if(o.length<3)return;e.save(),e.font="900 11px ui-monospace, monospace",e.textAlign="center",e.lineWidth=3,e.fillStyle="rgba(255,255,255,.96)";let r=n.branchMode==="pass"&&o.length>=4?"pass":o.includes(n.branchMode)?n.branchMode:"random";e.strokeStyle=r!=="random"?f.warn:f.green,j(e,t.x-24,t.y+11,48,24,!0,!0),e.fillStyle=r!=="random"?f.warn:f.ink;let s=jl(r);e.fillText(s,t.x,t.y+27),e.restore()}function Hi(e,t){let n=Oe(t.x,t.y);if(!n)return null;let o=A(n.col,n.row);if(Math.hypot(o.x-t.x,o.y-t.y)>y.cell*.38)return null;let r=I(n.col,n.row);return e.conveyorTiles.has(r)?r:null}function qi(e,t,n){if(!(!n||n.length<2))for(let o=0;oMath.abs(Math.sin((ae-B)/2))<.01)||ee.push(B)}}function oa(e,t,n){return n?ta(e,t,n).map(o=>pe.find(r=>r.name===o)?.angle).filter(o=>Number.isFinite(o)):[]}function ra(e){let t=new Map;for(let[n,o]of e.conveyorMeta||[]){let r=oa(e,n,o);r.length&&t.set(n,r)}for(let n of e.eggFarms)qi(e,t,ze(e,n)?.route);for(let n of e.scanners)for(let o of["left","right"])qi(e,t,Mn(e,o,de(n),n.id)?.route);return t}function ia(e,t,n){let o=n.slice(0,3);o.length&&(e.save(),e.fillStyle=f.ink,e.globalAlpha=.86,o.forEach((r,s)=>{let l=(s-(o.length-1)/2)*4,d=Math.cos(r+Math.PI/2),w=Math.sin(r+Math.PI/2);sa(e,t.x+d*l,t.y+w*l,r,6,5)}),e.restore())}function sa(e,t,n,o,r=11,s=8){e.save(),e.translate(t,n),e.rotate(o),e.beginPath(),e.moveTo(r,0),e.lineTo(-r*.72,-s),e.lineTo(-r*.72,s),e.closePath(),e.fill(),e.restore()}function la(e,t){e.save(),e.lineWidth=2,e.font="900 12px ui-monospace, monospace",e.textAlign="center";for(let n of t.scanners){let o=mo(n),r=[{type:"inputA",p:Re(n,"inputA"),label:"IN",color:f.green},{type:"left",p:Re(n,"left"),label:wo(o.left),color:xo(o.left)},{type:"right",p:Re(n,"right"),label:wo(o.right),color:xo(o.right)}];for(let s of r){if(!s.p)continue;let l=A(s.p.col,s.p.row),d=Fr(t,n,s.type||(s.label==="IN"?"inputA":null))||t.conveyorTiles.has(I(s.p.col,s.p.row));e.fillStyle=d?s.color:"#f4c2c2",e.strokeStyle=f.ink,j(e,l.x-18,l.y-12,36,24,!0,!0),e.fillStyle=d&&s.color===f.white?f.ink:f.white,e.fillText(s.label,l.x,l.y+4)}}e.restore()}function aa(e,t){for(let n of t.eggFarms)ca(e,n,t)}function ca(e,t,n){let o=A(t.col,t.row);if(e.save(),Rt(e,He.eggFarm,o.x-29,o.y-29,58,58)){Ze(e,o.x-29,o.y-29,58,58,t);let l=1+Math.max(0,Number(t.extraEggOutlet)||0);e.fillStyle="rgba(255,255,255,.92)",e.strokeStyle=f.ink,e.lineWidth=2,j(e,o.x+9,o.y+12,30,18,!0,!0),e.fillStyle=f.ink,e.font="900 10px ui-monospace, monospace",e.textAlign="center",e.fillText(`O${l}`,o.x+24,o.y+24),Ue(e,n,"eggFarm",t.id,o.x,o.y,68,68),e.restore();return}e.fillStyle=f.white,e.strokeStyle=f.ink,e.lineWidth=4,j(e,o.x-28,o.y-28,56,56,!0,!0),e.fillStyle=f.green,e.font="900 19px ui-monospace, monospace",e.textAlign="center",e.fillText("EGG",o.x,o.y-4);let r=1+Math.max(0,Number(t.extraEggOutlet)||0);e.font="900 11px ui-monospace, monospace",e.fillStyle=f.ink,e.fillText(`L${t.level} O${r}`,o.x,o.y+13),e.fillText(`${t.nextSpawn.toFixed(1)}s`,o.x,o.y+26);let s=Math.max(0,Math.min(1,t.shutterProgress||0));if(s>0){let l=56*s;e.fillStyle=f.ink,j(e,o.x-28,o.y-28,56,l,!0,!1),e.strokeStyle=f.white,e.lineWidth=2;for(let d=o.y-24;d=1&&(e.fillStyle=f.white,e.font="900 12px ui-monospace, monospace",e.fillText("SHUT",o.x,o.y+4))}Ze(e,o.x-28,o.y-28,56,56,t),Ue(e,n,"eggFarm",t.id,o.x,o.y,68,68),e.restore()}function ua(e,t){for(let n of t.scanners)da(e,n,t)}function da(e,t,n){let o=de(t);if(e.save(),t.kind==="manual"&&(n.manualCombo?.count||0)>=10){let w=n.manualCombo.count;e.save(),e.globalAlpha=Math.min(.55,.16+w/120),e.strokeStyle=w>=30?f.warn:f.green,e.lineWidth=Math.min(14,5+Math.floor(w/10)),e.shadowColor=e.strokeStyle,e.shadowBlur=Math.min(28,8+w),j(e,o.x-54,o.y-76,108,152,!1,!0),e.restore()}let r=t.kind==="auto"?He.scannerAuto:He.scannerManual;if(Rt(e,r,o.x-50,o.y-73,100,146)){t.kind==="auto"?Ze(e,o.x-50,o.y-73,100,146,t):Yi(e,t,o),Ue(e,n,"scanner",t.id,o.x,o.y,108,154),e.restore();return}e.fillStyle=t.kind==="auto"?"#d8ffe2":f.white,e.strokeStyle=f.ink,e.lineWidth=4,j(e,o.x-50,o.y-73,100,146,!0,!0),e.fillStyle=t.kind==="auto"?f.green:f.ink,e.font="900 19px ui-monospace, monospace",e.textAlign="center",e.fillText(`${t.kind==="auto"?"AUTO":`S${(t.slot??0)+1}`}`,o.x,o.y-48),e.font="900 11px ui-monospace, monospace";let s=t.keys?.left?.label||(t.slot===0?"A":"Left"),l=t.keys?.right?.label||(t.slot===0?"D":"Right");e.fillText(t.role===0?`${s}:M ${l}:NEXT`:`${s}:WASTE ${l}:TRUCK`,o.x,o.y-28);let d=t.queue.length;t.kind==="manual"?(Yi(e,t,o),e.fillStyle=d>0?f.green:f.muted,e.font="900 10px ui-monospace, monospace",e.fillText(`Q:${d}`,o.x,o.y+54)):(e.fillStyle=d>0?f.green:f.muted,e.font="900 12px ui-monospace, monospace",e.fillText(`Q:${d} CD:${t.cooldown.toFixed(1)}`,o.x,o.y+54),Ze(e,o.x-50,o.y-73,100,146,t)),Ue(e,n,"scanner",t.id,o.x,o.y,108,154),e.restore()}function Yi(e,t,n){if(t.kind!=="manual")return;let r=(t.slot??0)===0?[{side:"left",text:"A"},{side:"right",text:"D"}]:[{side:"left",text:"\u2190"},{side:"right",text:"\u2192"}];r[0].text=t.keys?.left?.label||r[0].text,r[1].text=t.keys?.right?.label||r[1].text;let s=t.keyPressTime>0?t.keyPressSide:null,l=n.x-30,d=n.y+18;e.save(),e.lineWidth=3,e.fillStyle="#f7fff5",e.strokeStyle=f.ink,j(e,l-4,d-4,68,25,!0,!0),e.font="900 19px ui-monospace, monospace",e.textAlign="center";for(let w=0;w.8,s=t.y+Math.sin(t.bob)*(r?5:1.5);if(e.save(),n&&(e.strokeStyle=f.green,e.lineWidth=5,e.beginPath(),e.arc(t.x,s,t.radius+8,0,Math.PI*2),e.stroke()),r&&(e.strokeStyle=f.danger,e.lineWidth=3,e.beginPath(),e.arc(t.x,s,t.radius+12+Math.sin(t.bob*2)*4,0,Math.PI*2),e.stroke()),t.sex==="poop")Xo(e,t.x,s,t.radius);else{let l=t.sex==="male"?He.chickMale:He.chickFemale;Rt(e,l,t.x-t.radius,s-t.radius,t.radius*2,t.radius*2)||(e.fillStyle=t.sex==="male"?f.male:f.female,e.strokeStyle=f.ink,e.lineWidth=5,e.beginPath(),e.arc(t.x,s,t.radius,0,Math.PI*2),e.fill(),e.stroke(),e.fillStyle=f.ink,e.beginPath(),e.arc(t.x-6,s-5,2.5,0,Math.PI*2),e.arc(t.x+6,s-5,2.5,0,Math.PI*2),e.fill(),e.fillStyle="#ffaa2e",e.beginPath(),e.moveTo(t.x,s+2),e.lineTo(t.x+9,s+6),e.lineTo(t.x,s+10),e.closePath(),e.fill())}e.restore()}function va(e,t){let n=null,o=1/0;for(let[r,s]of e.componentLookup.entries()){let l=Q(r),d=A(l.col,l.row),w=Math.hypot(t.x-d.x,t.y-d.y);wzi(o.type,o.id)===zi(t,n))}function Ue(e,t,n,o,r,s,l,d){ka(t,n,o)&&(e.save(),e.fillStyle="rgba(34,185,79,.18)",e.strokeStyle=f.green,e.lineWidth=(t.multiSelected||[]).length>1?6:4,e.setLineDash((t.multiSelected||[]).length>1?[]:[8,5]),j(e,r-l/2,s-d/2,l,d,!0,!0),e.restore())}function Ca(e,t,n,o){if(t.phase==="build"&&(t.multiSelected||[]).length>1){e.save(),e.fillStyle="rgba(255,255,255,.96)",e.strokeStyle=f.ink,e.lineWidth=3,j(e,54-t.view.x,66-t.view.y,214,42,!0,!0),e.fillStyle=f.ink,e.font="900 11px ui-monospace, monospace",e.fillText(`${t.multiSelected.length} ITEMS SELECTED`,65-t.view.x,83-t.view.y),e.fillStyle=f.muted,e.font="900 11px ui-monospace, monospace",e.fillText("drag any selected item to move group",65-t.view.x,99-t.view.y),e.restore();return}}function Sa(e,t,n){if(!n.cardTargetPick?.pending)return;let o=Ko(n);e.save(),e.fillStyle="rgba(0,0,0,.66)",e.beginPath();let r=n.view?.scale||1;e.rect(-(n.view?.x||0)/r,-(n.view?.y||0)/r,t.width/r,t.height/r);for(let w of o){let M=w.bounds,$=8;e.rect(M.x-$,M.y-$,M.w+$*2,M.h+$*2)}e.fill("evenodd");let s=n.cardTargetPick?.mode==="blockedCell";if(!o.length){e.fillStyle="rgba(255,255,255,.95)",e.strokeStyle=f.ink,e.lineWidth=3,j(e,(260-(n.view?.x||0))/r,(96-(n.view?.y||0))/r,420/r,42/r,!0,!0),e.fillStyle=f.danger,e.font="900 12px ui-monospace, monospace",e.textAlign="center",e.fillText(s?"NO BLOCKED CELL. PRESS ESC.":"NO VALID UPGRADE TARGET. PRESS ESC.",(470-(n.view?.x||0))/r,(122-(n.view?.y||0))/r),e.restore();return}e.fillStyle="rgba(255,255,255,.95)",e.strokeStyle=f.ink,e.lineWidth=3;let l=Math.max(1,n.cardTargetPick?.remaining||1),d=n.cardTargetPick?.mode==="autoScannerMenu"?"AUTO SCANNER ROUTE SETTINGS. CLICK EMPTY AREA TO CLOSE.":s?`CLICK BLOCKED CELL: ${l} LEFT. PRESS ESC TO CANCEL.`:"CLICK AN UPGRADEABLE MACHINE. PRESS ESC TO CANCEL.";j(e,(216-(n.view?.x||0))/r,(96-(n.view?.y||0))/r,560/r,42/r,!0,!0),e.fillStyle=f.ink,e.font="900 12px ui-monospace, monospace",e.textAlign="center",e.fillText(d,(496-(n.view?.x||0))/r,(122-(n.view?.y||0))/r),e.restore()}function Ma(e,t){if(t.phase!=="build"||!t.selectionBox)return;let n=t.selectionBox,o=Math.min(n.x1,n.x2),r=Math.min(n.y1,n.y2),s=Math.abs(n.x2-n.x1),l=Math.abs(n.y2-n.y1);e.save(),e.fillStyle="rgba(34,185,79,.12)",e.strokeStyle=f.green,e.lineWidth=2,e.setLineDash([7,4]),j(e,o,r,s,l,!0,!0),e.restore()}function Ta(e,t){let n=[...t.effects].sort((o,r)=>(o.priority??Ce.ambient)-(r.priority??Ce.ambient));for(let o of n){let r=Math.max(0,o.life/o.maxLife);e.save(),e.globalAlpha=r,o.type==="meat"?(e.fillStyle=f.green,j(e,o.x-o.size/2,o.y-o.size/2,o.size*1.5,o.size,!0,!1)):o.type==="sludge"?(e.fillStyle=f.poop,j(e,o.x-o.size/2,o.y-o.size/2,o.size*1.4,o.size,!0,!1)):o.type==="spawn"?(e.strokeStyle=f.green,e.lineWidth=3,e.beginPath(),e.arc(o.x,o.y,o.size+(1-r)*20,0,Math.PI*2),e.stroke()):o.type==="spark"?(e.fillStyle=o.color||f.danger,j(e,o.x-o.size/2,o.y-o.size/2,o.size,o.size,!0,!1)):o.type==="smoke"?(e.fillStyle="rgba(16,32,21,.24)",e.beginPath(),e.arc(o.x,o.y,o.size*(1.2-r*.2),0,Math.PI*2),e.fill()):o.type==="shockwave"?(e.strokeStyle=o.color||f.danger,e.lineWidth=4,e.beginPath(),e.arc(o.x,o.y,o.size+(1-r)*(o.maxSize||44),0,Math.PI*2),e.stroke()):o.type==="scannerPulse"?(e.strokeStyle=o.color||f.green,e.lineWidth=5,e.setLineDash([8,6]),e.beginPath(),e.arc(o.x,o.y,o.size+(1-r)*38,0,Math.PI*2),e.stroke()):o.type==="shred"?(e.fillStyle=o.color||f.ink,e.save(),e.translate(o.x,o.y),e.rotate(o.rot||0),j(e,-o.w/2,-o.h/2,o.w,o.h,!0,!1),e.restore()):o.type==="load"?(e.strokeStyle=o.color||f.green,e.lineWidth=3,e.beginPath(),e.moveTo(o.x-14,o.y),e.lineTo(o.x+14,o.y),e.moveTo(o.x,o.y-14),e.lineTo(o.x,o.y+14),e.stroke()):o.type==="erase"?(e.strokeStyle=f.ink,e.lineWidth=3,e.setLineDash([4,4]),j(e,o.x-o.size/2,o.y-o.size/2,o.size,o.size,!1,!0)):o.type==="rage"?(e.fillStyle=o.color||f.danger,e.font="900 16px ui-monospace, monospace",e.textAlign="center",e.fillText("!",o.x,o.y)):o.type==="flyingChick"&&(o.sex==="poop"?Xo(e,o.x,o.y,o.radius||11):Jo(e,o.x,o.y,o.sex,o.sex==="male")),e.restore()}}function ga(e,t){e.save(),e.font="900 19px ui-monospace, monospace",e.textAlign="center";for(let n of t.floatingTexts){let o=Math.max(0,n.life/n.maxLife);e.globalAlpha=o,e.lineWidth=5,e.strokeStyle=f.white,e.fillStyle=n.color,e.strokeText(n.text,n.x,n.y),e.fillText(n.text,n.x,n.y)}e.restore()}function Ea(e,t){let n=t.repairman;if(!(!!n?.active||t.phase==="build"&&!!n?.hiredForNextDay))return;e.save(),e.lineWidth=3,e.fillStyle="#fff0d1",e.strokeStyle=f.ink,e.beginPath(),e.arc(n.x,n.y,17,0,Math.PI*2),e.fill(),e.stroke(),e.fillStyle="#ffd6e7",e.beginPath(),e.arc(n.x-6,n.y-2,3.2,0,Math.PI*2),e.fill(),e.beginPath(),e.arc(n.x+6,n.y-2,3.2,0,Math.PI*2),e.fill();let r=!!t.cardEffects?.laborExploitation;e.fillStyle=r?f.danger:f.ink,e.beginPath(),e.arc(n.x-5,n.y-5,r?2.8:2.2,0,Math.PI*2),e.fill(),e.beginPath(),e.arc(n.x+5,n.y-5,r?2.8:2.2,0,Math.PI*2),e.fill(),e.strokeStyle=f.ink,e.lineWidth=r?3:2,e.beginPath(),e.arc(n.x,n.y+(r?0:1),r?8:7,.1*Math.PI,.9*Math.PI),e.stroke(),e.fillStyle=f.green,j(e,n.x-9,n.y+14,18,8,!0,!0),e.strokeStyle=f.ink,e.lineWidth=3,e.beginPath(),e.moveTo(n.x-11,n.y+18),e.lineTo(n.x-20,n.y+24),e.stroke(),e.beginPath(),e.moveTo(n.x+11,n.y+18),e.lineTo(n.x+20,n.y+24),e.stroke(),e.fillStyle=f.ink,e.font="900 11px ui-monospace, monospace",e.textAlign="center",e.fillText(n.active?"FIX":"HIRED",n.x,n.y+33),n.target?.label?qo(e,n.x,n.y-24,n.target.label,f.green):!n.active&&n.hiredForNextDay&&qo(e,n.x,n.y-24,"NEXT DAY",f.green),e.restore()}function Pa(e,t,n){e.save(),e.font="900 19px ui-monospace, monospace",e.textAlign="left",e.fillStyle=f.ink,e.fillStyle="rgba(255,255,255,.92)",e.strokeStyle=f.ink,e.lineWidth=3,j(e,n.width-284,18,260,34,!0,!0),e.fillStyle=f.green,e.font="900 12px ui-monospace, monospace",e.fillText(fn,n.width-270,40),e.font="900 19px ui-monospace, monospace",e.fillStyle=f.ink;let o=[];t.phase==="running"&&o.push(t.timeLeft<=0?"TIME UP: FARMS SHUT. CLEAR LINE.":"A/D: S1 \u2190/\u2192: S2"),t.phase==="build"&&o.push(`BUILD: ${t.buildTool||"SELECT"} | connect 1 EGG route to any exit`);let r=76;for(let s of o)e.strokeStyle=f.white,e.lineWidth=4,e.strokeText(s,24,r),e.fillText(s,24,r),r+=20;e.restore()}var Zi=[[{key:"a",code:"KeyA",label:"A"},{key:"d",code:"KeyD",label:"D"}],[{key:"arrowleft",code:"ArrowLeft",label:"Left"},{key:"arrowright",code:"ArrowRight",label:"Right"}],[{key:"j",code:"KeyJ",label:"J"},{key:"l",code:"KeyL",label:"L"}],[{key:"f",code:"KeyF",label:"F"},{key:"h",code:"KeyH",label:"H"}],[{key:"q",code:"KeyQ",label:"Q"},{key:"e",code:"KeyE",label:"E"}],[{key:"z",code:"KeyZ",label:"Z"},{key:"x",code:"KeyX",label:"X"}],[{key:"u",code:"KeyU",label:"U"},{key:"o",code:"KeyO",label:"O"}],[{key:"n",code:"KeyN",label:"N"},{key:"m",code:"KeyM",label:"M"}]];function Ra(e){let t=Zi[e]||Zi[0];return{left:{...t[0]},right:{...t[1]}}}function ji(e,t,n){let o={type:"eggFarm",id:e.nextId++,col:t,row:n,level:1,nextSpawn:2,lastInterval:2,extraEggOutlet:0,price:ht.eggFarm,builtSession:e.buildSession};return o.nextSpawn=vt(o),o.lastInterval=o.nextSpawn,o}function es(e,t,n,o){let r=o==="auto"?ve.autoScanner.price:ve.manualScanner.price,s=new Set(e.scanners.filter(d=>d.kind==="manual").map(d=>d.slot)),l=0;for(;s.has(l);)l+=1;return{type:"scanner",id:e.nextId++,kind:o,slot:o==="manual"?l:null,role:o==="manual"?l%2:0,col:t,row:n,level:1,queue:[],cooldown:0,price:r,builtSession:e.buildSession,autoMode:"standard",keys:o==="manual"?Ra(l):null}}function Wn(e){let t=(e.x-y.x)/y.cell,n=(e.y-y.y)/y.cell,o=Math.max(0,Math.min(y.cols-1,Math.round(t-.5))),r=Math.max(0,Math.min(y.rows-1,Math.round(n-.5))),s=Math.abs(e.x-y.x),l=Math.abs(e.x-(y.x+y.cols*y.cell)),d=Math.abs(e.y-y.y),w=Math.abs(e.y-(y.y+y.rows*y.cell)),M=[["left",s],["right",l],["top",d],["bottom",w]].sort(($,F)=>$[1]-F[1])[0][0];return M==="left"?{side:M,entry:{col:0,row:r}}:M==="right"?{side:M,entry:{col:y.cols-1,row:r}}:M==="top"?{side:M,entry:{col:o,row:0}}:{side:M,entry:{col:o,row:y.rows-1}}}function _n(e,t,n){let o={x:y.x+t.col*y.cell+y.cell/2,y:y.y+t.row*y.cell+y.cell/2},r={left:y.x,right:y.x+y.cols*y.cell,top:y.y,bottom:y.y+y.rows*y.cell};return e.entry={...t},e.side=n,n==="left"?(e.x=r.left-e.w-18,e.y=o.y-e.h/2):n==="right"?(e.x=r.right+18,e.y=o.y-e.h/2):n==="top"?(e.x=o.x-e.w/2,e.y=r.top-e.h-18):(e.x=o.x-e.w/2,e.y=r.bottom+18),e}function ts(e,t,n,o){let{entry:r,side:s}=Wn(n),l=ve[t],d=l?{name:l.name,w:l.body?.w||168,h:l.body?.h||110,price:l.price}:{name:t[0].toUpperCase()+t.slice(1),w:168,h:110,price:o},w={type:"facility",id:t,name:d.name,x:0,y:0,w:d.w,h:d.h,level:1,price:o||d.price,builtSession:e.buildSession};return _n(w,r,s)}function Da(e){return Math.random()({x:s.x,y:s.y})),r=o[0];return{id:e.nextId++,sex:n,x:r.x,y:r.y,route:o,targetIndex:o.length>1?1:o.length,stage:t.stage||"belt",scannerId:t.scannerId??null,nextScannerId:null,pendingScannerId:null,radius:n==="poop"?15:17,bob:Math.random()*Math.PI*2,queueIndex:-1,queueRoute:null}}function os({game:e,canvasPoint:t,updatePanels:n,equipmentAtPoint:o,fail:r,pointInGrid:s,rectOfFacility:l,rectsOverlap:d}){function w(x){return x?x.type==="conveyor"?`conveyor:${x.id||x.oldKey}`:x.type==="facility"?`facility:${x.id||x.ref?.id}`:`${x.type}:${x.id||x.ref?.id}`:""}function M(x){return x?x.type==="conveyor"?{type:"conveyor",id:x.oldKey}:{type:x.type,id:x.ref.id}:null}function $(x){let C=w(M(x));return(e.multiSelected||[]).some(O=>w(O)===C)}function F(x){return x?x.type==="eggFarm"?e.eggFarms.find(C=>C.id===x.id)||null:x.type==="scanner"?e.scanners.find(C=>C.id===x.id)||null:x.type==="facility"?e.facilities[x.id]||null:x.type==="conveyor"&&e.conveyorTiles.has(x.id)?{type:"conveyor",id:x.id}:null:null}function B(){let x=[];for(let C of e.conveyorTiles){let O=Q(C),Y=A(O.col,O.row);x.push({type:"conveyor",id:C,center:Y,cell:O})}for(let C of e.eggFarms)x.push({type:"eggFarm",id:C.id,ref:C,center:A(C.col,C.row),cell:{col:C.col,row:C.row}});for(let C of e.scanners)x.push({type:"scanner",id:C.id,ref:C,center:de(C),cell:{col:C.col,row:C.row}});for(let C of Object.values(e.facilities)){let O={x:C.x+C.w/2,y:C.y+C.h/2},Y=C.entry?A(C.entry.col,C.entry.row):O;x.push({type:"facility",id:C.id,ref:C,center:O,portCenter:Y})}return x}function ee(x,C){let O=Math.min(C.x1,C.x2),Y=Math.max(C.x1,C.x2),te=Math.min(C.y1,C.y2),se=Math.max(C.y1,C.y2);return x.x>=O&&x.x<=Y&&x.y>=te&&x.y<=se}function ae(x){let C=[];for(let O of B())(ee(O.center,x)||O.portCenter&&ee(O.portCenter,x))&&C.push({type:O.type,id:O.id});return e.multiSelected=C,e.selected=C[0]||null,n(),C.length}function V(){e.multiSelected=[],e.selected=null,n()}function ce(x){let C=t(x);return e.selectionBox={x1:C.x,y1:C.y,x2:C.x,y2:C.y},e.multiSelected=[],e.selected=null,n(),!0}function he(x){if(!e.selectionBox)return;let C=t(x);e.selectionBox.x2=C.x,e.selectionBox.y2=C.y}function Z(){if(!e.selectionBox)return 0;let x=e.selectionBox,C=Math.abs(x.x2-x.x1),O=Math.abs(x.y2-x.y1);return e.selectionBox=null,C<6&&O<6?(V(),0):ae(x)}function g(x){let C=M(x);return C?(e.multiSelected||[]).length&&$(x)?[...e.multiSelected]:[C]:[]}function _(x){let C=F(x);if(!C)return null;if(x.type==="conveyor"){let O=Q(x.id);return{...x,obj:C,oldKey:x.id,currentKey:x.id,meta:e.conveyorMeta.get(x.id)||{price:Be("conveyor"),builtSession:null},col:O.col,row:O.row}}return x.type==="eggFarm"||x.type==="scanner"?{...x,obj:C,col:C.col,row:C.row}:x.type==="facility"?{...x,obj:C,x:C.x,y:C.y,w:C.w,h:C.h,entry:C.entry?{...C.entry}:null,side:C.side,center:{x:C.x+C.w/2,y:C.y+C.h/2}}:null}function U(x){let C=t(x),O=o(C);if(!O)return!1;let Y=g(O),te=Y.map(_).filter(Boolean);return te.length?(e.groupDrag={start:C,selections:Y,origins:te,historySnapshot:nn(e),committed:!1,lastDCol:0,lastDRow:0},e.multiSelected=Y,e.selected=Y[0]||null,n(),!0):!1}function ie(){!e.groupDrag||e.groupDrag.committed||(e.undoStack.push(e.groupDrag.historySnapshot),e.redoStack=[],e.groupDrag.committed=!0)}function me(x,C,O){let Y=I(x,C),te=Ct(e,x,C);if(te&&!O.has(`eggFarm:${te.id}`))return!0;let se=St(e,x,C);return!!(se&&!O.has(`scanner:${se.id}`)||e.conveyorTiles.has(Y)&&!O.has(`conveyor:${Y}`))}function fe(x,C,O){let Y=[];for(let te of e.groupDrag.origins){if(te.type!=="facility")continue;let se={x:te.center.x+x,y:te.center.y+C},{entry:ke,side:ue}=Wn(se);if(!s(ke.col,ke.row)||e.blockedCells?.has?.(I(ke.col,ke.row)))return!0;let et={...te.obj,entry:{...ke},side:ue};_n(et,ke,ue),Y.push({id:te.id,rect:l(et)})}for(let te of Y)for(let se of Object.values(e.facilities))if(!O.has(`facility:${se.id}`)&&d(te.rect,l(se),12))return!0;for(let te=0;teJ.type==="conveyor");for(let J of et){let Se=J.currentKey||J.oldKey;e.conveyorTiles.delete(Se),e.conveyorMeta.delete(Se)}for(let J of et){let Se=I(J.col+te,J.row+se);e.conveyorTiles.add(Se),e.conveyorMeta.set(Se,J.meta||{price:Be("conveyor"),builtSession:null,uses:0}),J.currentKey=Se}for(let J of e.groupDrag.origins)if(J.type==="eggFarm")J.obj.col=J.col+te,J.obj.row=J.row+se;else if(J.type==="scanner")J.obj.col=J.col+te,J.obj.row=J.row+se;else if(J.type==="facility"){let Se={x:J.center.x+O,y:J.center.y+Y},{entry:qe,side:We}=Wn(Se);_n(J.obj,qe,We)}e.groupDrag.selections=e.groupDrag.origins.map(J=>J.type==="conveyor"?{type:"conveyor",id:J.currentKey||J.oldKey}:{type:J.type,id:J.id}),e.multiSelected=[...e.groupDrag.selections],e.selected=e.multiSelected[0]||null,xe(e),n()}function G(){e.groupDrag&&(xe(e),e.groupDrag=null,n())}return{selectionFromHit:M,startSelectionBox:ce,updateSelectionBox:he,finishSelectionBox:Z,startGroupDrag:U,updateGroupDrag:E,finishGroupDrag:G}}function rs({game:e,ui:t,canvas:n,canvasPoint:o,onUpdatePanels:r}){function s(){r&&r()}let l=null,d=!1,w=null,M=!1,$=["random","pass","up","right","down","left"],F={random:"RND",pass:"PASS",up:"UP",right:"RT",down:"DN",left:"LF"},B={w:48,h:24,y:23};function ee(){return wn(e)}function ae(){e.gridCols=y.cols,e.gridRows=y.rows,e.gridOwnedCells=vr(e)}function V(i){if(i){y.y-=i*y.cell,y.rows+=i,e.gridChunkOriginY=(e.gridChunkOriginY||0)-Math.floor(i/10),e.ownedCells=xn(e.ownedCells,i),e.blockedCells=xn(e.blockedCells,i),e.conveyorTiles=xn(e.conveyorTiles,i),e.conveyorMeta=Mr(e.conveyorMeta,i);for(let u of e.eggFarms||[])u.row+=i;for(let u of e.scanners||[])u.row+=i;for(let u of Object.values(e.facilities||{}))u.entry&&(u.entry.row+=i)}}function ce(i){e.ownedCells=e.ownedCells||new Set;for(let u of Sr(i))e.ownedCells.add(I(u.col,u.row))}function he(i){return e.phase!=="build"||e.cardDraft?.pending||e.cardTargetPick?.pending?null:wt(e).find(u=>u&&Cr(u,i))||null}function Z(i="right"){if(i&&typeof i=="object"&&Number.isFinite(i.chunkX)&&Number.isFinite(i.chunkY))return i;let u=["right","up"].includes(i)?i:"right";return kr(e,u)}function g(i="right"){let u=Z(i);if(e.phase!=="build"||e.cardDraft?.pending||e.cardTargetPick?.pending)return{ok:!1,reason:"Cannot expand now."};if(!u)return{ok:!1,reason:"Invalid expansion lot."};let h=u.direction||"right",m=ee();if(e.cashy.cols&&(y.cols=D.colStart+D.cols),D.rowStart+D.rows>y.rows&&(y.rows=D.rowStart+D.rows),ce(D),Tr(e,D.chunkX,D.chunkY),e.gridExpansionPurchases=(e.gridExpansionPurchases||0)+1,e.gridExpansionPurchasesByDirection[h]=(e.gridExpansionPurchasesByDirection[h]||0)+1,ae();let ne=hr({seed:`${e.runSeed}:grid-expansion:${D.chunkX},${D.chunkY}:${e.gridExpansionPurchases}`,colStart:D.colStart,colEnd:D.colStart+D.cols,rowStart:D.rowStart,rowEnd:D.rowStart+D.rows,targetRatio:H.map.blockedRatio});for(let It of ne)e.blockedCells.add(It);xe(e);let le=D.cols*D.rows,be=xt(D);return N(e,be.x+be.w/2,be.y+be.h/2,`${h.toUpperCase()} +${le} CELLS -${S(m)}`,f.green),s(),{ok:!0,direction:h,cost:m,addedCells:le,blocked:ne.size,chunkX:D.chunkX,chunkY:D.chunkY}}function _(i,u){return i>=0&&i=0&&u!u.has(m.id)&&me(h,ie(m),12))}function E(i,u,h=null){if(!_(i,u))return!1;if(U(i,u))return!0;let m=I(i,u),T=Ct(e,i,u);if(T&&!(h?.type==="eggFarm"&&h.ref?.id===T.id))return!0;let D=St(e,i,u);return!!(D&&!(h?.type==="scanner"&&h.ref?.id===D.id)||e.conveyorTiles.has(m)&&!(h?.type==="conveyor"&&h.oldKey===m))}function G(i,u,h=null){let m={col:i,row:u};for(let T of ot(m)){if(!_(T.col,T.row)||U(T.col,T.row))return!0;let D=I(T.col,T.row);if(Ct(e,T.col,T.row))return!0;let le=St(e,T.col,T.row);if(le&&!(h?.type==="scanner"&&h.ref?.id===le.id)||e.conveyorTiles.has(D))return!0}return!1}function x(){return e.selected?e.selected.type==="eggFarm"?e.eggFarms.find(i=>i.id===e.selected.id)||null:e.selected.type==="scanner"?e.scanners.find(i=>i.id===e.selected.id)||null:e.selected.type==="conveyor"?e.conveyorTiles.has(e.selected.id)?{type:"conveyor",id:e.selected.id}:null:e.selected.type==="facility"&&e.facilities[e.selected.id]||null:null}function C(i){return i?i.type==="eggFarm"?`${bt("eggFarm")} #${i.id}`:i.type==="scanner"?`${i.kind==="auto"?bt("autoScanner"):bt("manualScanner")} #${i.id}`:i.type==="conveyor"?e.conveyorMeta.get(i.id)?.kind==="boostConveyor"?bt("boostConveyor"):bt("conveyor"):i.type==="facility"?i.name:"Equipment":"Equipment"}function O(i){return i?.type==="conveyor"?e.conveyorMeta?.get(i.oldKey||i.ref?.id)?.price||gn(i):gn(i)}function Y(){return!!e.cardEffects?.usedMachineActive}function te(){return Y()?{usedMachine:!0,noRefund:!0,durabilityBaseMultiplier:.6}:{usedMachine:!1,noRefund:!1,durabilityBaseMultiplier:1}}function se(i){let u=te();return Object.assign(i,u),i}function ke(i){let u=i?.ref||i||{};if(i?.type==="eggFarm"||u.type==="eggFarm")return e.eggFarms.length<=1?"Cannot sell the last EGG FARM.":"";if((i?.type==="facility"||u.type==="facility")&&["mixer","trash","truck"].includes(u.baseId||u.id)){let h=u.baseId||u.id;if(Object.values(e.facilities).filter(T=>(T.baseId||T.id)===h).length<=1)return`Cannot sell the last ${u.shortName||u.name||u.id}.`}return""}function ue(i){t.buildStatus.textContent=i,N(e,n.width/2-e.view.x,86-e.view.y,i.toUpperCase(),f.danger)}function et(i){if(!i)return null;let u=I(i.col,i.row);return e.conveyorMeta.get(u)||null}function J(i){return i?(Array.isArray(i.outDirs)||(i.outDirs=[]),i.outDirs=[...new Set(i.outDirs.filter(u=>["right","left","down","up"].includes(u)))],$.includes(i.branchMode)||(i.branchMode="random"),i.kind||(i.kind="conveyor"),i.speedMultiplier||(i.speedMultiplier=i.kind==="boostConveyor"?2:1),i.outDirs):[]}function Se(i){return i?pe.filter(u=>e.conveyorTiles.has(I(i.col+u.dc,i.row+u.dr))).map(u=>u.name):[]}function qe(i,u){let h=pe.find(le=>le.name===u);if(!i||!h)return!1;let m=I(i.col+h.dc,i.row+h.dr),T=e.conveyorMeta.get(m);if(!T)return!1;let D=h.opposite;return[...new Set([...Array.isArray(T.outDirs)?T.outDirs:[],T.dir].filter(Boolean))].includes(D)}function We(i){if(!e.conveyorTiles.has(i))return[];let u=Q(i),h=e.conveyorMeta.get(i),m=J(h),T=Se(u),D=[...new Set([...m,h?.dir].filter(Boolean))].filter(be=>T.includes(be)&&!qe(u,be)),ne=T.filter(be=>!qe(u,be)),le=D.length?D:ne;return h&&(h.outDirs=m.filter(be=>T.includes(be)),h.dir&&!T.includes(h.dir)&&(h.dir=h.outDirs[0]||null),h.branchMode!=="random"&&h.branchMode!=="pass"&&!le.includes(h.branchMode)&&(h.branchMode="random"),h.branchMode==="pass"&&le.length<4&&(h.branchMode="random")),le}function Fe(i,u){let h=mt(i,u);if(!h)return!1;let m=I(i.col,i.row),T=I(u.col,u.row),D=e.conveyorMeta.get(m),ne=e.conveyorMeta.get(T);if(!D||!ne)return!1;let le=J(D),be=!le.includes(h)||D.dir!==h||!ne.dir;return le.includes(h)||le.push(h),D.dir=h,ne.dir||(ne.dir=h),be}function At(i){return i?We(I(i.col,i.row)):[]}function zn(i){if(!i)return!1;let u=I(i.col,i.row),h=e.conveyorMeta.get(u);if(!h)return!1;let m=At(i);if(!m.length)return!1;let T=J(h),D=h.dir&&m.includes(h.dir)?h.dir:T.find(be=>m.includes(be)),ne=m[(Math.max(-1,m.indexOf(D))+1)%m.length];if(!ne)return!1;Le(e),h.dir=ne,h.outDirs=[ne],h.branchMode="random",e.selected={type:"conveyor",id:u};let le=A(i.col,i.row);return N(e,le.x,le.y-22,jn(ne),f.ink),xe(e),s(),!0}function cn(){return H.facilities[e.buildTool==="boostConveyor"?"boostConveyor":"conveyor"]}function pt(i,u=null){if(!i)return{ok:!1,reason:oe.fail.outOfGrid};let{col:h,row:m}=i,T=I(h,m);if(e.conveyorTiles.has(T))return{ok:!1,exists:!0};if(U(h,m))return{ok:!1,reason:"Cannot build on blocked ground."};if(Ct(e,h,m)||St(e,h,m))return{ok:!1,reason:oe.fail.cellOccupied};let D=cn(),ne=D.id||"conveyor",le=Be(ne,e);if(e.cash=Math.abs(m)?{col:i.col+Math.sign(h),row:i.row}:{col:i.col,row:i.row+Math.sign(m)}}function Xn(i){d=!1,w=i?{col:i.col,row:i.row}:null,M=!1,l=null,Ht(i)}function $t(i){if(!i)return;if(!l)return Ht(i);let u=l,h=0;for(;(u.col!==i.col||u.row!==i.row)&&h<64;){let m=Vn(u,i);if(Ht(m),u=l||m,h+=1,!l)break}}function un(){!M&&w&&e.conveyorTiles.has(I(w.col,w.row))&&zn(w),w=null,M=!1,l=null,d=!1}function qt(i){if(!i)return ue(oe.fail.outOfGrid);let{col:u,row:h}=i;if(e.buildTool==="conveyor"||e.buildTool==="boostConveyor"){let m=pt(i,null);if(!m.ok)return ue(m.reason||oe.fail.cellOccupied);xe(e);return}if(U(u,h))return ue("Cannot build on blocked ground.");if(e.buildTool==="manualScanner"||e.buildTool==="autoScanner"?G(u,h):E(u,h))return ue(oe.fail.cellOccupied);e.buildTool==="eggFarm"?Zo(u,h):e.buildTool==="manualScanner"?zt(u,h,"manual"):e.buildTool==="autoScanner"&&zt(u,h,"auto")}function Yt(i){let u=Oe(i.x,i.y);if(["conveyor","boostConveyor","eggFarm","manualScanner","autoScanner"].includes(e.buildTool))return qt(u);if(Jt.includes(e.buildTool))return Jn(i,e.buildTool)}function Zo(i,u){let h=Be("eggFarm",e);if(e.cashD.kind==="manual").length>=8)return ue("Manual Scanner limit reached (8 max).");let m=h==="auto"?Be("autoScanner",e):Be("manualScanner",e);if(e.cashT.id!==u.ref.id),ct(e,m.x,m.y),xe(e)}if(u.type==="scanner"){let m=de(u.ref);c(u,m.x,m.y),e.scanners=e.scanners.filter(T=>T.id!==u.ref.id),ct(e,m.x,m.y),xe(e)}if(u.type==="facility"){let m=u.ref;c(u,m.x+m.w/2,m.y+m.h/2),delete e.facilities[m.id],ct(e,m.x+m.w/2,m.y+m.h/2),xe(e)}e.selected=null,e.multiSelected=[],s()}function b(i){let u=Object.values(e.facilities).find(ne=>{if(i.x>=ne.x&&i.x<=ne.x+ne.w&&i.y>=ne.y&&i.y<=ne.y+ne.h)return!0;if(!ne.entry)return!1;let be=A(ne.entry.col,ne.entry.row);return Math.abs(i.x-be.x)<=y.cell*.46&&Math.abs(i.y-be.y)<=y.cell*.46});if(u)return{type:"facility",ref:u};let h=Oe(i.x,i.y);if(!h)return null;let m=Ct(e,h.col,h.row);if(m)return{type:"eggFarm",ref:m};let T=St(e,h.col,h.row);if(T)return{type:"scanner",ref:T};let D=I(h.col,h.row);return e.conveyorTiles.has(D)?{type:"conveyor",oldKey:D,ref:{type:"conveyor",id:D}}:null}let k=os({game:e,canvasPoint:o,updatePanels:s,equipmentAtPoint:b,fail:ue,pointInGrid:_,rectOfFacility:ie,rectsOverlap:me});function v(i){return null}function P(){let i=x();i&&(i.type==="conveyor"?p(A(...Object.values(Q(i.id)))):i.type==="eggFarm"?p(A(i.col,i.row)):i.type==="scanner"?p(de(i)):i.type==="facility"&&p({x:i.x+i.w/2,y:i.y+i.h/2}))}function R(i,u,h="primary-button"){let m=document.createElement("button");return m.type="button",m.className=h,m.textContent=i,m.addEventListener("click",u),m}function z(){e.cardTargetPick?.mode==="autoScannerMenu"&&(e.cardTargetPick=null),t.modal.classList.remove("visible","equipment-popover"),t.modal.style.removeProperty("--popover-x"),t.modal.style.removeProperty("--popover-y")}function X(i){t.modalTitle.textContent="Auto Scanner",e.cardTargetPick={pending:!0,mode:"autoScannerMenu",cardId:null,targetKeys:[`scanner:${i.id}`],remaining:1},t.modalBody.innerHTML=`
-

Left route key: ${b}

-

Right route key: ${k}

-

Role: ${s.role===0?"Male left / others right":"Poop left / others right"}

+

Left / right output routing for this AUTO scanner.

+

Current: ${i.role===0?"Male left / others right":"Poop left / others right"}

+

Cooldown: ${Qe(i).toFixed(1)}s.

+
`,t.modalActions.innerHTML="";let u=R("Male Left / Others Right",()=>{Le(e),i.role=0,z(),s()},i.role===0?"facility-action warn":"facility-action"),h=R("Poop Left / Others Right",()=>{Le(e),i.role=1,z(),s()},i.role===1?"facility-action warn":"facility-action");t.modalActions.append(u,h,R("Close",z,"facility-action")),Zn(i),t.modal.classList.add("visible","equipment-popover"),s()}function Te(i){return i?.label||i?.key||"?"}function dn(i){let u=i.key===" "?"Space":i.key.length===1?i.key.toUpperCase():i.key.replace(/^Arrow/,"");return{key:i.key.toLowerCase(),code:i.code,label:u}}function Qn(i,u,h){for(let m of e.scanners.filter(T=>T.kind==="manual"))for(let T of["left","right"]){if(m.id===i.id&&T===u)continue;let D=m.keys?.[T];if(D&&(D.code===h.code||D.key===h.key))return!0}return!1}function Vt(i,u=""){if(!i||i.kind!=="manual")return;t.modalTitle.textContent=`Manual Scanner #${i.id}`;let h=Te(i.keys?.left),m=Te(i.keys?.right),T=Cs(i);t.modalBody.innerHTML=` +
+

Left route key: ${h}

+

Right route key: ${m}

${u?`

${u}

`:""} - ${$?`

${$}

`:""} -
`,t.modalActions.innerHTML="",t.modalActions.appendChild(Fe("Set Left Key",()=>T(s,"left"),"facility-action warn")),t.modalActions.appendChild(Fe("Set Right Key",()=>T(s,"right"),"facility-action warn")),t.modalActions.appendChild(Fe("Close",xt,"facility-action")),Te(s),t.modal.classList.add("visible","equipment-popover")}function C(s){let u=[{side:"left",binding:s.keys?.left},{side:"right",binding:s.keys?.right}];if(u[0].binding&&u[1].binding&&(u[0].binding.code===u[1].binding.code||u[0].binding.key===u[1].binding.key))return"Left and right keys conflict.";for(let b of u)if(b.binding)for(let k of e.scanners.filter($=>$.kind==="manual"&&$.id!==s.id)){if(k.keys?.left&&(k.keys.left.code===b.binding.code||k.keys.left.key===b.binding.key))return`Key conflicts with Manual Scanner #${k.id} left.`;if(k.keys?.right&&(k.keys.right.code===b.binding.code||k.keys.right.key===b.binding.key))return`Key conflicts with Manual Scanner #${k.id} right.`}return""}function T(s,u){t.modalBody.innerHTML=`

Press a key for ${u.toUpperCase()} route.

Esc cancels.

`,t.modalActions.innerHTML="";let b=k=>{if(k.preventDefault(),k.stopPropagation(),window.removeEventListener("keydown",b,!0),k.key==="Escape")return v(s);let $=p(k),B=h(s,u,$);Le(e),s.keys=s.keys||{left:null,right:null},s.keys[u]=$,i(),v(s,B?`${$.label} assigned, but another scanner already uses it.`:`${u.toUpperCase()} set to ${$.label}.`)};window.addEventListener("keydown",b,!0)}function L(){let s=_();if(!(!s||s.type!=="scanner"))return s.kind==="auto"?Yt(s):v(s)}function ne(s){if(!s)return{x:80,y:80};if(s.type==="conveyor"){let u=ee(s.id);return P(u.col,u.row)}return s.type==="eggFarm"?P(s.col,s.row):s.type==="scanner"?le(s):s.type==="facility"?{x:s.x+s.w/2,y:s.y+10}:{x:80,y:80}}function Te(s){let u=n.getBoundingClientRect(),b=ne(s),k=e.view?.scale||1,$=u.left+(b.x*k+(e.view?.x||0))/n.width*u.width+18,B=u.top+(b.y*k+(e.view?.y||0))/n.height*u.height-12,ce=Math.max(12,window.innerWidth-274),Ke=Math.max(12,window.innerHeight-220);t.modal.style.setProperty("--popover-x",`${Math.max(12,Math.min(ce,$))}px`),t.modal.style.setProperty("--popover-y",`${Math.max(12,Math.min(Ke,B))}px`)}function wt(){let s=_();if(!s)return;let u=No(s);t.modalTitle.textContent=ae(s),t.modalBody.innerHTML=` -
${u.map($=>`

${$}

`).join("")}
- ${["mixer","truck"].includes(s.id)?'

Income/Fine = ceil(base \xD7 1.05^upgrades)

':""}`,t.modalActions.innerHTML="",s.type==="scanner"&&s.kind==="auto"&&t.modalActions.appendChild(Fe("Auto Menu",()=>Yt(s),"facility-action warn")),s.type==="scanner"&&s.kind==="manual"&&t.modalActions.appendChild(Fe("Set Keys",()=>v(s),"facility-action warn"));let b=s.type==="conveyor"?{type:"conveyor",oldKey:s.id,ref:s}:{type:s.type,ref:s},k=x(b);if(k){let $=Fe(k,()=>{},"facility-action danger disabled");$.disabled=!0,t.modalActions.appendChild($)}else{let $=Tt(b,e),B=$.sameBuild?`Remove / Refund ${S($.amount)}`:`Sell ${S($.amount)}`;t.modalActions.appendChild(Fe(B,()=>{Rt(),xt()},"facility-action danger"))}Te(s),t.modal.classList.add("visible","equipment-popover")}function ts(s){e.phase==="build"&&(xe(),e.buildTool=e.buildTool===s?null:s,e.selected=null,e.multiSelected=[],e.selectionBox=null,i())}function ns(){let s=[];for(let[u,b]of Object.entries(e.facilities))if(s.push({id:`facility:${u}`,price:Z({type:"facility",ref:b}),x:b.x,y:b.y,w:b.w,h:b.h}),b.entry){let k=P(b.entry.col,b.entry.row);s.push({id:`facilityPort:${u}`,price:Z({type:"facility",ref:b}),x:k.x-18,y:k.y-18,w:36,h:36})}for(let u of e.eggFarms){let b=P(u.col,u.row);s.push({id:`eggFarm:${u.id}`,price:Z({type:"eggFarm",ref:u}),x:b.x-24,y:b.y-24,w:48,h:48})}for(let u of e.scanners){let b=le(u);s.push({id:`scanner:${u.id}`,price:Z({type:"scanner",ref:u}),x:b.x-52,y:b.y-36,w:104,h:72})}for(let u of e.conveyorTiles){let b=ee(u),k=P(b.col,b.row);s.push({id:`conveyor:${u}`,price:Re("conveyor",e),x:k.x-22,y:k.y-22,w:44,h:44})}return s}function No(s){de(e);let u=[];if(s.type==="eggFarm"){let[b,k]=Jt(s);u.push(`Price: ${S(Z(s))}`);{let $=x({type:s.type,ref:s});u.push($?`Sale: blocked (${$})`:`Sale value: ${S(Tt({type:s.type,ref:s},e).amount)}`)}u.push(`Level: ${s.level}/4`),u.push(`Random interval: ${b.toFixed(2)}-${k.toFixed(2)}s`),u.push(`Durability: ${nn(s)}% | Spawn delay x${gn(s).toFixed(2)}`)}else if(s.type==="scanner")u.push(`Price: ${S(Z(s))}`),u.push(`Sale value: ${S(Tt({type:s.type,ref:s},e).amount)}`),u.push(`Type: ${s.kind.toUpperCase()} / Standard`),u.push(`Role: ${s.role===0?"Male left / Others right":"Poop left / Others right"}`),u.push(`Queue: ${s.queue.length}`),s.kind==="auto"&&u.push(`Cooldown: ${s.cooldown.toFixed(1)}s / ${Je(s,e).toFixed(1)}s`),s.kind==="auto"&&u.push(`Durability: ${nn(s)}% | Delay x${Tn(s).toFixed(2)}`),s.kind==="manual"&&u.push("Durability: none | Always normal");else if(s.type==="conveyor"){let b=e.conveyorMeta.get(s.id),k=e.componentLookup?.get(s.id),$=k?e.congestion.get(k):null;u.push(`Price: ${S(Z({type:"conveyor",oldKey:s.id,ref:s}))}`),u.push(`Cell: ${s.id}`),u.push(`Congestion: ${$?Math.floor($.ratio*100):0}%`),u.push(`Durability: ${nn({meta:b})}% | Speed x${Mn({meta:b}).toFixed(2)}`),u.push("Flow follows drawn directions. Branches choose randomly; full branches are avoided when possible."),u.push(`Sale value: ${S(Tt({type:"conveyor",oldKey:s.id,ref:s},e).amount)}`)}else if(s.type==="facility"){u.push(`Price: ${S(Z(s))}`);{let b=x({type:s.type,ref:s});u.push(b?`Sale: blocked (${b})`:`Sale value: ${S(Tt({type:s.type,ref:s},e).amount)}`)}if(u.push(["mixer","truck","trash"].includes(s.id)?`Level: ${s.level} / no cap`:`Level: ${s.level}`),s.id==="truck"&&u.push("Durability: none | Always normal"),["mixer","trash"].includes(s.id)&&u.push(`Durability: ${nn(s)}% | Extra delay ${ko(e,s.id).toFixed(2)}s`),s.id==="mixer"&&(u.push(`Income: ${S(lt(e))} per chick | upgrade x${st(e,"mixer").toFixed(3)}`),u.push(`Poop fine: -${S(jt(e))}`)),s.id==="truck"&&(u.push(`Income: ${S(Ie(e))} per target cargo | upgrade x${st(e,"truck").toFixed(3)}`),u.push(`Poop shipment fine: -${S(en(e))}`)),s.id==="trash"){let b=Mt(e),k=at(b);u.push(`Bonus cards: ${b}/${Gt()}`),u.push(`Bonus chance: ${(k*100).toFixed(2)}% for ${S(1)} per processed item.`),u.push(`Expected value: ${k.toFixed(3)} JPY per item.`)}s.entry&&u.push(`Receiver: edge cell ${s.entry.col},${s.entry.row} (${s.side})`)}return u}function os(s){if(!s||e.phase!=="build")return"";let u=wn(e).find(b=>b.type===s.type&&b.id===s.id);return u?u.message||"Not connected to conveyor route":""}function rs(s){return s?s.type==="conveyor"?"A stubborn belt tile. It only cares about the next cell.":s.type==="eggFarm"?"A tiny gatehouse producing questionable eggs on schedule.":s.type==="scanner"&&s.kind==="auto"?"An automated judge. Faster than hands, still very sure of itself.":s.type==="scanner"?"A manual checkpoint. The operator is the algorithm.":s.type==="facility"&&s.id==="mixer"?"Male chicks become revenue here. Do not feed it poop.":s.type==="facility"&&s.id==="trash"?"Poop goes in. Sometimes coins come out, for reasons best left unaudited.":s.type==="facility"&&s.id==="truck"?"The shipping endpoint. Correct cargo pays; wrong cargo complains.":"Factory equipment.":""}return{buildAtCell:De,buildAtPoint:je,eraseAtPoint:mt,equipmentAtPoint:et,startSelectionBox:tt.startSelectionBox,updateSelectionBox:tt.updateSelectionBox,finishSelectionBox:tt.finishSelectionBox,startGroupDrag:tt.startGroupDrag,updateGroupDrag:tt.updateGroupDrag,finishGroupDrag:tt.finishGroupDrag,selectedObject:_,selectedTitle:ae,equipmentPrice:Z,selectedUpgradeCost:sn,selectedInfoLines:No,flavorText:rs,disconnectedWarningFor:os,gridExpansionCost:w,buyGridExpansion:se,expansionOfferAtPoint:I,beginConveyorDrag:_e,continueConveyorDrag:V,endConveyorDrag:xe,removeSelected:Rt,switchScannerRole:L,showManualScannerMenu:v,showAutoScannerMenu:Yt,showSelectedMenu:wt,setBuildTool:ts,fail:m,isEquipmentCell:W,equipmentHitBoxes:ns,routeFromFarmToScanner:Ye}}function Gi({game:e,ui:t,build:n,startGame:o,beginCardDraft:r,activeQueuedChick:i}){function l(E){return String(E).replace(/[&<>"']/g,A=>({"&":"&","<":"<",">":">",'"':""","'":"'"})[A])}function f(){return Math.round($n(e))}function w(){if(e.phase!=="build"||e.cardDraft?.pending||e.cardTargetPick?.pending)return!1;let E=!1;for(let A of Un)if(!(ot.includes(A)&&e.facilities[A])&&(E=!0,e.cash>=Re(A,e)))return!1;return n?.gridExpansionCost&&e.cash>=n.gridExpansionCost()?!1:E}function M(){R(),D(),se(),j(),I()}function R(){for(let[A,x]of Object.entries(t.buttons))x?.classList?.contains("tool-button")&&x.classList.toggle("active",e.buildTool===A);for(let A of Un){let x=t.buttons[A];if(!x)continue;let m=Re(A,e),U=x.querySelector("span");U&&(U.textContent=A==="conveyor"?`${S(m)} / tile`:S(m));let K=ot.includes(A)&&!!e.facilities[A],J=A==="manualScanner"&&e.scanners.filter(ye=>ye.kind==="manual").length>=8,oe=e.cashYe(e,ye)?.scannerId).length,A=Zt(e),x=Math.max(0,Math.floor(e.lastResult?.profit??e.stats?.profit??0)),m=bn(x,e),U=e.phase==="running"?Math.max(1,(e.totals.turnsCompleted||0)+1):Math.max(1,Number(e.turn)||1),K=lo(e,U),J=e.lastBuildFees?.turn===U?e.lastBuildFees.fairiesTribute||0:null,oe=e.lastBuildFees?.turn===U&&e.lastBuildFees.reduction||0;t.turnSummary.innerHTML=[`Seed: ${l(e.runSeed||"none")} | Grid box: ${e.gridRows||10}\xD7${e.gridCols||20} | Owned: ${e.gridOwnedCells||e.ownedCells?.size||(e.gridRows||10)*(e.gridCols||20)} cells | Blocked: ${e.blockedCells?.size||0} cells`,`Truck target: ${ze(e).toUpperCase()} | Male fine: -${S(so(e))}`,`Poop fine: Mixer -${S(jt(e))} / Shipment -${S(en(e))}`,`ZUNDA TAX on Next Day: -${S(m.tax)} | basis net ${S(x)} | taxable ${S(m.taxable)} \xD7 ${m.ratePercent}% | exemption ${S(m.exemption)} | 95% at ${S(m.maxRateCash)}`,J!=null?`Fairies tribute paid before Build: -${S(J)}${oe?` | Flattery -${S(oe)}`:""}`:`Fairies tribute before Build: -${S(K.amount)}${K.reduction?` | Flattery -${S(K.reduction)}`:""}`,`Maintenance: worst ${Mo(e).worst.percent}% dirty (${Mo(e).worst.label}) | Repairman: ${e.repairman?.hiredForNextDay?"hired next day":e.repairman?.active?"working":"none"}`,`Manual combo: ${e.manualCombo?.count||0} | Next expansion: ${S(n?.gridExpansionCost?n.gridExpansionCost():0)} | Card draft: ${e.cardDraft?.pending?Math.max(1,e.cardDraft.picksRemaining||1)+" pick(s) left":"ready after each day"} | Belt: ${f()}px/s | Farms with scanner route: ${E}/${e.eggFarms.length}`,e.cardEffects?.usedMachineActive?'Procurement: USED MACHINE MODE / no refunds / 60% durability':"",A.length?`Blocked: ${A[0]}`:`${te.status.allPortsConnected}`].join("
")}function se(){if(!t.contractPanel)return;let E=e.contractOffer;if(e.phase!=="build"){t.contractPanel.innerHTML=te.status.eventPanelRunning;return}if(!E){t.contractPanel.className="contract-card empty",e.turn+1Event lock active.No irregular events during Days 1-7. First possible forced event: Day ${bt}.`:t.contractPanel.innerHTML="No event.Random forced events can appear later.";return}t.contractPanel.className="contract-card accepted",t.contractPanel.innerHTML=` + ${T?`

${T}

`:""} + `,t.modalActions.innerHTML="",t.modalActions.appendChild(R("Set Left Key",()=>jo(i,"left"),"facility-action warn")),t.modalActions.appendChild(R("Set Right Key",()=>jo(i,"right"),"facility-action warn")),t.modalActions.appendChild(R("Close",z,"facility-action")),Zn(i),t.modal.classList.add("visible","equipment-popover")}function Cs(i){let u=[{side:"left",binding:i.keys?.left},{side:"right",binding:i.keys?.right}];if(u[0].binding&&u[1].binding&&(u[0].binding.code===u[1].binding.code||u[0].binding.key===u[1].binding.key))return"Left and right keys conflict.";for(let h of u)if(h.binding)for(let m of e.scanners.filter(T=>T.kind==="manual"&&T.id!==i.id)){if(m.keys?.left&&(m.keys.left.code===h.binding.code||m.keys.left.key===h.binding.key))return`Key conflicts with Manual Scanner #${m.id} left.`;if(m.keys?.right&&(m.keys.right.code===h.binding.code||m.keys.right.key===h.binding.key))return`Key conflicts with Manual Scanner #${m.id} right.`}return""}function jo(i,u){t.modalBody.innerHTML=`

Press a key for ${u.toUpperCase()} route.

Esc cancels.

`,t.modalActions.innerHTML="";let h=m=>{if(m.preventDefault(),m.stopPropagation(),window.removeEventListener("keydown",h,!0),m.key==="Escape")return Vt(i);let T=dn(m),D=Qn(i,u,T);Le(e),i.keys=i.keys||{left:null,right:null},i.keys[u]=T,s(),Vt(i,D?`${T.label} assigned, but another scanner already uses it.`:`${u.toUpperCase()} set to ${T.label}.`)};window.addEventListener("keydown",h,!0)}function Ss(){let i=x();if(!(!i||i.type!=="scanner"))return i.kind==="auto"?X(i):Vt(i)}function Ms(i){if(!i)return{x:80,y:80};if(i.type==="conveyor"){let u=Q(i.id);return A(u.col,u.row)}return i.type==="eggFarm"?A(i.col,i.row):i.type==="scanner"?de(i):i.type==="facility"?{x:i.x+i.w/2,y:i.y+10}:{x:80,y:80}}function Zn(i){let u=n.getBoundingClientRect(),h=Ms(i),m=e.view?.scale||1,T=u.left+(h.x*m+(e.view?.x||0))/n.width*u.width+18,D=u.top+(h.y*m+(e.view?.y||0))/n.height*u.height-12,ne=Math.max(12,window.innerWidth-274),le=Math.max(12,window.innerHeight-220);t.modal.style.setProperty("--popover-x",`${Math.max(12,Math.min(ne,T))}px`),t.modal.style.setProperty("--popover-y",`${Math.max(12,Math.min(le,D))}px`)}function Ts(){let i=x();if(!i)return;let u=tr(i);t.modalTitle.textContent=C(i),t.modalBody.innerHTML=` +
${u.map(T=>`

${T}

`).join("")}
+ ${["mixer","truck"].includes(i.id)?'

Income/Fine = ceil(base \xD7 1.05^upgrades)

':""}`,t.modalActions.innerHTML="",i.type==="scanner"&&i.kind==="auto"&&t.modalActions.appendChild(R("Auto Menu",()=>X(i),"facility-action warn")),i.type==="scanner"&&i.kind==="manual"&&t.modalActions.appendChild(R("Set Keys",()=>Vt(i),"facility-action warn"));let h=i.type==="conveyor"?{type:"conveyor",oldKey:i.id,ref:i}:{type:i.type,ref:i},m=ke(h);if(m){let T=R(m,()=>{},"facility-action danger disabled");T.disabled=!0,t.modalActions.appendChild(T)}else{let T=gt(h,e),D=T.sameBuild?`Remove / Refund ${S(T.amount)}`:`Sell ${S(T.amount)}`;t.modalActions.appendChild(R(D,()=>{P(),z()},"facility-action danger"))}Zn(i),t.modal.classList.add("visible","equipment-popover")}function gs(i){e.phase==="build"&&(un(),e.buildTool=e.buildTool===i?null:i,e.selected=null,e.multiSelected=[],e.selectionBox=null,s())}function Es(){let i=[];for(let[u,h]of Object.entries(e.facilities))if(i.push({id:`facility:${u}`,price:O({type:"facility",ref:h}),x:h.x,y:h.y,w:h.w,h:h.h}),h.entry){let m=A(h.entry.col,h.entry.row);i.push({id:`facilityPort:${u}`,price:O({type:"facility",ref:h}),x:m.x-18,y:m.y-18,w:36,h:36})}for(let u of e.eggFarms){let h=A(u.col,u.row);i.push({id:`eggFarm:${u.id}`,price:O({type:"eggFarm",ref:u}),x:h.x-24,y:h.y-24,w:48,h:48})}for(let u of e.scanners){let h=de(u);i.push({id:`scanner:${u.id}`,price:O({type:"scanner",ref:u}),x:h.x-50,y:h.y-73,w:100,h:146})}for(let u of e.conveyorTiles){let h=Q(u),m=A(h.col,h.row),T=e.conveyorMeta.get(u)||{};i.push({id:`conveyor:${u}`,price:T.price||Be(T.kind==="boostConveyor"?"boostConveyor":"conveyor",e),x:m.x-22,y:m.y-22,w:44,h:44})}return i}function jn(i){return F[i]||F.random}function Ps(i){return We(i)}function Rs(i){return Ps(i).length>=3}function er(i){if(e.phase!=="build"||e.cardDraft?.pending||e.cardTargetPick?.pending)return null;for(let u of e.conveyorTiles){if(!Rs(u))continue;let h=Q(u),m=A(h.col,h.row),T=m.x-B.w/2,D=m.y+B.y-B.h/2;if(i.x>=T&&i.x<=T+B.w&&i.y>=D&&i.y<=D+B.h)return u}return null}function Ds(i){let u=er(i);if(!u)return!1;let h=e.conveyorMeta.get(u);if(!h)return!1;let m=We(u);if(m.length<2)return!1;Le(e);let T=$.includes(h.branchMode)?h.branchMode:"random",D=["random",...m.length>=4?["pass"]:[],...pe.map(le=>le.name).filter(le=>m.includes(le))];h.branchMode=D[(Math.max(0,D.indexOf(T))+1)%D.length],e.selected={type:"conveyor",id:u};let ne=A(...Object.values(Q(u)));return N(e,ne.x,ne.y-22,jn(h.branchMode),f.ink),s(),!0}function tr(i){ye(e);let u=[],h=i?.type==="facility"?i.baseId||i.id:"";if(i.type==="eggFarm"){let[m,T]=Zt(i);u.push(`Price: ${S(O(i))}`);{let D=ke({type:i.type,ref:i});u.push(D?`Sale: blocked (${D})`:`Sale value: ${S(gt({type:i.type,ref:i},e).amount)}`)}u.push(`Level: ${i.level}/4`),u.push(`Random interval: ${m.toFixed(2)}-${T.toFixed(2)}s`),u.push(`Durability: ${rn(i)}% | Spawn delay x${Bn(i).toFixed(2)}`)}else if(i.type==="scanner")u.push(`Price: ${S(O(i))}`),u.push(`Sale value: ${S(gt({type:i.type,ref:i},e).amount)}`),u.push(`Type: ${i.kind.toUpperCase()} / Standard`),i.kind==="auto"&&u.push(`Role: ${i.role===0?"Male left / Others right":"Poop left / Others right"}`),u.push(`Queue: ${i.queue.length}`),i.kind==="auto"&&u.push(`Cooldown: ${i.cooldown.toFixed(1)}s / ${Qe(i,e).toFixed(1)}s`),i.kind==="auto"&&u.push(`Durability: ${rn(i)}% | Delay x${Fn(i).toFixed(2)}`),i.kind==="manual"&&u.push("Durability: none | Always normal");else if(i.type==="conveyor"){let m=e.conveyorMeta.get(i.id),T=e.componentLookup?.get(i.id),D=T?e.congestion.get(T):null;u.push(`Price: ${S(O({type:"conveyor",oldKey:i.id,ref:i}))}`),u.push(`Cell: ${i.id}`),u.push(`Type: ${m?.kind==="boostConveyor"?"BOOST BELT":"Normal Belt"} | Speed x${Number(m?.speedMultiplier||1).toFixed(1)}`),u.push(`Congestion: ${D?Math.floor(D.ratio*100):0}%`),u.push(`Durability: ${rn({meta:m})}% | Speed x${In({meta:m}).toFixed(2)}`),u.push(`Branch mode: ${jn(m?.branchMode||"random")}`),u.push("Click the branch marker to cycle Random / Up / Right / Down / Left."),u.push(`Sale value: ${S(gt({type:"conveyor",oldKey:i.id,ref:i},e).amount)}`)}else if(i.type==="facility"){u.push(`Price: ${S(O(i))}`);{let m=ke({type:i.type,ref:i});u.push(m?`Sale: blocked (${m})`:`Sale value: ${S(gt({type:i.type,ref:i},e).amount)}`)}if(u.push(["mixer","truck","trash"].includes(i.id)?`Level: ${i.level} / no cap`:`Level: ${i.level}`),h==="truck"&&u.push("Durability: none | Always normal"),["mixer","trash"].includes(h)&&u.push(`Durability: ${rn(i)}% | Extra delay ${Lo(e,i).toFixed(2)}s`),h==="mixer"&&(u.push(`Income: ${S(it(e))} per chick | upgrade x${rt(e,"mixer").toFixed(3)}`),u.push(`Poop fine: -${S(en(e))}`)),h==="truck"&&(u.push(`Income: ${S(Ne(e))} per target cargo | upgrade x${rt(e,"truck").toFixed(3)}`),u.push(`Poop shipment fine: -${S(tn(e))}`)),h==="trash"){let m=Tt(e),T=st(m);u.push(`Bonus cards: ${m}/${Nt()}`),u.push(`Bonus chance: ${(T*100).toFixed(2)}% for ${S(1)} per processed item.`),u.push(`Expected value: ${T.toFixed(3)} JPY per item.`)}i.entry&&u.push(`Receiver: edge cell ${i.entry.col},${i.entry.row} (${i.side})`)}return u}function As(i){if(!i||e.phase!=="build")return"";let u=Tn(e).find(h=>h.type===i.type&&h.id===i.id);return u?u.message||"Not connected to conveyor route":""}function $s(i){return i?i.type==="conveyor"&&e.conveyorMeta.get(i.id)?.kind==="boostConveyor"?"Moves chicks twice as fast. Wears down like a normal belt.":i.type==="conveyor"?"A stubborn belt tile. It only cares about the next cell.":i.type==="eggFarm"?"A tiny gatehouse producing questionable eggs on schedule.":i.type==="scanner"&&i.kind==="auto"?"Slower than hands without upgrades.":i.type==="scanner"?"A manual checkpoint. The operator is the algorithm.":i.type==="facility"&&(i.baseId||i.id)==="mixer"?"Male chicks become revenue here. Do not feed it poop.":i.type==="facility"&&(i.baseId||i.id)==="trash"?"Poop goes in. Sometimes coins come out, for reasons best left unaudited.":i.type==="facility"&&(i.baseId||i.id)==="truck"?"The shipping endpoint. Correct cargo pays; wrong cargo complains.":"Factory equipment.":""}return{buildAtCell:qt,buildAtPoint:Yt,eraseAtPoint:p,equipmentAtPoint:b,startSelectionBox:k.startSelectionBox,updateSelectionBox:k.updateSelectionBox,finishSelectionBox:k.finishSelectionBox,startGroupDrag:k.startGroupDrag,updateGroupDrag:k.updateGroupDrag,finishGroupDrag:k.finishGroupDrag,selectedObject:x,selectedTitle:C,equipmentPrice:O,selectedUpgradeCost:v,selectedInfoLines:tr,flavorText:$s,disconnectedWarningFor:As,gridExpansionCost:ee,buyGridExpansion:g,expansionOfferAtPoint:he,beginConveyorDrag:Xn,continueConveyorDrag:$t,endConveyorDrag:un,branchSwitcherAtPoint:er,cycleBranchModeAtPoint:Ds,removeSelected:P,switchScannerRole:Ss,showManualScannerMenu:Vt,showAutoScannerMenu:X,showSelectedMenu:Ts,setBuildTool:gs,fail:ue,isEquipmentCell:E,equipmentHitBoxes:Es,routeFromFarmToScanner:ze}}function is({game:e,ui:t,build:n,startGame:o,beginCardDraft:r,activeQueuedChick:s}){function l(E){return String(E).replace(/[&<>"']/g,G=>({"&":"&","<":"<",">":">",'"':""","'":"'"})[G])}function d(){return Math.round(Gn(e))}function w(){if(e.phase!=="build"||e.cardDraft?.pending||e.cardTargetPick?.pending)return!1;let E=!1;for(let G of no)if(E=!0,e.cash>=Be(G,e))return!1;return E}function M(){$(),F(),ae(),ee(),B()}function $(){for(let[G,x]of Object.entries(t.buttons))x?.classList?.contains("tool-button")&&x.classList.toggle("active",e.buildTool===G);for(let G of no){let x=t.buttons[G];if(!x)continue;let C=Be(G,e),O=x.querySelector("span");O&&(O.textContent=G==="conveyor"||G==="boostConveyor"?`${S(C)} / tile`:S(C));let Y=G==="manualScanner"&&e.scanners.filter(se=>se.kind==="manual").length>=8,te=e.cashze(e,ke)?.scannerId).length,G=jt(e),x=Math.max(0,Math.floor(e.lastResult?.profit??e.stats?.profit??0)),C=En(x,e),O=e.phase==="running"?Math.max(1,(e.totals.turnsCompleted||0)+1):Math.max(1,Number(e.turn)||1),Y=Mo(e,O),te=e.lastBuildFees?.turn===O?e.lastBuildFees.fairiesTribute||0:null,se=e.lastBuildFees?.turn===O&&e.lastBuildFees.reduction||0;t.turnSummary.innerHTML=[`Seed: ${l(e.runSeed||"none")} | Grid box: ${e.gridRows||10}x${e.gridCols||20} | Owned: ${e.gridOwnedCells||e.ownedCells?.size||(e.gridRows||10)*(e.gridCols||20)} cells | Blocked: ${e.blockedCells?.size||0} cells`,`Truck target: ${Ve(e).toUpperCase()} | Male fine: -${S(So(e))}`,`Poop fine: Mixer -${S(en(e))} / Shipment -${S(tn(e))}`,`ZUNDA TAX on Next Day: -${S(C.tax)} | basis net ${S(x)} | taxable ${S(C.taxable)} x${C.ratePercent}% | exemption ${S(C.exemption)} | 95% at ${S(C.maxRateCash)}`,te!=null?`Fairies tribute paid before Build: -${S(te)}${se?` | Flattery -${S(se)}`:""}`:`Fairies tribute before Build: -${S(Y.amount)}${Y.reduction?` | Flattery -${S(Y.reduction)}`:""}`,`Maintenance: worst ${Go(e).worst.percent}% dirty (${Go(e).worst.label}) | Repairman: ${e.repairman?.hiredForNextDay?"hired next day":e.repairman?.active?"working":"none"}`,`Manual combo: ${e.manualCombo?.count||0} | Card draft: ${e.cardDraft?.pending?Math.max(1,e.cardDraft.picksRemaining||1)+" pick(s) left":"ready after each day"} | Belt: ${d()}px/s | Farms with scanner route: ${E}/${e.eggFarms.length}`,e.cardEffects?.usedMachineActive?'Procurement: USED MACHINE MODE / no refunds / 60% durability':"",G.length?`Blocked: ${G[0]}`:`${oe.status.allPortsConnected}`].join("
")}function ae(){if(!t.contractPanel)return;let E=e.contractOffer;if(e.phase!=="build"){t.contractPanel.innerHTML=oe.status.eventPanelRunning;return}if(!E){t.contractPanel.className="contract-card empty",e.turn+1Event lock active.No irregular events during Days 1-7. First possible forced event: Day ${yt}.`:t.contractPanel.innerHTML="No event.Random forced events can appear later.";return}t.contractPanel.className="contract-card accepted",t.contractPanel.innerHTML=`
FORCED NEXT DAY

${E.title}

@@ -15,41 +19,43 @@ var H={version:"v27.6 direct conveyor chick movement",time:{daySeconds:60,farmSh Target: ${E.targetAmount} truck exports Reward: ${S(E.reward)} Output x${E.productionMultiplier} / Poop x${E.poopMultiplier} -
`}function z(){R(),t.shell.classList.toggle("phase-running",e.phase==="running"),t.shell.classList.toggle("phase-build",e.phase==="build"),t.shell.classList.toggle("phase-gameover",e.phase==="gameover"),t.shell.classList.toggle("phase-title",e.phase==="title"),t.shell.classList.toggle("phase-loanDecision",e.phase==="loanDecision"),t.money.textContent=S(e.cash),t.money.classList.toggle("cash-negative",e.cash<0),t.money.classList.toggle("cash-positive",e.cash>zt),t.turn.textContent=e.turn,t.timeLeft.textContent=e.phase==="running"&&e.timeLeft<=0&&e.shutdownTimeLeft>0?`+${e.shutdownTimeLeft.toFixed(1)}s`:`${Math.max(0,e.timeLeft).toFixed(1)}s`,t.timeLeft.closest?.(".hud-card")?.classList.toggle("time-critical",e.phase==="running"&&(e.timeLeft<=10||e.timeLeft<=0&&e.shutdownTimeLeft>0)),t.phase.textContent=ue(),t.comboCount&&(t.comboCount.textContent=e.manualCombo?.count||0),X(),t.turnProfit.textContent=S(e.stats.profit),t.turnProfit.classList.toggle("cash-negative",e.stats.profit<0),t.turnProfit.classList.toggle("cash-positive",e.stats.profit>0),t.mixerCount.textContent=e.stats.mixerCount,t.truckFemaleCount.textContent=e.stats.truckFemale,t.truckMaleCount.textContent=e.stats.truckMale,t.poopCount.textContent=e.stats.poopSpawned,t.buttons.s1Left.disabled=e.phase!=="running"||!i(0),t.buttons.s1Right.disabled=e.phase!=="running"||!i(0),t.buttons.s2Left.disabled=e.phase!=="running"||!i(1),t.buttons.s2Right.disabled=e.phase!=="running"||!i(1);let E=e.scanners.find(m=>m.kind==="manual"&&m.slot===0),A=e.scanners.find(m=>m.kind==="manual"&&m.slot===1);E&&(t.buttons.s1Left.textContent=`S1 ${E.keys?.left?.label||"A"} -> Mixer`,t.buttons.s1Right.textContent=`S1 ${E.keys?.right?.label||"D"} -> S2`),A&&(t.buttons.s2Left.textContent=`S2 ${A.keys?.left?.label||"Left"} -> Waste`,t.buttons.s2Right.textContent=`S2 ${A.keys?.right?.label||"Right"} -> Truck`);let x=bn(Math.max(0,Math.floor(e.lastResult?.profit??e.stats?.profit??0)),e).tax;t.buttons.nextTurn.textContent=e.phase==="build"&&x>0?`Next Day - ZUNDA ${S(x)}`:te.actions.nextDay,t.buttons.nextTurn.disabled=e.phase!=="build"||!!e.cardDraft?.pending||!!e.cardTargetPick?.pending||!Mr(e)}function X(){if(!t.eventBrief||!t.targetBrief||!t.speedBrief)return;let E=ze(e).toUpperCase();t.targetBrief.textContent=`TRUCK TARGET: ${E}`,t.speedBrief.textContent=`BELT: ${f()} px/s | COMBO: ${e.manualCombo?.count||0}`,t.speedBrief.title=`Conveyor speed does not increase by day. Degraded belts reduce speed. Cap: ${Dt}px/s.`;let A=e.contractActive,x=e.contractOffer;A?(t.eventBrief.textContent=`ACTIVE EVENT: ${A.route} | Output x${A.productionMultiplier} | Poop x${A.poopMultiplier}`,t.eventBrief.className="event-brief active"):e.phase==="build"&&x?(t.eventBrief.textContent=`NEXT DAY EVENT: ${x.route} | Target ${x.targetAmount} | Reward ${S(x.reward)}`,t.eventBrief.className="event-brief next"):e.phase==="build"&&e.turn+1
\u2642Male->MixerS1: A
\u2640Female->TruckS1: D, then S2: right
\u{1F4A9}Poop->ShredderS1: D, then S2: left

Build phase: connect at least one EGG through a scanner to Mixer, Shredder, or Truck before Next Day. One day is one run.

',t.modalActions.innerHTML="",t.modalActions.appendChild(ae(te.actions.startGame,o,"primary-button")),t.modal.classList.remove("equipment-popover","gameover-modal"),t.modal.classList.add("visible")}function g(E){let A=K=>`${K>0?"+":""}${S(K)}`,x=K=>`${K>0?"-":""}${S(K)}`,m=E.profit>=0?"cash-positive":"cash-negative",U=(E.mixerPoopFine||0)+(E.truckPoopFine||0)+(E.maleTruckFine||0)+(E.explosionDamage||0)+(E.cardRerollCost||0);t.modalTitle.textContent=`Day ${E.turn} Settlement`,t.modalBody.innerHTML=` -
Net result${S(E.profit)}
+ `}function V(){$(),t.shell.classList.toggle("phase-running",e.phase==="running"),t.shell.classList.toggle("phase-build",e.phase==="build"),t.shell.classList.toggle("phase-gameover",e.phase==="gameover"),t.shell.classList.toggle("phase-title",e.phase==="title"),t.shell.classList.toggle("phase-loanDecision",e.phase==="loanDecision"),t.money.textContent=S(e.cash),t.money.classList.toggle("cash-negative",e.cash<0),t.money.classList.toggle("cash-positive",e.cash>Xt),t.turn.textContent=e.turn,t.timeLeft.textContent=e.phase==="running"&&e.timeLeft<=0&&e.shutdownTimeLeft>0?`+${e.shutdownTimeLeft.toFixed(1)}s`:`${Math.max(0,e.timeLeft).toFixed(1)}s`,t.timeLeft.closest?.(".hud-card")?.classList.toggle("time-critical",e.phase==="running"&&(e.timeLeft<=10||e.timeLeft<=0&&e.shutdownTimeLeft>0)),t.phase.textContent=he(),t.comboCount&&(t.comboCount.textContent=e.manualCombo?.count||0),ce(),t.turnProfit.textContent=S(e.stats.profit),t.turnProfit.classList.toggle("cash-negative",e.stats.profit<0),t.turnProfit.classList.toggle("cash-positive",e.stats.profit>0),t.mixerCount.textContent=e.stats.mixerCount,t.truckFemaleCount.textContent=e.stats.truckFemale,t.truckMaleCount.textContent=e.stats.truckMale,t.poopCount.textContent=e.stats.poopSpawned,t.buttons.s1Left.disabled=e.phase!=="running"||!s(0),t.buttons.s1Right.disabled=e.phase!=="running"||!s(0),t.buttons.s2Left.disabled=e.phase!=="running"||!s(1),t.buttons.s2Right.disabled=e.phase!=="running"||!s(1);let E=e.scanners.find(C=>C.kind==="manual"&&C.slot===0),G=e.scanners.find(C=>C.kind==="manual"&&C.slot===1);E&&(t.buttons.s1Left.textContent=`S1 ${E.keys?.left?.label||"A"} -> Mixer`,t.buttons.s1Right.textContent=`S1 ${E.keys?.right?.label||"D"} -> S2`),G&&(t.buttons.s2Left.textContent=`S2 ${G.keys?.left?.label||"Left"} -> Waste`,t.buttons.s2Right.textContent=`S2 ${G.keys?.right?.label||"Right"} -> Truck`);let x=En(Math.max(0,Math.floor(e.lastResult?.profit??e.stats?.profit??0)),e).tax;t.buttons.nextTurn.textContent=e.phase==="build"&&x>0?`Next Day - ZUNDA ${S(x)}`:oe.actions.nextDay,t.buttons.nextTurn.disabled=e.phase!=="build"||!!e.cardDraft?.pending||!!e.cardTargetPick?.pending||!Wr(e)}function ce(){if(!t.eventBrief||!t.targetBrief||!t.speedBrief)return;let E=Ve(e).toUpperCase();t.targetBrief.textContent=`TRUCK TARGET: ${E}`,t.speedBrief.textContent=`BELT: ${d()} px/s | COMBO: ${e.manualCombo?.count||0}`,t.speedBrief.title=`Conveyor speed does not increase by day. Degraded belts reduce speed. Cap: ${Bt}px/s.`;let G=e.contractActive,x=e.contractOffer;G?(t.eventBrief.textContent=`ACTIVE EVENT: ${G.route} | Output x${G.productionMultiplier} | Poop x${G.poopMultiplier}`,t.eventBrief.className="event-brief active"):e.phase==="build"&&x?(t.eventBrief.textContent=`NEXT DAY EVENT: ${x.route} | Target ${x.targetAmount} | Reward ${S(x.reward)}`,t.eventBrief.className="event-brief next"):e.phase==="build"&&e.turn+1
\u9AEB\uFF68\u8737\uFF76\u7E5D\uFF7B/span>Male->MixerS1: A
\u9AEB\uFF68\u870A\u50B5\x80Female->TruckS1: D, then S2: right
\u30FB\u6ED3\uFF68\u9811\uFF43Poop->ShredderS1: D, then S2: left

Build phase: connect at least one EGG through a scanner to Mixer, Shredder, or Truck before Next Day. One day is one run.

',t.modalActions.innerHTML="",t.modalActions.appendChild(ie(oe.actions.startGame,o,"primary-button")),t.modal.classList.remove("equipment-popover","gameover-modal"),t.modal.classList.add("visible")}function g(E){let G=Y=>`${Y>0?"+":""}${S(Y)}`,x=Y=>`${Y>0?"-":""}${S(Y)}`,C=E.profit>=0?"cash-positive":"cash-negative",O=(E.mixerPoopFine||0)+(E.truckPoopFine||0)+(E.maleTruckFine||0)+(E.explosionDamage||0)+(E.cardRerollCost||0);t.modalTitle.textContent=`Day ${E.turn} Settlement`,t.modalBody.innerHTML=` +
Net result${S(E.profit)}
-
Chick shipment income${A(E.chickShipmentIncome||0)}
-
Poop shipment income${A(E.poopShipmentIncome||0)}
-
Mixer income${A(E.mixerRevenue||0)}
-
Manual combo bonus${A(E.manualComboBonus||0)}
-
Chemical subsidy${A(E.chemicalWeaponSubsidy||0)}
-
Contract bonus${A(E.contractBonus||0)}
-
Penalties${x(U)}
+
Chick shipment income${G(E.chickShipmentIncome||0)}
+
Poop shipment income${G(E.poopShipmentIncome||0)}
+
Mixer income${G(E.mixerRevenue||0)}
+
Composter income${G(E.composterIncome||0)}
+
DUD refund income${G(E.dudRefundIncome||0)}
+
Manual combo bonus${G(E.manualComboBonus||0)}
+
Chemical subsidy${G(E.chemicalWeaponSubsidy||0)}
+
Contract bonus${G(E.contractBonus||0)}
+
Penalties${x(O)}
Fairies fee${x(E.fairiesTribute?.amount||E.fairiesTribute||0)}
Repair worker wages${x(E.repairWorkerWages||0)}
Loan repayments${x(E.loanRepayment||0)}
-

Income ${S(E.revenue)} - losses ${S(E.penalty)} = ${S(E.profit)}

`,t.modalActions.innerHTML="",t.modalActions.appendChild(ae("Choose Upgrade Card",r||Z,"primary-button")),t.modal.classList.remove("equipment-popover","gameover-modal"),t.modal.classList.add("visible")}function W(E,A){let x=Math.max(0,e.cardEffects?.rescueLoanCharges||0);t.modalTitle.textContent="Emergency Loan Available",t.modalBody.innerHTML=` +

Income ${S(E.revenue)} - losses ${S(E.penalty)} = ${S(E.profit)}

`,t.modalActions.innerHTML="",t.modalActions.appendChild(ie("Choose Upgrade Card",r||me,"primary-button")),t.modal.classList.remove("equipment-popover","gameover-modal"),t.modal.classList.add("visible")}function _(E,G){let x=Math.max(0,e.cardEffects?.rescueLoanCharges||0);t.modalTitle.textContent="Emergency Loan Available",t.modalBody.innerHTML=`

End-of-day cash is below ${S(0)}.

Current money${S(e.cash)}
Emergency Loan+${S(500)}
Cards remaining${x}
-

Use one Emergency Loan to gain ${S(500)} immediately. If cash is still negative, another decision will be shown if another card remains.

`,t.modalActions.innerHTML="",t.modalActions.appendChild(ae("Accept Game Over",A,"facility-action danger")),t.modalActions.appendChild(ae("Use Emergency Loan",E,"primary-button")),t.modal.classList.remove("equipment-popover","gameover-modal"),t.modal.classList.add("visible")}function _(){let E=e.totals.processed,A=E?Math.round(e.totals.correct/E*100):0,x=Vr(e);t.modalTitle.textContent=te.phases.gameover,t.modalBody.innerHTML=`

Your cash went negative.

-
Final Score${x.score}
Survival${e.totals.turnsCompleted} days
Final Cash${S(e.cash)}
Accuracy${A}%
+

Use one Emergency Loan to gain ${S(500)} immediately. If cash is still negative, another decision will be shown if another card remains.

`,t.modalActions.innerHTML="",t.modalActions.appendChild(ie("Accept Game Over",G,"facility-action danger")),t.modalActions.appendChild(ie("Use Emergency Loan",E,"primary-button")),t.modal.classList.remove("equipment-popover","gameover-modal"),t.modal.classList.add("visible")}function U(){let E=e.totals.processed,G=E?Math.round(e.totals.correct/E*100):0,x=di(e);t.modalTitle.textContent=oe.phases.gameover,t.modalBody.innerHTML=`

Your cash went negative.

+
Final Score${x.score}
Survival${e.totals.turnsCompleted} days
Final Cash${S(e.cash)}
Accuracy${G}%
Score breakdown -
Formula
Base = Cash + Total revenue + Factory value \xD7 0.5 + Contract bonus \xD7 0.5 = ${S(x.base)}
Daily earned = ceil(Base \xF7 Days) = ${S(x.dailyEarned)}
Score = max(0, floor(Base + Daily earned + Correct\xD75 - Total outflow - Day penalty)) = ${x.score}
+
Formula
Base = Cash + Total revenue + Factory value \u7E5D\uFF7B\u7E5D\uFF7B\u7E5D\uFF7B0.5 + Contract bonus \u7E5D\uFF7B\u7E5D\uFF7B\u7E5D\uFF7B0.5 = ${S(x.base)}
Daily earned = ceil(Base \u7E5D\uFF7B\u7E5D\uFF7B\u30FB\uFF7D\u30FB\uFF77 Days) = ${S(x.dailyEarned)}
Score = max(0, floor(Base + Daily earned + Correct\u7E5D\uFF7B\u7E5D\uFF7B\u7E5D\uFF7B - Total outflow - Day penalty)) = ${x.score}
Factory Value${S(x.factoryValue)}
Total Revenue${S(e.totals.revenue)}
Daily Earned${S(x.dailyEarned)} / day
Total Outflow${S(e.totals.penalty)}
ZUNDA TAX${S(e.totals.zundaTax)}
Fairies Tribute${S(e.totals.fairiesTribute||0)}
Correct Sorts${e.totals.correct}
Auto Sorted${e.totals.autoSorted}
Poop Control${e.totals.poopTrash} trashed / ${e.totals.poopTruck} shipped / ${e.totals.poopMixer} mixer
Explosion Penalty${S(e.totals.explosionDamage)}
Contract Bonus${S(e.totals.contractBonus)}
Day Penalty-${x.dayPenalty}
Contracts${e.totals.contractSuccess} success / ${e.totals.contractFailed} failed
-
`,t.modalActions.innerHTML="",t.modalActions.appendChild(ae(te.actions.restart||"Restart",o,"primary-button restart-button")),t.modal.classList.remove("equipment-popover"),t.modal.classList.add("visible","gameover-modal")}function ae(E,A,x="primary-button"){let m=document.createElement("button");return m.type="button",m.className=x,m.textContent=E,m.addEventListener("click",A),m}function Z(){t.modal.classList.remove("visible","equipment-popover","gameover-modal"),t.modal.style.removeProperty("--popover-x"),t.modal.style.removeProperty("--popover-y")}function me(){}return{updatePanels:M,updateUI:z,showTitle:ie,showTurnResult:g,showEmergencyLoanDecision:W,showGameOver:_,hideModal:Z,checkGameOver:me}}function Oo(e){e.totals.processed+=1}function Kt(e){e.stats.correct+=1,e.totals.correct+=1}function ft(e){e.stats.mistakes+=1,e.totals.mistakes+=1}function Ui(e){e.stats.autoSorted+=1,e.totals.autoSorted+=1}function Wi(e){e.stats.poopSpawned+=1}function _i(e){e.stats.mixerCount+=1,Oo(e)}function Ki(e){e.stats.trashCount+=1,Oo(e)}function Hi(e,t){t==="female"?e.stats.truckFemale+=1:t==="male"&&(e.stats.truckMale+=1),Oo(e)}function Fn(e,t){let n=t==="truck"?"poopTruck":t==="trash"?"poopTrash":t==="mixer"?"poopMixer":null;n&&(e.stats[n]+=1,e.totals[n]+=1)}function qi({game:e,currentConveyorSpeed:t,onGameOverCheck:n}){function o(c){return St(c)*gn(c)*ki(e)/Rr(e)}function r(c){let p=P(c.col,c.row),h=1+vi(e,c),v=l(c,h);if(!v.length)return i(c,p),!0;let C=0,T=0;for(let L of v){let ne=P(L.col,L.row),Te=f(ne);if(Te){Te.stoppedTimer=.85,T+=1;continue}let wt=Bi(e,{stage:"belt",scannerId:null,route:[ne]});wt.sex==="poop"&&Wi(e),e.chicks.push(wt),li(e,c),C+=1}return C>0?(Jr(e,p.x,p.y),C>1&&G(e,p.x,p.y-28,`x${C}`,d.green),!0):T===0}function i(c,p){(c.noRouteNoticeCooldown||0)<=0&&(G(e,p.x,p.y-24,"NO BELT: PAUSED",d.warn),c.noRouteNoticeCooldown=2.5)}function l(c,p){let v=ge.map(C=>({col:c.col+C.dc,row:c.row+C.dr})).filter(C=>Ee(C.col,C.row)&&e.conveyorTiles.has(F(C.col,C.row))).map(C=>({p:C,blocked:I(null,P(C.col,C.row))?1:0,roll:Math.random()}));return v.sort((C,T)=>C.blocked-T.blocked||C.roll-T.roll),v.slice(0,Math.max(1,p||1)).map(C=>C.p)}function f(c){return e.chicks.find(p=>p.stage!=="flying"&&Math.hypot(p.x-c.x,p.y-c.y)0;if(e.timeLeft=Math.max(0,e.timeLeft-c),v&&e.timeLeft<=0&&p(),e.timeLeft<=0){e.shutdownTimeLeft>0&&(e.shutdownTimeLeft=Math.max(0,e.shutdownTimeLeft-c));for(let C of e.eggFarms)C.shutterProgress=Math.min(1,(C.shutterProgress||0)+c*2.8)}if(v&&e.timeLeft>0){for(let C of e.eggFarms)if(C.noRouteNoticeCooldown>0&&(C.noRouteNoticeCooldown=Math.max(0,C.noRouteNoticeCooldown-c)),C.nextSpawn-=c,C.nextSpawn<=0){let T=r(C);C.nextSpawn=T?o(C):.15,C.lastInterval=C.nextSpawn}}for(let C=e.chicks.length-1;C>=0;C-=1){let T=e.chicks[C];T.bob+=c*7,T.stoppedTimer>0&&(T.stoppedTimer=Math.max(0,T.stoppedTimer-c)),T.stage!=="queued"&&R(T,C,c)}J(c),Rt(),(e.timeLeft>0||e.shutdownTimeLeft>0)&&Fe(),e.timeLeft<=0&&(e.chicks.length===0?h():e.shutdownTimeLeft<=0&&M())}function M(){if(e.cleanupBlown)return;e.cleanupBlown=!0,e.cleanupTimer=1;let c=[...e.chicks];for(let p of c)uo(e,p.sex,p.x,p.y,-Math.PI/2,!0);e.chicks=[];for(let p of e.scanners)p.queue=[];if(Rt(),c.length){let p=c.reduce((v,C)=>v+C.x,0)/c.length,h=c.reduce((v,C)=>v+C.y,0)/c.length;Xe(e,p,h,d.warn,58),G(e,p,h-24,"CLEANUP",d.warn)}}function R(c,p,h){if(D(c)){c.stoppedTimer=.35;return}if(x(c,h),!(c.route&&c.targetIndex=c.route.length)return!1;let p=c.route[c.targetIndex];return I(c,p)}function I(c,p){if(!p)return!1;for(let h of e.chicks)if(!(c&&h.id===c.id)&&h.stage!=="flying"&&Math.hypot(h.x-p.x,h.y-p.y)1)&&c.push({x:p.x,y:p.y})}function se(c){let p=e.conveyorMeta?.get?.(c)||{},h=[];return Array.isArray(p.outDirs)&&h.push(...p.outDirs),p.dir&&h.push(p.dir),[...new Set(h)].filter(v=>ge.some(C=>C.name===v))}function z(c){return ge.find(p=>p.name===c)||null}function X(c,p){return e.scanners.find(h=>h.col===c&&h.row===p)||null}function ue(c,p){let h=z(p);if(!h)return null;let v=X(c.col+h.dc,c.row+h.dr);if(!v)return null;let C=ve(v,"inputA");return C&&C.col===c.col&&C.row===c.row?v:null}function ie(c){return Object.entries(e.facilities||{}).find(([,p])=>p?.entry&&p.entry.col===c.col&&p.entry.row===c.row)||null}function g(c,p){return c.side==="left"&&p==="left"||c.side==="right"&&p==="right"||c.side==="top"&&p==="up"||c.side==="bottom"&&p==="down"}function W(c,p){let h=z(p);if(!h)return null;let v={col:c.col+h.dc,row:c.row+h.dr};if(Ee(v.col,v.row)&&e.conveyorTiles.has(F(v.col,v.row)))return{type:"conveyor",dirName:p,target:P(v.col,v.row),nextCell:v};let C=ue(c,p);if(C)return{type:"scanner",dirName:p,target:le(C),scanner:C};let T=ie(c);return T&&g(T[1],p)?{type:"facility",dirName:p,facilityId:T[0],facility:T[1],target:P(c.col,c.row)}:null}function _(c,p,h){let v=h.map(ne=>W(p,ne)).filter(Boolean);if(!v.length)return null;let T=v.filter(ne=>ne.type!=="facility").filter(ne=>!I(c,ne.target)),L=T.length?T:v.filter(ne=>ne.type==="facility");return L.length?L[Math.floor(Math.random()*L.length)]:null}function ae(c,p){let h=[];j(h,{x:c.x,y:c.y}),j(h,p),c.route=h.length?h:[{x:c.x,y:c.y}],c.targetIndex=c.route.length>1?1:c.route.length}function Z(c,p){let h=$e(c.x,c.y);if(!h)return et(p,"OFF GRID");let v=F(h.col,h.row);if(!e.conveyorTiles.has(v))return et(p,"OFF BELT");let C=se(v);if(!C.length){c.stoppedTimer=.25;return}let T=_(c,h,C);if(!T){c.stoppedTimer=.25;return}if(T.type==="facility"){T.facilityId==="mixer"?On(p):T.facilityId==="truck"?Pt(p):T.facilityId==="trash"?mt(p):et(p,"DONE");return}T.type==="scanner"&&(c.pendingScannerId=T.scanner.id),ae(c,T.target)}function me(c){let p=$e(c.x,c.y);if(!p)return null;let h=F(p.col,p.row);return e.conveyorTiles.has(h)?h:null}function E(c){let p=me(c)||me(c.route?.[c.targetIndex]||c);return t(p)}function A(c,p){let h=me(p);!h||c.lastWearKey===h||(c.lastWearKey=h,si(e,h))}function x(c,p){let h=E(c)*p;for(;h>0&&c.targetIndex=L?(c.x=v.x,c.y=v.y,A(c,v),c.targetIndex+=1,h-=L):(c.x+=C/L*h,c.y+=T/L*h,h=0)}}function m(c,p,h){c.stage="queued",c.route=null,c.queueRoute=h?h.map(v=>({x:v.x,y:v.y})):[le(p)],c.scannerId=p.id,p.queue.includes(c.id)||p.queue.push(c.id),U(p)}function U(c){c.queue=c.queue.filter(h=>e.chicks.some(v=>v.id===h&&v.stage==="queued"));let p=y.cell*.86;c.queue.forEach((h,v)=>{let C=e.chicks.find(T=>T.id===h);C&&(K(C,C.queueRoute||[le(c)],v*p),C.queueIndex=v)})}function K(c,p,h){if(!p||!p.length)return;if(p.length===1||h<=0){let T=p[p.length-1];c.x=T.x,c.y=T.y;return}let v=h;for(let T=p.length-1;T>0;T-=1){let L=p[T-1],ne=p[T],Te=Math.hypot(ne.x-L.x,ne.y-L.y);if(v<=Te){let wt=1-v/Te;c.x=L.x+(ne.x-L.x)*wt,c.y=L.y+(ne.y-L.y)*wt;return}v-=Te}let C=p[0];c.x=C.x,c.y=C.y}function J(c){for(let p of e.scanners){if(p.cooldown>0&&(p.cooldown=Math.max(0,p.cooldown-c)),p.keyPressTime>0&&(p.keyPressTime=Math.max(0,p.keyPressTime-c)),U(p),p.kind!=="auto"||p.cooldown>0||!p.queue.length)continue;let h=p.queue[0],v=e.chicks.findIndex(C=>C.id===h);if(v<0){p.queue.shift();continue}rn(v,jn(p,e.chicks[v]),!0)&&(ai(e,p),p.cooldown=Je(p,e),Ui(e))}}function oe(c){let p=Xn(e,c);return!p||!p.queue.length?null:e.chicks.find(h=>h.id===p.queue[0])||null}function ye(c,p){if(e.phase!=="running")return;let h=Xn(e,c);Ne(h,p)}function Ne(c,p){if(e.phase!=="running"||!c||c.kind!=="manual"||(c.keyPressTime=.18,c.keyPressSide=p,!c.queue.length))return;let h=e.chicks.findIndex(v=>v.id===c.queue[0]);h>=0&&rn(h,p,!1)}function _e(c,p,h){return c.kind==="manual"&&jn(c,p)===h}function V(c){let p=e.manualCombo?.count||0;if(e.manualCombo||(e.manualCombo={count:0,lastBonus:0}),e.manualCombo.count=0,e.manualCombo.lastBonus=0,e.stats.manualComboFailure=(e.stats.manualComboFailure||0)+1,e.totals.manualComboFailure=(e.totals.manualComboFailure||0)+1,p>=10){let h=le(c);gt(e,10,.25),G(e,h.x,h.y-36,"COMBO RESET",d.danger)}}function xe(c){e.manualCombo||(e.manualCombo={count:0,lastBonus:0}),e.manualCombo.count=Math.max(0,e.manualCombo.count||0)+1,e.stats.manualComboSuccess=(e.stats.manualComboSuccess||0)+1,e.totals.manualComboSuccess=(e.totals.manualComboSuccess||0)+1;let p=e.manualCombo.count;if(e.manualCombo.lastBonus=0,p<10)return 0;let h=Math.max(1,Math.floor(p/10)),v=e.scanners.filter(ne=>ne.kind==="manual").length,C=Math.min(1,(20+v*10)/100),T=0;for(let ne=0;ne0){Ae(e,L),e.stats.manualComboBonus=(e.stats.manualComboBonus||0)+L,e.totals.manualComboBonus=(e.totals.manualComboBonus||0)+L,e.manualCombo.lastBonus=L;let ne=le(c);G(e,ne.x,ne.y-42,`COMBO +${S(L)}`,d.green)}return L}function De(c,p){let h=ve(c,p);if(!h)return null;let v=F(h.col,h.row);if(e.conveyorTiles.has(v))return{...h};let T=(Ce(e).scannerPorts.get(c.id)?.[p]?.keys||[]).filter(Boolean);if(!T.length)return null;let L=T.map(ee);return L.find(Te=>Te.col===h.col&&Te.row===h.row)||L[0]||null}function je(c,p,h,v){let C=[];j(C,{x:c.x,y:c.y});let T=ve(p,h);return T&&j(C,P(T.col,T.row)),j(C,P(v.col,v.row)),C}function rn(c,p,h){let v=e.chicks[c];if(!v||v.stage!=="queued")return!1;let C=it(e,v.scannerId);if(!C)return!1;let T=De(C,p);if(!T)return!h&&C.kind==="manual"&&V(C),G(e,v.x,v.y-16,p==="left"?"NO LEFT BELT":"NO RIGHT BELT",d.danger),!1;!h&&C.kind==="manual"&&(_e(C,v,p)?xe(C):V(C)),C.queue=C.queue.filter(ne=>ne!==v.id),v.stage="belt",v.route=je(v,C,p,T),v.targetIndex=v.route.length>1?1:v.route.length,v.scannerId=null,v.nextScannerId=null,v.pendingScannerId=null,v.queueRoute=null,v.queueIndex=-1;let L=h?d.green:p==="left"?d.mixerBlue:d.truckPink;return Qr(e,le(C).x,le(C).y,L),G(e,v.x,v.y-20,h?"AUTO":p.toUpperCase(),L),!0}function ht(c){let p=e.chicks[c];return p?(e.chicks.splice(c,1),p):null}function On(c){if(e.facilities.mixer?.processingCooldown>0){let L=e.chicks[c];L&&(L.stoppedTimer=.25);return}let h=ht(c);if(!h)return;let{x:v,y:C}=h;if(_i(e),vo(e,"mixer"),Co(e,"mixer"),h.sex==="poop"){let L=Gr(e);Fn(e,"mixer"),ft(e),jr(e,v,C),G(e,v,C-18,`-${S(L)}`,d.danger);return}let T=Nr(e);h.sex==="male"?Kt(e):ft(e),Zr(e,v,C),G(e,v,C-18,`+${S(T)}`,d.green)}function Pt(c){let p=ht(c);if(!p)return;let{x:h,y:v}=p;tt(p.sex),ti(e,h,v,p.sex),Hi(e,p.sex);let C=ze(e),T=Ar(e,p.sex);if(p.sex==="poop"){let L=Ur(e);Fn(e,"truck"),T?Kt(e):ft(e),G(e,h,v-18,`-${S(L)}`,d.danger);return}if(p.sex==="female"){T?(Kt(e),G(e,h,v-18,`+${S(Ie(e))}`,d.green)):(ft(e),G(e,h,v-18,C==="male"?"REJECT":"NO SALE",d.warn));return}if(T){Kt(e),G(e,h,v-18,`+${S(Ie(e))}`,d.green);return}if(Dr(e)){let L=Wr(e);ft(e),G(e,h,v-18,`-${S(L)}`,d.danger)}else ft(e),G(e,h,v-18,"REJECT",d.warn)}function mt(c){if(e.facilities.trash?.processingCooldown>0){let ne=e.chicks[c];ne&&(ne.stoppedTimer=.25);return}let h=ht(c);if(!h)return;let{x:v,y:C}=h;ei(e,v,C,h.sex),Ki(e),vo(e,"trash"),Co(e,"trash");let T=Lr(e),L=T.amount>0?` +${S(T.amount)}`:"";h.sex==="poop"?(Fn(e,"trash"),Kt(e),G(e,v,C-18,`CLEAN${L}`,T.amount>0?d.green:d.green)):(ft(e),G(e,v,C-18,T.amount>0?`WASTE${L}`:"WASTE",T.amount>0?d.green:d.ink))}function et(c,p){let h=ht(c);h&&G(e,h.x,h.y-12,p,d.muted)}function tt(c){let p=e.facilities.truck;p&&(e.truckCargo.push({sex:c,x:O(22,p.w-22),y:O(66,p.h-24)}),e.truckCargo.length>45&&e.truckCargo.shift())}function sn(c){let p=$e(c.x,c.y);if(p){let T=F(p.col,p.row);if(e.conveyorTiles.has(T))return T}let h=kr(e,c.x,c.y);if(!h)return null;let v=ee(h),C=P(v.col,v.row);return Math.hypot(c.x-C.x,c.y-C.y)<=y.cell*.55?h:null}function Rt(){let{components:c,cellToComponent:p}=br(e);for(let h of e.chicks){if(h.stage==="flying")continue;let v=sn(h);if(!v)continue;let C=p.get(v),T=c.get(C);T&&(T.count+=1)}for(let h of c.values())h.ratio=h.count/h.capacity;e.congestion=c,e.componentLookup=p}function Fe(){for(let c of e.congestion.values()){if(c.ratio>.8&&c.ratio<1)for(let p of xt(c.id).slice(0,3))Math.random()<.06&&ni(e,p.x,p.y-p.radius-8);if(c.ratio>=1){let p=e.lastExplodedComponent.get(c.id)||0,h=performance.now();h-p>900&&(e.lastExplodedComponent.set(c.id,h),Yt(c))}}}function xt(c){return e.chicks.filter(p=>{let h=sn(p);return h&&e.componentLookup?.get(h)===c})}function Yt(c){let p=xt(c.id);if(!p.length)return;for(let C of p)uo(e,C.sex,C.x,C.y);e.chicks=e.chicks.filter(C=>!p.some(T=>T.id===C.id));for(let C of e.scanners)C.queue=C.queue.filter(T=>e.chicks.some(L=>L.id===T));for(let C of c.cells){let T=ee(C),L=P(T.col,T.row);Xe(e,L.x,L.y,d.danger),ut(e,L.x,L.y,6),kn(e,L.x,L.y,4)}gt(e,18,.75);let h=ee(c.cells[0]),v=P(h.col,h.row);G(e,v.x,v.y-22,"100% JAM EXPLOSION",d.danger)}return{activeQueuedChick:oe,currentSpawnDelay:o,sortSlot:ye,updateCongestion:Rt,sortScanner:Ne,updateRunning:w}}var re=document.getElementById("gameCanvas"),jl=re.getContext("2d"),N={shell:document.getElementById("gameShell"),money:document.getElementById("money"),turn:document.getElementById("turn"),timeLeft:document.getElementById("timeLeft"),phase:document.getElementById("phaseLabel"),comboCount:document.getElementById("comboCount"),eventBrief:document.getElementById("eventBrief"),targetBrief:document.getElementById("targetBrief"),speedBrief:document.getElementById("speedBrief"),turnProfit:document.getElementById("turnProfit"),mixerCount:document.getElementById("mixerCount"),truckFemaleCount:document.getElementById("truckFemaleCount"),truckMaleCount:document.getElementById("truckMaleCount"),poopCount:document.getElementById("poopCount"),buildStatus:document.getElementById("buildStatus"),turnSummary:document.getElementById("turnSummary"),contractPanel:document.getElementById("contractPanel"),modal:document.getElementById("modal"),modalTitle:document.getElementById("modalTitle"),modalBody:document.getElementById("modalBody"),modalActions:document.getElementById("modalActions"),hoverTooltip:document.getElementById("hoverTooltip"),manualScannerMonitor:document.getElementById("manualScannerMonitor"),hireRepairmanButton:document.getElementById("hireRepairmanButton"),expandGridButton:document.getElementById("expandGridButton"),debug:{panel:document.getElementById("debugPanel"),infiniteCash:document.getElementById("debugInfiniteCash"),dayInput:document.getElementById("debugDayInput"),setDay:document.getElementById("debugSetDayButton"),cardSelect:document.getElementById("debugCardSelect"),grantCard:document.getElementById("debugGrantCardButton"),grantAllCards:document.getElementById("debugGrantAllCardsButton"),readout:document.getElementById("debugReadout")},buttons:{s1Left:document.getElementById("scanner1MixerButton"),s1Right:document.getElementById("scanner1TruckButton"),s2Left:document.getElementById("scanner2MixerButton"),s2Right:document.getElementById("scanner2TruckButton"),nextTurn:document.getElementById("nextTurnButton"),conveyor:document.getElementById("buildConveyorButton"),eggFarm:document.getElementById("buildEggFarmButton"),autoScanner:document.getElementById("buildAutoScannerButton"),manualScanner:document.getElementById("buildManualScannerButton"),mixer:document.getElementById("buildMixerButton"),trash:document.getElementById("buildTrashButton"),truck:document.getElementById("buildTruckButton"),erase:document.getElementById("eraseButton"),undo:document.getElementById("undoButton"),redo:document.getElementById("redoButton")}};function ea(){let e={conveyor:"conveyor",eggFarm:"eggFarm",autoScanner:"autoScanner",manualScanner:"manualScanner",mixer:"mixer",trash:"trash",truck:"truck"};for(let[t,n]of Object.entries(e))N.buttons[t]&&(N.buttons[t].innerHTML=zo(n))}function ta(){N.debug?.panel&&N.debug.cardSelect&&(N.debug.cardSelect.innerHTML=_t.map(e=>``).join(""))}var a=zn(),Y,q,be,Ht;function na(e=null){let t=$n(a,e);return e?t*xo(a,e):t}function oa(){Object.assign(a,zn()),a.phase="running",a.view={x:0,y:0,scale:1},Vn(a),Zi(),de(a),q.hideModal(),be.updateCongestion(),q.updatePanels(),q.updateUI(),qt()}function ra(){let e=Math.max(1,Math.floor(Number(N.debug.dayInput?.value)||1));a.turn=e,N.debug.dayInput&&(N.debug.dayInput.value=String(e)),G(a,re.width/2-a.view.x,86-a.view.y,`DAY ${e}`,d.green),q.updatePanels(),q.updateUI(),qt(`Day set to ${e}.`)}function ia(){let e=N.debug.cardSelect?.value,t=Eo(a,e);q.updatePanels(),q.updateUI(),qt(t.reason)}function sa(){let t=_t.map(n=>Eo(a,n)).filter(n=>n.ok).length;q.updatePanels(),q.updateUI(),qt(`Granted ${t}/${_t.length} cards.`)}function la(){N.debug?.infiniteCash?.checked&&a.cash<999999&&(a.cash=999999,a.totals.maxCash=Math.max(a.totals.maxCash,a.cash))}function qt(e=""){N.debug?.readout&&(N.debug.readout.textContent=[e,`D${a.turn}`,S(a.cash),`Combo ${a.manualCombo?.count||0}`,`Rescue ${a.cardEffects?.rescueLoanCharges||0}`,`Chem ${a.cardEffects?.chemicalWeaponSubsidy||0}`].filter(Boolean).join(" | "))}function aa(){if(a.phase!=="build")return;if(a.cardDraft?.pending||a.cardTargetPick?.pending){Y.fail(a.cardTargetPick?.pending?"Choose upgrade target first.":"Choose an upgrade card first."),q.updatePanels();return}let e=Zt(a);if(e.length){Y.fail(`Cannot start: ${e[0]}`),q.updatePanels();return}de(a),xr(a),fi(a);let t=Math.max(0,Math.floor(a.lastResult?.profit??a.stats?.profit??0)),n=Hr(a,t),o=zr(a);a.lastStartFees={zundaTax:n.tax||0,loanRepayment:o.amount||0,fairiesTribute:0},n.tax>0&&G(a,re.width/2-a.view.x,116-a.view.y,`ZUNDA TAX -${S(n.tax)}`,d.danger),o.amount>0&&G(a,re.width/2-a.view.x,146-a.view.y,`LOAN -${S(o.amount)}`,d.danger),Er(a),a.phase="running",a.turn+=1,a.timeLeft=At,a.shutdownTimeLeft=0,a.cleanupTimer=null,a.cleanupBlown=!1,a.chicks=[],a.effects=[],a.floatingTexts=[],a.shake={time:0,strength:0},a.truckCargo=[],Zi(),a.stats=Yn(),a.stats.zundaTax=0,a.stats.loanRepayment=a.lastStartFees?.loanRepayment||0,a.stats.repairWorkerWages=a.pendingRepairWorkerWages||0;let r=a.stats.loanRepayment+a.stats.repairWorkerWages;r>0&&(a.stats.penalty+=r,a.stats.profit-=r),a.pendingRepairWorkerWages=0;for(let i of a.scanners)i.queue=[],i.cooldown=0;for(let i of a.eggFarms)i.nextSpawn=be.currentSpawnDelay(i),i.lastInterval=i.nextSpawn,i.shutterProgress=0,i.shutterSparked=!1;a.buildTool=null,a.selected=null,a.groupDrag=null,a.selectionBox=null,a.pan=null,be.updateCongestion(),q.updatePanels(),q.updateUI()}function ca(){de(a);let e=di(a);e.ok?G(a,re.width/2-a.view.x,96-a.view.y,`Repairman hired -${S(e.cost)}`,d.green):(G(a,re.width/2-a.view.x,96-a.view.y,e.reason||"Cannot hire repairman.",d.danger),Y?.fail?.(e.reason||"Cannot hire repairman.")),q.updatePanels(),q.updateUI()}function ua(){if(a.phase!=="running")return;let e=Kr(a),t=Fr(a),n=Yr(a);if(e.adjusted>0){let i=a.facilities.truck,l=i?i.x+i.w/2:re.width/2-a.view.x,f=i?i.y+26:96-a.view.y;G(a,l,f,`${e.target==="poop"?"POOP SHIP":"SHIP"} +${S(e.adjusted)}`,d.green)}t?.bonus>0&&G(a,re.width/2-a.view.x,96-a.view.y,`CONTRACT +${S(t.bonus)}`,d.green),n.amount>0&&G(a,re.width/2-a.view.x,116-a.view.y,`CHEM SUBSIDY +${S(n.amount)}`,d.green),Pr(a);let o=Math.max(1,(a.totals.turnsCompleted||0)+1),r=qr(a,o);if(a.lastBuildFees={fairiesTribute:r.amount||0,reduction:r.reduction||0,turn:o},r.amount>0&&G(a,re.width/2-a.view.x,146-a.view.y,`FAIRIES -${S(r.amount)}`,d.danger),a.totals.turnsCompleted+=1,a.lastResult={...a.stats,ship:e,contract:t,chemical:n,rescue:{amount:0,used:0,remaining:a.cardEffects?.rescueLoanCharges||0},fairiesTribute:r,turn:a.turn,cash:a.cash},a.cash<0){if((a.cardEffects?.rescueLoanCharges||0)>0){a.phase="loanDecision",q.showEmergencyLoanDecision(Ji,In);return}a.phase="gameover",q.showGameOver();return}Xi()}function Xi(){a.phase="build",a.truckCargo=[],a.chicks=[],a.shutdownTimeLeft=0,a.cleanupTimer=null,a.cleanupBlown=!1,a.buildSession+=1,a.undoStack=[],a.redoStack=[],a.timeLeft=0;for(let e of a.scanners)e.queue=[];for(let e of a.eggFarms)e.shutterProgress=0,e.shutterSparked=!1;pi(a),a.contractOffer=gr(a),Ht&&Ht.prepareDraft(),a.cardTargetPick=null,a.buildTool=null,a.groupDrag=null,a.selectionBox=null,a.pan=null,q.updatePanels(),q.showTurnResult(a.lastResult)}function Ji(){if((a.cardEffects?.rescueLoanCharges||0)<=0)return In();a.cardEffects.rescueLoanCharges-=1,a.stats.rescueLoan=(a.stats.rescueLoan||0)+500,a.totals.rescueLoan=(a.totals.rescueLoan||0)+500;let e=Ae(a,500);a.lastResult={...a.lastResult||{},...a.stats,rescue:{amount:(a.lastResult?.rescue?.amount||0)+e,used:(a.lastResult?.rescue?.used||0)+1,remaining:a.cardEffects.rescueLoanCharges},cash:a.cash},G(a,re.width/2-a.view.x,176-a.view.y,`RESCUE +${S(e)}`,d.green),a.cash>=0?Xi():(a.cardEffects?.rescueLoanCharges||0)>0?(a.phase="loanDecision",q.showEmergencyLoanDecision(Ji,In)):In()}function In(){a.phase="gameover",q.showGameOver()}function da(){a.shutdownTimeLeft<=0&&(a.shutdownTimeLeft=a.shutdownGraceSeconds||10);for(let e of a.eggFarms){if(e.shutterSparked)continue;e.shutterProgress=.02,e.shutterSparked=!0;let t=P(e.col,e.row);Xe(a,t.x,t.y,d.ink),ut(a,t.x,t.y+10,10),G(a,t.x,t.y-34,"SHUT +10s",d.ink)}}function Qi(e){a.lastTimestamp||(a.lastTimestamp=e);let t=Math.min((e-a.lastTimestamp)/1e3,.05);a.lastTimestamp=e,la(),a.phase==="running"&&be.updateRunning(t,{closeFarmShutters:da,completeTurn:ua}),oi(a,t,re,Y.equipmentHitBoxes),be.updateCongestion(),Ri(jl,re,a,{activeQueuedChick:be.activeQueuedChick,selectedObject:Y.selectedObject,selectedTitle:Y.selectedTitle}),q.updateUI(),es(),qt(),requestAnimationFrame(Qi)}function Ln(){return a.view?.scale||1}function on(){a.view||(a.view={x:0,y:0,scale:1}),a.view.scale==null&&(a.view.scale=1)}function Bn(e){let t=re.getBoundingClientRect();return{x:(e.clientX-t.left)*re.width/t.width,y:(e.clientY-t.top)*re.height/t.height}}function pt(e){let t=Bn(e),n=Ln();return{x:(t.x-(a.view?.x||0))/n,y:(t.y-(a.view?.y||0))/n}}function fa(e){let t=Ln();return{x:e.x*t+(a.view?.x||0),y:e.y*t+(a.view?.y||0)}}function pa(){let e={left:y.x-90,top:y.y-130,right:y.x+y.cols*y.cell+260,bottom:y.y+y.rows*y.cell+220};if(a.phase==="build")for(let t of vt(a)){let n=Ct(t);e.left=Math.min(e.left,n.x-60),e.top=Math.min(e.top,n.y-80),e.right=Math.max(e.right,n.x+n.w+80),e.bottom=Math.max(e.bottom,n.y+n.h+80)}return e}function Yi(e,t){if(e<=t)return[e,t];let n=(e+t)/2;return[n,n]}function Ze(){on();let e=Ln(),t=pa(),[n,o]=Yi(re.width-160-t.right*e,110-t.left*e),[r,i]=Yi(re.height-150-t.bottom*e,150-t.top*e);a.view.x=Vt(a.view.x,n,o),a.view.y=Vt(a.view.y,r,i)}function Zi(){on(),a.view.scale=1;let e=a.eggFarms?.[0],t=e?P(e.col,e.row):{x:y.x,y:y.y};a.view.x=280-t.x,a.view.y=190-t.y,Ze()}function ya(e){let t=Bn(e);on(),a.pan={start:t,viewX:a.view.x,viewY:a.view.y}}function ha(e){if(!a.pan)return;let t=Bn(e);on(),a.view.x=a.pan.viewX+t.x-a.pan.start.x,a.view.y=a.pan.viewY+t.y-a.pan.start.y,Ze()}function ma(e){e.preventDefault(),on();let t=Bn(e),n=pt(e),o=Math.exp(-e.deltaY*.0012),r=Vt(Ln()*o,.55,1.8);a.view.scale=r,a.view.x=t.x-n.x*r,a.view.y=t.y-n.y*r,Ze(),yt()}function xa(e){return e?e.type==="conveyor"?{type:"conveyor",id:e.oldKey}:{type:e.type,id:e.ref.id}:null}function wa(e){return e?e.type==="conveyor"?{type:"conveyor",id:e.oldKey}:e.ref||null:null}function yt(){N.hoverTooltip&&(N.hoverTooltip.classList.remove("visible"),N.hoverTooltip.setAttribute("aria-hidden","true"),a.hover=null)}function ba(e,t){if(!N.hoverTooltip||a.phase!=="build"||a.cardTargetPick?.pending||a.groupDrag||a.selectionBox||a.pan)return yt();let n=wa(t);if(!n)return yt();let o=Y.selectedTitle(n),r=Y.flavorText?Y.flavorText(n):"",i=Y.disconnectedWarningFor?Y.disconnectedWarningFor(n):"",l=Y.selectedInfoLines(n).slice(0,7);N.hoverTooltip.innerHTML=`${o}${i?`${i}`:r?`${r}`:""}${l.map(M=>`${M}`).join("")}`;let f=Math.min(window.innerWidth-330,e.clientX+18),w=Math.min(window.innerHeight-220,e.clientY+18);N.hoverTooltip.style.left=`${Math.max(8,f)}px`,N.hoverTooltip.style.top=`${Math.max(8,w)}px`,N.hoverTooltip.classList.add("visible"),N.hoverTooltip.setAttribute("aria-hidden","false"),a.hover={type:t.type,id:t.oldKey||t.ref?.id||null}}function ka(e){if(a.phase!=="build")return yt();ba(e,Y.equipmentAtPoint(pt(e)))}function ji(e){yt();let t=pt(e),n=Y.equipmentAtPoint(t),o=xa(n);if(!o){a.selected=null,a.multiSelected=[],q.updatePanels();return}a.selected=o,a.multiSelected=[o],q.updatePanels();let r=Y.selectedObject();r?.type==="scanner"&&r.kind==="manual"&&Y.showManualScannerMenu(r)}function zi(e){return String(e).replace(/[&<>"']/g,t=>({"&":"&","<":"<",">":">",'"':""","'":"'"})[t])}function va(e){let t=e?.queue?.[0];return t==null?null:a.chicks.find(n=>n.id===t)||null}function Ca(e){if(!e)return'WAIT';let t=e.sex==="poop"?"\u{1F4A9} POOP":e.sex==="male"?"\u2642 MALE":"\u2640 FEMALE";return`${t}`}function Sa(e,t){let n=new Set(a.eggFarms.map(w=>F(w.col,w.row))),o=new Map(a.scanners.map(w=>[F(w.col,w.row),w])),r=new Set(Object.values(a.facilities||{}).map(w=>w.entry?F(w.entry.col,w.entry.row):null).filter(Boolean)),i=t?.sex||"",l=i==="poop"?"\u{1F4A9}":i==="male"?"\u2642":i==="female"?"\u2640":"",f=[];for(let w=e.row-2;w<=e.row+2;w+=1)for(let M=e.col-2;M<=e.col+2;M+=1){let R=F(M,w),D=["mini-cell"],I="";M===e.col&&w===e.row?(D.push("scanner"),i&&D.push(i),I=l||"S"):w===e.row-1&&M===e.col?(D.push("input"),I="IN"):n.has(R)?(D.push("egg"),I="E"):o.has(R)?(D.push("scanner-other"),I="S"):r.has(R)?(D.push("port"),I="P"):a.conveyorTiles.has(R)?(D.push("belt"),I="\u2022"):a.blockedCells?.has?.(R)&&(D.push("blocked"),I="\xD7"),f.push(`${I}`)}return`
${f.join("")}
`}function Ma(e){let t=fa(P(e.col,e.row)),n=76;return t.x<-n||t.y<-n||t.x>re.width+n||t.y>re.height+n}function es(){if(!N.manualScannerMonitor)return;if(a.phase!=="running"){N.manualScannerMonitor.classList.remove("visible"),N.manualScannerMonitor.innerHTML="";return}let e=a.scanners.filter(t=>t.kind==="manual"&&Ma(t));if(!e.length){N.manualScannerMonitor.classList.remove("visible"),N.manualScannerMonitor.innerHTML="";return}N.manualScannerMonitor.innerHTML=e.map(t=>{let n=va(t),o=t.keys?.left?.label||(t.slot===0?"A":"Left"),r=t.keys?.right?.label||(t.slot===0?"D":"Right"),i=t.role===0?"MIXER":"WASTE",l=t.role===0?"S2":"TRUCK",f=n?"":" disabled";return` + `,t.modalActions.innerHTML="",t.modalActions.appendChild(ie(oe.actions.restart||"Restart",o,"primary-button restart-button")),t.modal.classList.remove("equipment-popover"),t.modal.classList.add("visible","gameover-modal")}function ie(E,G,x="primary-button"){let C=document.createElement("button");return C.type="button",C.className=x,C.textContent=E,C.addEventListener("click",G),C}function me(){t.modal.classList.remove("visible","equipment-popover","gameover-modal"),t.modal.style.removeProperty("--popover-x"),t.modal.style.removeProperty("--popover-y")}function fe(){}return{updatePanels:M,updateUI:V,showTitle:Z,showTurnResult:g,showEmergencyLoanDecision:_,showGameOver:U,hideModal:me,checkGameOver:fe}}function Qo(e){e.totals.processed+=1}function _t(e){e.stats.correct+=1,e.totals.correct+=1}function ut(e){e.stats.mistakes+=1,e.totals.mistakes+=1}function ss(e){e.stats.autoSorted+=1,e.totals.autoSorted+=1}function ls(e){e.stats.poopSpawned+=1}function as(e){e.stats.mixerCount+=1,Qo(e)}function cs(e){e.stats.trashCount+=1,Qo(e)}function us(e,t){t==="female"?e.stats.truckFemale+=1:t==="male"&&(e.stats.truckMale+=1),Qo(e)}function Kn(e,t){let n=t==="truck"?"poopTruck":t==="trash"?"poopTrash":t==="mixer"?"poopMixer":null;n&&(e.stats[n]+=1,e.totals[n]+=1)}function ds({game:e,currentConveyorSpeed:t,onGameOverCheck:n}){function o(c){return vt(c)*Bn(c)*Gi(e)/Yr(e)}function r(c){let p=A(c.col,c.row),b=1+Ui(e,c),k=l(c,b);if(!k.length)return s(c,p),!0;let v=0,P=0;for(let R of k){let z=A(R.col,R.row),X=d(z);if(X){X.stoppedTimer=.85,P+=1;continue}let Te=ns(e,{stage:"belt",scannerId:null,route:[z]});Te.sex==="poop"&&ls(e),e.chicks.push(Te),Mi(e,c),v+=1}return v>0?(pi(e,p.x,p.y),v>1&&N(e,p.x,p.y-28,`x${v}`,f.green),!0):P===0}function s(c,p){(c.noRouteNoticeCooldown||0)<=0&&(N(e,p.x,p.y-24,"NO BELT: PAUSED",f.warn),c.noRouteNoticeCooldown=2.5)}function l(c,p){let k=pe.map(v=>({col:c.col+v.dc,row:c.row+v.dr})).filter(v=>Pe(v.col,v.row)&&e.conveyorTiles.has(I(v.col,v.row))).map(v=>({p:v,blocked:B(null,A(v.col,v.row))?1:0,roll:Math.random()}));return k.sort((v,P)=>v.blocked-P.blocked||v.roll-P.roll),k.slice(0,Math.max(1,p||1)).map(v=>v.p)}function d(c){return e.chicks.find(p=>p.stage!=="flying"&&Math.hypot(p.x-c.x,p.y-c.y)0;if(e.timeLeft=Math.max(0,e.timeLeft-c),k&&e.timeLeft<=0&&p(),e.timeLeft<=0){e.shutdownTimeLeft>0&&(e.shutdownTimeLeft=Math.max(0,e.shutdownTimeLeft-c));for(let v of e.eggFarms)v.shutterProgress=Math.min(1,(v.shutterProgress||0)+c*2.8)}if(k&&e.timeLeft>0){for(let v of e.eggFarms)if(v.noRouteNoticeCooldown>0&&(v.noRouteNoticeCooldown=Math.max(0,v.noRouteNoticeCooldown-c)),v.nextSpawn-=c,v.nextSpawn<=0){let P=r(v);v.nextSpawn=P?o(v):.15,v.lastInterval=v.nextSpawn}}for(let v=e.chicks.length-1;v>=0;v-=1){let P=e.chicks[v];P.bob+=c*7,P.stoppedTimer>0&&(P.stoppedTimer=Math.max(0,P.stoppedTimer-c)),P.stage!=="queued"&&$(P,v,c)}ue(c),Yt(),e.timeLeft<=0&&(e.chicks.length===0?b():e.shutdownTimeLeft<=0&&M())}function M(){if(e.cleanupBlown)return;e.cleanupBlown=!0,e.cleanupTimer=1;let c=[...e.chicks];for(let p of c)Eo(e,p.sex,p.x,p.y,-Math.PI/2,!0);e.chicks=[];for(let p of e.scanners)p.queue=[];if(Yt(),c.length){let p=c.reduce((k,v)=>k+v.x,0)/c.length,b=c.reduce((k,v)=>k+v.y,0)/c.length;Je(e,p,b,f.warn,58),N(e,p,b-24,"CLEANUP",f.warn)}}function $(c,p,b){if(F(c)){c.stoppedTimer=.35;return}if(Y(c,b),!(c.route&&c.targetIndex=c.route.length)return!1;let p=c.route[c.targetIndex];return B(c,p)}function B(c,p){if(!p)return!1;for(let b of e.chicks)if(!(c&&b.id===c.id)&&b.stage!=="flying"&&Math.hypot(b.x-p.x,b.y-p.y)1)&&c.push({x:p.x,y:p.y})}function ae(c){let p=e.conveyorMeta?.get?.(c)||{},b=[];Array.isArray(p.outDirs)&&b.push(...p.outDirs),p.dir&&b.push(p.dir);let k=[...new Set(b)].filter(X=>pe.some(Te=>Te.name===X)),v=Q(c),P=pe.filter(X=>U(v,X.name)).map(X=>X.name),R=k.filter(X=>P.includes(X)&&!ie(v,X)),z=P.filter(X=>!ie(v,X));return R.length?R:z}function V(c){return pe.find(p=>p.name===c)||null}function ce(c,p){return e.scanners.find(b=>b.col<=c&&c<=b.col+1&&b.row-1<=p&&p<=b.row+1)||null}function he(c,p){let b=V(p);if(!b)return null;let k=ce(c.col+b.dc,c.row+b.dr);if(!k)return null;let v=Re(k,"inputA");return v&&v.col===c.col&&v.row===c.row?k:null}function Z(c){return Object.entries(e.facilities||{}).find(([,p])=>p?.entry&&p.entry.col===c.col&&p.entry.row===c.row)||null}function g(c){return c?.baseId||c?.id||""}function _(c,p){return c.side==="left"&&p==="left"||c.side==="right"&&p==="right"||c.side==="top"&&p==="up"||c.side==="bottom"&&p==="down"}function U(c,p){let b=V(p);if(!b)return null;let k={col:c.col+b.dc,row:c.row+b.dr};if(Pe(k.col,k.row)&&e.conveyorTiles.has(I(k.col,k.row)))return{type:"conveyor",dirName:p,target:A(k.col,k.row),nextCell:k};let v=he(c,p);if(v)return{type:"scanner",dirName:p,target:de(v),scanner:v};let P=Z(c);return P&&_(P[1],p)?{type:"facility",dirName:p,facilityId:P[0],facility:P[1],target:A(c.col,c.row)}:null}function ie(c,p){let b=V(p);if(!b)return!1;let k=e.conveyorMeta?.get?.(I(c.col+b.dc,c.row+b.dr));return k?[...new Set([...Array.isArray(k.outDirs)?k.outDirs:[],k.dir].filter(Boolean))].includes(b.opposite):!1}function me(c,p){return p&&(p.type==="facility"||!B(c,p.target))}function fe(c,p,b,k="random"){let v=b.map(X=>U(p,X)).filter(Boolean);if(!v.length)return null;if(k==="pass"){let X=c.prevConveyorCell?Q(c.prevConveyorCell):null,Te=X?mt(X,p):null,dn=Te?v.find(Qn=>Qn.dirName===Te):null;if(me(c,dn))return dn}if(k&&k!=="random"){let X=v.find(Te=>Te.dirName===k);if(me(c,X))return X}let R=v.filter(X=>X.type!=="facility").filter(X=>!B(c,X.target)),z=R.length?R:v.filter(X=>X.type==="facility");return z.length?z[Math.floor(Math.random()*z.length)]:null}function E(c,p){let b=[];ee(b,{x:c.x,y:c.y}),ee(b,p),c.route=b.length?b:[{x:c.x,y:c.y}],c.targetIndex=c.route.length>1?1:c.route.length}function G(c,p){let b=Oe(c.x,c.y);if(!b)return $t(p,"OFF GRID");let k=I(b.col,b.row);if(!e.conveyorTiles.has(k))return $t(p,"OFF BELT");let v=e.conveyorMeta?.get(k)||{},P=ae(k);if(!P.length){c.stoppedTimer=.25;return}let R=fe(c,b,P,v.branchMode||"random");if(!R){c.stoppedTimer=.25;return}if(R.type==="facility"){let z=g(R.facility);z==="mixer"?Ht(p,R.facility):z==="truck"?Vn(p,R.facility):z==="trash"?Xn(p,R.facility):$t(p,"DONE");return}R.type==="scanner"&&(c.pendingScannerId=R.scanner.id),c.prevConveyorCell=k,E(c,R.target)}function x(c){let p=Oe(c.x,c.y);if(!p)return null;let b=I(p.col,p.row);return e.conveyorTiles.has(b)?b:null}function C(c){let p=x(c)||x(c.route?.[c.targetIndex]||c);return t(p)}function O(c,p){let b=x(p);!b||c.lastWearKey===b||(c.lastWearKey=b,Si(e,b))}function Y(c,p){let b=C(c)*p;for(;b>0&&c.targetIndex=R?(c.x=k.x,c.y=k.y,O(c,k),c.targetIndex+=1,b-=R):(c.x+=v/R*b,c.y+=P/R*b,b=0)}}function te(c,p,b){c.stage="queued",c.route=null,c.queueRoute=b?b.map(k=>({x:k.x,y:k.y})):[de(p)],c.scannerId=p.id,p.queue.includes(c.id)||p.queue.push(c.id),se(p)}function se(c){c.queue=c.queue.filter(b=>e.chicks.some(k=>k.id===b&&k.stage==="queued"));let p=y.cell*.86;c.queue.forEach((b,k)=>{let v=e.chicks.find(P=>P.id===b);v&&(ke(v,v.queueRoute||[de(c)],k*p),v.queueIndex=k)})}function ke(c,p,b){if(!p||!p.length)return;if(p.length===1||b<=0){let P=p[p.length-1];c.x=P.x,c.y=P.y;return}let k=b;for(let P=p.length-1;P>0;P-=1){let R=p[P-1],z=p[P],X=Math.hypot(z.x-R.x,z.y-R.y);if(k<=X){let Te=1-k/X;c.x=R.x+(z.x-R.x)*Te,c.y=R.y+(z.y-R.y)*Te;return}k-=X}let v=p[0];c.x=v.x,c.y=v.y}function ue(c){for(let p of e.scanners){if(p.cooldown>0&&(p.cooldown=Math.max(0,p.cooldown-c)),p.keyPressTime>0&&(p.keyPressTime=Math.max(0,p.keyPressTime-c)),se(p),p.kind!=="auto"||p.cooldown>0||!p.queue.length)continue;let b=p.queue[0],k=e.chicks.findIndex(v=>v.id===b);if(k<0){p.queue.shift();continue}cn(k,bo(p,e.chicks[k]),!0)&&(Ti(e,p),p.cooldown=Qe(p,e),ss(e))}}function et(c){let p=fo(e,c);return!p||!p.queue.length?null:e.chicks.find(b=>b.id===p.queue[0])||null}function J(c,p){if(e.phase!=="running")return;let b=fo(e,c);Se(b,p)}function Se(c,p){if(e.phase!=="running"||!c||c.kind!=="manual"||(c.keyPressTime=.18,c.keyPressSide=p,!c.queue.length))return;let b=e.chicks.findIndex(k=>k.id===c.queue[0]);b>=0&&cn(b,p,!1)}function qe(c,p,b){return c.kind==="manual"&&bo(c,p)===b}function We(c){let p=e.manualCombo?.count||0;if(e.manualCombo||(e.manualCombo={count:0,lastBonus:0}),e.manualCombo.count=0,e.manualCombo.lastBonus=0,e.stats.manualComboFailure=(e.stats.manualComboFailure||0)+1,e.totals.manualComboFailure=(e.totals.manualComboFailure||0)+1,p>=10){let b=de(c);Et(e,10,.25),N(e,b.x,b.y-36,"COMBO RESET",f.danger)}}function Fe(c){e.manualCombo||(e.manualCombo={count:0,lastBonus:0}),e.manualCombo.count=Math.max(0,e.manualCombo.count||0)+1,e.stats.manualComboSuccess=(e.stats.manualComboSuccess||0)+1,e.totals.manualComboSuccess=(e.totals.manualComboSuccess||0)+1;let p=e.manualCombo.count;if(e.manualCombo.lastBonus=0,p<10)return 0;let b=Math.max(1,Math.floor(p/10)),k=e.scanners.filter(z=>z.kind==="manual").length,v=Math.min(1,(20+k*10)/100),P=0;for(let z=0;z0){Ae(e,R),e.stats.manualComboBonus=(e.stats.manualComboBonus||0)+R,e.totals.manualComboBonus=(e.totals.manualComboBonus||0)+R,e.manualCombo.lastBonus=R;let z=de(c);N(e,z.x,z.y-42,`COMBO +${S(R)}`,f.green)}return R}function At(c,p){let b=Re(c,p);if(!b)return null;let k=I(b.col,b.row);if(e.conveyorTiles.has(k))return{...b};let P=(De(e).scannerPorts.get(c.id)?.[p]?.keys||[]).filter(Boolean);if(!P.length)return null;let R=P.map(Q);return R.find(X=>X.col===b.col&&X.row===b.row)||R[0]||null}function zn(c,p,b,k){let v=[];ee(v,{x:c.x,y:c.y});let P=Re(p,b);return P&&ee(v,A(P.col,P.row)),ee(v,A(k.col,k.row)),v}function cn(c,p,b){let k=e.chicks[c];if(!k||k.stage!=="queued")return!1;let v=Mt(e,k.scannerId);if(!v)return!1;let P=At(v,p);if(!P)return!b&&v.kind==="manual"&&We(v),N(e,k.x,k.y-16,p==="left"?"NO LEFT BELT":"NO RIGHT BELT",f.danger),!1;!b&&v.kind==="manual"&&(qe(v,k,p)?Fe(v):We(v)),v.queue=v.queue.filter(z=>z!==k.id),k.stage="belt",k.route=zn(k,v,p,P),k.targetIndex=k.route.length>1?1:k.route.length,k.scannerId=null,k.nextScannerId=null,k.pendingScannerId=null,k.queueRoute=null,k.queueIndex=-1;let R=b?f.green:p==="left"?f.mixerBlue:f.truckPink;return yi(e,de(v).x,de(v).y,R),N(e,k.x,k.y-20,b?"AUTO":p.toUpperCase(),R),!0}function pt(c){let p=e.chicks[c];return p?(e.chicks.splice(c,1),p):null}function Ht(c,p=e.facilities.mixer){if(p?.processingCooldown>0){let R=e.chicks[c];R&&(R.stoppedTimer=.25);return}let b=pt(c);if(!b)return;let{x:k,y:v}=b;if(as(e),Oo(e,p),No(e,p),b.sex==="poop"){let R=ni(e);Kn(e,"mixer"),ut(e),mi(e,k,v),N(e,k,v-18,`-${S(R)}`,f.danger);return}let P=ti(e);b.sex==="male"?_t(e):ut(e),hi(e,k,v),N(e,k,v-18,`+${S(P)}`,f.green)}function Vn(c,p=e.facilities.truck){let b=pt(c);if(!b)return;let{x:k,y:v}=b;un(b.sex,p),wi(e,k,v,b.sex),us(e,b.sex);let P=Ve(e),R=Vr(e,b.sex);if(b.sex==="poop"){let z=oi(e);Kn(e,"truck"),R?_t(e):ut(e),N(e,k,v-18,`-${S(z)}`,f.danger);return}if(b.sex==="female"){R?(_t(e),N(e,k,v-18,`+${S(Ne(e))}`,f.green)):(ut(e),N(e,k,v-18,P==="male"?"REJECT":"NO SALE",f.warn));return}if(R){_t(e),N(e,k,v-18,`+${S(Ne(e))}`,f.green);return}if(Xr(e)){let z=ri(e);ut(e),N(e,k,v-18,`-${S(z)}`,f.danger)}else ut(e),N(e,k,v-18,"REJECT",f.warn)}function Xn(c,p=e.facilities.trash){if(p?.processingCooldown>0){let z=e.chicks[c];z&&(z.stoppedTimer=.25);return}let b=pt(c);if(!b)return;let{x:k,y:v}=b;bi(e,k,v,b.sex),cs(e),Oo(e,p),No(e,p);let P=Zr(e),R=P.amount>0?` +${S(P.amount)}`:"";if(b.sex==="poop"){Kn(e,"trash"),_t(e);let z=Math.max(0,Number(e.cardEffects?.composter)||0)*3;if(z>0){let X=Ae(e,z);e.stats.composterIncome=(e.stats.composterIncome||0)+X,e.totals.composterIncome=(e.totals.composterIncome||0)+X,N(e,k,v-36,`COMPOST +${S(X)}`,f.green)}N(e,k,v-18,`CLEAN${R}`,P.amount>0?f.green:f.green)}else ut(e),N(e,k,v-18,P.amount>0?`WASTE${R}`:"WASTE",P.amount>0?f.green:f.ink)}function $t(c,p){let b=pt(c);b&&N(e,b.x,b.y-12,p,f.muted)}function un(c,p=e.facilities.truck){let b=p||e.facilities.truck;b&&(e.truckCargo.push({sex:c,x:W(22,b.w-22),y:W(66,b.h-24)}),e.truckCargo.length>45&&e.truckCargo.shift())}function qt(c){let p=Oe(c.x,c.y);if(p){let P=I(p.col,p.row);if(e.conveyorTiles.has(P))return P}let b=Nr(e,c.x,c.y);if(!b)return null;let k=Q(b),v=A(k.col,k.row);return Math.hypot(c.x-v.x,c.y-v.y)<=y.cell*.55?b:null}function Yt(){let{components:c,cellToComponent:p}=Or(e);for(let b of e.chicks){if(b.stage==="flying")continue;let k=qt(b);if(!k)continue;let v=p.get(k),P=c.get(v);P&&(P.count+=1)}for(let b of c.values())b.ratio=b.count/b.capacity;e.congestion=c,e.componentLookup=p}function Zo(){for(let c of e.congestion.values()){if(c.ratio>.8&&c.ratio<1)for(let p of zt(c.id).slice(0,3))Math.random()<.06&&xi(e,p.x,p.y-p.radius-8);if(c.ratio>=1){let p=e.lastExplodedComponent.get(c.id)||0,b=performance.now();b-p>900&&(e.lastExplodedComponent.set(c.id,b),Jn(c))}}}function zt(c){return e.chicks.filter(p=>{let b=qt(p);return b&&e.componentLookup?.get(b)===c})}function Jn(c){let p=zt(c.id);if(!p.length)return;for(let v of p)Eo(e,v.sex,v.x,v.y);e.chicks=e.chicks.filter(v=>!p.some(P=>P.id===v.id));for(let v of e.scanners)v.queue=v.queue.filter(P=>e.chicks.some(R=>R.id===P));for(let v of c.cells){let P=Q(v),R=A(P.col,P.row);Je(e,R.x,R.y,f.danger),at(e,R.x,R.y,6),Pn(e,R.x,R.y,4)}Et(e,18,.75);let b=Q(c.cells[0]),k=A(b.col,b.row);N(e,k.x,k.y-22,"100% JAM EXPLOSION",f.danger)}return{activeQueuedChick:et,currentSpawnDelay:o,sortSlot:J,updateCongestion:Yt,sortScanner:Se,updateRunning:w}}var re=document.getElementById("gameCanvas"),Aa=re.getContext("2d"),L={shell:document.getElementById("gameShell"),money:document.getElementById("money"),turn:document.getElementById("turn"),timeLeft:document.getElementById("timeLeft"),phase:document.getElementById("phaseLabel"),comboCount:document.getElementById("comboCount"),eventBrief:document.getElementById("eventBrief"),targetBrief:document.getElementById("targetBrief"),speedBrief:document.getElementById("speedBrief"),turnProfit:document.getElementById("turnProfit"),mixerCount:document.getElementById("mixerCount"),truckFemaleCount:document.getElementById("truckFemaleCount"),truckMaleCount:document.getElementById("truckMaleCount"),poopCount:document.getElementById("poopCount"),buildStatus:document.getElementById("buildStatus"),turnSummary:document.getElementById("turnSummary"),contractPanel:document.getElementById("contractPanel"),modal:document.getElementById("modal"),modalTitle:document.getElementById("modalTitle"),modalBody:document.getElementById("modalBody"),modalActions:document.getElementById("modalActions"),hoverTooltip:document.getElementById("hoverTooltip"),manualScannerMonitor:document.getElementById("manualScannerMonitor"),hireRepairmanButton:document.getElementById("hireRepairmanButton"),debug:{panel:document.getElementById("debugPanel"),infiniteCash:document.getElementById("debugInfiniteCash"),dayInput:document.getElementById("debugDayInput"),setDay:document.getElementById("debugSetDayButton"),zeroTimer:document.getElementById("debugZeroTimerButton"),cardSelect:document.getElementById("debugCardSelect"),grantCard:document.getElementById("debugGrantCardButton"),grantAllCards:document.getElementById("debugGrantAllCardsButton"),readout:document.getElementById("debugReadout")},buttons:{s1Left:document.getElementById("scanner1MixerButton"),s1Right:document.getElementById("scanner1TruckButton"),s2Left:document.getElementById("scanner2MixerButton"),s2Right:document.getElementById("scanner2TruckButton"),nextTurn:document.getElementById("nextTurnButton"),conveyor:document.getElementById("buildConveyorButton"),boostConveyor:document.getElementById("buildBoostConveyorButton"),eggFarm:document.getElementById("buildEggFarmButton"),autoScanner:document.getElementById("buildAutoScannerButton"),manualScanner:document.getElementById("buildManualScannerButton"),mixer:document.getElementById("buildMixerButton"),trash:document.getElementById("buildTrashButton"),truck:document.getElementById("buildTruckButton"),erase:document.getElementById("eraseButton"),undo:document.getElementById("undoButton"),redo:document.getElementById("redoButton")}};function $a(){let e={conveyor:"conveyor",boostConveyor:"boostConveyor",eggFarm:"eggFarm",autoScanner:"autoScanner",manualScanner:"manualScanner",mixer:"mixer",trash:"trash",truck:"truck"};for(let[t,n]of Object.entries(e))L.buttons[t]&&(L.buttons[t].innerHTML=ur(n))}function Ia(){L.debug?.panel&&L.debug.cardSelect&&(L.debug.cardSelect.innerHTML=Wt.map(e=>``).join(""))}var a=co(),q,K,Me,Kt;function Fa(e=null){let t=Gn(a,e);return e?t*$o(a,e):t}function Ba(){Object.assign(a,co()),a.phase="running",a.view={x:0,y:0,scale:1},uo(a),xs(),ye(a),K.hideModal(),Me.updateCongestion(),K.updatePanels(),K.updateUI(),Dt()}function La(){let e=Math.max(1,Math.floor(Number(L.debug.dayInput?.value)||1));a.turn=e,L.debug.dayInput&&(L.debug.dayInput.value=String(e)),N(a,re.width/2-a.view.x,86-a.view.y,`DAY ${e}`,f.green),K.updatePanels(),K.updateUI(),Dt(`Day set to ${e}.`)}function Oa(){a.timeLeft=0,a.phase==="running"&&bs(),N(a,re.width/2-a.view.x,86-a.view.y,"TIMER 0",f.warn),K.updatePanels(),K.updateUI(),Dt("Timer set to 0.")}function Na(){let e=L.debug.cardSelect?.value,t=_o(a,e);L.debug.grantCard&&(L.debug.grantCard.textContent=t.ok?"Granted":"Grant failed",window.setTimeout(()=>{L.debug.grantCard&&(L.debug.grantCard.textContent="Grant Card")},900)),K.updatePanels(),K.updateUI(),Dt(t.reason)}function Ga(){let t=Wt.map(n=>_o(a,n)).filter(n=>n.ok).length;K.updatePanels(),K.updateUI(),Dt(`Granted ${t}/${Wt.length} cards.`)}function Ua(){L.debug?.infiniteCash?.checked&&a.cash<999999&&(a.cash=999999,a.totals.maxCash=Math.max(a.totals.maxCash,a.cash))}function Dt(e=""){L.debug?.readout&&(L.debug.readout.textContent=[e,`D${a.turn}`,S(a.cash),`Combo ${a.manualCombo?.count||0}`,`Rescue ${a.cardEffects?.rescueLoanCharges||0}`,`Chem ${a.cardEffects?.chemicalWeaponSubsidy||0}`].filter(Boolean).join(" | "))}function Wa(){if(a.phase!=="build")return;if(a.cardDraft?.pending||a.cardTargetPick?.pending){q.fail(a.cardTargetPick?.pending?"Choose upgrade target first.":"Choose an upgrade card first."),K.updatePanels();return}let e=jt(a);if(e.length){q.fail(`Cannot start: ${e[0]}`),K.updatePanels();return}ye(a),Br(a),Ai(a);let t=Math.max(0,Math.floor(a.lastResult?.profit??a.stats?.profit??0)),n=li(a,t),o=ui(a);a.lastStartFees={zundaTax:n.tax||0,loanRepayment:o.amount||0,fairiesTribute:0},n.tax>0&&N(a,re.width/2-a.view.x,116-a.view.y,`ZUNDA TAX -${S(n.tax)}`,f.danger),o.amount>0&&N(a,re.width/2-a.view.x,146-a.view.y,`LOAN -${S(o.amount)}`,f.danger),Hr(a),a.phase="running",a.turn+=1,a.timeLeft=Ft,a.shutdownTimeLeft=0,a.cleanupTimer=null,a.cleanupBlown=!1,a.chicks=[],a.effects=[],a.floatingTexts=[],a.shake={time:0,strength:0},a.truckCargo=[],xs(),a.stats=ao(),a.stats.zundaTax=0,a.stats.loanRepayment=a.lastStartFees?.loanRepayment||0,a.stats.repairWorkerWages=a.pendingRepairWorkerWages||0;let r=a.stats.loanRepayment+a.stats.repairWorkerWages;r>0&&(a.stats.penalty+=r,a.stats.profit-=r),a.pendingRepairWorkerWages=0;for(let s of a.scanners)s.queue=[],s.cooldown=0;for(let s of a.eggFarms)s.nextSpawn=Me.currentSpawnDelay(s),s.lastInterval=s.nextSpawn,s.shutterProgress=0,s.shutterSparked=!1;a.buildTool=null,a.selected=null,a.groupDrag=null,a.selectionBox=null,a.pan=null,Me.updateCongestion(),K.updatePanels(),K.updateUI()}function _a(){ye(a);let e=Di(a);e.ok?N(a,re.width/2-a.view.x,96-a.view.y,`Repairman hired -${S(e.cost)}`,f.green):(N(a,re.width/2-a.view.x,96-a.view.y,e.reason||"Cannot hire repairman.",f.danger),q?.fail?.(e.reason||"Cannot hire repairman.")),K.updatePanels(),K.updateUI()}function Ka(){if(a.phase!=="running")return;let e=si(a),t=Jr(a),n=ci(a);if(e.adjusted>0){let s=a.facilities.truck,l=s?s.x+s.w/2:re.width/2-a.view.x,d=s?s.y+26:96-a.view.y;N(a,l,d,`${e.target==="poop"?"POOP SHIP":"SHIP"} +${S(e.adjusted)}`,f.green)}t?.bonus>0&&N(a,re.width/2-a.view.x,96-a.view.y,`CONTRACT +${S(t.bonus)}`,f.green),n.amount>0&&N(a,re.width/2-a.view.x,116-a.view.y,`CHEM SUBSIDY +${S(n.amount)}`,f.green),qr(a);let o=Math.max(1,(a.totals.turnsCompleted||0)+1),r=ai(a,o);if(a.lastBuildFees={fairiesTribute:r.amount||0,reduction:r.reduction||0,turn:o},r.amount>0&&N(a,re.width/2-a.view.x,146-a.view.y,`FAIRIES -${S(r.amount)}`,f.danger),a.totals.turnsCompleted+=1,a.lastResult={...a.stats,ship:e,contract:t,chemical:n,rescue:{amount:0,used:0,remaining:a.cardEffects?.rescueLoanCharges||0},fairiesTribute:r,turn:a.turn,cash:a.cash},a.cash<0){if((a.cardEffects?.rescueLoanCharges||0)>0){a.phase="loanDecision",K.showEmergencyLoanDecision(ms,Hn);return}a.phase="gameover",K.showGameOver();return}hs()}function hs(){a.phase="build",a.truckCargo=[],a.chicks=[],a.shutdownTimeLeft=0,a.cleanupTimer=null,a.cleanupBlown=!1,a.buildSession+=1,a.undoStack=[],a.redoStack=[],a.timeLeft=0;for(let e of a.scanners)e.queue=[];for(let e of a.eggFarms)e.shutterProgress=0,e.shutterSparked=!1;$i(a),a.contractOffer=Kr(a),Kt&&Kt.prepareDraft(),a.cardTargetPick=null,a.buildTool=null,a.groupDrag=null,a.selectionBox=null,a.pan=null,K.updatePanels(),K.showTurnResult(a.lastResult)}function ms(){if((a.cardEffects?.rescueLoanCharges||0)<=0)return Hn();a.cardEffects.rescueLoanCharges-=1,a.stats.rescueLoan=(a.stats.rescueLoan||0)+500,a.totals.rescueLoan=(a.totals.rescueLoan||0)+500;let e=Ae(a,500);a.lastResult={...a.lastResult||{},...a.stats,rescue:{amount:(a.lastResult?.rescue?.amount||0)+e,used:(a.lastResult?.rescue?.used||0)+1,remaining:a.cardEffects.rescueLoanCharges},cash:a.cash},N(a,re.width/2-a.view.x,176-a.view.y,`RESCUE +${S(e)}`,f.green),a.cash>=0?hs():(a.cardEffects?.rescueLoanCharges||0)>0?(a.phase="loanDecision",K.showEmergencyLoanDecision(ms,Hn)):Hn()}function Hn(){a.phase="gameover",K.showGameOver()}function bs(){a.shutdownTimeLeft<=0&&(a.shutdownTimeLeft=a.shutdownGraceSeconds||10);for(let e of a.eggFarms){if(e.shutterSparked)continue;e.shutterProgress=.02,e.shutterSparked=!0;let t=A(e.col,e.row);Je(a,t.x,t.y,f.ink),at(a,t.x,t.y+10,10),N(a,t.x,t.y-34,"SHUT +10s",f.ink)}}function ws(e){a.lastTimestamp||(a.lastTimestamp=e);let t=Math.min((e-a.lastTimestamp)/1e3,.05);a.lastTimestamp=e,Ua(),a.phase==="running"&&Me.updateRunning(t,{closeFarmShutters:bs,completeTurn:Ka}),vi(a,t,re,q.equipmentHitBoxes),Me.updateCongestion(),Vi(Aa,re,a,{activeQueuedChick:Me.activeQueuedChick,selectedObject:q.selectedObject,selectedTitle:q.selectedTitle}),K.updateUI(),ks(),Dt(),requestAnimationFrame(ws)}function qn(){return a.view?.scale||1}function an(){a.view||(a.view={x:0,y:0,scale:1}),a.view.scale==null&&(a.view.scale=1)}function Yn(e){let t=re.getBoundingClientRect();return{x:(e.clientX-t.left)*re.width/t.width,y:(e.clientY-t.top)*re.height/t.height}}function dt(e){let t=Yn(e),n=qn();return{x:(t.x-(a.view?.x||0))/n,y:(t.y-(a.view?.y||0))/n}}function Ha(e){let t=qn();return{x:e.x*t+(a.view?.x||0),y:e.y*t+(a.view?.y||0)}}function qa(){let e={left:y.x-90,top:y.y-130,right:y.x+y.cols*y.cell+260,bottom:y.y+y.rows*y.cell+220};if(a.phase==="build")for(let t of wt(a)){let n=xt(t);e.left=Math.min(e.left,n.x-60),e.top=Math.min(e.top,n.y-80),e.right=Math.max(e.right,n.x+n.w+80),e.bottom=Math.max(e.bottom,n.y+n.h+80)}return e}function fs(e,t){if(e<=t)return[e,t];let n=(e+t)/2;return[n,n]}function ft(){an();let e=qn(),t=qa(),[n,o]=fs(re.width-160-t.right*e,110-t.left*e),[r,s]=fs(re.height-150-t.bottom*e,150-t.top*e);a.view.x=Qt(a.view.x,n,o),a.view.y=Qt(a.view.y,r,s)}function xs(){an(),a.view.scale=1;let e=a.eggFarms?.[0],t=e?A(e.col,e.row):{x:y.x,y:y.y};a.view.x=280-t.x,a.view.y=190-t.y,ft()}function Ya(e){let t=Yn(e);an(),a.pan={start:t,viewX:a.view.x,viewY:a.view.y}}function za(e){if(!a.pan)return;let t=Yn(e);an(),a.view.x=a.pan.viewX+t.x-a.pan.start.x,a.view.y=a.pan.viewY+t.y-a.pan.start.y,ft()}function Va(e){e.preventDefault(),an();let t=Yn(e),n=dt(e),o=Math.exp(-e.deltaY*.0012),r=Qt(qn()*o,.55,1.8);a.view.scale=r,a.view.x=t.x-n.x*r,a.view.y=t.y-n.y*r,ft(),je()}function Xa(e){return e?e.type==="conveyor"?{type:"conveyor",id:e.oldKey}:{type:e.type,id:e.ref.id}:null}function Ja(e){return e?e.type==="conveyor"?{type:"conveyor",id:e.oldKey}:e.ref||null:null}function je(){L.hoverTooltip&&(L.hoverTooltip.classList.remove("visible"),L.hoverTooltip.setAttribute("aria-hidden","true"),a.hover=null)}function Qa(e,t){if(!L.hoverTooltip||a.phase!=="build"||a.cardTargetPick?.pending||a.groupDrag||a.selectionBox||a.pan)return je();let n=Ja(t);if(!n)return je();let o=q.selectedTitle(n),r=q.flavorText?q.flavorText(n):"",s=q.disconnectedWarningFor?q.disconnectedWarningFor(n):"",l=q.selectedInfoLines(n).slice(0,7);L.hoverTooltip.innerHTML=`${o}${s?`${s}`:r?`${r}`:""}${l.map(M=>`${M}`).join("")}`;let d=Math.min(window.innerWidth-330,e.clientX+18),w=Math.min(window.innerHeight-220,e.clientY+18);L.hoverTooltip.style.left=`${Math.max(8,d)}px`,L.hoverTooltip.style.top=`${Math.max(8,w)}px`,L.hoverTooltip.classList.add("visible"),L.hoverTooltip.setAttribute("aria-hidden","false"),a.hover={type:t.type,id:t.oldKey||t.ref?.id||null}}function Za(e){if(a.phase!=="build")return je();Qa(e,q.equipmentAtPoint(dt(e)))}function vs(e){je();let t=dt(e),n=q.equipmentAtPoint(t),o=Xa(n);if(!o){a.selected=null,a.multiSelected=[],K.updatePanels();return}a.selected=o,a.multiSelected=[o],K.updatePanels();let r=q.selectedObject();r?.type==="scanner"&&r.kind==="manual"&&q.showManualScannerMenu(r),r?.type==="scanner"&&r.kind==="auto"&&q.showAutoScannerMenu(r)}function ln(){a.buildTool=null,a.selected=null,a.multiSelected=[],a.selectionBox=null,a.cardTargetPick?.mode==="autoScannerMenu"&&(a.cardTargetPick=null),K.hideModal(),je(),K.updatePanels(),K.updateUI()}function ps(e){return String(e).replace(/[&<>"']/g,t=>({"&":"&","<":"<",">":">",'"':""","'":"'"})[t])}function ja(e){let t=e?.queue?.[0];return t==null?null:a.chicks.find(n=>n.id===t)||null}function ec(e){if(!e)return'WAIT';let t=e.sex==="poop"?"\u{1F4A9} POOP":e.sex==="male"?"\u2642 MALE":"\u2640 FEMALE";return`${t}`}function tc(e,t){let n=new Set(a.eggFarms.map(w=>I(w.col,w.row))),o=new Map(a.scanners.map(w=>[I(w.col,w.row),w])),r=new Set(Object.values(a.facilities||{}).map(w=>w.entry?I(w.entry.col,w.entry.row):null).filter(Boolean)),s=t?.sex||"",l=s==="poop"?"\u{1F4A9}":s==="male"?"\u2642":s==="female"?"\u2640":"",d=[];for(let w=e.row-2;w<=e.row+2;w+=1)for(let M=e.col-2;M<=e.col+2;M+=1){let $=I(M,w),F=["mini-cell"],B="";M===e.col&&w===e.row?(F.push("scanner"),s&&F.push(s),B=l||"S"):w===e.row-1&&M===e.col?(F.push("input"),B="IN"):n.has($)?(F.push("egg"),B="E"):o.has($)?(F.push("scanner-other"),B="S"):r.has($)?(F.push("port"),B="P"):a.conveyorTiles.has($)?(F.push("belt"),B="\u2022"):a.blockedCells?.has?.($)&&(F.push("blocked"),B="\xD7"),d.push(`${B}`)}return`
${d.join("")}
`}function nc(e){let t=Ha(A(e.col,e.row)),n=76;return t.x<-n||t.y<-n||t.x>re.width+n||t.y>re.height+n}function ks(){if(!L.manualScannerMonitor)return;if(a.phase!=="running"){L.manualScannerMonitor.classList.remove("visible"),L.manualScannerMonitor.innerHTML="";return}let e=a.scanners.filter(t=>t.kind==="manual"&&nc(t));if(!e.length){L.manualScannerMonitor.classList.remove("visible"),L.manualScannerMonitor.innerHTML="";return}L.manualScannerMonitor.innerHTML=e.map(t=>{let n=ja(t),o=t.keys?.left?.label||(t.slot===0?"A":"Left"),r=t.keys?.right?.label||(t.slot===0?"D":"Right"),s=t.role===0?"MIXER":"WASTE",l=t.role===0?"S2":"TRUCK",d=n?"":" disabled";return`
S${(t.slot??0)+1}OFFSCREEN / Q:${t.queue.length}
- IN${Ca(n)}SCANNER + IN${ec(n)}SCANNER
- ${Sa(t,n)} + ${tc(t,n)}
- - + +
-
`}).join(""),N.manualScannerMonitor.classList.add("visible")}N.manualScannerMonitor?.addEventListener("click",e=>{let t=e.target.closest("button[data-scanner-id]");if(!t||t.disabled)return;let n=a.scanners.find(o=>o.id===Number(t.dataset.scannerId));n&&(be.sortScanner(n,t.dataset.side),es())});re.addEventListener("contextmenu",e=>e.preventDefault());re.addEventListener("wheel",ma,{passive:!1});re.addEventListener("pointerdown",e=>{if(a.phase!=="build")return;re.setPointerCapture(e.pointerId);let t=pt(e);if(e.button===2){ya(e);return}if(e.button!==0)return;if(a.cardTargetPick?.pending){Ht.chooseTargetAtPoint(t);return}let n=Y.expansionOfferAtPoint?.(t);if(n){Y.buyGridExpansion(n),Ze(),q.updatePanels(),q.updateUI();return}if(a.buildTool==="erase"){Y.eraseAtPoint(t);return}if(a.buildTool){if(a.buildTool==="conveyor"){Y.beginConveyorDrag($e(t.x,t.y));return}if(Y.equipmentAtPoint(t)){a.buildTool=null,ji(e);return}Y.buildAtPoint(t);return}Y.startGroupDrag(e)||Y.startSelectionBox(e)});re.addEventListener("pointermove",e=>{if(a.phase!=="build"){yt();return}ka(e),a.pan&&e.buttons&2&&ha(e),a.groupDrag&&e.buttons&1&&Y.updateGroupDrag(e),a.selectionBox&&e.buttons&1&&Y.updateSelectionBox(e),a.buildTool==="conveyor"&&e.buttons&1&&Y.continueConveyorDrag($e(pt(e).x,pt(e).y)),a.buildTool==="erase"&&e.buttons&1&&Y.eraseAtPoint(pt(e))});re.addEventListener("pointerup",e=>{a.selectionBox?Y.finishSelectionBox():a.groupDrag&&(a.groupDrag.committed||ji(e),Y.finishGroupDrag()),a.buildTool==="conveyor"&&Y.endConveyorDrag?.(),a.groupDrag=null,a.pan=null;try{re.releasePointerCapture(e.pointerId)}catch{}});re.addEventListener("pointerleave",yt);re.addEventListener("pointercancel",()=>{Y?.endConveyorDrag?.(),a.groupDrag=null,a.selectionBox=null,a.pan=null,yt()});document.addEventListener("pointerdown",e=>{!N.modal.classList.contains("visible")||!N.modal.classList.contains("equipment-popover")||e.target.closest("#modal .modal-card")||q.hideModal()},!0);N.buttons.s1Left.addEventListener("click",()=>be.sortSlot(0,"left"));N.buttons.s1Right.addEventListener("click",()=>be.sortSlot(0,"right"));N.buttons.s2Left.addEventListener("click",()=>be.sortSlot(1,"left"));N.buttons.s2Right.addEventListener("click",()=>be.sortSlot(1,"right"));N.buttons.nextTurn.addEventListener("click",aa);N.buttons.conveyor.addEventListener("click",()=>Y.setBuildTool("conveyor"));N.buttons.eggFarm.addEventListener("click",()=>Y.setBuildTool("eggFarm"));N.buttons.autoScanner.addEventListener("click",()=>Y.setBuildTool("autoScanner"));N.buttons.manualScanner.addEventListener("click",()=>Y.setBuildTool("manualScanner"));N.buttons.mixer.addEventListener("click",()=>Y.setBuildTool("mixer"));N.buttons.trash.addEventListener("click",()=>Y.setBuildTool("trash"));N.buttons.truck.addEventListener("click",()=>Y.setBuildTool("truck"));N.buttons.erase.addEventListener("click",()=>Y.setBuildTool("erase"));N.hireRepairmanButton?.addEventListener("click",ca);N.expandGridButton?.addEventListener("click",()=>{Y?.buyGridExpansion?.("right"),Ze(),q.updatePanels(),q.updateUI()});N.debug.setDay?.addEventListener("click",ra);N.debug.grantCard?.addEventListener("click",ia);N.debug.grantAllCards?.addEventListener("click",sa);N.buttons.undo.addEventListener("click",()=>{ao(a)&&(Ze(),q.updatePanels(),q.updateUI())});N.buttons.redo.addEventListener("click",()=>{co(a)&&(Ze(),q.updatePanels(),q.updateUI())});function Vi(e,t,n){let o=e.keys?.[t];return o?o.code&&n.code===o.code||o.key&&n.key.toLowerCase()===o.key:!1}window.addEventListener("keydown",e=>{if(e.repeat)return;let t=e.key.toLowerCase();if(a.phase==="running")for(let n of a.scanners.filter(o=>o.kind==="manual")){if(Vi(n,"left",e)){e.preventDefault(),be.sortScanner(n,"left");return}if(Vi(n,"right",e)){e.preventDefault(),be.sortScanner(n,"right");return}}a.phase==="build"&&(e.ctrlKey||e.metaKey)&&t==="z"&&(e.preventDefault(),ao(a)&&(Ze(),q.updatePanels(),q.updateUI())),a.phase==="build"&&(e.ctrlKey||e.metaKey)&&(t==="y"||e.shiftKey&&t==="z")&&(e.preventDefault(),co(a)&&(Ze(),q.updatePanels(),q.updateUI())),e.key==="Escape"&&a.cardTargetPick?.pending&&(e.preventDefault(),Ht.cancelTargetPick())});Y=Ni({game:a,ui:N,canvas:re,canvasPoint:pt,onUpdatePanels:()=>q?.updatePanels()});be=qi({game:a,currentConveyorSpeed:na,onGameOverCheck:()=>q?.checkGameOver()});Ht=Mi({game:a,ui:N,onUpdatePanels:()=>q?.updatePanels()});q=Gi({game:a,ui:N,build:Y,startGame:oa,beginCardDraft:()=>Ht.showDraft(),activeQueuedChick:be.activeQueuedChick});ea();ta();Vn(a);de(a);be.updateCongestion();q.updatePanels();q.updateUI();q.showTitle();qt();requestAnimationFrame(Qi); + `}).join(""),L.manualScannerMonitor.classList.add("visible")}L.manualScannerMonitor?.addEventListener("click",e=>{let t=e.target.closest("button[data-scanner-id]");if(!t||t.disabled)return;let n=a.scanners.find(o=>o.id===Number(t.dataset.scannerId));n&&(Me.sortScanner(n,t.dataset.side),ks())});re.addEventListener("contextmenu",e=>e.preventDefault());re.addEventListener("wheel",Va,{passive:!1});re.addEventListener("pointerdown",e=>{if(a.phase!=="build")return;re.setPointerCapture(e.pointerId);let t=dt(e);if(e.button===2){Ya(e);return}if(e.button!==0)return;if(a.cardTargetPick?.mode==="autoScannerMenu"){ln();return}if(a.cardTargetPick?.pending){Kt.chooseTargetAtPoint(t);return}let n=q.expansionOfferAtPoint?.(t);if(n){q.buyGridExpansion(n),ft(),K.updatePanels(),K.updateUI();return}if(q.cycleBranchModeAtPoint?.(t)){K.updatePanels(),K.updateUI();return}if(a.buildTool==="erase"){if(!q.equipmentAtPoint(t)){ln();return}q.eraseAtPoint(t);return}if(a.buildTool){let o=Oe(t.x,t.y);if(!o&&!["mixer","trash","truck"].includes(a.buildTool)){ln();return}if(a.buildTool==="conveyor"||a.buildTool==="boostConveyor"){q.beginConveyorDrag(o);return}if(q.equipmentAtPoint(t)){a.buildTool=null,vs(e);return}q.buildAtPoint(t);return}q.startGroupDrag(e)||q.startSelectionBox(e)});re.addEventListener("pointermove",e=>{if(a.phase!=="build"){je();return}Za(e),a.pan&&e.buttons&2&&za(e),a.groupDrag&&e.buttons&1&&q.updateGroupDrag(e),a.selectionBox&&e.buttons&1&&q.updateSelectionBox(e),(a.buildTool==="conveyor"||a.buildTool==="boostConveyor")&&e.buttons&1&&q.continueConveyorDrag(Oe(dt(e).x,dt(e).y)),a.buildTool==="erase"&&e.buttons&1&&q.eraseAtPoint(dt(e))});re.addEventListener("pointerup",e=>{a.selectionBox?q.finishSelectionBox():a.groupDrag&&(a.groupDrag.committed||vs(e),q.finishGroupDrag()),(a.buildTool==="conveyor"||a.buildTool==="boostConveyor")&&q.endConveyorDrag?.(),a.groupDrag=null,a.pan=null;try{re.releasePointerCapture(e.pointerId)}catch{}});re.addEventListener("pointerleave",je);re.addEventListener("pointercancel",()=>{q?.endConveyorDrag?.(),a.groupDrag=null,a.selectionBox=null,a.pan=null,je()});document.addEventListener("pointerdown",e=>{!L.modal.classList.contains("visible")||!L.modal.classList.contains("equipment-popover")||e.target.closest("#modal .modal-card")||ln()},!0);L.buttons.s1Left.addEventListener("click",()=>Me.sortSlot(0,"left"));L.buttons.s1Right.addEventListener("click",()=>Me.sortSlot(0,"right"));L.buttons.s2Left.addEventListener("click",()=>Me.sortSlot(1,"left"));L.buttons.s2Right.addEventListener("click",()=>Me.sortSlot(1,"right"));L.buttons.nextTurn.addEventListener("click",Wa);L.buttons.conveyor.addEventListener("click",()=>q.setBuildTool("conveyor"));L.buttons.boostConveyor.addEventListener("click",()=>q.setBuildTool("boostConveyor"));L.buttons.eggFarm.addEventListener("click",()=>q.setBuildTool("eggFarm"));L.buttons.autoScanner.addEventListener("click",()=>q.setBuildTool("autoScanner"));L.buttons.manualScanner.addEventListener("click",()=>q.setBuildTool("manualScanner"));L.buttons.mixer.addEventListener("click",()=>q.setBuildTool("mixer"));L.buttons.trash.addEventListener("click",()=>q.setBuildTool("trash"));L.buttons.truck.addEventListener("click",()=>q.setBuildTool("truck"));L.buttons.erase.addEventListener("click",()=>q.setBuildTool("erase"));L.hireRepairmanButton?.addEventListener("click",_a);L.debug.setDay?.addEventListener("click",La);L.debug.zeroTimer?.addEventListener("click",Oa);L.debug.grantCard?.addEventListener("click",Na);L.debug.grantAllCards?.addEventListener("click",Ga);L.buttons.undo.addEventListener("click",()=>{To(a)&&(ft(),K.updatePanels(),K.updateUI())});L.buttons.redo.addEventListener("click",()=>{go(a)&&(ft(),K.updatePanels(),K.updateUI())});function ys(e,t,n){let o=e.keys?.[t];return o?o.code&&n.code===o.code||o.key&&n.key.toLowerCase()===o.key:!1}window.addEventListener("keydown",e=>{if(e.repeat)return;let t=e.key.toLowerCase();if(a.phase==="running")for(let n of a.scanners.filter(o=>o.kind==="manual")){if(ys(n,"left",e)){e.preventDefault(),Me.sortScanner(n,"left");return}if(ys(n,"right",e)){e.preventDefault(),Me.sortScanner(n,"right");return}}a.phase==="build"&&(e.ctrlKey||e.metaKey)&&t==="z"&&(e.preventDefault(),To(a)&&(ft(),K.updatePanels(),K.updateUI())),a.phase==="build"&&(e.ctrlKey||e.metaKey)&&(t==="y"||e.shiftKey&&t==="z")&&(e.preventDefault(),go(a)&&(ft(),K.updatePanels(),K.updateUI())),e.key==="Escape"&&a.cardTargetPick?.pending&&(e.preventDefault(),a.cardTargetPick.mode==="autoScannerMenu"?ln():Kt.cancelTargetPick())});q=rs({game:a,ui:L,canvas:re,canvasPoint:dt,onUpdatePanels:()=>K?.updatePanels()});Me=ds({game:a,currentConveyorSpeed:Fa,onGameOverCheck:()=>K?.checkGameOver()});Kt=Ki({game:a,ui:L,onUpdatePanels:()=>K?.updatePanels()});K=is({game:a,ui:L,build:q,startGame:Ba,beginCardDraft:()=>Kt.showDraft(),activeQueuedChick:Me.activeQueuedChick});$a();Ia();uo(a);ye(a);Me.updateCongestion();K.updatePanels();K.updateUI();K.showTitle();Dt();requestAnimationFrame(ws); diff --git a/index.html b/index.html index d0aedad..a17a4a9 100644 --- a/index.html +++ b/index.html @@ -12,9 +12,9 @@
-
CASH¥250
+
CASH・・250
TIME60.0s
-
NET¥0
+
NET・・0
DAY1
PHASETitle
COMBO0
@@ -46,25 +46,20 @@

Add / Edit Equipment

- - - - - - - + + + + + + + + - - +
Build tools unlock after each day.
- -
-

Irregular One-Day Event

-
Irregular forced events appear during Build phase.
-

Status

@@ -88,6 +83,7 @@ + diff --git a/src/core/balance.js b/src/core/balance.js index 1df22a9..1d2c089 100644 --- a/src/core/balance.js +++ b/src/core/balance.js @@ -53,7 +53,7 @@ export const BALANCE = { }, production: { poopRate: 0.08, - autoScannerCooldown: 2.25, + autoScannerCooldown: 3.5, autoScannerUpgradeRate: 0.95, autoScannerMinCooldown: 0.5, eggSpawnRanges: [ @@ -171,6 +171,40 @@ export const BALANCE = { description: 'Conveyor speed +7.5%.', tags: ['CONVEYOR', 'PASSIVE'] }, + { + id: 'sparePartsBin', + title: 'SPARE PARTS BIN', + rarity: 'common', + type: 'instant', + description: 'Restores 2% durability to every machine. A box of almost-compatible parts.', + tags: ['MAINTENANCE', 'ACTIVE'], + weightBase: 100, + weightLossPerCopy: 2 + }, + { + id: 'dudRefund', + title: 'DUD REFUND', + rarity: 'common', + type: 'instant', + description: 'DUDs now come with paperwork. Gain JPY 50 per copy when you pick a DUD.', + tags: ['CARD', 'ECONOMY', 'PASSIVE'] + }, + { + id: 'freeReroll', + title: 'FREE REROLL', + rarity: 'common', + type: 'instant', + description: 'The first bad ideas are free.', + tags: ['CARD', 'PASSIVE'] + }, + { + id: 'composter', + title: 'COMPOSTER', + rarity: 'common', + type: 'instant', + description: 'Turns shredder waste into suspicious fertilizer. Gain JPY 3 per copy when SHREDDER processes poop.', + tags: ['POOP', 'ECONOMY', 'PASSIVE'] + }, { id: 'extraEggOutlet', title: 'Extra Egg Outlet', @@ -283,6 +317,10 @@ export const BALANCE = { preventiveMaintenance: 0, dudFilter: 0, durabilityCoating: 0, + sparePartsBin: 0, + dudRefund: 0, + freeReroll: 0, + composter: 0, laborExploitation: 0, usedMachineActive: false, rescueLoanCharges: 0, @@ -330,6 +368,10 @@ export const BALANCE = { id: 'conveyor', type: 'conveyor', name: 'Conveyor', shortName: 'Belt', price: 30, buildable: true, upgradeable: false }, + boostConveyor: { + id: 'boostConveyor', type: 'conveyor', name: 'Boost Conveyor', shortName: 'BOOST BELT', price: 50, + buildable: true, upgradeable: false, speedMultiplier: 2 + }, eggFarm: { id: 'eggFarm', type: 'eggFarm', name: 'Egg Farm', shortName: 'EGG', price: 250, buildable: true, upgradeable: true, maxLevel: 4, upgradeCosts: [null, 300, 720, 1600] diff --git a/src/core/config.js b/src/core/config.js index 9997553..020a362 100644 --- a/src/core/config.js +++ b/src/core/config.js @@ -24,7 +24,7 @@ export const EGG_SPAWN_RANGES = BALANCE.production.eggSpawnRanges; export const GRID = BALANCE.grid; export const FACILITY_DEFS = BALANCE.facilities; -export const BUILD_TOOL_IDS = ['conveyor', 'eggFarm', 'autoScanner', 'manualScanner', 'mixer', 'trash', 'truck']; +export const BUILD_TOOL_IDS = ['conveyor', 'boostConveyor', 'eggFarm', 'autoScanner', 'manualScanner', 'mixer', 'trash', 'truck']; export const MACHINE_FACILITY_IDS = ['mixer', 'trash', 'truck']; export const INCOME_FACILITY_IDS = ['mixer', 'truck']; diff --git a/src/core/entities.js b/src/core/entities.js index f769b41..9aa24fe 100644 --- a/src/core/entities.js +++ b/src/core/entities.js @@ -33,17 +33,19 @@ export function createEggFarm(game, col, row) { export function createScanner(game, col, row, kind) { const cost = kind === 'auto' ? FACILITY_DEFS.autoScanner.price : FACILITY_DEFS.manualScanner.price; - const manualCount = game.scanners.filter(s => s.kind === 'manual').length; + const usedManualSlots = new Set(game.scanners.filter(s => s.kind === 'manual').map(s => s.slot)); + let manualSlot = 0; + while (usedManualSlots.has(manualSlot)) manualSlot += 1; return { type: 'scanner', id: game.nextId++, kind, - slot: kind === 'manual' ? manualCount : null, - role: manualCount % 2, + slot: kind === 'manual' ? manualSlot : null, + role: kind === 'manual' ? manualSlot % 2 : 0, col, row, level: 1, queue: [], cooldown: 0, price: cost, builtSession: game.buildSession, autoMode: 'standard', - keys: kind === 'manual' ? manualKeysForSlot(manualCount) : null + keys: kind === 'manual' ? manualKeysForSlot(manualSlot) : null }; } diff --git a/src/core/state.js b/src/core/state.js index b0c45c0..7894488 100644 --- a/src/core/state.js +++ b/src/core/state.js @@ -33,6 +33,9 @@ export function newTurnStats() { chemicalWeaponSubsidy: 0, rescueLoan: 0, cardRerollCost: 0, + dudRefundIncome: 0, + composterIncome: 0, + sparePartsRepair: 0, mixerPoopFine: 0, truckPoopFine: 0, maleTruckFine: 0, @@ -74,6 +77,9 @@ export function newTotalStats() { manualComboFailure: 0, repairWorkerWages: 0, cardRerollCost: 0, + dudRefundIncome: 0, + composterIncome: 0, + sparePartsRepair: 0, mixerPoopFine: 0, truckPoopFine: 0, maleTruckFine: 0, @@ -135,7 +141,7 @@ export function createGame() { lastExplodedComponent: new Map(), contractOffer: null, contractActive: null, - cardEffects: { bearing: 0, legalWork: 0, fairiesFlatteryNext: 0, crowdedFarming: 0, extraEggOutlet: 0, hatchingFeed: 0, safetyCover: 0, recyclingSubsidy: 0, chemicalWeaponSubsidy: 0, preventiveMaintenance: 0, dudFilter: 0, durabilityCoating: 0, laborExploitation: 0, usedMachineActive: false, rescueLoanCharges: 0, loans: [] }, + cardEffects: { bearing: 0, legalWork: 0, fairiesFlatteryNext: 0, crowdedFarming: 0, extraEggOutlet: 0, hatchingFeed: 0, safetyCover: 0, recyclingSubsidy: 0, chemicalWeaponSubsidy: 0, preventiveMaintenance: 0, dudFilter: 0, durabilityCoating: 0, sparePartsBin: 0, dudRefund: 0, freeReroll: 0, composter: 0, laborExploitation: 0, usedMachineActive: false, rescueLoanCharges: 0, loans: [] }, cardDraft: { pending: false, choices: [], rerolls: 0, picksRemaining: 0 }, cardTargetPick: null, manualCombo: { count: 0, lastBonus: 0 }, @@ -182,15 +188,15 @@ export function defaultFacilities() { const INITIAL_CONVEYOR_BACKBONE = Object.freeze([ // Egg Farm -> S1 top input. The S1/S2 rows were moved down so unrelated // belt streams keep at least one blank tile between side-by-side flows. - [1, 1], [2, 1], [3, 1], [4, 1], [5, 1], [6, 1], [7, 1], [8, 1], [8, 2], [8, 3], + [1, 1], [2, 1], [3, 1], [4, 1], [5, 1], [6, 1], [7, 1], [8, 1], [8, 2], // S1 left output -> Mixer receiver on the left grid edge. [7, 4], [6, 4], [5, 4], [4, 4], [3, 4], [2, 4], [1, 4], [0, 4], // S1 right output -> S2 top input. - [9, 4], [10, 4], [11, 4], [12, 4], [13, 4], [14, 4], [15, 4], [15, 5], [15, 6], + [10, 4], [11, 4], [12, 4], [13, 4], [14, 4], [15, 4], [15, 5], // S2 left output -> Waste Shredder receiver on bottom grid edge. [14, 7], [13, 7], [12, 7], [12, 8], [12, 9], // S2 right output -> Truck receiver on the bottom grid edge. - [16, 7], [17, 7], [18, 7], [18, 8], [18, 9] + [17, 7], [18, 7], [18, 8], [18, 9] ]); const INITIAL_SCANNERS = Object.freeze([ @@ -213,13 +219,19 @@ function addConveyorTile(game, col, row, options = {}) { uses: 0, durability: BALANCE.maintenance.durability.conveyor, maintenanceType: 'conveyor', + kind: options.kind || 'conveyor', + speedMultiplier: options.speedMultiplier || 1, dir: options.dir || null, - outDirs: Array.isArray(options.outDirs) ? [...options.outDirs] : [] + outDirs: Array.isArray(options.outDirs) ? [...options.outDirs] : [], + branchMode: options.branchMode || 'random' }); } else if (options.dir || options.outDirs) { const meta = game.conveyorMeta.get(k); if (options.dir) meta.dir = options.dir; if (Array.isArray(options.outDirs)) meta.outDirs = [...new Set([...(meta.outDirs || []), ...options.outDirs])]; + if (!meta.branchMode) meta.branchMode = 'random'; + if (!meta.kind) meta.kind = 'conveyor'; + if (!meta.speedMultiplier) meta.speedMultiplier = 1; } } @@ -242,9 +254,20 @@ function markInitialConveyorDirection(game, from, to) { function scannerPortCells(scanner) { return [ - { col: scanner.col, row: scanner.row - 1 }, + { col: scanner.col, row: scanner.row - 2 }, { col: scanner.col - 1, row: scanner.row }, - { col: scanner.col + 1, row: scanner.row } + { col: scanner.col + 2, row: scanner.row } + ].filter(p => inGrid(p.col, p.row)); +} + +function scannerBodyCells(scanner) { + return [ + { col: scanner.col, row: scanner.row - 1 }, + { col: scanner.col + 1, row: scanner.row - 1 }, + { col: scanner.col, row: scanner.row }, + { col: scanner.col + 1, row: scanner.row }, + { col: scanner.col, row: scanner.row + 1 }, + { col: scanner.col + 1, row: scanner.row + 1 } ].filter(p => inGrid(p.col, p.row)); } @@ -261,7 +284,7 @@ function protectedInitialCells(game) { const reserved = new Set([...game.conveyorTiles]); const add = p => { if (p && inGrid(p.col, p.row)) reserved.add(key(p.col, p.row)); }; for (const scanner of game.scanners) { - add(scanner); + for (const p of scannerBodyCells(scanner)) add(p); for (const p of scannerPortCells(scanner)) add(p); } for (const farm of game.eggFarms) { diff --git a/src/core/text.js b/src/core/text.js index e387544..065f9a3 100644 --- a/src/core/text.js +++ b/src/core/text.js @@ -56,7 +56,7 @@ export function equipmentName(id) { export function buildToolPriceText(id) { const def = FACILITY_DEFS[id]; if (!def) return ''; - if (id === 'conveyor') return `${yen(def.price)} / tile`; + if (id === 'conveyor' || id === 'boostConveyor') return `${yen(def.price)} / tile`; if (id === 'autoScanner') return `${yen(def.price)} / ${AUTO_SCANNER_COOLDOWN.toFixed(1)}s cooldown`; if (id === 'trash') return yen(def.price); return yen(def.price); @@ -65,8 +65,9 @@ export function buildToolPriceText(id) { export function buildToolFlavorText(id) { const flavors = { conveyor: 'A narrow green belt. Routes decide whether chicks live, ship, or become invoices.', + boostConveyor: 'Moves chicks twice as fast. Wears down like a normal belt.', eggFarm: 'A tiny gatehouse producing questionable eggs on schedule.', - autoScanner: 'An automated judge. Faster than hands, still very sure of itself.', + autoScanner: 'Slower than hands without upgrades.', manualScanner: 'A manual checkpoint. The operator is the algorithm.', mixer: 'Male chicks become revenue here. Do not feed it poop.', trash: 'A polite shredder for poop and other regrets.', diff --git a/src/game.js b/src/game.js index f652d66..d51faaf 100644 --- a/src/game.js +++ b/src/game.js @@ -28,12 +28,12 @@ const ui = { hoverTooltip: document.getElementById('hoverTooltip'), manualScannerMonitor: document.getElementById('manualScannerMonitor'), hireRepairmanButton: document.getElementById('hireRepairmanButton'), - expandGridButton: document.getElementById('expandGridButton'), debug: { panel: document.getElementById('debugPanel'), infiniteCash: document.getElementById('debugInfiniteCash'), dayInput: document.getElementById('debugDayInput'), setDay: document.getElementById('debugSetDayButton'), + zeroTimer: document.getElementById('debugZeroTimerButton'), cardSelect: document.getElementById('debugCardSelect'), grantCard: document.getElementById('debugGrantCardButton'), grantAllCards: document.getElementById('debugGrantAllCardsButton'), @@ -41,7 +41,7 @@ const ui = { }, buttons: { s1Left: document.getElementById('scanner1MixerButton'), s1Right: document.getElementById('scanner1TruckButton'), s2Left: document.getElementById('scanner2MixerButton'), s2Right: document.getElementById('scanner2TruckButton'), - nextTurn: document.getElementById('nextTurnButton'), conveyor: document.getElementById('buildConveyorButton'), eggFarm: document.getElementById('buildEggFarmButton'), autoScanner: document.getElementById('buildAutoScannerButton'), manualScanner: document.getElementById('buildManualScannerButton'), mixer: document.getElementById('buildMixerButton'), trash: document.getElementById('buildTrashButton'), truck: document.getElementById('buildTruckButton'), erase: document.getElementById('eraseButton'), undo: document.getElementById('undoButton'), redo: document.getElementById('redoButton') + nextTurn: document.getElementById('nextTurnButton'), conveyor: document.getElementById('buildConveyorButton'), boostConveyor: document.getElementById('buildBoostConveyorButton'), eggFarm: document.getElementById('buildEggFarmButton'), autoScanner: document.getElementById('buildAutoScannerButton'), manualScanner: document.getElementById('buildManualScannerButton'), mixer: document.getElementById('buildMixerButton'), trash: document.getElementById('buildTrashButton'), truck: document.getElementById('buildTruckButton'), erase: document.getElementById('eraseButton'), undo: document.getElementById('undoButton'), redo: document.getElementById('redoButton') } }; @@ -49,6 +49,7 @@ const ui = { function initializeStaticText() { const buttonToolMap = { conveyor: 'conveyor', + boostConveyor: 'boostConveyor', eggFarm: 'eggFarm', autoScanner: 'autoScanner', manualScanner: 'manualScanner', @@ -105,9 +106,22 @@ function debugSetDay() { updateDebugReadout(`Day set to ${day}.`); } +function debugZeroTimer() { + game.timeLeft = 0; + if (game.phase === 'running') closeFarmShutters(); + floating(game, canvas.width / 2 - game.view.x, 86 - game.view.y, 'TIMER 0', THEME.warn); + uiSystem.updatePanels(); + uiSystem.updateUI(); + updateDebugReadout('Timer set to 0.'); +} + function debugGrantSelectedCard() { const id = ui.debug.cardSelect?.value; const result = debugGrantCard(game, id); + if (ui.debug.grantCard) { + ui.debug.grantCard.textContent = result.ok ? 'Granted' : 'Grant failed'; + window.setTimeout(() => { if (ui.debug.grantCard) ui.debug.grantCard.textContent = 'Grant Card'; }, 900); + } uiSystem.updatePanels(); uiSystem.updateUI(); updateDebugReadout(result.reason); @@ -440,6 +454,19 @@ function clickSelect(event) { uiSystem.updatePanels(); const obj = build.selectedObject(); if (obj?.type === 'scanner' && obj.kind === 'manual') build.showManualScannerMenu(obj); + if (obj?.type === 'scanner' && obj.kind === 'auto') build.showAutoScannerMenu(obj); +} + +function cancelBuildAction() { + game.buildTool = null; + game.selected = null; + game.multiSelected = []; + game.selectionBox = null; + if (game.cardTargetPick?.mode === 'autoScannerMenu') game.cardTargetPick = null; + uiSystem.hideModal(); + hideHoverTooltip(); + uiSystem.updatePanels(); + uiSystem.updateUI(); } function escapeHtml(value) { @@ -557,13 +584,21 @@ canvas.addEventListener('pointerdown', event => { const world = canvasPoint(event); if (event.button === 2) { startPan(event); return; } if (event.button !== 0) return; + if (game.cardTargetPick?.mode === 'autoScannerMenu') { cancelBuildAction(); return; } if (game.cardTargetPick?.pending) { cardSystem.chooseTargetAtPoint(world); return; } const expansionOffer = build.expansionOfferAtPoint?.(world); if (expansionOffer) { build.buyGridExpansion(expansionOffer); clampCamera(); uiSystem.updatePanels(); uiSystem.updateUI(); return; } - if (game.buildTool === 'erase') { build.eraseAtPoint(world); return; } + if (build.cycleBranchModeAtPoint?.(world)) { uiSystem.updatePanels(); uiSystem.updateUI(); return; } + if (game.buildTool === 'erase') { + if (!build.equipmentAtPoint(world)) { cancelBuildAction(); return; } + build.eraseAtPoint(world); + return; + } if (game.buildTool) { - if (game.buildTool === 'conveyor') { - build.beginConveyorDrag(pointToCell(world.x, world.y)); + const cell = pointToCell(world.x, world.y); + if (!cell && !['mixer', 'trash', 'truck'].includes(game.buildTool)) { cancelBuildAction(); return; } + if (game.buildTool === 'conveyor' || game.buildTool === 'boostConveyor') { + build.beginConveyorDrag(cell); return; } const hit = build.equipmentAtPoint(world); @@ -580,7 +615,7 @@ canvas.addEventListener('pointermove', event => { if (game.pan && (event.buttons & 2)) updatePan(event); if (game.groupDrag && (event.buttons & 1)) build.updateGroupDrag(event); if (game.selectionBox && (event.buttons & 1)) build.updateSelectionBox(event); - if (game.buildTool === 'conveyor' && (event.buttons & 1)) build.continueConveyorDrag(pointToCell(canvasPoint(event).x, canvasPoint(event).y)); + if ((game.buildTool === 'conveyor' || game.buildTool === 'boostConveyor') && (event.buttons & 1)) build.continueConveyorDrag(pointToCell(canvasPoint(event).x, canvasPoint(event).y)); if (game.buildTool === 'erase' && (event.buttons & 1)) build.eraseAtPoint(canvasPoint(event)); }); canvas.addEventListener('pointerup', event => { @@ -589,7 +624,7 @@ canvas.addEventListener('pointerup', event => { if (!game.groupDrag.committed) clickSelect(event); build.finishGroupDrag(); } - if (game.buildTool === 'conveyor') build.endConveyorDrag?.(); + if (game.buildTool === 'conveyor' || game.buildTool === 'boostConveyor') build.endConveyorDrag?.(); game.groupDrag = null; game.pan = null; try { canvas.releasePointerCapture(event.pointerId); } catch (_) { /* noop */ } }); @@ -598,14 +633,14 @@ canvas.addEventListener('pointercancel', () => { build?.endConveyorDrag?.(); gam document.addEventListener('pointerdown', event => { if (!ui.modal.classList.contains('visible') || !ui.modal.classList.contains('equipment-popover')) return; if (event.target.closest('#modal .modal-card')) return; - uiSystem.hideModal(); + cancelBuildAction(); }, true); ui.buttons.s1Left.addEventListener('click', () => chicks.sortSlot(0, 'left')); ui.buttons.s1Right.addEventListener('click', () => chicks.sortSlot(0, 'right')); ui.buttons.s2Left.addEventListener('click', () => chicks.sortSlot(1, 'left')); ui.buttons.s2Right.addEventListener('click', () => chicks.sortSlot(1, 'right')); -ui.buttons.nextTurn.addEventListener('click', startNextTurn); ui.buttons.conveyor.addEventListener('click', () => build.setBuildTool('conveyor')); ui.buttons.eggFarm.addEventListener('click', () => build.setBuildTool('eggFarm')); ui.buttons.autoScanner.addEventListener('click', () => build.setBuildTool('autoScanner')); ui.buttons.manualScanner.addEventListener('click', () => build.setBuildTool('manualScanner')); ui.buttons.mixer.addEventListener('click', () => build.setBuildTool('mixer')); ui.buttons.trash.addEventListener('click', () => build.setBuildTool('trash')); ui.buttons.truck.addEventListener('click', () => build.setBuildTool('truck')); ui.buttons.erase.addEventListener('click', () => build.setBuildTool('erase')); +ui.buttons.nextTurn.addEventListener('click', startNextTurn); ui.buttons.conveyor.addEventListener('click', () => build.setBuildTool('conveyor')); ui.buttons.boostConveyor.addEventListener('click', () => build.setBuildTool('boostConveyor')); ui.buttons.eggFarm.addEventListener('click', () => build.setBuildTool('eggFarm')); ui.buttons.autoScanner.addEventListener('click', () => build.setBuildTool('autoScanner')); ui.buttons.manualScanner.addEventListener('click', () => build.setBuildTool('manualScanner')); ui.buttons.mixer.addEventListener('click', () => build.setBuildTool('mixer')); ui.buttons.trash.addEventListener('click', () => build.setBuildTool('trash')); ui.buttons.truck.addEventListener('click', () => build.setBuildTool('truck')); ui.buttons.erase.addEventListener('click', () => build.setBuildTool('erase')); ui.hireRepairmanButton?.addEventListener('click', hireRepairman); -ui.expandGridButton?.addEventListener('click', () => { build?.buyGridExpansion?.('right'); clampCamera(); uiSystem.updatePanels(); uiSystem.updateUI(); }); ui.debug.setDay?.addEventListener('click', debugSetDay); +ui.debug.zeroTimer?.addEventListener('click', debugZeroTimer); ui.debug.grantCard?.addEventListener('click', debugGrantSelectedCard); ui.debug.grantAllCards?.addEventListener('click', debugGrantAllCards); ui.buttons.undo.addEventListener('click', () => { if (undo(game)) { clampCamera(); uiSystem.updatePanels(); uiSystem.updateUI(); } }); ui.buttons.redo.addEventListener('click', () => { if (redo(game)) { clampCamera(); uiSystem.updatePanels(); uiSystem.updateUI(); } }); @@ -626,7 +661,11 @@ window.addEventListener('keydown', event => { } if (game.phase === 'build' && (event.ctrlKey || event.metaKey) && name === 'z') { event.preventDefault(); if (undo(game)) { clampCamera(); uiSystem.updatePanels(); uiSystem.updateUI(); } } if (game.phase === 'build' && (event.ctrlKey || event.metaKey) && (name === 'y' || (event.shiftKey && name === 'z'))) { event.preventDefault(); if (redo(game)) { clampCamera(); uiSystem.updatePanels(); uiSystem.updateUI(); } } - if (event.key === 'Escape' && game.cardTargetPick?.pending) { event.preventDefault(); cardSystem.cancelTargetPick(); } + if (event.key === 'Escape' && game.cardTargetPick?.pending) { + event.preventDefault(); + if (game.cardTargetPick.mode === 'autoScannerMenu') cancelBuildAction(); + else cardSystem.cancelTargetPick(); + } }); diff --git a/src/render/draw.js b/src/render/draw.js index fe00cfb..9ee9654 100644 --- a/src/render/draw.js +++ b/src/render/draw.js @@ -254,18 +254,79 @@ function drawConveyors(ctx, game) { const directionMarkers = collectConveyorDirectionMarkers(game); for (const k of game.conveyorTiles) { const c = cellCenter(...Object.values(parseKey(k))); + const meta = game.conveyorMeta.get(k) || {}; const ratio = componentRatio(game, k); - ctx.fillStyle = ratio > 0.5 ? mixHex(THEME.white, '#ffd6d6', Math.max(0, (ratio - .5) / .5)) : THEME.white; + const baseFill = meta.kind === 'boostConveyor' ? '#fff2b8' : THEME.white; + ctx.fillStyle = ratio > 0.5 ? mixHex(baseFill, '#ffd6d6', Math.max(0, (ratio - .5) / .5)) : baseFill; ctx.strokeStyle = THEME.ink; ctx.lineWidth = 2; rect(ctx, c.x - 8, c.y - 8, 16, 16, true, true); - drawDirtOverlay(ctx, c.x - 14, c.y - 14, 28, 28, { meta: game.conveyorMeta.get(k) }); + if (meta.kind === 'boostConveyor') { + ctx.save(); + ctx.strokeStyle = THEME.warn; + ctx.lineWidth = 3; + ctx.beginPath(); ctx.moveTo(c.x - 15, c.y + 13); ctx.lineTo(c.x - 3, c.y - 13); ctx.lineTo(c.x + 2, c.y - 2); ctx.lineTo(c.x + 15, c.y - 13); ctx.stroke(); + ctx.restore(); + } + drawDirtOverlay(ctx, c.x - 14, c.y - 14, 28, 28, { meta }); drawSelection(ctx, game, 'conveyor', k, c.x, c.y, 38, 38); drawConveyorDirectionMarkers(ctx, c, directionMarkers.get(k) || []); + drawBranchModeLabel(ctx, c, meta, reachableConveyorDirs(game, k, meta)); if (ratio > 0.5) label(ctx, c.x, c.y - 15, `${Math.floor(ratio * 100)}%`, THEME.danger); } ctx.restore(); } +function branchModeText(mode) { + return { random: 'RND', pass: 'PASS', up: 'UP', right: 'RT', down: 'DN', left: 'LF' }[mode || 'random'] || 'RND'; +} + +function neighborPointsIntoCell(game, p, dirName) { + const d = DIRS.find(item => item.name === dirName); + if (!d) return false; + const meta = game.conveyorMeta?.get?.(key(p.col + d.dc, p.row + d.dr)); + if (!meta) return false; + const names = [...new Set([...(Array.isArray(meta.outDirs) ? meta.outDirs : []), meta.dir].filter(Boolean))]; + return names.includes(d.opposite); +} + +function reachableConveyorDirs(game, k, meta) { + const p = parseKey(k); + const neighborNames = DIRS + .filter(d => game.conveyorTiles.has(key(p.col + d.dc, p.row + d.dr))) + .map(d => d.name); + const explicit = [...new Set([...(Array.isArray(meta.outDirs) ? meta.outDirs : []), meta.dir].filter(Boolean))] + .filter(name => neighborNames.includes(name) && !neighborPointsIntoCell(game, p, name)); + const inferred = neighborNames.filter(name => !neighborPointsIntoCell(game, p, name)); + return explicit.length ? explicit : inferred; +} + +function effectiveConveyorDirs(game, k, meta) { + const p = parseKey(k); + const neighborNames = DIRS + .filter(d => game.conveyorTiles.has(key(p.col + d.dc, p.row + d.dr))) + .map(d => d.name); + const explicit = [...new Set([...(Array.isArray(meta.outDirs) ? meta.outDirs : []), meta.dir].filter(Boolean))] + .filter(name => neighborNames.includes(name) && !neighborPointsIntoCell(game, p, name)); + const inferred = neighborNames.filter(name => !neighborPointsIntoCell(game, p, name)); + return explicit.length ? explicit : inferred; +} + +function drawBranchModeLabel(ctx, center, meta, dirs) { + if (dirs.length < 3) return; + ctx.save(); + ctx.font = '900 11px ui-monospace, monospace'; + ctx.textAlign = 'center'; + ctx.lineWidth = 3; + ctx.fillStyle = 'rgba(255,255,255,.96)'; + const mode = meta.branchMode === 'pass' && dirs.length >= 4 ? 'pass' : (dirs.includes(meta.branchMode) ? meta.branchMode : 'random'); + ctx.strokeStyle = mode !== 'random' ? THEME.warn : THEME.green; + rect(ctx, center.x - 24, center.y + 11, 48, 24, true, true); + ctx.fillStyle = mode !== 'random' ? THEME.warn : THEME.ink; + const text = branchModeText(mode); + ctx.fillText(text, center.x, center.y + 27); + ctx.restore(); +} + function cellKeyFromPoint(game, p) { const cell = pointToCell(p.x, p.y); if (!cell) return null; @@ -290,12 +351,9 @@ function addMarkerFromRoute(game, markers, route) { if (!list.some(x => Math.abs(Math.sin((x - angle) / 2)) < 0.01)) list.push(angle); } } -function explicitConveyorAngles(meta) { +function explicitConveyorAngles(game, k, meta) { if (!meta) return []; - const names = []; - if (Array.isArray(meta.outDirs)) names.push(...meta.outDirs); - if (meta.dir) names.push(meta.dir); - return [...new Set(names)] + return effectiveConveyorDirs(game, k, meta) .map(name => DIRS.find(d => d.name === name)?.angle) .filter(angle => Number.isFinite(angle)); } @@ -303,7 +361,7 @@ function explicitConveyorAngles(meta) { function collectConveyorDirectionMarkers(game) { const markers = new Map(); for (const [k, meta] of game.conveyorMeta || []) { - const angles = explicitConveyorAngles(meta); + const angles = explicitConveyorAngles(game, k, meta); if (angles.length) markers.set(k, angles); } for (const farm of game.eggFarms) addMarkerFromRoute(game, markers, routeFromFarmToScanner(game, farm)?.route); @@ -399,39 +457,39 @@ function drawScanner(ctx, scanner, game) { ctx.lineWidth = Math.min(14, 5 + Math.floor(combo / 10)); ctx.shadowColor = ctx.strokeStyle; ctx.shadowBlur = Math.min(28, 8 + combo); - rect(ctx, c.x - 60, c.y - 44, 120, 88, false, true); + rect(ctx, c.x - 54, c.y - 76, 108, 152, false, true); ctx.restore(); } const img = scanner.kind === 'auto' ? assets.scannerAuto : assets.scannerManual; - if (drawImageIfLoaded(ctx, img, c.x - 54, c.y - 38, 108, 76)) { - if (scanner.kind === 'auto') drawDirtOverlay(ctx, c.x - 54, c.y - 38, 108, 76, scanner); + if (drawImageIfLoaded(ctx, img, c.x - 50, c.y - 73, 100, 146)) { + if (scanner.kind === 'auto') drawDirtOverlay(ctx, c.x - 50, c.y - 73, 100, 146, scanner); else drawManualKeyboardIcon(ctx, scanner, c); - drawSelection(ctx, game, 'scanner', scanner.id, c.x, c.y, 116, 84); + drawSelection(ctx, game, 'scanner', scanner.id, c.x, c.y, 108, 154); ctx.restore(); return; } ctx.fillStyle = scanner.kind === 'auto' ? '#d8ffe2' : THEME.white; ctx.strokeStyle = THEME.ink; ctx.lineWidth = 4; - rect(ctx, c.x - 52, c.y - 36, 104, 72, true, true); + rect(ctx, c.x - 50, c.y - 73, 100, 146, true, true); ctx.fillStyle = scanner.kind === 'auto' ? THEME.green : THEME.ink; ctx.font = '900 19px ui-monospace, monospace'; ctx.textAlign = 'center'; - ctx.fillText(`${scanner.kind === 'auto' ? 'AUTO' : `S${(scanner.slot ?? 0) + 1}`}`, c.x, c.y - 14); + ctx.fillText(`${scanner.kind === 'auto' ? 'AUTO' : `S${(scanner.slot ?? 0) + 1}`}`, c.x, c.y - 48); ctx.font = '900 11px ui-monospace, monospace'; const leftKey = scanner.keys?.left?.label || (scanner.slot === 0 ? 'A' : 'Left'); const rightKey = scanner.keys?.right?.label || (scanner.slot === 0 ? 'D' : 'Right'); - ctx.fillText(scanner.role === 0 ? `${leftKey}:M ${rightKey}:NEXT` : `${leftKey}:WASTE ${rightKey}:TRUCK`, c.x, c.y + 3); + ctx.fillText(scanner.role === 0 ? `${leftKey}:M ${rightKey}:NEXT` : `${leftKey}:WASTE ${rightKey}:TRUCK`, c.x, c.y - 28); const q = scanner.queue.length; if (scanner.kind === 'manual') { drawManualKeyboardIcon(ctx, scanner, c); ctx.fillStyle = q > 0 ? THEME.green : THEME.muted; ctx.font = '900 10px ui-monospace, monospace'; - ctx.fillText(`Q:${q}`, c.x, c.y + 32); + ctx.fillText(`Q:${q}`, c.x, c.y + 54); } else { ctx.fillStyle = q > 0 ? THEME.green : THEME.muted; ctx.font = '900 12px ui-monospace, monospace'; - ctx.fillText(`Q:${q} CD:${scanner.cooldown.toFixed(1)}`, c.x, c.y + 25); - drawDirtOverlay(ctx, c.x - 52, c.y - 36, 104, 72, scanner); + ctx.fillText(`Q:${q} CD:${scanner.cooldown.toFixed(1)}`, c.x, c.y + 54); + drawDirtOverlay(ctx, c.x - 50, c.y - 73, 100, 146, scanner); } - drawSelection(ctx, game, 'scanner', scanner.id, c.x, c.y, 116, 84); + drawSelection(ctx, game, 'scanner', scanner.id, c.x, c.y, 108, 154); ctx.restore(); } @@ -445,7 +503,7 @@ function drawManualKeyboardIcon(ctx, scanner, c) { labels[1].text = scanner.keys?.right?.label || labels[1].text; const pressedSide = scanner.keyPressTime > 0 ? scanner.keyPressSide : null; const baseX = c.x - 30; - const baseY = c.y + 8; + const baseY = c.y + 18; ctx.save(); ctx.lineWidth = 3; ctx.fillStyle = '#f7fff5'; @@ -506,9 +564,13 @@ function receiverTitle(id, game) { if (id === 'truck') return { title: `IN: ${targetForTruck(game).toUpperCase()}`, type: targetForTruck(game), color: THEME.truckPink }; return { title: 'IN', type: 'female', color: THEME.green }; } -function drawFacilityReceiver(ctx, game, id) { - const f = game.facilities[id]; +function facilityKind(fOrId) { + return typeof fOrId === 'string' ? fOrId : (fOrId?.baseId || fOrId?.id || ''); +} +function drawFacilityReceiver(ctx, game, facility) { + const f = typeof facility === 'string' ? game.facilities[facility] : facility; if (!f?.entry) return; + const id = facilityKind(f); const c = cellCenter(f.entry.col, f.entry.row); const info = receiverTitle(id, game); ctx.save(); @@ -527,12 +589,13 @@ function drawFacilityReceiver(ctx, game, id) { ctx.restore(); } function drawFacilities(ctx, game) { - if (game.facilities.mixer) drawMixer(ctx, game); - if (game.facilities.trash) drawTrash(ctx, game); - if (game.facilities.truck) drawTruck(ctx, game); - drawFacilityReceiver(ctx, game, 'mixer'); - drawFacilityReceiver(ctx, game, 'trash'); - drawFacilityReceiver(ctx, game, 'truck'); + for (const f of Object.values(game.facilities || {})) { + const kind = facilityKind(f); + if (kind === 'mixer') drawMixer(ctx, game, f); + else if (kind === 'trash') drawTrash(ctx, game, f); + else if (kind === 'truck') drawTruck(ctx, game, f); + } + for (const f of Object.values(game.facilities || {})) drawFacilityReceiver(ctx, game, f); } function drawExternalDuct(ctx, f) { if (!f?.entry) return; @@ -551,11 +614,10 @@ function drawExternalDuct(ctx, f) { ctx.beginPath(); ctx.moveTo(c.x, c.y); ctx.lineTo(bx, by); ctx.stroke(); ctx.restore(); } -function drawMixer(ctx, game) { - const m = game.facilities.mixer; +function drawMixer(ctx, game, m = game.facilities.mixer) { ctx.save(); drawExternalDuct(ctx, m); - if (drawImageIfLoaded(ctx, assets.mixer, m.x, m.y, m.w, m.h)) { drawDirtOverlay(ctx, m.x, m.y, m.w, m.h, m); drawSelection(ctx, game, 'facility', 'mixer', m.x + m.w / 2, m.y + m.h / 2, m.w + 12, m.h + 12); ctx.restore(); return; } + if (drawImageIfLoaded(ctx, assets.mixer, m.x, m.y, m.w, m.h)) { drawDirtOverlay(ctx, m.x, m.y, m.w, m.h, m); drawSelection(ctx, game, 'facility', m.id, m.x + m.w / 2, m.y + m.h / 2, m.w + 12, m.h + 12); ctx.restore(); return; } ctx.fillStyle = THEME.white; ctx.strokeStyle = THEME.ink; ctx.lineWidth = 4; rect(ctx, m.x, m.y, m.w, m.h, true, true); ctx.fillStyle = THEME.ink; ctx.font = '900 19px ui-monospace, monospace'; ctx.textAlign = 'center'; ctx.fillText(`MIXER L${m.level}`, m.x + m.w / 2, m.y + 28); ctx.font = '900 12px ui-monospace, monospace'; ctx.fillText(`PAY ${yen(upgradedMixerPrice(game))}`, m.x + m.w / 2, m.y + 48); @@ -563,27 +625,25 @@ function drawMixer(ctx, game) { ctx.strokeStyle = THEME.green; ctx.lineWidth = 5; for (let i = 0; i < 3; i += 1) { ctx.beginPath(); ctx.arc(m.x + m.w / 2, m.y + 78, 16 + i * 8, 0, Math.PI * 1.5); ctx.stroke(); } drawDirtOverlay(ctx, m.x, m.y, m.w, m.h, m); - drawSelection(ctx, game, 'facility', 'mixer', m.x + m.w / 2, m.y + m.h / 2, m.w + 12, m.h + 12); + drawSelection(ctx, game, 'facility', m.id, m.x + m.w / 2, m.y + m.h / 2, m.w + 12, m.h + 12); ctx.restore(); } -function drawTrash(ctx, game) { - const t = game.facilities.trash; +function drawTrash(ctx, game, t = game.facilities.trash) { ctx.save(); drawExternalDuct(ctx, t); - if (drawImageIfLoaded(ctx, assets.shredder, t.x, t.y, t.w, t.h)) { drawDirtOverlay(ctx, t.x, t.y, t.w, t.h, t); drawSelection(ctx, game, 'facility', 'trash', t.x + t.w / 2, t.y + t.h / 2, t.w + 12, t.h + 12); ctx.restore(); return; } + if (drawImageIfLoaded(ctx, assets.shredder, t.x, t.y, t.w, t.h)) { drawDirtOverlay(ctx, t.x, t.y, t.w, t.h, t); drawSelection(ctx, game, 'facility', t.id, t.x + t.w / 2, t.y + t.h / 2, t.w + 12, t.h + 12); ctx.restore(); return; } ctx.fillStyle = THEME.white; ctx.strokeStyle = THEME.ink; ctx.lineWidth = 4; rect(ctx, t.x, t.y, t.w, t.h, true, true); ctx.fillStyle = THEME.ink; ctx.font = '900 19px ui-monospace, monospace'; ctx.textAlign = 'center'; ctx.fillText(`SHREDDER L${t.level}`, t.x + t.w / 2, t.y + 25); for (let i = 0; i < 7; i += 1) { ctx.fillRect(t.x + 34 + i * 16, t.y + 48, 7, t.h - 66); } drawTargetBadge(ctx, t.x + 14, t.y + t.h - 42, t.w - 28, 'SEND POOP', 'poop', 'shredder'); drawDirtOverlay(ctx, t.x, t.y, t.w, t.h, t); - drawSelection(ctx, game, 'facility', 'trash', t.x + t.w / 2, t.y + t.h / 2, t.w + 12, t.h + 12); + drawSelection(ctx, game, 'facility', t.id, t.x + t.w / 2, t.y + t.h / 2, t.w + 12, t.h + 12); ctx.restore(); } -function drawTruck(ctx, game) { - const t = game.facilities.truck; +function drawTruck(ctx, game, t = game.facilities.truck) { ctx.save(); drawExternalDuct(ctx, t); - if (drawImageIfLoaded(ctx, assets.truck, t.x, t.y, t.w, t.h)) { drawSelection(ctx, game, 'facility', 'truck', t.x + t.w / 2, t.y + t.h / 2, t.w + 12, t.h + 12); ctx.restore(); return; } + if (drawImageIfLoaded(ctx, assets.truck, t.x, t.y, t.w, t.h)) { drawSelection(ctx, game, 'facility', t.id, t.x + t.w / 2, t.y + t.h / 2, t.w + 12, t.h + 12); ctx.restore(); return; } ctx.fillStyle = THEME.white; ctx.strokeStyle = THEME.ink; ctx.lineWidth = 4; rect(ctx, t.x, t.y, t.w, t.h, true, true); ctx.fillStyle = THEME.ink; ctx.font = '900 19px ui-monospace, monospace'; ctx.textAlign = 'center'; ctx.fillText(`TRUCK L${t.level}`, t.x + t.w / 2, t.y + 24); ctx.font = '900 11px ui-monospace, monospace'; ctx.fillText(`UNIT ${yen(upgradedTruckPrice(game))}`, t.x + t.w / 2, t.y + 42); @@ -591,7 +651,7 @@ function drawTruck(ctx, game) { drawTargetBadge(ctx, t.x + 15, t.y + 50, t.w - 30, `SEND ${truckTargetType.toUpperCase()}`, truckTargetType, game.contractOffer && !game.contractActive ? 'next event' : 'truck cargo'); ctx.fillStyle = THEME.greenSoft; rect(ctx, t.x + 13, t.y + 88, t.w - 26, t.h - 103, true, false); for (const cargo of game.truckCargo) cargo.sex === 'poop' ? drawTinyPoop(ctx, t.x + cargo.x, t.y + cargo.y) : drawTinyChick(ctx, t.x + cargo.x, t.y + cargo.y, cargo.sex, cargo.sex === 'male'); - drawSelection(ctx, game, 'facility', 'truck', t.x + t.w / 2, t.y + t.h / 2, t.w + 12, t.h + 12); + drawSelection(ctx, game, 'facility', t.id, t.x + t.w / 2, t.y + t.h / 2, t.w + 12, t.h + 12); ctx.restore(); } function drawChicks(ctx, game, activeQueuedChick) { @@ -700,7 +760,9 @@ function drawCardTargetOverlay(ctx, canvas, game) { ctx.strokeStyle = THEME.ink; ctx.lineWidth = 3; const remaining = Math.max(1, game.cardTargetPick?.remaining || 1); - const msg = isBlockedCellMode ? `CLICK BLOCKED CELL: ${remaining} LEFT. PRESS ESC TO CANCEL.` : 'CLICK AN UPGRADEABLE MACHINE. PRESS ESC TO CANCEL.'; + const msg = game.cardTargetPick?.mode === 'autoScannerMenu' + ? 'AUTO SCANNER ROUTE SETTINGS. CLICK EMPTY AREA TO CLOSE.' + : isBlockedCellMode ? `CLICK BLOCKED CELL: ${remaining} LEFT. PRESS ESC TO CANCEL.` : 'CLICK AN UPGRADEABLE MACHINE. PRESS ESC TO CANCEL.'; rect(ctx, (216 - (game.view?.x || 0)) / scale, (96 - (game.view?.y || 0)) / scale, 560 / scale, 42 / scale, true, true); ctx.fillStyle = THEME.ink; ctx.font = '900 12px ui-monospace, monospace'; diff --git a/src/systems/buildSystem.js b/src/systems/buildSystem.js index b0ebb7f..64f189d 100644 --- a/src/systems/buildSystem.js +++ b/src/systems/buildSystem.js @@ -1,12 +1,12 @@ import { BALANCE } from '../core/balance.js'; -import { MACHINE_FACILITY_IDS, GRID, THEME } from '../core/config.js'; +import { DIRS, MACHINE_FACILITY_IDS, GRID, THEME } from '../core/config.js'; import { getSpawnRange } from '../core/state.js'; import { createEggFarm, createScanner, createFacility } from '../core/entities.js'; import { generateBlockedCellsInRect } from '../core/mapGen.js'; import { expansionCost, expansionLot, expansionLots, lotFromChunk, lotBounds, lotContainsPoint, cellsInLot, isOwnedCell, countOwnedCells, shiftRowIndexedSet, shiftRowIndexedMap, ownChunk } from '../core/gridExpansion.js'; import { key, parseKey, pointToCell, cellCenter, yen, directionNameBetweenCells } from '../core/utils.js'; import { TEXT, equipmentName } from '../core/text.js'; -import { farmAt, scannerAt, scannerCenter, routeFromFarmToScanner, refreshRoutingAfterEdit, disconnectedBuildWarnings } from './routing.js'; +import { farmAt, scannerAt, scannerCenter, scannerFootprintCells, routeFromFarmToScanner, refreshRoutingAfterEdit, disconnectedBuildWarnings } from './routing.js'; import { buildPrice, equipmentBasePrice, incomeMultiplier, mixerPoopPenalty, refundCash, spendCash, truckPoopPenalty, upgradedMixerPrice, upgradedTruckPrice, resaleValueFor, shredderUpgradeCount, shredderBonusChance, shredderBonusMaxCards } from './economy.js'; import { autoScannerCooldownSeconds } from './cards.js'; import { record } from './history.js'; @@ -19,6 +19,11 @@ export function createBuildSystem({ game, ui, canvas, canvasPoint, onUpdatePanel let lastConveyorBuildCell = null; let conveyorDragRecordedDirectionEdit = false; + let conveyorDragStartCell = null; + let conveyorDragMoved = false; + const BRANCH_MODES = ['random', 'pass', 'up', 'right', 'down', 'left']; + const BRANCH_LABELS = { random: 'RND', pass: 'PASS', up: 'UP', right: 'RT', down: 'DN', left: 'LF' }; + const BRANCH_MARKER = { w: 48, h: 24, y: 23 }; function gridExpansionCost() { return expansionCost(game); } @@ -128,6 +133,20 @@ export function createBuildSystem({ game, ui, canvas, canvasPoint, onUpdatePanel return false; } + function scannerPlacementBlocked(col, row, moving = null) { + const draft = { col, row }; + for (const cell of scannerFootprintCells(draft)) { + if (!pointInGrid(cell.col, cell.row) || isBlockedCell(cell.col, cell.row)) return true; + const k = key(cell.col, cell.row); + const existingFarm = farmAt(game, cell.col, cell.row); + if (existingFarm) return true; + const existingScanner = scannerAt(game, cell.col, cell.row); + if (existingScanner && !(moving?.type === 'scanner' && moving.ref?.id === existingScanner.id)) return true; + if (game.conveyorTiles.has(k)) return true; + } + return false; + } + function selectedObject() { if (!game.selected) return null; if (game.selected.type === 'eggFarm') return game.eggFarms.find(f => f.id === game.selected.id) || null; @@ -141,12 +160,13 @@ export function createBuildSystem({ game, ui, canvas, canvasPoint, onUpdatePanel if (!obj) return 'Equipment'; if (obj.type === 'eggFarm') return `${equipmentName('eggFarm')} #${obj.id}`; if (obj.type === 'scanner') return `${obj.kind === 'auto' ? equipmentName('autoScanner') : equipmentName('manualScanner')} #${obj.id}`; - if (obj.type === 'conveyor') return equipmentName('conveyor'); + if (obj.type === 'conveyor') return game.conveyorMeta.get(obj.id)?.kind === 'boostConveyor' ? equipmentName('boostConveyor') : equipmentName('conveyor'); if (obj.type === 'facility') return obj.name; return 'Equipment'; } function equipmentPrice(hit) { + if (hit?.type === 'conveyor') return game.conveyorMeta?.get(hit.oldKey || hit.ref?.id)?.price || equipmentBasePrice(hit); return equipmentBasePrice(hit); } @@ -171,8 +191,9 @@ export function createBuildSystem({ game, ui, canvas, canvasPoint, onUpdatePanel if (hit?.type === 'eggFarm' || obj.type === 'eggFarm') { return game.eggFarms.length <= 1 ? 'Cannot sell the last EGG FARM.' : ''; } - if ((hit?.type === 'facility' || obj.type === 'facility') && ['mixer', 'trash', 'truck'].includes(obj.id)) { - const count = Object.values(game.facilities).filter(f => f.id === obj.id).length; + if ((hit?.type === 'facility' || obj.type === 'facility') && ['mixer', 'trash', 'truck'].includes(obj.baseId || obj.id)) { + const kind = obj.baseId || obj.id; + const count = Object.values(game.facilities).filter(f => (f.baseId || f.id) === kind).length; if (count <= 1) return `Cannot sell the last ${obj.shortName || obj.name || obj.id}.`; } return ''; @@ -193,9 +214,47 @@ export function createBuildSystem({ game, ui, canvas, canvasPoint, onUpdatePanel if (!meta) return []; if (!Array.isArray(meta.outDirs)) meta.outDirs = []; meta.outDirs = [...new Set(meta.outDirs.filter(dir => ['right', 'left', 'down', 'up'].includes(dir)))]; + if (!BRANCH_MODES.includes(meta.branchMode)) meta.branchMode = 'random'; + if (!meta.kind) meta.kind = 'conveyor'; + if (!meta.speedMultiplier) meta.speedMultiplier = meta.kind === 'boostConveyor' ? 2 : 1; return meta.outDirs; } + function conveyorNeighborOutDirs(cell) { + if (!cell) return []; + return DIRS.filter(d => game.conveyorTiles.has(key(cell.col + d.dc, cell.row + d.dr))).map(d => d.name); + } + + function neighborPointsIntoCell(cell, dirName) { + const d = DIRS.find(item => item.name === dirName); + if (!cell || !d) return false; + const nk = key(cell.col + d.dc, cell.row + d.dr); + const meta = game.conveyorMeta.get(nk); + if (!meta) return false; + const incoming = d.opposite; + const dirs = [...new Set([...(Array.isArray(meta.outDirs) ? meta.outDirs : []), meta.dir].filter(Boolean))]; + return dirs.includes(incoming); + } + + function reachableOutDirsForKey(k) { + if (!game.conveyorTiles.has(k)) return []; + const cell = parseKey(k); + const meta = game.conveyorMeta.get(k); + const explicit = normalizeOutDirs(meta); + const neighbors = conveyorNeighborOutDirs(cell); + const explicitReachable = [...new Set([...explicit, meta?.dir].filter(Boolean))] + .filter(dir => neighbors.includes(dir) && !neighborPointsIntoCell(cell, dir)); + const inferredExits = neighbors.filter(dir => !neighborPointsIntoCell(cell, dir)); + const reachable = explicitReachable.length ? explicitReachable : inferredExits; + if (meta) { + meta.outDirs = explicit.filter(dir => neighbors.includes(dir)); + if (meta.dir && !neighbors.includes(meta.dir)) meta.dir = meta.outDirs[0] || null; + if (meta.branchMode !== 'random' && meta.branchMode !== 'pass' && !reachable.includes(meta.branchMode)) meta.branchMode = 'random'; + if (meta.branchMode === 'pass' && reachable.length < 4) meta.branchMode = 'random'; + } + return reachable; + } + function markConveyorDirection(from, to) { const dir = directionNameBetweenCells(from, to); if (!dir) return false; @@ -212,6 +271,38 @@ export function createBuildSystem({ game, ui, canvas, canvasPoint, onUpdatePanel return changed; } + function availableConveyorOutDirs(cell) { + if (!cell) return []; + return reachableOutDirsForKey(key(cell.col, cell.row)); + } + + function cycleSingleConveyorDirection(cell) { + if (!cell) return false; + const k = key(cell.col, cell.row); + const meta = game.conveyorMeta.get(k); + if (!meta) return false; + const choices = availableConveyorOutDirs(cell); + if (!choices.length) return false; + const explicit = normalizeOutDirs(meta); + const current = meta.dir && choices.includes(meta.dir) ? meta.dir : explicit.find(dir => choices.includes(dir)); + const next = choices[(Math.max(-1, choices.indexOf(current)) + 1) % choices.length]; + if (!next) return false; + record(game); + meta.dir = next; + meta.outDirs = [next]; + meta.branchMode = 'random'; + game.selected = { type: 'conveyor', id: k }; + const c = cellCenter(cell.col, cell.row); + floating(game, c.x, c.y - 22, branchModeLabel(next), THEME.ink); + refreshRoutingAfterEdit(game); + updatePanels(); + return true; + } + + function conveyorToolDef() { + return BALANCE.facilities[game.buildTool === 'boostConveyor' ? 'boostConveyor' : 'conveyor']; + } + function buildConveyorCell(cell, incomingDir = null) { if (!cell) return { ok: false, reason: TEXT.fail.outOfGrid }; const { col, row } = cell; @@ -219,7 +310,9 @@ export function createBuildSystem({ game, ui, canvas, canvasPoint, onUpdatePanel if (game.conveyorTiles.has(k)) return { ok: false, exists: true }; if (isBlockedCell(col, row)) return { ok: false, reason: 'Cannot build on blocked ground.' }; if (farmAt(game, col, row) || scannerAt(game, col, row)) return { ok: false, reason: TEXT.fail.cellOccupied }; - const cost = buildPrice('conveyor', game); + const toolDef = conveyorToolDef(); + const kind = toolDef.id || 'conveyor'; + const cost = buildPrice(kind, game); if (game.cash < cost) return { ok: false, reason: TEXT.fail.notEnoughCash }; record(game); spendCash(game, cost); @@ -231,8 +324,11 @@ export function createBuildSystem({ game, ui, canvas, canvasPoint, onUpdatePanel uses: 0, durability: durabilityCapFor(game, 'conveyor', quality.durabilityBaseMultiplier), maintenanceType: 'conveyor', + kind, + speedMultiplier: toolDef.speedMultiplier || 1, dir: incomingDir || null, outDirs: [], + branchMode: 'random', ...quality }); game.selected = { type: 'conveyor', id: k }; @@ -246,6 +342,7 @@ export function createBuildSystem({ game, ui, canvas, canvasPoint, onUpdatePanel const exists = game.conveyorTiles.has(currentKey); const previous = lastConveyorBuildCell; if (previous && previous.col === cell.col && previous.row === cell.row) return; + if (previous) conveyorDragMoved = true; if (exists) { if (previous && markConveyorDirection(previous, cell)) { @@ -280,6 +377,8 @@ export function createBuildSystem({ game, ui, canvas, canvasPoint, onUpdatePanel function beginConveyorDrag(cell) { conveyorDragRecordedDirectionEdit = false; + conveyorDragStartCell = cell ? { col: cell.col, row: cell.row } : null; + conveyorDragMoved = false; lastConveyorBuildCell = null; handleConveyorDragCell(cell); } @@ -299,6 +398,11 @@ export function createBuildSystem({ game, ui, canvas, canvasPoint, onUpdatePanel } function endConveyorDrag() { + if (!conveyorDragMoved && conveyorDragStartCell && game.conveyorTiles.has(key(conveyorDragStartCell.col, conveyorDragStartCell.row))) { + cycleSingleConveyorDirection(conveyorDragStartCell); + } + conveyorDragStartCell = null; + conveyorDragMoved = false; lastConveyorBuildCell = null; conveyorDragRecordedDirectionEdit = false; } @@ -306,14 +410,14 @@ export function createBuildSystem({ game, ui, canvas, canvasPoint, onUpdatePanel function buildAtCell(cell) { if (!cell) return fail(TEXT.fail.outOfGrid); const { col, row } = cell; - if (game.buildTool === 'conveyor') { + if (game.buildTool === 'conveyor' || game.buildTool === 'boostConveyor') { const result = buildConveyorCell(cell, null); if (!result.ok) return fail(result.reason || TEXT.fail.cellOccupied); refreshRoutingAfterEdit(game); return; } if (isBlockedCell(col, row)) return fail('Cannot build on blocked ground.'); - if (isEquipmentCell(col, row)) return fail(TEXT.fail.cellOccupied); + if ((game.buildTool === 'manualScanner' || game.buildTool === 'autoScanner') ? scannerPlacementBlocked(col, row) : isEquipmentCell(col, row)) return fail(TEXT.fail.cellOccupied); if (game.buildTool === 'eggFarm') buildFarm(col, row); else if (game.buildTool === 'manualScanner') buildScanner(col, row, 'manual'); else if (game.buildTool === 'autoScanner') buildScanner(col, row, 'auto'); @@ -321,7 +425,7 @@ export function createBuildSystem({ game, ui, canvas, canvasPoint, onUpdatePanel function buildAtPoint(p) { const cell = pointToCell(p.x, p.y); - if (['conveyor', 'eggFarm', 'manualScanner', 'autoScanner'].includes(game.buildTool)) return buildAtCell(cell); + if (['conveyor', 'boostConveyor', 'eggFarm', 'manualScanner', 'autoScanner'].includes(game.buildTool)) return buildAtCell(cell); if (MACHINE_FACILITY_IDS.includes(game.buildTool)) return buildFacility(p, game.buildTool); } @@ -342,6 +446,7 @@ export function createBuildSystem({ game, ui, canvas, canvasPoint, onUpdatePanel if (kind === 'manual' && game.scanners.filter(s => s.kind === 'manual').length >= 8) return fail('Manual Scanner limit reached (8 max).'); const cost = kind === 'auto' ? buildPrice('autoScanner', game) : buildPrice('manualScanner', game); if (game.cash < cost) return fail(TEXT.fail.notEnoughCash); + if (scannerPlacementBlocked(col, row)) return fail(TEXT.fail.cellOccupied); record(game); spendCash(game, cost); const scanner = applyBuildQuality(createScanner(game, col, row, kind)); @@ -354,16 +459,18 @@ export function createBuildSystem({ game, ui, canvas, canvasPoint, onUpdatePanel function buildFacility(p, id) { const cost = buildPrice(id, game); - if (game.facilities[id]) return fail(TEXT.fail.facilityExists); if (game.cash < cost) return fail(TEXT.fail.notEnoughCash); const f = applyBuildQuality(createFacility(game, id, p, cost)); + const storageId = game.facilities[id] ? `${id}:${game.nextId++}` : id; + f.baseId = id; + f.id = storageId; if (f.entry && isBlockedCell(f.entry.col, f.entry.row)) return fail('Receiver cell is blocked.'); if (facilityOverlaps(f)) return fail(TEXT.fail.facilityOverlap); record(game); spendCash(game, cost); - game.facilities[id] = f; + game.facilities[storageId] = f; refreshRoutingAfterEdit(game); - game.selected = { type: 'facility', id }; + game.selected = { type: 'facility', id: storageId }; floating(game, p.x, p.y - 14, `-${yen(cost)}`, THEME.ink); } @@ -371,7 +478,7 @@ export function createBuildSystem({ game, ui, canvas, canvasPoint, onUpdatePanel const resale = resaleValueFor(hit, game); if (resale.amount <= 0) return; refundCash(game, resale.amount); - const label = resale.sameBuild ? 'REFUND' : 'SOLD 50%'; + const label = resale.sameBuild ? 'REFUND' : 'SOLD'; floating(game, x, y - 16, `${label} +${yen(resale.amount)}`, THEME.green); } @@ -461,6 +568,7 @@ export function createBuildSystem({ game, ui, canvas, canvasPoint, onUpdatePanel } function hideModal() { + if (game.cardTargetPick?.mode === 'autoScannerMenu') game.cardTargetPick = null; ui.modal.classList.remove('visible', 'equipment-popover'); ui.modal.style.removeProperty('--popover-x'); ui.modal.style.removeProperty('--popover-y'); @@ -468,13 +576,26 @@ export function createBuildSystem({ game, ui, canvas, canvasPoint, onUpdatePanel function showAutoScannerMenu(scanner) { ui.modalTitle.textContent = 'Auto Scanner'; - ui.modalBody.innerHTML = `

Standard auto scanner.

  • Role 0: male left, others right.
  • Role 1: poop left, others right.
  • Cooldown: ${autoScannerCooldownSeconds(scanner).toFixed(1)}s.
`; + game.cardTargetPick = { + pending: true, + mode: 'autoScannerMenu', + cardId: null, + targetKeys: [`scanner:${scanner.id}`], + remaining: 1 + }; + ui.modalBody.innerHTML = ` +
+

Left / right output routing for this AUTO scanner.

+

Current: ${scanner.role === 0 ? 'Male left / others right' : 'Poop left / others right'}

+

Cooldown: ${autoScannerCooldownSeconds(scanner).toFixed(1)}s.

+
`; ui.modalActions.innerHTML = ''; - const r0 = modalButton('Set Role 0', () => { record(game); scanner.role = 0; hideModal(); updatePanels(); }); - const r1 = modalButton('Set Role 1', () => { record(game); scanner.role = 1; hideModal(); updatePanels(); }); - ui.modalActions.append(r0, r1); + const r0 = modalButton('Male Left / Others Right', () => { record(game); scanner.role = 0; hideModal(); updatePanels(); }, scanner.role === 0 ? 'facility-action warn' : 'facility-action'); + const r1 = modalButton('Poop Left / Others Right', () => { record(game); scanner.role = 1; hideModal(); updatePanels(); }, scanner.role === 1 ? 'facility-action warn' : 'facility-action'); + ui.modalActions.append(r0, r1, modalButton('Close', hideModal, 'facility-action')); positionEquipmentPopover(scanner); ui.modal.classList.add('visible', 'equipment-popover'); + updatePanels(); } function formatKeyBinding(binding) { @@ -507,7 +628,6 @@ export function createBuildSystem({ game, ui, canvas, canvasPoint, onUpdatePanel

Left route key: ${left}

Right route key: ${right}

-

Role: ${scanner.role === 0 ? 'Male left / others right' : 'Poop left / others right'}

${message ? `

${message}

` : ''} ${conflict ? `

${conflict}

` : ''}
`; @@ -625,14 +745,64 @@ export function createBuildSystem({ game, ui, canvas, canvasPoint, onUpdatePanel if (f.entry) { const c = cellCenter(f.entry.col, f.entry.row); boxes.push({ id: `facilityPort:${id}`, price: equipmentPrice({ type: 'facility', ref: f }), x: c.x - 18, y: c.y - 18, w: 36, h: 36 }); } } for (const farm of game.eggFarms) { const c = cellCenter(farm.col, farm.row); boxes.push({ id: `eggFarm:${farm.id}`, price: equipmentPrice({ type: 'eggFarm', ref: farm }), x: c.x - 24, y: c.y - 24, w: 48, h: 48 }); } - for (const scanner of game.scanners) { const c = scannerCenter(scanner); boxes.push({ id: `scanner:${scanner.id}`, price: equipmentPrice({ type: 'scanner', ref: scanner }), x: c.x - 52, y: c.y - 36, w: 104, h: 72 }); } - for (const k of game.conveyorTiles) { const p = parseKey(k); const c = cellCenter(p.col, p.row); boxes.push({ id: `conveyor:${k}`, price: buildPrice('conveyor', game), x: c.x - 22, y: c.y - 22, w: 44, h: 44 }); } + for (const scanner of game.scanners) { const c = scannerCenter(scanner); boxes.push({ id: `scanner:${scanner.id}`, price: equipmentPrice({ type: 'scanner', ref: scanner }), x: c.x - 50, y: c.y - 73, w: 100, h: 146 }); } + for (const k of game.conveyorTiles) { + const p = parseKey(k); + const c = cellCenter(p.col, p.row); + const meta = game.conveyorMeta.get(k) || {}; + boxes.push({ id: `conveyor:${k}`, price: meta.price || buildPrice(meta.kind === 'boostConveyor' ? 'boostConveyor' : 'conveyor', game), x: c.x - 22, y: c.y - 22, w: 44, h: 44 }); + } return boxes; } + function branchModeLabel(mode) { + return BRANCH_LABELS[mode] || BRANCH_LABELS.random; + } + + function branchOutDirsForKey(k) { + return reachableOutDirsForKey(k); + } + + function isBranchKey(k) { + const dirs = branchOutDirsForKey(k); + return dirs.length >= 3; + } + + function branchSwitcherAtPoint(p) { + if (game.phase !== 'build' || game.cardDraft?.pending || game.cardTargetPick?.pending) return null; + for (const k of game.conveyorTiles) { + if (!isBranchKey(k)) continue; + const cell = parseKey(k); + const c = cellCenter(cell.col, cell.row); + const left = c.x - BRANCH_MARKER.w / 2; + const top = c.y + BRANCH_MARKER.y - BRANCH_MARKER.h / 2; + if (p.x >= left && p.x <= left + BRANCH_MARKER.w && p.y >= top && p.y <= top + BRANCH_MARKER.h) return k; + } + return null; + } + + function cycleBranchModeAtPoint(p) { + const k = branchSwitcherAtPoint(p); + if (!k) return false; + const meta = game.conveyorMeta.get(k); + if (!meta) return false; + const reachable = reachableOutDirsForKey(k); + if (reachable.length < 2) return false; + record(game); + const current = BRANCH_MODES.includes(meta.branchMode) ? meta.branchMode : 'random'; + const modes = ['random', ...(reachable.length >= 4 ? ['pass'] : []), ...DIRS.map(d => d.name).filter(mode => reachable.includes(mode))]; + meta.branchMode = modes[(Math.max(0, modes.indexOf(current)) + 1) % modes.length]; + game.selected = { type: 'conveyor', id: k }; + const c = cellCenter(...Object.values(parseKey(k))); + floating(game, c.x, c.y - 22, branchModeLabel(meta.branchMode), THEME.ink); + updatePanels(); + return true; + } + function selectedInfoLines(obj) { ensureMaintenanceState(game); const lines = []; + const facilityKind = obj?.type === 'facility' ? (obj.baseId || obj.id) : ''; if (obj.type === 'eggFarm') { const [min, max] = getSpawnRange(obj); lines.push(`Price: ${yen(equipmentPrice(obj))}`); @@ -644,7 +814,7 @@ export function createBuildSystem({ game, ui, canvas, canvasPoint, onUpdatePanel lines.push(`Price: ${yen(equipmentPrice(obj))}`); lines.push(`Sale value: ${yen(resaleValueFor({ type: obj.type, ref: obj }, game).amount)}`); lines.push(`Type: ${obj.kind.toUpperCase()} / Standard`); - lines.push(`Role: ${obj.role === 0 ? 'Male left / Others right' : 'Poop left / Others right'}`); + if (obj.kind === 'auto') lines.push(`Role: ${obj.role === 0 ? 'Male left / Others right' : 'Poop left / Others right'}`); lines.push(`Queue: ${obj.queue.length}`); if (obj.kind === 'auto') lines.push(`Cooldown: ${obj.cooldown.toFixed(1)}s / ${autoScannerCooldownSeconds(obj, game).toFixed(1)}s`); if (obj.kind === 'auto') lines.push(`Durability: ${remainingPercent(obj)}% | Delay x${autoScannerDelayMultiplier(obj).toFixed(2)}`); @@ -656,25 +826,27 @@ export function createBuildSystem({ game, ui, canvas, canvasPoint, onUpdatePanel const c = comp ? game.congestion.get(comp) : null; lines.push(`Price: ${yen(equipmentPrice({ type: 'conveyor', oldKey: obj.id, ref: obj }))}`); lines.push(`Cell: ${obj.id}`); + lines.push(`Type: ${meta?.kind === 'boostConveyor' ? 'BOOST BELT' : 'Normal Belt'} | Speed x${Number(meta?.speedMultiplier || 1).toFixed(1)}`); lines.push(`Congestion: ${c ? Math.floor(c.ratio * 100) : 0}%`); lines.push(`Durability: ${remainingPercent({ meta })}% | Speed x${performanceFactor({ meta }).toFixed(2)}`); - lines.push('Flow follows drawn directions. Branches choose randomly; full branches are avoided when possible.'); + lines.push(`Branch mode: ${branchModeLabel(meta?.branchMode || 'random')}`); + lines.push('Click the branch marker to cycle Random / Up / Right / Down / Left.'); lines.push(`Sale value: ${yen(resaleValueFor({ type: 'conveyor', oldKey: obj.id, ref: obj }, game).amount)}`); } else if (obj.type === 'facility') { lines.push(`Price: ${yen(equipmentPrice(obj))}`); { const reason = lastProtectedSaleReason({ type: obj.type, ref: obj }); lines.push(reason ? `Sale: blocked (${reason})` : `Sale value: ${yen(resaleValueFor({ type: obj.type, ref: obj }, game).amount)}`); } lines.push(['mixer', 'truck', 'trash'].includes(obj.id) ? `Level: ${obj.level} / no cap` : `Level: ${obj.level}`); - if (obj.id === 'truck') lines.push('Durability: none | Always normal'); - if (['mixer', 'trash'].includes(obj.id)) lines.push(`Durability: ${remainingPercent(obj)}% | Extra delay ${facilityProcessingDelay(game, obj.id).toFixed(2)}s`); - if (obj.id === 'mixer') { + if (facilityKind === 'truck') lines.push('Durability: none | Always normal'); + if (['mixer', 'trash'].includes(facilityKind)) lines.push(`Durability: ${remainingPercent(obj)}% | Extra delay ${facilityProcessingDelay(game, obj).toFixed(2)}s`); + if (facilityKind === 'mixer') { lines.push(`Income: ${yen(upgradedMixerPrice(game))} per chick | upgrade x${incomeMultiplier(game, 'mixer').toFixed(3)}`); lines.push(`Poop fine: -${yen(mixerPoopPenalty(game))}`); } - if (obj.id === 'truck') { + if (facilityKind === 'truck') { lines.push(`Income: ${yen(upgradedTruckPrice(game))} per target cargo | upgrade x${incomeMultiplier(game, 'truck').toFixed(3)}`); lines.push(`Poop shipment fine: -${yen(truckPoopPenalty(game))}`); } - if (obj.id === 'trash') { + if (facilityKind === 'trash') { const cards = shredderUpgradeCount(game); const chance = shredderBonusChance(cards); lines.push(`Bonus cards: ${cards}/${shredderBonusMaxCards()}`); @@ -696,13 +868,14 @@ export function createBuildSystem({ game, ui, canvas, canvasPoint, onUpdatePanel function flavorText(obj) { if (!obj) return ''; + if (obj.type === 'conveyor' && game.conveyorMeta.get(obj.id)?.kind === 'boostConveyor') return 'Moves chicks twice as fast. Wears down like a normal belt.'; if (obj.type === 'conveyor') return 'A stubborn belt tile. It only cares about the next cell.'; if (obj.type === 'eggFarm') return 'A tiny gatehouse producing questionable eggs on schedule.'; - if (obj.type === 'scanner' && obj.kind === 'auto') return 'An automated judge. Faster than hands, still very sure of itself.'; + if (obj.type === 'scanner' && obj.kind === 'auto') return 'Slower than hands without upgrades.'; if (obj.type === 'scanner') return 'A manual checkpoint. The operator is the algorithm.'; - if (obj.type === 'facility' && obj.id === 'mixer') return 'Male chicks become revenue here. Do not feed it poop.'; - if (obj.type === 'facility' && obj.id === 'trash') return 'Poop goes in. Sometimes coins come out, for reasons best left unaudited.'; - if (obj.type === 'facility' && obj.id === 'truck') return 'The shipping endpoint. Correct cargo pays; wrong cargo complains.'; + if (obj.type === 'facility' && (obj.baseId || obj.id) === 'mixer') return 'Male chicks become revenue here. Do not feed it poop.'; + if (obj.type === 'facility' && (obj.baseId || obj.id) === 'trash') return 'Poop goes in. Sometimes coins come out, for reasons best left unaudited.'; + if (obj.type === 'facility' && (obj.baseId || obj.id) === 'truck') return 'The shipping endpoint. Correct cargo pays; wrong cargo complains.'; return 'Factory equipment.'; } @@ -718,6 +891,7 @@ export function createBuildSystem({ game, ui, canvas, canvasPoint, onUpdatePanel selectedInfoLines, flavorText, disconnectedWarningFor, gridExpansionCost, buyGridExpansion, expansionOfferAtPoint, beginConveyorDrag, continueConveyorDrag, endConveyorDrag, + branchSwitcherAtPoint, cycleBranchModeAtPoint, removeSelected, switchScannerRole, showManualScannerMenu, showAutoScannerMenu, showSelectedMenu, setBuildTool, fail, isEquipmentCell, equipmentHitBoxes, diff --git a/src/systems/cards.js b/src/systems/cards.js index 2ea3553..61bbeeb 100644 --- a/src/systems/cards.js +++ b/src/systems/cards.js @@ -3,7 +3,7 @@ import { nextSpawnDelay } from '../core/state.js'; import { cellCenter, key, yen } from '../core/utils.js'; import { applyPenalty, applyRevenue, upgradedMixerPrice, upgradedTruckPrice, shredderUpgradeCount, rawShredderUpgradeCount, shredderBonusChance, shredderBonusMaxCards } from './economy.js'; import { floating, shake, eraseEffect, shockwave, sparkBurst, smokeBurst } from './effects.js'; -import { averageConveyorPerformance, autoScannerDelayMultiplier, ensureMaintenanceState } from './maintenance.js'; +import { averageConveyorPerformance, autoScannerDelayMultiplier, ensureMaintenanceState, repairAllEquipment } from './maintenance.js'; import { scannerCenter, refreshRoutingAfterEdit } from './routing.js'; const COMMON_WEIGHT = CARD_BALANCE.commonWeight; @@ -31,6 +31,7 @@ export function ensureCardState(game) { if (game.cardEffects.drawBonus != null) delete game.cardEffects.drawBonus; if (!game.cardDraft) game.cardDraft = { pending: false, choices: [], rerolls: 0, picksRemaining: 0 }; if (game.cardDraft.picksRemaining == null) game.cardDraft.picksRemaining = game.cardDraft.pending ? 1 : 0; + if (game.cardDraft.freeRerolls == null) game.cardDraft.freeRerolls = 0; return game.cardEffects; } @@ -47,8 +48,9 @@ export function extraEggOutletCount(_game, farm = null) { export function conveyorSpeedForGame(game, conveyorKey = null) { const effects = ensureCardState(game); const speed = CONVEYOR_SPEED * Math.pow(BEARING_SPEED_MULTIPLIER, Math.max(0, effects.bearing || 0)); + const beltMultiplier = conveyorKey ? Math.max(0.1, Number(game?.conveyorMeta?.get?.(conveyorKey)?.speedMultiplier) || 1) : 1; const perf = conveyorKey ? 1 : averageConveyorPerformance(game); - return Math.min(CONVEYOR_SPEED_MAX, speed * perf); + return Math.min(CONVEYOR_SPEED_MAX, speed * perf) * beltMultiplier; } export function autoScannerCooldownSeconds(scanner, game = null) { @@ -155,9 +157,11 @@ export function targetsForCard(game, cardOrId) { .filter(f => Math.max(0, Number(f.extraEggOutlet) || 0) < 3) .map(f => ({ type: 'eggOutlet', id: f.id, col: f.col, row: f.row, farm: f })); if (card.target === 'autoScanner') return game.scanners.filter(s => s.kind === 'auto'); - if (card.target === 'mixer') return game.facilities.mixer ? [game.facilities.mixer] : []; - if (card.target === 'truck') return game.facilities.truck ? [game.facilities.truck] : []; - if (card.target === 'trash') return game.facilities.trash && rawShredderUpgradeCount(game) < shredderBonusMaxCards() ? [game.facilities.trash] : []; + if (card.target === 'mixer') return Object.values(game.facilities || {}).filter(f => (f.baseId || f.id) === 'mixer'); + if (card.target === 'truck') return Object.values(game.facilities || {}).filter(f => (f.baseId || f.id) === 'truck'); + if (card.target === 'trash') return rawShredderUpgradeCount(game) < shredderBonusMaxCards() + ? Object.values(game.facilities || {}).filter(f => (f.baseId || f.id) === 'trash') + : []; return []; } @@ -190,17 +194,22 @@ function availableCards(game) { }); } -function rarityWeight(card) { +function cardWeight(game, card) { + if (card.weightBase != null) { + const held = effectCount(game, card.id); + return Math.max(0, Number(card.weightBase) - Math.max(0, Number(card.weightLossPerCopy) || 0) * held); + } if (card.rarity === 'ultraRare') return ULTRA_RARE_WEIGHT; if (card.rarity === 'rare') return RARE_WEIGHT; return COMMON_WEIGHT; } -function weightedPick(pool) { - const total = pool.reduce((sum, card) => sum + rarityWeight(card), 0); +function weightedPick(game, pool) { + const total = pool.reduce((sum, card) => sum + cardWeight(game, card), 0); + if (total <= 0) return pool[Math.floor(Math.random() * pool.length)]; let roll = Math.random() * total; for (const card of pool) { - roll -= rarityWeight(card); + roll -= cardWeight(game, card); if (roll <= 0) return card; } return pool[pool.length - 1]; @@ -247,7 +256,7 @@ export function dealCards(game, count = BASE_DRAFT_SIZE) { let pool = [...source]; while (choices.length < count && source.length) { if (!pool.length) pool = [...source]; - const picked = weightedPick(pool); + const picked = weightedPick(game, pool); choices.push(picked); const i = pool.findIndex(card => card.id === picked.id); if (i >= 0) pool.splice(i, 1); @@ -277,7 +286,7 @@ function boundsForTarget(target) { } if (target.type === 'scanner') { const c = scannerCenter(target); - return { x: c.x - 58, y: c.y - 42, w: 116, h: 84, cx: c.x, cy: c.y }; + return { x: c.x - 54, y: c.y - 77, w: 108, h: 154, cx: c.x, cy: c.y }; } if (target.type === 'facility') { return { x: target.x, y: target.y, w: target.w, h: target.h, cx: target.x + target.w / 2, cy: target.y + target.h / 2 }; @@ -287,6 +296,13 @@ function boundsForTarget(target) { export function cardTargetBounds(game) { if (!game.cardTargetPick?.pending) return []; + if (game.cardTargetPick.mode === 'autoScannerMenu') { + const allowed = new Set(game.cardTargetPick.targetKeys || []); + return game.scanners + .filter(scanner => scanner.kind === 'auto' && allowed.has(targetKey(scanner))) + .map(scanner => ({ key: targetKey(scanner), label: 'AUTO ROUTE SETTINGS', target: scanner, bounds: boundsForTarget(scanner) })) + .filter(item => item.bounds); + } const allowed = new Set(game.cardTargetPick.targetKeys || []); return targetsForCard(game, game.cardTargetPick.cardId) .filter(target => allowed.has(targetKey(target))) @@ -344,6 +360,16 @@ function applyInstantCard(game, card) { if (card.id === 'preventiveMaintenance') inc('preventiveMaintenance'); if (card.id === 'dudFilter') inc('dudFilter'); if (card.id === 'durabilityCoating') { inc('durabilityCoating'); ensureMaintenanceState(game); } + if (card.id === 'sparePartsBin') { + inc('sparePartsBin'); + const repaired = repairAllEquipment(game, 0.02); + game.stats.sparePartsRepair = (game.stats.sparePartsRepair || 0) + repaired; + game.totals.sparePartsRepair = (game.totals.sparePartsRepair || 0) + repaired; + floating(game, GRID.x + GRID.cols * GRID.cell / 2, GRID.y - 30, 'REPAIR +2%', THEME.green); + } + if (card.id === 'dudRefund') inc('dudRefund'); + if (card.id === 'freeReroll') inc('freeReroll'); + if (card.id === 'composter') inc('composter'); if (card.id === 'flattery') inc('fairiesFlatteryNext'); if (card.id === 'usedMachine') effects.usedMachineActive = true; if (card.id === 'newMachine') effects.usedMachineActive = false; @@ -432,6 +458,10 @@ function cardDescription(game, card) { return `Held ${held}. DUD chance per card ${formatPercent(before)} -> ${formatPercent(after)}.`; } if (card.id === 'durabilityCoating') return `Held ${e.durabilityCoating || 0}. After pick: maximum durability ${formatPercent(Math.pow(1.03, (e.durabilityCoating || 0) + 1))}.`; + if (card.id === 'sparePartsBin') return `Held ${e.sparePartsBin || 0}. Immediately restores 2% durability to every maintained machine. Offer weight: ${Math.max(0, 100 - 2 * Math.max(0, e.sparePartsBin || 0))}.`; + if (card.id === 'dudRefund') return `Held ${e.dudRefund || 0}. After pick: DUD clicks award ${yen(50 * ((e.dudRefund || 0) + 1))}.`; + if (card.id === 'freeReroll') return `Held ${e.freeReroll || 0}. After pick: each card screen starts with ${(e.freeReroll || 0) + 1} free reroll${(e.freeReroll || 0) + 1 === 1 ? '' : 's'}.`; + if (card.id === 'composter') return `Held ${e.composter || 0}. After pick: SHREDDER poop grants ${yen(3 * ((e.composter || 0) + 1))}.`; if (card.id === 'flattery') return `Next Fairies tribute -50%. Held for next tribute: ${e.fairiesFlatteryNext || 0}.`; if (card.id === 'usedMachine') return 'Enable Used Machines: future equipment costs 50%, has no SELL refund, and starts with 60% durability.'; if (card.id === 'newMachine') return 'Cancel Used Machines. Future equipment returns to normal price, refund, and durability.'; @@ -456,7 +486,7 @@ export function createCardSystem({ game, ui, onUpdatePanels }) { function prepareDraft() { ensureCardState(game); game.cardTargetPick = null; - game.cardDraft = { pending: true, choices: dealCards(game, BASE_DRAFT_SIZE), rerolls: 0, picksRemaining: 1 }; + game.cardDraft = { pending: true, choices: dealCards(game, BASE_DRAFT_SIZE), rerolls: 0, freeRerolls: effectCount(game, 'freeReroll'), picksRemaining: 1 }; } function finishDraft() { @@ -496,6 +526,13 @@ export function createCardSystem({ game, ui, onUpdatePanels }) { } function chooseDud(card, buttonEl) { + const refund = effectCount(game, 'dudRefund') * 50; + if (refund > 0) { + const amount = applyRevenue(game, refund); + game.stats.dudRefundIncome = (game.stats.dudRefundIncome || 0) + amount; + game.totals.dudRefundIncome = (game.totals.dudRefundIncome || 0) + amount; + floating(game, GRID.x + GRID.cols * GRID.cell / 2, GRID.y - 30, `DUD REFUND +${yen(amount)}`, THEME.green); + } shake(game, 34, 0.62); if (document.body?.animate) { document.body.animate([ @@ -566,8 +603,7 @@ export function createCardSystem({ game, ui, onUpdatePanels }) { const card = cardById(game.cardTargetPick.cardId); const target = targetAtPoint(game, p); if (!card || !target) { - const label = game.cardTargetPick.mode === 'blockedCell' ? 'SELECT BLOCKED CELL' : 'SELECT UPGRADE TARGET'; - floating(game, p.x, p.y - 18, label, THEME.danger); + cancelTargetPick(); return true; } if (card.id === 'dynamite') { @@ -587,6 +623,14 @@ export function createCardSystem({ game, ui, onUpdatePanels }) { } function doReroll() { + const free = Math.max(0, game.cardDraft?.freeRerolls || 0); + if (free > 0) { + game.cardDraft.freeRerolls = free - 1; + game.cardDraft.rerolls += 1; + redrawChoices(BASE_DRAFT_SIZE); + showDraft(); + return; + } const cost = rerollCost(game); if (game.cash < cost) return; applyPenalty(game, cost); @@ -617,6 +661,7 @@ export function createCardSystem({ game, ui, onUpdatePanels }) { if (!game.cardDraft.choices?.length) redrawChoices(BASE_DRAFT_SIZE); const choices = game.cardDraft.choices || []; const cost = rerollCost(game); + const free = Math.max(0, game.cardDraft?.freeRerolls || 0); const remaining = Math.max(1, game.cardDraft.picksRemaining || 1); ui.modalTitle.textContent = 'Choose Upgrade Card'; ui.modalBody.innerHTML = `

Choose ${remaining} card${remaining === 1 ? '' : 's'} before Build phase. Dud cards can be flicked away without consuming a pick. Equipment cards target machines; Dynamite targets blocked cells.

`; @@ -626,11 +671,18 @@ export function createCardSystem({ game, ui, onUpdatePanels }) { const reroll = document.createElement('button'); reroll.type = 'button'; reroll.className = 'facility-action warn reroll-button'; - reroll.textContent = `Reroll ${yen(cost)}`; - reroll.disabled = game.cash < cost; - reroll.title = reroll.disabled ? 'Not enough cash' : `Reroll count today: ${(game.cardDraft.rerolls || 0) + 1}`; + reroll.textContent = free > 0 ? `Reroll (FREE x${free})` : `Reroll ${yen(cost)}`; + reroll.disabled = free <= 0 && game.cash < cost; + reroll.title = free > 0 ? `${free} free reroll${free === 1 ? '' : 's'} remaining.` : (reroll.disabled ? 'Not enough cash' : ''); reroll.addEventListener('click', doReroll); ui.modalActions.appendChild(reroll); + const skip = document.createElement('button'); + skip.type = 'button'; + skip.className = 'facility-action'; + skip.textContent = 'No Card'; + skip.title = 'Skip this upgrade pick and enter Build phase.'; + skip.addEventListener('click', finishDraft); + ui.modalActions.appendChild(skip); ui.modal.classList.remove('equipment-popover'); ui.modal.classList.add('visible'); } diff --git a/src/systems/chickSystem.js b/src/systems/chickSystem.js index de6546e..ee13c2f 100644 --- a/src/systems/chickSystem.js +++ b/src/systems/chickSystem.js @@ -1,7 +1,7 @@ import { DIRS, GRID, THEME } from '../core/config.js'; import { createChick } from '../core/entities.js'; import { nextSpawnDelay } from '../core/state.js'; -import { key, parseKey, pointToCell, cellCenter, randomBetween, yen, inGrid } from '../core/utils.js'; +import { key, parseKey, pointToCell, cellCenter, randomBetween, yen, inGrid, directionNameBetweenCells } from '../core/utils.js'; import { scannerById, scannerBySlot, scannerCenter, scannerConnector, nearestConveyorKey, buildConveyorComponents, autoSideFor, ensureFactoryGraph } from './routing.js'; import { applyMixerIncome, applyMixerPoopFine, applyTruckPoopFine, applyWrongTruckFine, upgradedTruckPrice, applyShredderBonus, applyRevenue } from './economy.js'; import { autoScannerCooldownSeconds, eggProductionDelayMultiplier, extraEggOutletCount } from './cards.js'; @@ -104,7 +104,6 @@ export function createChickSystem({ game, currentConveyorSpeed, onGameOverCheck } updateScannerQueues(dt); updateCongestion(); - if (game.timeLeft > 0 || game.shutdownTimeLeft > 0) checkCongestionExplosions(); if (game.timeLeft <= 0) { if (game.chicks.length === 0) completeTurn(); else if (game.shutdownTimeLeft <= 0) blowOffRemainingForCleanup(); @@ -172,7 +171,14 @@ export function createChickSystem({ game, currentConveyorSpeed, onGameOverCheck const dirs = []; if (Array.isArray(meta.outDirs)) dirs.push(...meta.outDirs); if (meta.dir) dirs.push(meta.dir); - return [...new Set(dirs)].filter(dir => DIRS.some(d => d.name === dir)); + const explicit = [...new Set(dirs)].filter(dir => DIRS.some(d => d.name === dir)); + const cell = parseKey(cellKey); + const inferred = DIRS + .filter(d => movementOptionFromDir(cell, d.name)) + .map(d => d.name); + const cleanExplicit = explicit.filter(dir => inferred.includes(dir) && !neighborPointsIntoCell(cell, dir)); + const inferredExits = inferred.filter(dir => !neighborPointsIntoCell(cell, dir)); + return cleanExplicit.length ? cleanExplicit : inferredExits; } function dirByName(name) { @@ -180,7 +186,7 @@ export function createChickSystem({ game, currentConveyorSpeed, onGameOverCheck } function scannerAtBodyCell(col, row) { - return game.scanners.find(scanner => scanner.col === col && scanner.row === row) || null; + return game.scanners.find(scanner => scanner.col <= col && col <= scanner.col + 1 && scanner.row - 1 <= row && row <= scanner.row + 1) || null; } function scannerReceivingFrom(cell, dirName) { @@ -196,6 +202,10 @@ export function createChickSystem({ game, currentConveyorSpeed, onGameOverCheck return Object.entries(game.facilities || {}).find(([, f]) => f?.entry && f.entry.col === cell.col && f.entry.row === cell.row) || null; } + function facilityKind(f) { + return f?.baseId || f?.id || ''; + } + function dirExitsToFacility(facility, dirName) { return (facility.side === 'left' && dirName === 'left') || (facility.side === 'right' && dirName === 'right') @@ -219,9 +229,32 @@ export function createChickSystem({ game, currentConveyorSpeed, onGameOverCheck return null; } - function pickMovementOption(chick, cell, dirs) { + function neighborPointsIntoCell(cell, dirName) { + const d = dirByName(dirName); + if (!d) return false; + const meta = game.conveyorMeta?.get?.(key(cell.col + d.dc, cell.row + d.dr)); + if (!meta) return false; + const names = [...new Set([...(Array.isArray(meta.outDirs) ? meta.outDirs : []), meta.dir].filter(Boolean))]; + return names.includes(d.opposite); + } + + function optionAvailableForBranch(chick, opt) { + return opt && (opt.type === 'facility' || !targetBlockedByChick(chick, opt.target)); + } + + function pickMovementOption(chick, cell, dirs, branchMode = 'random') { const candidates = dirs.map(dir => movementOptionFromDir(cell, dir)).filter(Boolean); if (!candidates.length) return null; + if (branchMode === 'pass') { + const previous = chick.prevConveyorCell ? parseKey(chick.prevConveyorCell) : null; + const forward = previous ? directionNameBetweenCells(previous, cell) : null; + const pass = forward ? candidates.find(opt => opt.dirName === forward) : null; + if (optionAvailableForBranch(chick, pass)) return pass; + } + if (branchMode && branchMode !== 'random') { + const preferred = candidates.find(opt => opt.dirName === branchMode); + if (optionAvailableForBranch(chick, preferred)) return preferred; + } const moving = candidates.filter(opt => opt.type !== 'facility'); const empty = moving.filter(opt => !targetBlockedByChick(chick, opt.target)); const pool = empty.length ? empty : candidates.filter(opt => opt.type === 'facility'); @@ -242,24 +275,27 @@ export function createChickSystem({ game, currentConveyorSpeed, onGameOverCheck if (!cell) return removeChick(index, 'OFF GRID'); const currentKey = key(cell.col, cell.row); if (!game.conveyorTiles.has(currentKey)) return removeChick(index, 'OFF BELT'); + const meta = game.conveyorMeta?.get(currentKey) || {}; const dirs = conveyorOutDirNames(currentKey); if (!dirs.length) { chick.stoppedTimer = 0.25; return; } - const option = pickMovementOption(chick, cell, dirs); + const option = pickMovementOption(chick, cell, dirs, meta.branchMode || 'random'); if (!option) { chick.stoppedTimer = 0.25; return; } if (option.type === 'facility') { - if (option.facilityId === 'mixer') resolveMixer(index); - else if (option.facilityId === 'truck') resolveTruck(index); - else if (option.facilityId === 'trash') resolveTrash(index); + const kind = facilityKind(option.facility); + if (kind === 'mixer') resolveMixer(index, option.facility); + else if (kind === 'truck') resolveTruck(index, option.facility); + else if (kind === 'trash') resolveTrash(index, option.facility); else removeChick(index, 'DONE'); return; } if (option.type === 'scanner') chick.pendingScannerId = option.scanner.id; + chick.prevConveyorCell = currentKey; setSingleSegmentRoute(chick, option.target); } @@ -497,8 +533,7 @@ export function createChickSystem({ game, currentConveyorSpeed, onGameOverCheck return chick; } - function resolveMixer(index) { - const mixer = game.facilities.mixer; + function resolveMixer(index, mixer = game.facilities.mixer) { if (mixer?.processingCooldown > 0) { const chick = game.chicks[index]; if (chick) chick.stoppedTimer = 0.25; @@ -508,8 +543,8 @@ export function createChickSystem({ game, currentConveyorSpeed, onGameOverCheck if (!chick) return; const { x, y } = chick; countMixer(game); - recordFacilityProcess(game, 'mixer'); - setFacilityCooldownAfterProcess(game, 'mixer'); + recordFacilityProcess(game, mixer); + setFacilityCooldownAfterProcess(game, mixer); if (chick.sex === 'poop') { const penalty = applyMixerPoopFine(game); countPoopDestination(game, 'mixer'); @@ -525,11 +560,11 @@ export function createChickSystem({ game, currentConveyorSpeed, onGameOverCheck floating(game, x, y - 18, `+${yen(amount)}`, THEME.green); } - function resolveTruck(index) { + function resolveTruck(index, truck = game.facilities.truck) { const chick = takeChick(index); if (!chick) return; const { x, y } = chick; - addTruckCargo(chick.sex); + addTruckCargo(chick.sex, truck); truckLoadEffect(game, x, y, chick.sex); countTruckCargo(game, chick.sex); const target = truckTarget(game); @@ -567,8 +602,7 @@ export function createChickSystem({ game, currentConveyorSpeed, onGameOverCheck } } - function resolveTrash(index) { - const trash = game.facilities.trash; + function resolveTrash(index, trash = game.facilities.trash) { if (trash?.processingCooldown > 0) { const chick = game.chicks[index]; if (chick) chick.stoppedTimer = 0.25; @@ -579,13 +613,20 @@ export function createChickSystem({ game, currentConveyorSpeed, onGameOverCheck const { x, y } = chick; shredEffect(game, x, y, chick.sex); countTrash(game); - recordFacilityProcess(game, 'trash'); - setFacilityCooldownAfterProcess(game, 'trash'); + recordFacilityProcess(game, trash); + setFacilityCooldownAfterProcess(game, trash); const bonus = applyShredderBonus(game); const bonusText = bonus.amount > 0 ? ` +${yen(bonus.amount)}` : ''; if (chick.sex === 'poop') { countPoopDestination(game, 'trash'); countCorrect(game); + const compost = Math.max(0, Number(game.cardEffects?.composter) || 0) * 3; + if (compost > 0) { + const compostAmount = applyRevenue(game, compost); + game.stats.composterIncome = (game.stats.composterIncome || 0) + compostAmount; + game.totals.composterIncome = (game.totals.composterIncome || 0) + compostAmount; + floating(game, x, y - 36, `COMPOST +${yen(compostAmount)}`, THEME.green); + } floating(game, x, y - 18, `CLEAN${bonusText}`, bonus.amount > 0 ? THEME.green : THEME.green); } else { countMistake(game); @@ -599,8 +640,8 @@ export function createChickSystem({ game, currentConveyorSpeed, onGameOverCheck floating(game, chick.x, chick.y - 12, label, THEME.muted); } - function addTruckCargo(sex) { - const t = game.facilities.truck; + function addTruckCargo(sex, truck = game.facilities.truck) { + const t = truck || game.facilities.truck; if (!t) return; game.truckCargo.push({ sex, x: randomBetween(22, t.w - 22), y: randomBetween(66, t.h - 24) }); if (game.truckCargo.length > 45) game.truckCargo.shift(); diff --git a/src/systems/economy.js b/src/systems/economy.js index 97d9484..5ef0ffa 100644 --- a/src/systems/economy.js +++ b/src/systems/economy.js @@ -21,7 +21,7 @@ function explosionDamageMultiplier(game) { export function equipmentBasePrice(objOrHit) { const obj = objOrHit?.ref || objOrHit || {}; const type = objOrHit?.type || obj.type; - if (type === 'conveyor') return FACILITY_DEFS.conveyor.price; + if (type === 'conveyor') return obj.price || FACILITY_DEFS[obj.kind]?.price || FACILITY_DEFS.conveyor.price; if (type === 'eggFarm') return obj.price || FACILITY_DEFS.eggFarm.price; if (type === 'scanner') return obj.price || (obj.kind === 'auto' ? FACILITY_DEFS.autoScanner.price : FACILITY_DEFS.manualScanner.price); if (type === 'facility') return obj.price || FACILITY_DEFS[obj.id]?.price || 0; diff --git a/src/systems/history.js b/src/systems/history.js index e121ae8..13886c8 100644 --- a/src/systems/history.js +++ b/src/systems/history.js @@ -68,6 +68,7 @@ export function restore(game, text) { if (game.cardEffects.drawBonus != null) delete game.cardEffects.drawBonus; game.cardDraft = data.cardDraft || { pending: false, choices: [], rerolls: 0, picksRemaining: 0 }; if (game.cardDraft.picksRemaining == null) game.cardDraft.picksRemaining = game.cardDraft.pending ? 1 : 0; + if (game.cardDraft.freeRerolls == null) game.cardDraft.freeRerolls = 0; game.cardTargetPick = data.cardTargetPick || null; game.groupDrag = null; game.selectionBox = null; diff --git a/src/systems/maintenance.js b/src/systems/maintenance.js index a5bec62..b17815d 100644 --- a/src/systems/maintenance.js +++ b/src/systems/maintenance.js @@ -48,6 +48,9 @@ export function ensureMaintenanceState(game) { for (const [k, meta] of game.conveyorMeta || []) { if (meta.uses == null) meta.uses = 0; if (meta.maintenanceType == null) meta.maintenanceType = 'conveyor'; + if (!meta.kind) meta.kind = 'conveyor'; + if (!meta.speedMultiplier) meta.speedMultiplier = meta.kind === 'boostConveyor' ? 2 : 1; + if (!meta.branchMode) meta.branchMode = 'random'; const desired = durabilityCapFor(game, 'conveyor', qualityMultiplierOf(meta)); if (meta.durability == null || meta.durability < desired) meta.durability = desired; } @@ -138,9 +141,20 @@ export function eggSpawnDelayMultiplier(farm) { return delayMultiplier(farm); } -export function facilityProcessingDelay(game, id) { +function facilityKind(target) { + return target?.baseId || target?.id || target; +} + +function facilityFromTarget(game, target) { + if (!target) return null; + if (typeof target === 'object') return target; + return game.facilities?.[target] || null; +} + +export function facilityProcessingDelay(game, target) { ensureMaintenanceState(game); - const f = game.facilities?.[id]; + const f = facilityFromTarget(game, target); + const id = facilityKind(f || target); if (!f || !['mixer', 'trash'].includes(id)) return 0; attachMaintenance(f, id); const t = Math.max(0, delayMultiplier(f) - 1); @@ -169,25 +183,26 @@ export function recordAutoScan(game, scanner) { scanner.maintenance.uses = Math.min(scanner.maintenance.durability, (scanner.maintenance.uses || 0) + degradationUseMultiplier(game)); } -export function recordFacilityProcess(game, id) { +export function recordFacilityProcess(game, target) { ensureMaintenanceState(game); - const f = game.facilities?.[id]; + const f = facilityFromTarget(game, target); + const id = facilityKind(f || target); if (!f || !['mixer', 'trash'].includes(id)) return; attachMaintenance(f, id); f.maintenance.uses = Math.min(f.maintenance.durability, (f.maintenance.uses || 0) + degradationUseMultiplier(game)); } -export function setFacilityCooldownAfterProcess(game, id) { - const f = game.facilities?.[id]; +export function setFacilityCooldownAfterProcess(game, target) { + const f = facilityFromTarget(game, target); if (!f) return 0; - const delay = facilityProcessingDelay(game, id); + const delay = facilityProcessingDelay(game, f); f.processingCooldown = Math.max(f.processingCooldown || 0, delay); return delay; } export function updateProcessingCooldowns(game, dt) { - for (const id of ['mixer', 'trash']) { - const f = game.facilities?.[id]; + for (const f of Object.values(game.facilities || {})) { + if (!['mixer', 'trash'].includes(f.baseId || f.id)) continue; if (f?.processingCooldown > 0) f.processingCooldown = Math.max(0, f.processingCooldown - dt); } } @@ -195,7 +210,7 @@ export function updateProcessingCooldowns(game, dt) { export function equipmentMaintenanceTargets(game) { ensureMaintenanceState(game); const targets = []; - for (const [k, meta] of game.conveyorMeta || []) targets.push({ type: 'conveyor', key: k, meta, label: `Belt ${k}`, center: cellCenter(parseKey(k).col, parseKey(k).row) }); + for (const [k, meta] of game.conveyorMeta || []) targets.push({ type: 'conveyor', key: k, meta, label: `${meta.kind === 'boostConveyor' ? 'Boost Belt' : 'Belt'} ${k}`, center: cellCenter(parseKey(k).col, parseKey(k).row) }); for (const farm of game.eggFarms || []) targets.push({ type: 'eggFarm', ref: farm, label: `EGG #${farm.id}`, center: cellCenter(farm.col, farm.row) }); for (const scanner of (game.scanners || []).filter(s => s.kind === 'auto')) targets.push({ type: 'autoScanner', ref: scanner, label: `AUTO #${scanner.id}`, center: cellCenter(scanner.col, scanner.row) }); for (const id of ['mixer', 'trash']) { @@ -211,6 +226,18 @@ function targetDurability(t) { return t.meta ? durabilityOf(t) : durabilityOf(t. function reduceTargetUses(t, amount) { setUses(t.meta ? t : t.ref, Math.max(0, targetUses(t) - amount)); } function targetKey(t) { return t.type === 'conveyor' ? `conveyor:${t.key}` : `${t.type}:${t.ref?.id || t.ref?.type || t.type}`; } +export function repairAllEquipment(game, percent = 0.02) { + ensureMaintenanceState(game); + const pct = Math.max(0, Number(percent) || 0); + let repaired = 0; + for (const t of equipmentMaintenanceTargets(game)) { + const before = targetUses(t); + reduceTargetUses(t, targetDurability(t) * pct); + repaired += Math.max(0, before - targetUses(t)) / targetDurability(t) * 100; + } + return repaired; +} + export function hireRepairmanForNextDay(game) { ensureMaintenanceState(game); if (game.repairman.hiredForNextDay) return { ok: false, reason: 'Repairman already hired.' }; diff --git a/src/systems/routing.js b/src/systems/routing.js index 3bad2fc..6095865 100644 --- a/src/systems/routing.js +++ b/src/systems/routing.js @@ -5,11 +5,29 @@ import { key, parseKey, inGrid, cellCenter, distance, sameCell } from '../core/u // ----------------------------------------------------------------------------- // Object lookup // ----------------------------------------------------------------------------- -export function scannerCenter(scanner) { return cellCenter(scanner.col, scanner.row); } +export function scannerFootprintCells(scanner) { + if (!scanner) return []; + return [ + { col: scanner.col, row: scanner.row - 1 }, + { col: scanner.col + 1, row: scanner.row - 1 }, + { col: scanner.col, row: scanner.row }, + { col: scanner.col + 1, row: scanner.row }, + { col: scanner.col, row: scanner.row + 1 }, + { col: scanner.col + 1, row: scanner.row + 1 } + ]; +} +export function scannerCenter(scanner) { + const c = cellCenter(scanner.col, scanner.row); + return { x: c.x + GRID.cell / 2, y: c.y }; +} export function farmAt(game, col, row) { return game.eggFarms.find(f => f.col === col && f.row === row) || null; } -export function scannerAt(game, col, row) { return game.scanners.find(s => s.col === col && s.row === row) || null; } +export function scannerAt(game, col, row) { return game.scanners.find(s => scannerFootprintCells(s).some(p => p.col === col && p.row === row)) || null; } export function scannerById(game, id) { return game.scanners.find(s => s.id === id) || null; } export function scannerBySlot(game, slot) { return game.scanners.find(s => s.kind === 'manual' && s.slot === slot) || null; } +function facilityKind(f) { return f?.baseId || f?.id || ''; } +function facilityEntriesForKind(game, kind) { + return Object.entries(game.facilities || {}).filter(([, f]) => facilityKind(f) === kind); +} // ----------------------------------------------------------------------------- // Port definitions. Ports prefer the exact connector cell, but also accept @@ -17,10 +35,10 @@ export function scannerBySlot(game, slot) { return game.scanners.find(s => s.kin // ----------------------------------------------------------------------------- export function scannerConnector(scanner, type) { return { - inputA: { col: scanner.col, row: scanner.row - 1 }, + inputA: { col: scanner.col, row: scanner.row - 2 }, inputB: null, left: { col: scanner.col - 1, row: scanner.row }, - right: { col: scanner.col + 1, row: scanner.row } + right: { col: scanner.col + 2, row: scanner.row } }[type]; } @@ -54,21 +72,22 @@ function graphPortConveyorCells(game, point, blocked = [], preferred = []) { // ----------------------------------------------------------------------------- export function scannerInputCells(game, scanner) { const connector = scannerConnector(scanner, 'inputA'); - return visualPortConveyorCells(game, connector, [ { col: scanner.col, row: scanner.row } ]); + return visualPortConveyorCells(game, connector, scannerFootprintCells(scanner)); } function outputStartCells(game, scanner, side) { const connector = scannerConnector(scanner, side); - return visualPortConveyorCells(game, connector, [ { col: scanner.col, row: scanner.row } ]); + return visualPortConveyorCells(game, connector, scannerFootprintCells(scanner)); } export function facilityEntryPoint(game, dest) { - const f = game.facilities[dest]; + const f = game.facilities[dest] || facilityEntriesForKind(game, dest)[0]?.[1]; if (!f) return null; if (f.entry) return cellCenter(f.entry.col, f.entry.row); - if (dest === 'mixer') return { x: f.x + f.w, y: f.y + f.h * 0.52 }; - if (dest === 'truck') return { x: f.x, y: f.y + f.h * 0.52 }; - if (dest === 'trash') return { x: f.x + f.w / 2, y: f.y + 8 }; + const kind = facilityKind(f) || dest; + if (kind === 'mixer') return { x: f.x + f.w, y: f.y + f.h * 0.52 }; + if (kind === 'truck') return { x: f.x, y: f.y + f.h * 0.52 }; + if (kind === 'trash') return { x: f.x + f.w / 2, y: f.y + 8 }; return { x: f.x + f.w / 2, y: f.y + f.h / 2 }; } @@ -102,6 +121,25 @@ function conveyorOutDirNames(game, cellKey) { return [...new Set(dirs)].filter(dir => DIRS.some(d => d.name === dir)); } +function neighborPointsIntoCell(game, p, dirName) { + const d = DIRS.find(item => item.name === dirName); + if (!d) return false; + const meta = game.conveyorMeta?.get?.(graphNodeKey(p.col + d.dc, p.row + d.dr)); + if (!meta) return false; + const names = [...new Set([...(Array.isArray(meta.outDirs) ? meta.outDirs : []), meta.dir].filter(Boolean))]; + return names.includes(d.opposite); +} + +function inferredConveyorOutDirNames(game, cellKey) { + const p = graphCell(cellKey); + const neighbors = DIRS + .filter(d => game.conveyorTiles.has(graphNodeKey(p.col + d.dc, p.row + d.dr))) + .map(d => d.name); + const explicit = conveyorOutDirNames(game, cellKey).filter(dir => neighbors.includes(dir) && !neighborPointsIntoCell(game, p, dir)); + const inferred = neighbors.filter(dir => !neighborPointsIntoCell(game, p, dir)); + return explicit.length ? explicit : inferred; +} + function emptyFactoryGraph(game) { return { version: game.routingVersion || 0, @@ -140,8 +178,8 @@ export function buildFactoryGraph(game) { for (const k of graph.cells) { const p = graphCell(k); - const explicitDirs = conveyorOutDirNames(game, k); - const dirs = explicitDirs.length ? DIRS.filter(d => explicitDirs.includes(d.name)) : DIRS; + const outDirs = inferredConveyorOutDirNames(game, k); + const dirs = DIRS.filter(d => outDirs.includes(d.name)); for (const d of dirs) { const nk = graphNodeKey(p.col + d.dc, p.row + d.dr); if (graph.cells.has(nk)) graph.adjacency.get(k).push({ key: nk, dir: d.name }); @@ -191,7 +229,7 @@ function indexGraphPorts(game, graph) { const ports = {}; for (const type of ['inputA', 'left', 'right']) { const cell = scannerConnector(scanner, type); - const cells = graphPortConveyorCells(game, cell, [ { col: scanner.col, row: scanner.row } ]) + const cells = graphPortConveyorCells(game, cell, scannerFootprintCells(scanner)) .filter(p => graph.cells.has(graphNodeKey(p.col, p.row))); const cellKeys = cells.map(p => graphNodeKey(p.col, p.row)); const connected = cellKeys.length > 0; @@ -265,13 +303,13 @@ function dirBetween(a, b) { return DIRS.find(d => d.dc === dc && d.dr === dr)?.name || null; } -function isCrossInGraph(graph, cellKey) { +function isPassCross(game, graph, cellKey) { + if (game.conveyorMeta?.get?.(cellKey)?.branchMode !== 'pass') return false; const ns = graph.adjacency.get(cellKey) || []; const names = new Set(ns.map(n => n.dir)); return ns.length === 4 && names.has('left') && names.has('right') && names.has('up') && names.has('down'); } -// A four-way cross conveyor is an overpass/crossing. Chicks never turn there. export function bfsAllRoutes(game, start, isGoal) { const graph = ensureFactoryGraph(game); const startK = graphNodeKey(start.col, start.row); @@ -302,7 +340,7 @@ export function bfsAllRoutes(game, start, isGoal) { for (const n of graph.adjacency.get(cur.key) || []) { const next = graphCell(n.key); const outDir = dirBetween(cur, next); - if (cur.incoming !== 'none' && isCrossInGraph(graph, cur.key) && outDir !== cur.incoming) continue; + if (cur.incoming !== 'none' && isPassCross(game, graph, cur.key) && outDir !== cur.incoming) continue; const nextDepth = cur.depth + 1; if (nextDepth > bestGoalDepth) continue; const nextStateK = `${n.key}|${outDir}`; @@ -524,20 +562,23 @@ function routeToNextScanner(game, scanner, side, fromPoint, connector, starts, a function routeToFacility(game, scanner, side, fromPoint, connector, starts, dest, advance) { const graph = ensureFactoryGraph(game); - const port = graph.facilityPorts.get(dest); - if (!port?.connected) return null; + const ports = [...graph.facilityPorts.entries()] + .filter(([id, port]) => port?.connected && facilityKind(game.facilities?.[id]) === dest); + if (!ports.length) return null; const candidates = []; for (const start of starts) { - const goalKeys = (port.keys || [port.key]).filter(Boolean); - if (!goalKeys.length) continue; - const isGoal = p => goalKeys.includes(graphNodeKey(p.col, p.row)); - isGoal.cacheKey = `toFacility:${start.col},${start.row}:${dest}:v${graph.version}`; - const routes = bfsAllRoutes(game, start, isGoal); - for (const cells of routes) candidates.push({ key: `start${key(start.col, start.row)}:${dest}:${cells.length}:${key(cells[cells.length - 1].col, cells[cells.length - 1].row)}`, cells }); + for (const [facilityId, port] of ports) { + const goalKeys = (port.keys || [port.key]).filter(Boolean); + if (!goalKeys.length) continue; + const isGoal = p => goalKeys.includes(graphNodeKey(p.col, p.row)); + isGoal.cacheKey = `toFacility:${start.col},${start.row}:${facilityId}:v${graph.version}`; + const routes = bfsAllRoutes(game, start, isGoal); + for (const cells of routes) candidates.push({ key: `start${key(start.col, start.row)}:${facilityId}:${cells.length}:${key(cells[cells.length - 1].col, cells[cells.length - 1].row)}`, cells, facilityId }); + } } const chosen = chooseRoundRobin(game, `scanner:${scanner.id}:${side}:${dest}`, candidates, advance); if (!chosen) return null; - return { destination: dest, route: routeWithConnector(fromPoint, connector, chosen.cells, facilityEntryPoint(game, dest)) }; + return { destination: dest, facilityId: chosen.facilityId, route: routeWithConnector(fromPoint, connector, chosen.cells, facilityEntryPoint(game, chosen.facilityId)) }; } // ----------------------------------------------------------------------------- @@ -579,8 +620,7 @@ export function eggFarmHasValidRoute(game, farm) { export function facilityHasValidRoute(game, dest) { const graph = ensureFactoryGraph(game); - if (!graph.facilityPorts.get(dest)?.connected) return false; - return game.scanners.some(scanner => scannerOutputReachesDestination(game, scanner, dest)); + return [...graph.facilityPorts.entries()].some(([id, port]) => port?.connected && facilityKind(game.facilities?.[id]) === dest); } export function disconnectedBuildWarnings(game) { @@ -590,8 +630,10 @@ export function disconnectedBuildWarnings(game) { if (!eggFarmHasValidRoute(game, farm)) warnings.push({ type: 'eggFarm', id: farm.id, ref: farm, message: 'This Egg will not produce chicks' }); } for (const id of MACHINE_FACILITY_IDS) { - const facility = game.facilities?.[id]; - if (facility && !facilityHasValidRoute(game, id)) warnings.push({ type: 'facility', id, ref: facility, message: 'This facility cannot receive items' }); + for (const [facilityId, facility] of facilityEntriesForKind(game, id)) { + const graph = ensureFactoryGraph(game); + if (!graph.facilityPorts.get(facilityId)?.connected) warnings.push({ type: 'facility', id: facilityId, ref: facility, message: 'This facility cannot receive items' }); + } } return warnings; } @@ -605,7 +647,7 @@ export function minimumStartConnectionIssues(game) { if (outputRouteReachesFacility(game, scanner)) return []; } const farmCount = game.eggFarms?.length || 0; - const exitCount = ['mixer', 'trash', 'truck'].filter(id => graph.facilityPorts.get(id)?.connected).length; + const exitCount = [...graph.facilityPorts.entries()].filter(([id, port]) => port?.connected && ['mixer', 'trash', 'truck'].includes(facilityKind(game.facilities?.[id]))).length; if (!farmCount) return ['Build at least one EGG before starting the next day.']; if (!graph.metrics.farmOutputs) return ['Connect at least one EGG to a conveyor.']; if (!game.scanners?.length) return ['Build at least one scanner and connect it to an EGG route.']; @@ -624,12 +666,14 @@ export function validateFactoryGraph(game, graph = ensureFactoryGraph(game), opt const labels = { mixer: 'Mixer', trash: 'Shredder', truck: 'Truck' }; for (const id of MACHINE_FACILITY_IDS) { - const f = game.facilities[id]; - if (!f) { + const facilities = facilityEntriesForKind(game, id); + if (!facilities.length) { issues.push(`${labels[id] || id} is missing`); continue; } - if (!graph.facilityPorts.get(id)?.connected) issues.push(`${labels[id] || id} receiver has no conveyor`); + for (const [facilityId] of facilities) { + if (!graph.facilityPorts.get(facilityId)?.connected) issues.push(`${labels[id] || id} receiver has no conveyor`); + } } for (const scanner of game.scanners) { diff --git a/src/systems/selectionSystem.js b/src/systems/selectionSystem.js index e5bc635..505a729 100644 --- a/src/systems/selectionSystem.js +++ b/src/systems/selectionSystem.js @@ -1,7 +1,7 @@ import { GRID } from '../core/config.js'; import { key, parseKey, cellCenter } from '../core/utils.js'; import { nearestGridEdge, layoutFacilityOnEdge } from '../core/entities.js'; -import { farmAt, scannerAt, scannerCenter, refreshRoutingAfterEdit } from './routing.js'; +import { farmAt, scannerAt, scannerCenter, scannerFootprintCells, refreshRoutingAfterEdit } from './routing.js'; import { snapshot } from './history.js'; import { buildPrice } from './economy.js'; @@ -192,12 +192,15 @@ export function createSelectionSystem({ game, canvasPoint, updatePanels, equipme for (const origin of game.groupDrag.origins) { if (origin.type === 'facility') continue; const col = origin.col + dcol, row = origin.row + drow; - if (!pointInGrid(col, row)) return fail('Selection outside grid'); - if (game.blockedCells?.has?.(key(col, row))) return fail('Selection hits blocked ground'); - const tk = key(col, row); - if (targetCells.has(tk)) return fail('Selection overlap'); - if (cellOccupiedByNonSelected(col, row, selectedTokens)) return fail('Cell occupied'); - targetCells.add(tk); + const cells = origin.type === 'scanner' ? scannerFootprintCells({ col, row }) : [{ col, row }]; + for (const cell of cells) { + if (!pointInGrid(cell.col, cell.row)) return fail('Selection outside grid'); + if (game.blockedCells?.has?.(key(cell.col, cell.row))) return fail('Selection hits blocked ground'); + const tk = key(cell.col, cell.row); + if (targetCells.has(tk)) return fail('Selection overlap'); + if (cellOccupiedByNonSelected(cell.col, cell.row, selectedTokens)) return fail('Cell occupied'); + targetCells.add(tk); + } } if (facilityDragWouldOverlap(dx, dy, selectedTokens)) return fail('Facility overlap'); commitGroupDragHistory(); diff --git a/src/systems/uiSystem.js b/src/systems/uiSystem.js index 639aeda..94d2e6c 100644 --- a/src/systems/uiSystem.js +++ b/src/systems/uiSystem.js @@ -18,11 +18,9 @@ export function createUISystem({ game, ui, build, startGame, beginCardDraft, act if (game.phase !== 'build' || game.cardDraft?.pending || game.cardTargetPick?.pending) return false; let hasBuildableOption = false; for (const id of BUILD_TOOL_IDS) { - if (MACHINE_FACILITY_IDS.includes(id) && !!game.facilities[id]) continue; hasBuildableOption = true; if (game.cash >= buildPrice(id, game)) return false; } - if (build?.gridExpansionCost && game.cash >= build.gridExpansionCost()) return false; return hasBuildableOption; } @@ -44,32 +42,20 @@ export function createUISystem({ game, ui, build, startGame, beginCardDraft, act if (!btn) continue; const price = buildPrice(id, game); const priceSpan = btn.querySelector('span'); - if (priceSpan) priceSpan.textContent = id === 'conveyor' ? `${yen(price)} / tile` : yen(price); - const uniqueAlreadyBuilt = MACHINE_FACILITY_IDS.includes(id) && !!game.facilities[id]; + if (priceSpan) priceSpan.textContent = (id === 'conveyor' || id === 'boostConveyor') ? `${yen(price)} / tile` : yen(price); const manualLimitReached = id === 'manualScanner' && game.scanners.filter(s => s.kind === 'manual').length >= 8; const unaffordable = game.cash < price; - btn.disabled = game.phase !== 'build' || !!game.cardDraft?.pending || !!game.cardTargetPick?.pending || unaffordable || uniqueAlreadyBuilt || manualLimitReached; + btn.disabled = game.phase !== 'build' || !!game.cardDraft?.pending || !!game.cardTargetPick?.pending || unaffordable || manualLimitReached; if (btn.disabled && game.buildTool === id) game.buildTool = null; btn.classList.toggle('unaffordable', unaffordable); - btn.classList.toggle('already-built', uniqueAlreadyBuilt || manualLimitReached); + btn.classList.toggle('already-built', manualLimitReached); btn.title = unaffordable ? `Need ${yen(price - game.cash)} more` : manualLimitReached ? 'Manual Scanner limit reached (8 max)' - : uniqueAlreadyBuilt - ? `${FACILITY_DEFS[id]?.name || id} already exists` : ''; } if (ui.buttons.erase) ui.buttons.erase.disabled = game.phase !== 'build' || !!game.cardDraft?.pending || !!game.cardTargetPick?.pending; - if (ui.expandGridButton && build?.gridExpansionCost) { - const cost = build.gridExpansionCost(); - const priceSpan = ui.expandGridButton.querySelector('span'); - if (priceSpan) priceSpan.textContent = `${yen(cost)} / +10×10`; - const blocked = game.phase !== 'build' || !!game.cardDraft?.pending || !!game.cardTargetPick?.pending || game.cash < cost; - ui.expandGridButton.disabled = blocked; - ui.expandGridButton.classList.toggle('unaffordable', game.cash < cost); - ui.expandGridButton.title = game.cash < cost ? `Need ${yen(cost - game.cash)} more` : `Add the next right 10×10 lot, or click an upper/right lot button on the canvas. Next purchase count: ${(game.gridExpansionPurchases || 0) + 1}`; - } if (ui.hireRepairmanButton) { const hired = !!game.repairman?.hiredForNextDay; const cost = repairmanDailyCost(game); @@ -110,15 +96,15 @@ export function createUISystem({ game, ui, build, startGame, beginCardDraft, act const lastTribute = game.lastBuildFees?.turn === tributeDay ? game.lastBuildFees.fairiesTribute || 0 : null; const lastTributeReduction = game.lastBuildFees?.turn === tributeDay ? game.lastBuildFees.reduction || 0 : 0; ui.turnSummary.innerHTML = [ - `Seed: ${escapeHtml(game.runSeed || 'none')} | Grid box: ${game.gridRows || 10}×${game.gridCols || 20} | Owned: ${game.gridOwnedCells || game.ownedCells?.size || ((game.gridRows || 10) * (game.gridCols || 20))} cells | Blocked: ${game.blockedCells?.size || 0} cells`, + `Seed: ${escapeHtml(game.runSeed || 'none')} | Grid box: ${game.gridRows || 10}x${game.gridCols || 20} | Owned: ${game.gridOwnedCells || game.ownedCells?.size || ((game.gridRows || 10) * (game.gridCols || 20))} cells | Blocked: ${game.blockedCells?.size || 0} cells`, `Truck target: ${truckTarget(game).toUpperCase()} | Male fine: -${yen(maleTruckPenalty(game))}`, `Poop fine: Mixer -${yen(mixerPoopPenalty(game))} / Shipment -${yen(truckPoopPenalty(game))}`, - `ZUNDA TAX on Next Day: -${yen(tax.tax)} | basis net ${yen(zundaBasisProfit)} | taxable ${yen(tax.taxable)} × ${tax.ratePercent}% | exemption ${yen(tax.exemption)} | 95% at ${yen(tax.maxRateCash)}`, + `ZUNDA TAX on Next Day: -${yen(tax.tax)} | basis net ${yen(zundaBasisProfit)} | taxable ${yen(tax.taxable)} x${tax.ratePercent}% | exemption ${yen(tax.exemption)} | 95% at ${yen(tax.maxRateCash)}`, lastTribute != null ? `Fairies tribute paid before Build: -${yen(lastTribute)}${lastTributeReduction ? ` | Flattery -${yen(lastTributeReduction)}` : ''}` : `Fairies tribute before Build: -${yen(tribute.amount)}${tribute.reduction ? ` | Flattery -${yen(tribute.reduction)}` : ''}`, `Maintenance: worst ${maintenanceSummary(game).worst.percent}% dirty (${maintenanceSummary(game).worst.label}) | Repairman: ${game.repairman?.hiredForNextDay ? 'hired next day' : game.repairman?.active ? 'working' : 'none'}`, - `Manual combo: ${game.manualCombo?.count || 0} | Next expansion: ${yen(build?.gridExpansionCost ? build.gridExpansionCost() : 0)} | Card draft: ${game.cardDraft?.pending ? Math.max(1, game.cardDraft.picksRemaining || 1) + ' pick(s) left' : 'ready after each day'} | Belt: ${displaySpeed()}px/s | Farms with scanner route: ${connected}/${game.eggFarms.length}`, + `Manual combo: ${game.manualCombo?.count || 0} | Card draft: ${game.cardDraft?.pending ? Math.max(1, game.cardDraft.picksRemaining || 1) + ' pick(s) left' : 'ready after each day'} | Belt: ${displaySpeed()}px/s | Farms with scanner route: ${connected}/${game.eggFarms.length}`, game.cardEffects?.usedMachineActive ? 'Procurement: USED MACHINE MODE / no refunds / 60% durability' : '', issues.length ? `Blocked: ${issues[0]}` : `${TEXT.status.allPortsConnected}` ].join('
'); @@ -165,7 +151,7 @@ export function createUISystem({ game, ui, build, startGame, beginCardDraft, act ui.timeLeft.textContent = game.phase === 'running' && game.timeLeft <= 0 && game.shutdownTimeLeft > 0 ? `+${game.shutdownTimeLeft.toFixed(1)}s` : `${Math.max(0, game.timeLeft).toFixed(1)}s`; - ui.timeLeft.closest?.('.hud-card')?.classList.toggle('time-critical', game.phase === 'running' && (game.timeLeft <= 10 || (game.timeLeft <= 0 && game.shutdownTimeLeft > 0))); + ui.timeLeft.closest?.('.hud-card')?.classList.toggle('time-critical', game.phase === 'running' && (game.timeLeft <= 10 || (game.timeLeft <= 0 && game.shutdownTimeLeft > 0))); ui.phase.textContent = phaseLabel(); if (ui.comboCount) ui.comboCount.textContent = game.manualCombo?.count || 0; updatePriorityStrip(); @@ -228,7 +214,7 @@ export function createUISystem({ game, ui, build, startGame, beginCardDraft, act function showTitle() { ui.modalTitle.textContent = TEXT.versionTitle; - ui.modalBody.innerHTML = `
♂Male->MixerS1: A
♀Female->TruckS1: D, then S2: right
💩Poop->ShredderS1: D, then S2: left

Build phase: connect at least one EGG through a scanner to Mixer, Shredder, or Truck before Next Day. One day is one run.

`; + ui.modalBody.innerHTML = `
髫ィ蜷カ繝サ/span>Male->MixerS1: A
髫ィ蜊債€Female->TruckS1: D, then S2: right
・滓ィ頑cPoop->ShredderS1: D, then S2: left

Build phase: connect at least one EGG through a scanner to Mixer, Shredder, or Truck before Next Day. One day is one run.

`; ui.modalActions.innerHTML = ''; ui.modalActions.appendChild(button(TEXT.actions.startGame, startGame, 'primary-button')); ui.modal.classList.remove('equipment-popover', 'gameover-modal'); @@ -247,6 +233,8 @@ export function createUISystem({ game, ui, build, startGame, beginCardDraft, act
Chick shipment income${positive(r.chickShipmentIncome || 0)}
Poop shipment income${positive(r.poopShipmentIncome || 0)}
Mixer income${positive(r.mixerRevenue || 0)}
+
Composter income${positive(r.composterIncome || 0)}
+
DUD refund income${positive(r.dudRefundIncome || 0)}
Manual combo bonus${positive(r.manualComboBonus || 0)}
Chemical subsidy${positive(r.chemicalWeaponSubsidy || 0)}
Contract bonus${positive(r.contractBonus || 0)}
@@ -289,7 +277,7 @@ export function createUISystem({ game, ui, build, startGame, beginCardDraft, act ui.modalBody.innerHTML = `

Your cash went negative.

Final Score${fs.score}
Survival${game.totals.turnsCompleted} days
Final Cash${yen(game.cash)}
Accuracy${accuracy}%
Score breakdown -
Formula
Base = Cash + Total revenue + Factory value × 0.5 + Contract bonus × 0.5 = ${yen(fs.base)}
Daily earned = ceil(Base ÷ Days) = ${yen(fs.dailyEarned)}
Score = max(0, floor(Base + Daily earned + Correct×5 - Total outflow - Day penalty)) = ${fs.score}
+
Formula
Base = Cash + Total revenue + Factory value 繝サ繝サ繝サ0.5 + Contract bonus 繝サ繝サ繝サ0.5 = ${yen(fs.base)}
Daily earned = ceil(Base 繝サ繝サ・ス・キ Days) = ${yen(fs.dailyEarned)}
Score = max(0, floor(Base + Daily earned + Correct繝サ繝サ繝サ - Total outflow - Day penalty)) = ${fs.score}
Factory Value${yen(fs.factoryValue)}
Total Revenue${yen(game.totals.revenue)}
Daily Earned${yen(fs.dailyEarned)} / day
Total Outflow${yen(game.totals.penalty)}
ZUNDA TAX${yen(game.totals.zundaTax)}
Fairies Tribute${yen(game.totals.fairiesTribute || 0)}
Correct Sorts${game.totals.correct}
Auto Sorted${game.totals.autoSorted}
Poop Control${game.totals.poopTrash} trashed / ${game.totals.poopTruck} shipped / ${game.totals.poopMixer} mixer
Explosion Penalty${yen(game.totals.explosionDamage)}
Contract Bonus${yen(game.totals.contractBonus)}
Day Penalty-${fs.dayPenalty}
Contracts${game.totals.contractSuccess} success / ${game.totals.contractFailed} failed
`; ui.modalActions.innerHTML = '';