diff --git a/FUTURE_IDEAS.md b/FUTURE_IDEAS.md new file mode 100644 index 0000000..c4df103 --- /dev/null +++ b/FUTURE_IDEAS.md @@ -0,0 +1,12 @@ +# Future Ideas + +These items are intentionally not implemented in v29.00. + +- Capacitor or battery storage for smoothing biomass and coal voltage pulses. +- Transformer or power-combiner station for a late-game managed bus where generators can be combined. +- Editable machine power priorities. +- Advanced power statistics such as average voltage, brownout count, utilization, and loss heat maps. +- Additional wire tiers beyond regular wire at 0.5V per tile and insulated wire at 0.15V per tile. +- Zunda balancing controls such as shorter duration, consumption cooldown, reduced zunda-only quality conversion, or farm appetite limits. +- Repair-access visualization for reachable cells and suggested footbridge locations. +- Power-aware card ideas after the base power economy is measured. diff --git a/README.md b/README.md index 19d991e..eed6ea5 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,24 @@ -# Chick Sorter +# Chick Sorter v29.00 + +## v29.00 systems + +Power is a simplified deterministic voltage network. Manual Scanners, Egg Farms, regular Conveyors, Boost Conveyors, Power Wires, Insulated Power Wires, Footbridges, the Starter Generator, Biomass Generators, and Coal Generators do not require electricity. Mixers need 10V and 4V Voltage Load, Shredders need 8V and 3V Voltage Load, Shipping Trucks need 6V and 2V Voltage Load, Auto Scanners need 8V and 2V Voltage Load, Sausage Machines need 12V and 5V Voltage Load, Maintenance Rooms need 6V and 1V Voltage Load, and Inbound Trucks need 5V and 1V Voltage Load. + +Regular Power Wire costs 10 yen per tile and loses 0.5V per traversed tile. Insulated Power Wire costs 22 yen per tile and loses 0.15V per traversed tile. Wire branches do not divide voltage, loops do not increase voltage, and separate generators are not summed. Each generator admits machine loads deterministically; its bus voltage is generator output minus admitted Voltage Load. + +Every new game includes a free 2x2 Starter Generator with 25V output and free initial regular wiring to the initial Mixer, Shredder, and Shipping Truck. The starter generator cannot be sold and has no build button. + +Biomass Generators are 3x3, cost 520 yen, and accept male chicks, female chicks, and poop. Each accepted fuel item adds +5V for 2 seconds. Coal Generators are 2x3, cost 680 yen, and accept coal only. Each coal adds +15V for 3 seconds. Fuel pulses in one generator stack additively and are cleared by day end. + +Inbound Trucks cost 350 yen and can import coal and zunda mochi. Coal costs 2 yen each, zunda mochi costs 4 yen each, and each truck can order up to 80 combined items per day. Imports are charged at day start, distributed over the 60-second day with deterministic jitter, retained while the output is blocked, and not refunded if undelivered by day end. + +Egg Farm production is capped by the theoretical capacity of one Boost Conveyor, derived from live conveyor speed divided by minimum item spacing. Production above the cap becomes quality overflow using `1 + 0.45 * overflowRatio^0.85`, clamped from 1.0x to 2.5x. Female shipments and sausage shipments use continuous quality multipliers; meat inherits chick quality and sausages use the arithmetic mean of two meat inputs. Mixer blocked-output fallback remains exactly +5 yen and ignores quality. + +Zunda mochi on a conveyor cell orthogonally adjacent to an Egg Farm is consumed by that farm. Each item adds 8 seconds of boost up to 30 seconds. While boosted, the farm production interval is multiplied by 0.55, with the existing 0.25 second safe minimum. + +Repair workers use four-directional grid pathing. They can walk on owned, unobstructed ground; they cannot walk through machines or uncovered conveyor cells, and power wires do not block them. Workers exit maintenance rooms from a reachable perimeter cell and repair from a reachable orthogonally adjacent work cell. + +Footbridges cost 90 yen and live on an overlay layer with wires. They cover 1x3 or 3x1 cells; press R while the Footbridge tool is active to rotate placement. They allow repair-worker pathing over conveyor cells without changing conveyor transport, direction editing, or branch behavior. Serve this folder with a static HTTP server and open `index.html`. @@ -11,9 +31,9 @@ The launch page uses: Run `npm install` once, then `npm run build` after editing JavaScript. -## Current gameplay specification (v28.21) +## Current gameplay specification (v29.00) -### Boost conveyor rendering hotfix (v28.21) +### Preserved boost conveyor rendering behavior - Fixed a runtime `ReferenceError` caused by an undeclared `boost` variable in the animated conveyor renderer. - Added a runtime smoke test for both normal and boost conveyor drawing. diff --git a/deploy/chick-sorter-v29.00.zip b/deploy/chick-sorter-v29.00.zip new file mode 100644 index 0000000..93af062 Binary files /dev/null and b/deploy/chick-sorter-v29.00.zip differ diff --git a/dist/game.js b/dist/game.js index a4d13f9..c28eb51 100644 --- a/dist/game.js +++ b/dist/game.js @@ -1,52 +1,68 @@ -var V={version:"v28.21 BOOST DRAW HOTFIX",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:850,costGrowthRate:.75,truncateUnit:50}},map:{blockedRatio:0,blockedExpansionIncrement:.0075,blockedRatioMax:.12},economy:{income:{mixer:5,truck:10,sausage:26},poopFine:10,fairiesTribute:{perDay:10,profitExemption:300,legalWorkExemptionBonus:250,profitRate:.12},incomeUpgradeRate:1.1,explosionDamageDivisor:30,maleTruckFinePerHalfDay:30,shredderBonus:{maxCards:30,payoutCoefficient:1.6,payoutExponent:1.25,baseHitChance:.28,hitChancePerUpgrade:.018,maxHitChance:.82,jackpotBaseChance:.01,jackpotChancePerUpgrade:.0025,jackpotMaxChance:.08,bigWinBaseChance:.07,bigWinChancePerUpgrade:.0015,bigWinMaxChance:.12,doubleChance:.2,jackpotMultiplier:10,bigWinMultiplier:4,doubleMultiplier:2},chemicalWeaponSubsidy:{poopPerBlock:70,yenPerBlock:1200}},production:{poopRate:.08,autoScannerCooldown:1.5,autoScannerUpgradeRate:.95,autoScannerCardUpgradeRate:.95,autoScannerMinCooldown:.5,poopRateCardBonus:.04,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},effects:{explosionHorizontalVelocityMultiplier:.82},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:"autoScannerTuning",title:"AUTO SCANNER Tuning",rarity:"common",type:"instant",description:"All AUTO SCANNER cooldowns use default cooldown x0.95 per copy.",requiresFacility:"autoScanner",tags:["SCANNER","PASSIVE"]},{id:"upgradeMixer",title:"MIXER Improvement",rarity:"common",type:"equipmentUpgrade",target:"mixer",description:"Choose one MIXER. Each level improves meat quality and raises sausage sale value by 10%.",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:"upgradeSausageMaker",title:"SAUSAGE MACHINE Improvement",rarity:"common",type:"equipmentUpgrade",target:"sausageMaker",description:"Choose one SAUSAGE MACHINE. Each level raises sausage sale value by 10%.",tags:["ECONOMY","SAUSAGE","ACTIVE"]},{id:"upgradeTrash",title:"SHREDDER Improvement",rarity:"common",type:"equipmentUpgrade",target:"trash",description:"Adds a nonlinear scrap-lottery upgrade. More copies raise both payout and win chance; max 30.",tags:["POOP","ACTIVE"]},{id:"crowdedFarming",title:"Crowded Farming",rarity:"common",type:"instant",description:"EGG production interval -5%. Equipment deterioration +4%. Max 10 cards.",requiresFacility:"eggFarm",tags:["EGG","RISK","PASSIVE"]},{id:"hatchingFeed",title:"Hatching Feed",rarity:"common",type:"instant",description:"EGG production interval -4%. Poop rate +3%. No card limit.",requiresFacility:"eggFarm",tags:["EGG","POOP","PASSIVE"]},{id:"poopRateBooster",title:"Poop Culture",rarity:"common",type:"instant",description:"Base poop rate +4 percentage points. No card limit.",tags:["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.",requiresAnyFacility:["conveyor","eggFarm","autoScanner","manualScanner","mixer","trash","sausageMaker"],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.",requiresAnyFacility:["conveyor","eggFarm","autoScanner","manualScanner","mixer","trash","sausageMaker"],tags:["MAINTENANCE","PASSIVE"]},{id:"bearing",title:"High-Quality Bearing",rarity:"common",type:"instant",description:"Conveyor speed +7.5%.",requiresFacility:"conveyor",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.",requiresAnyFacility:["conveyor","eggFarm","autoScanner","manualScanner","mixer","trash","sausageMaker"],tags:["MAINTENANCE","ACTIVE"]},{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.",requiresFacility:"trash",tags:["POOP","ECONOMY","PASSIVE"]},{id:"scannerQueueSpacing",title:"Scanner Queue Spacing",rarity:"common",type:"instant",description:"Manual and Auto Scanner queue spacing -15%. No card limit.",requiresAnyFacility:["manualScanner","autoScanner"],tags:["SCANNER","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:"Choose and remove up to 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:"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:"Fairies tribute profit exemption +JPY 250.",tags:["ECONOMY","RARE","PASSIVE"]},{id:"chemicalWeaponSubsidy",title:"Chemical Weapons Subsidy",rarity:"ultraRare",type:"instant",description:"Each day, gain JPY 1200 per 70 poop shipped by truck. No card limit.",tags:["POOP","ULTRA RARE","PASSIVE"]},{id:"laborExploitation",title:"Motivational Exploitation",rarity:"ultraRare",type:"instant",description:"Repair room work time -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,autoScannerTuning:0,poopRateBooster:0,crowdedFarming:0,hatchingFeed:0,safetyCover:0,recyclingSubsidy:0,chemicalWeaponSubsidy:0,preventiveMaintenance:0,dudFilter:0,durabilityCoating:0,sparePartsBin:0,dudRefund:0,freeReroll:0,composter:0,scannerQueueSpacing:0,laborExploitation:0,usedMachineActive:!1,rescueLoanCharges:0},commonWeight:8,rareWeight:2,ultraRareWeight:.45,baseDraftSize:3,maxDudsPerDraft:2,dudChancePerCard:.3,dudChanceReductionPerFilter:.05,extraCardsAddedPicks:2,rerollCost:{dayDivisor:2,multiplier:100}},events:{eventChance:.1,firstTurn:8},maintenance:{fullPerformanceUntilWear:.5,minimumPerformance:.5,durability:{conveyor:1300,eggFarm:475,autoScanner:850,mixer:775,trash:950,sausageMaker:825},processingDelayMaxSeconds:{mixer:1.2,trash:1,sausageMaker:.9},repairman:{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:75,buildable:!0,upgradeable:!1,speedMultiplier:2},eggFarm:{id:"eggFarm",type:"eggFarm",name:"Egg Farm",shortName:"EGG",price:250,buildable:!0,upgradeable:!0,maxLevel:4},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:180,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}},sausageMaker:{id:"sausageMaker",type:"facility",name:"Sausage Machine",shortName:"SAUSAGE",price:380,buildable:!0,upgradeable:!0,incomeKey:"sausage",footprint:{cols:3,rows:3},body:{w:138,h:138},outputProduct:"sausage"},maintenanceRoom:{id:"maintenanceRoom",type:"maintenanceRoom",name:"Maintenance Room",shortName:"REPAIR ROOM",price:220,buildable:!0,upgradeable:!1,footprint:{cols:2,rows:2}}}};var si=V.version,Gt=V.time.daySeconds,Rn=V.cash.starting,xe=V.economy,ai=V.time.farmShutdownGraceSeconds,Qn=V.production.autoScannerCooldown,li=V.production.autoScannerUpgradeRate,Yo=V.production.autoScannerCardUpgradeRate||V.production.autoScannerUpgradeRate,Vo=V.production.autoScannerMinCooldown,ci=V.events.eventChance,Pn=V.events.firstTurn,zo=V.production.poopRate,Xo=V.production.poopRateCardBonus||0,Jn=V.conveyor.baseSpeed,tn=V.conveyor.maxSpeed,Zn=V.conveyor.bearingSpeedMultiplier,Ue=V.cards,ui=V.cards.definitions,jo=V.cards.defaultEffects,Et=V.production.eggSpawnRanges,f=V.grid,Te=V.facilities,Qo=["conveyor","boostConveyor","eggFarm","autoScanner","manualScanner","mixer","trash","truck","sausageMaker","maintenanceRoom"],eo=["mixer","trash","truck"],to=["sausageMaker"],Jo=[...eo,...to],di=["mixer","trash","truck"],fi=["mixer","truck","sausageMaker"],Rt=Object.fromEntries(Object.entries(Te).map(([e,t])=>[e,t.price])),fe=[{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"}],b={bg:"#111318",ink:"#090a0d",muted:"#8f9bab",green:"#7bd84f",greenSoft:"#273c24",mixerBlue:"#4aa3ff",truckPink:"#ffb33e",wasteGreen:"#88d35a",meatRed:"#d96b72",sausageOrange:"#d78b3f",danger:"#e34a36",warn:"#ffb827",white:"#e6edf2",poop:"#7b4a23",male:"#4aa3ff",female:"#ff6fae"},Ie={ambient:0,important:1,critical:2};function x(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 re(e){let[t,n]=String(e).split(",").map(Number);return{col:t,row:n}}function An(e,t,n){return Math.max(t,Math.min(n,e))}function z(e,t){return e+Math.random()*(t-e)}function $e(e,t){return e>=0&&e=0&&tMath.round(An(r,0,255)).toString(16).padStart(2,"0");return`#${o(e)}${o(t)}${o(n)}`}function In(e,t,n){let o=pi(e),r=pi(t);return Wa(Zo(o.r,r.r,n),Zo(o.g,r.g,n),Zo(o.b,r.b,n))}var ce={appTitle:"Chick Sorter",versionTitle:`Chick Sorter ${si}`,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."},routeLabels:{mixer:"MIXER",truck:"TRUCK",trash:"WASTE",sausageMaker:"SAUSAGE",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 Pt(e){return Te[e]?.name||String(e||"").replace(/^[a-z]/,t=>t.toUpperCase())}function Ha(e){let t=Te[e];return t?e==="conveyor"||e==="boostConveyor"?`${x(t.price)} / tile`:e==="autoScanner"?`${x(t.price)} / ${Qn.toFixed(1)}s cooldown`:e==="trash"?x(t.price):x(t.price):""}function Ka(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:"Processes about 40 items per day at Lv1, roughly 2.1 Lv1 farms.",manualScanner:"A manual checkpoint. The operator is the algorithm.",mixer:"Makes meat when OUT is clear; otherwise falls back to direct sale.",trash:"A polite shredder for poop and other regrets.",truck:"Ships target chicks and all sausages. Wrong live cargo still leaves a paper trail.",sausageMaker:"A 3\xD73 grid machine. Left inlet takes 2 meat; right outlet emits 1 sausage.",maintenanceRoom:"2\xD72 repair bay. Each room dispatches one repairman during the day."}[e]||""}function qa(e){return{conveyor:"./assets/dark_factory/facilities/conveyor/conveyor_straight_h_16.png",boostConveyor:"./assets/dark_factory/facilities/conveyor/boost_conveyor_straight_h_16.png",eggFarm:"./assets/dark_factory/facilities/machines/egg_farm_16.png?v=dark_factory",autoScanner:"./assets/dark_factory/facilities/machines/auto_scanner_32x48.png",manualScanner:"./assets/dark_factory/facilities/machines/manual_scanner_32x48.png",mixer:"./assets/dark_factory/facilities/machines/mixer_32.png",trash:"./assets/dark_factory/facilities/machines/shredder_32.png",truck:"./assets/dark_factory/facilities/vehicles/truck_32.png",sausageMaker:"./assets/dark_factory/facilities/machines/mixer_32.png",maintenanceRoom:"./assets/dark_factory/facilities/utility/maintenance_room_32.png"}[e]||""}function hi(e){let t=Ka(e),n=qa(e);return`${n?``:""}${Pt(e)}${Ha(e)}${t?`${t}`:""}`}function mi(e){return ce.routeLabels[e]||String(e).toUpperCase()}function bi(e){let t=2166136261;for(let n=0;n>>0}function vi(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 wi(e,{base:t=0,increment:n=.0075,max:o=.12}={}){let r=Math.max(0,Math.floor(Number(e)||0));return Number(Math.min(o,t+r*n).toFixed(6))}function oo(){return`RUN-${Date.now().toString(36)}-${Math.floor(Math.random()*1e6).toString(36)}`.toUpperCase()}function Si({seed:e,targetRatio:t=.2,reserved:n=[]}={}){let o=vi(bi(e||oo())),r=new Set(n.map(c=>typeof c=="string"?c:I(c.col,c.row))),i=[];for(let c=0;c0;c-=1){let h=Math.floor(o()*(c+1));[i[c],i[h]]=[i[h],i[c]]}let a=Math.round(f.cols*f.rows*t+1e-9);return new Set(i.slice(0,Math.min(a,i.length)).map(c=>c.k))}function ki({seed:e,colStart:t=0,colEnd:n=f.cols,rowStart:o=0,rowEnd:r=f.rows,targetRatio:i=.2,reserved:a=[]}={}){let c=vi(bi(e||oo())),h=new Set(a.map(Z=>typeof Z=="string"?Z:I(Z.col,Z.row))),g=[],C=Math.max(0,Math.floor(t)),P=Math.max(C,Math.floor(n)),L=Math.max(0,Math.floor(o)),K=Math.max(L,Math.floor(r));for(let Z=L;Z0;Z-=1){let Q=Math.floor(c()*(Z+1));[g[Z],g[Q]]=[g[Q],g[Z]]}let j=Math.round((P-C)*(K-L)*i+1e-9);return new Set(g.slice(0,Math.min(j,g.length)).map(Z=>Z.k))}var nn=10,gi=2,Ci=1;function on(e,t){return`${e},${t}`}function Ya(e){let[t,n]=String(e).split(",").map(Number);return{chunkX:t,chunkY:n}}function ro(e){let t=V.grid.expansion||{},n=Math.max(1,(e.gridExpansionPurchases||0)+1),o=Math.max(0,Number(t.costBase)||850),r=Math.max(0,Number(t.costGrowthRate)||.75),i=o*(1+r*(n-1)),a=Math.max(1,t.truncateUnit||50);return Math.floor(i/a)*a}function Mi(e){e.ownedCells=new Set,e.ownedChunks=new Set,e.gridChunkOriginX=0,e.gridChunkOriginY=0,e.gridExpansionPurchases=0,e.gridExpansionPurchasesByDirection={left:0,right:0,up:0,down:0};for(let t=0;t=0&&t=0&&n{let a=(o[r.direction]??9)-(o[i.direction]??9);return a||(r.chunkY!==i.chunkY?r.chunkY-i.chunkY:r.chunkX-i.chunkX)})}function Ei(e,t="right"){let n=Ut(e);return n.find(o=>o.direction===t)||n[0]||null}function dt(e){return{x:f.x+e.colStart*f.cell,y:f.y+e.rowStart*f.cell,w:e.cols*f.cell,h:e.rows*f.cell}}function tr(e){let t=dt(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 Ri(e,t){let n=tr(e);return t.x>=n.x&&t.x<=n.x+n.w&&t.y>=n.y&&t.y<=n.y+n.h}function Pi(e){let t=[];for(let n=e.rowStart;n$e(t.col,t.row))}function el(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}].filter(t=>$e(t.col,t.row))}function tl(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=>$e(t.col,t.row))}function Oi(e){let t=new Set([...e.conveyorTiles]),n=o=>{o&&$e(o.col,o.row)&&t.add(I(o.col,o.row))};for(let o of e.scanners){for(let r of el(o))n(r);for(let r of Za(o))n(r)}for(let o of e.eggFarms){n(o);for(let r of tl(o))n(r)}for(let o of Object.values(e.facilities))n(o.entry);return t}function Di(e){let t=Xa.map(([n,o])=>({col:n,row:o}));for(let{col:n,row:o}of t)Bi(e,n,o);for(let n=0;n({...n,keys:n.keys?{left:{...n.keys.left},right:{...n.keys.right}}:null,queue:[]})),e.eggFarms=Qa.map(n=>{let o=Wt(n);return{...n,nextSpawn:o,lastInterval:o}});let t=Oi(e);e.blockedCells=Si({seed:e.runSeed,targetRatio:V.map.blockedRatio,reserved:[...t]}),nl(e),Di(e),e.routingVersion=(e.routingVersion||0)+1,e.factoryGraph=null,e.factoryGraphCommittedVersion=-1}function ft(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}]:[]}function ge(e){let t=$(e.col,e.row);return{x:t.x+f.cell/2,y:t.y-f.cell/2}}function it(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=>ft(o).some(r=>r.col===t&&r.row===n))||null}function Ht(e,t){return e.scanners.find(n=>n.id===t)||null}function ar(e,t){return e.scanners.find(n=>n.kind==="manual"&&n.slot===t)||null}function sn(e){return e?.baseId||e?.id||""}function lr(e,t){return Object.entries(e.facilities||{}).filter(([,n])=>sn(n)===t)}function lo(e,t){return!!(t&&$e(t.col,t.row)&&(!e?.ownedCells?.size||e.ownedCells.has(I(t.col,t.row))))}function pt(e,t,n){return Object.values(e?.facilities||{}).find(o=>o?.gridPlaced?t>=o.col&&t=o.row&&n({col:e.col+t.dc,row:e.row+t.dr})).filter(t=>$e(t.col,t.row)):[]}function _i(e,t,n=[],o=[]){if(!t||!$e(t.col,t.row))return[];let r=[],i=a=>{!a||!$e(a.col,a.row)||!e.conveyorTiles.has(I(a.col,a.row))||n.some(c=>c&&no(a,c))||r.some(c=>no(c,a))||r.push({...a,viaTolerance:!no(a,t)})};i(t);for(let a of o)i(a);for(let a of Ni(t))i(a);return r}function sr(e,t,n=[],o=[]){let r=_i(e,t,n,o),i=r.filter(a=>!a.viaTolerance);return i.length?i:r}function ol(e,t){let n=Fe(t,"inputA");return!n||!$e(n.col,n.row)?[]:sr(e,n,ft(t))}function rl(e,t,n){let o=Fe(t,n);return _i(e,o,ft(t))}function il(e,t){let n=e.facilities[t]||lr(e,t)[0]?.[1];if(!n)return null;let o=nt(e,n);if(o)return $(o.col,o.row);let r=sn(n)||t;return r==="mixer"?{x:n.x+n.w,y:n.y+n.h*.52}:r==="truck"?{x:n.x,y:n.y+n.h*.52}:r==="trash"?{x:n.x+n.w/2,y:n.y+8}:{x:n.x+n.w/2,y:n.y+n.h/2}}function Gi(e,t,n){return n==="inputA"?ol(e,t).length>0:rl(e,t,n).length>0}function Me(e){e.branchCounters?.clear?.(),e.routeCache=null,e.routingVersion=(e.routingVersion||0)+1,e.factoryGraph=null,e.factoryGraphCommittedVersion=-1,e.frameCache&&(e.frameCache.routingVersion=-1,e.frameCache.congestionFrame=-1,e.frameCache.draw={})}function We(e,t){return I(e,t)}function rn(e){return re(e)}function sl(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=>fe.some(i=>i.name===r))}function Li(e,t,n){let o=fe.find(a=>a.name===n);if(!o)return!1;let r=e.conveyorMeta?.get?.(We(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 al(e,t){let n=rn(t),o=e.conveyorMeta?.get?.(t)||{},r=fe.filter(C=>e.conveyorTiles.has(We(n.col+C.dc,n.row+C.dr))).map(C=>C.name),i=sl(e,t).filter(C=>r.includes(C)),a=i.length?i:r;if(r.length===1&&(!i.length||i.includes(r[0]))&&Li(e,n,r[0]))return[];if(r.length<3)return a;let c=new Set(r.filter(C=>Li(e,n,C))),h=r.filter(C=>!c.has(C)),g=c.size?h:[...new Set(a)];return!g.length&&!c.size&&(g=r),o.branchMode&&o.branchMode!=="random"&&g.length>=2&&g.includes(o.branchMode)?[o.branchMode]:g}function ll(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 cl(e){let t=ll(e);for(let o of e.conveyorTiles)t.cells.add(o),t.adjacency.set(o,[]);for(let o of t.cells){let r=rn(o),i=al(e,o),a=fe.filter(c=>i.includes(c.name));for(let c of a){let h=We(r.col+c.dc,r.row+c.dr);t.cells.has(h)&&t.adjacency.get(o).push({key:h,dir:c.name})}}let n=0;for(let[o,r]of t.adjacency.entries()){n+=r.length;let i=new Set(r.map(a=>a.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),ul(t),dl(e,t),t.issues=Cl(e,t,{includeRoutes:!1}),t}function ul(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 a=r.shift();i.push(a);for(let c of e.adjacency.get(a)||[])e.cellToComponent.has(c.key)||(e.cellToComponent.set(c.key,o),r.push(c.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 dl(e,t){for(let n of e.scanners){let o={};for(let r of["inputA","left","right"]){let i=Fe(n,r),a=sr(e,i,ft(n)).filter(g=>t.cells.has(We(g.col,g.row))),c=a.map(g=>We(g.col,g.row)),h=c.length>0;if(o[r]={type:r,cell:i,cells:a,keys:c,key:c[0]||null,connected:h},t.metrics.scannerPorts+=1,h&&(t.metrics.connectedScannerPorts+=1),r==="inputA")for(let g of c)t.scannerInputs.set(g,n.id)}t.scannerPorts.set(n.id,o)}for(let[n,o]of Object.entries(e.facilities)){let r=nt(e,o);if(!r)continue;let i=sr(e,r).filter(h=>t.cells.has(We(h.col,h.row))),a=i.map(h=>We(h.col,h.row)),c=a.length>0;t.facilityPorts.set(n,{id:n,cell:{...r},cells:i,keys:a,key:a[0]||null,connected:c}),t.metrics.facilityPorts+=1,c&&(t.metrics.connectedFacilityPorts+=1)}for(let n of e.eggFarms){let o=Ni({col:n.col,row:n.row}).map(r=>We(r.col,r.row)).filter(r=>t.cells.has(r));t.farmOutputs.set(n.id,o),t.metrics.farmOutputs+=o.length}}function Je(e){let t=e.routingVersion||0;return(!e.factoryGraph||e.factoryGraph.version!==t)&&(e.factoryGraph=cl(e)),e.factoryGraph}function Ui(e){let t=Je(e);return e.factoryGraphCommittedVersion=t.version,e.factoryGraphSnapshot=fl(t),t}function fl(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 pl(e,t){let n=t.col-e.col,o=t.row-e.row;return fe.find(r=>r.dc===n&&r.dr===o)?.name||null}function cr(e,t,n){let o=Je(e),r=We(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(le=>le.map(ue=>({...ue})));let a=`${r}|none`,c=[{key:r,col:t.col,row:t.row,incoming:"none",depth:0}],h=new Map([[a,0]]),g=new Map,C=[],P=1/0,L=8,K=24,j=80;for(;c.length;){let le=c.shift();if(le.depth>P)break;let ue=`${le.key}|${le.incoming}`;if(n(le)){if(C.push(ue),P=le.depth,C.length>=K)break;continue}for(let D of o.adjacency.get(le.key)||[]){let U=rn(D.key),ee=pl(le,U),O=le.depth+1;if(O>P)continue;let G=`${D.key}|${ee}`,Y=h.get(G);if(Y==null)h.set(G,O),g.set(G,[ue]),c.push({key:D.key,col:U.col,row:U.row,incoming:ee,depth:O});else if(Y===O){let ie=g.get(G)||[];ie.length{if(Z.length>=j)return;let[D]=le.split("|"),U=[rn(D),...ue];if(le===a||D===r){Z.push(U);return}for(let ee of g.get(le)||[])Q(ee,U)};for(let le of C)Q(le,[]);let ke=[],pe=new Set;for(let le of Z){let ue=le.map(D=>We(D.col,D.row)).join(">");pe.has(ue)||(pe.add(ue),ke.push(le))}return n.cacheKey&&o.pathCache.set(i,ke.map(le=>le.map(ue=>({...ue})))),ke}function Wi(e){let t=Je(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 uo(e,t=null){let n=e.map(o=>$(o.col,o.row));return t&&n.push(t),n}function At(e,t){t&&(!e.length||yi(e[e.length-1],t)>1)&&e.push(t)}function yl(e,t,n,o=null){let r=[{x:e.x,y:e.y}];At(r,$(t.col,t.row));for(let i of uo(n,o))At(r,i);return r}function hl(e,t){let n=uo(e),o=Fe(t,"inputA");return o&&At(n,$(o.col,o.row)),At(n,ge(t)),n}function ml(e,t,n,o){let r=[{x:e.x,y:e.y}];At(r,$(t.col,t.row));for(let a of uo(n))At(r,a);let i=Fe(o,"inputA");return i&&At(r,$(i.col,i.row)),At(r,ge(o)),r}function bl(e,t,n=0){let o=$(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 L=C.roleBias??0,K=P.roleBias??0;if(L!==K)return L-K;let j=C.cells?.length??C.route?.length??Number.POSITIVE_INFINITY,Z=P.cells?.length??P.route?.length??Number.POSITIVE_INFINITY;if(j!==Z)return j-Z;let Q=C.key||JSON.stringify(C.route||C.cells||C),ke=P.key||JSON.stringify(P.route||P.cells||P);return Q.localeCompare(ke)});if(!o||r.length===1)return r[0];let i=r[0].roleBias??0,a=r.filter(C=>(C.roleBias??0)===i),c=a.map(C=>({item:C,score:vl(e,C)})),h=Math.min(...c.map(C=>C.score));a=c.filter(C=>C.score===h).map(C=>C.item);let g=e.branchCounters.get(t)||0;return e.branchCounters.set(t,g+1),a[Math.floor(Math.random()*a.length)]}function Hi(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 ur(e){let t=Hi(e);return{left:t.left.destination,right:t.right.destination}}function dr(e,t){return Hi(e).left.match===t.sex?"left":"right"}function fr(e){return mi(e)}function pr(e){return{mixer:"#2477ff",truck:"#ff6aa8",trash:"#22b94f",sausageMaker:"#d78b3f","scanner-role-1":"#526456",scanner:"#526456",input:"#526456"}[e]||"#102015"}function Kt(e,t,n=!1){let o=Je(e),r=(o.farmOutputs.get(t.id)||[]).map(rn),i=[];for(let h of r){let g=P=>o.scannerInputs.has(We(P.col,P.row));g.cacheKey=`farmInput:${h.col},${h.row}:v${o.version}`;let C=cr(e,h,g);for(let P of C){let L=P[P.length-1],K=o.scannerInputs.get(We(L.col,L.row)),j=Ht(e,K);j&&i.push({key:`${j.id}:${L.col},${L.row}:${P.length}`,scanner:j,cells:P})}}let a=co(e,`farm:${t.id}`,i,n);if(a)return{scannerId:a.scanner.id,route:hl(a.cells,a.scanner)};let c=co(e,`farm:${t.id}:output`,r.map(h=>({key:I(h.col,h.row),cells:[h]})),n);return c?{scannerId:null,route:uo(c.cells)}:null}function Ki(e,t,n,o,r=!1){let i=Ht(e,o);if(!i)return null;let c=Je(e).scannerPorts.get(i.id)?.[t],h=Fe(i,t),g=c?.connected?(c.keys||[c.key]).filter(Boolean).map(rn):[];if(!h||!g.length)return null;let C=ur(i)[t];return C==="scanner-role-1"?wl(e,i,t,n,h,g,r):Sl(e,i,t,n,h,g,C,r)}function wl(e,t,n,o,r,i,a){let c=Je(e),h=e.scanners.filter(P=>P.id!==t.id&&c.scannerPorts.get(P.id)?.inputA?.connected),g=[];for(let P of i)for(let L of h){let K=c.scannerPorts.get(L.id)?.inputA?.key;if(!K)continue;let j=(c.scannerPorts.get(L.id)?.inputA?.keys||[K]).filter(Boolean),Z=pe=>j.includes(We(pe.col,pe.row));Z.cacheKey=`toScanner:${P.col},${P.row}:${L.id}:v${c.version}`;let Q=cr(e,P,Z),ke=L.role===1?0:1;for(let pe of Q)g.push({key:`${ke}:start${I(P.col,P.row)}:s${L.id}:${pe.length}:${I(pe[pe.length-1].col,pe[pe.length-1].row)}`,target:L,cells:pe,roleBias:ke})}let C=co(e,`scanner:${t.id}:${n}:toRole1`,g,a);return C?{destination:"scanner",nextScannerId:C.target.id,route:ml(o,r,C.cells,C.target)}:null}function Sl(e,t,n,o,r,i,a,c){let h=Je(e),g=[...h.facilityPorts.entries()].filter(([L,K])=>K?.connected&&sn(e.facilities?.[L])===a);if(!g.length)return null;let C=[];for(let L of i)for(let[K,j]of g){let Z=(j.keys||[j.key]).filter(Boolean);if(!Z.length)continue;let Q=pe=>Z.includes(We(pe.col,pe.row));Q.cacheKey=`toFacility:${L.col},${L.row}:${K}:v${h.version}`;let ke=cr(e,L,Q);for(let pe of ke)C.push({key:`start${I(L.col,L.row)}:${K}:${pe.length}:${I(pe[pe.length-1].col,pe[pe.length-1].row)}`,cells:pe,facilityId:K})}let P=co(e,`scanner:${t.id}:${n}:${a}`,C,c);return P?{destination:a,facilityId:P.facilityId,route:yl(o,r,P.cells,il(e,P.facilityId))}:null}function qi(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=Ki(e,o,ge(t),t.id,!1);if(r){if(["mixer","trash","truck","sausageMaker"].includes(r.destination))return!0;if(r.destination==="scanner"){let i=Ht(e,r.nextScannerId);if(qi(e,i,n))return!0}}}return!1}function kl(e,t){return!!Kt(e,t,!1)?.scannerId}function an(e){if(e.phase!=="build")return[];let t=[];for(let n of e.eggFarms||[])kl(e,n)||t.push({type:"eggFarm",id:n.id,ref:n,message:"This Egg will not produce chicks"});for(let n of Jo)for(let[o,r]of lr(e,n))Je(e).facilityPorts.get(o)?.connected||t.push({type:"facility",id:o,ref:r,message:"This facility cannot receive items"});return t}function gl(e){let t=Je(e);for(let r of e.eggFarms||[]){let i=Kt(e,r,!1);if(!i?.scannerId)continue;let a=Ht(e,i.scannerId);if(qi(e,a))return[]}let n=e.eggFarms?.length||0,o=[...t.facilityPorts.entries()].filter(([r,i])=>i?.connected&&["mixer","trash","truck"].includes(sn(e.facilities?.[r]))).length;return n?t.metrics.farmOutputs?e.scanners?.length?o?["No route from an EGG scanner to a receiver."]:["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 ao(e){return e.kind==="manual"?`S${(e.slot??0)+1}`:`Auto Scanner #${e.id}`}function Cl(e,t=Je(e),n={}){let o=n.includeRoutes!==!1,r=[],i={mixer:"Mixer",trash:"Shredder",truck:"Truck",sausageMaker:"Sausage Machine"};for(let a of Jo){let c=lr(e,a);if(!c.length){di.includes(a)&&r.push(`${i[a]||a} is missing`);continue}for(let[h]of c)t.facilityPorts.get(h)?.connected||r.push(`${i[a]||a} receiver has no conveyor`)}for(let a of e.scanners){let c=t.scannerPorts.get(a.id)||{};if(c.inputA?.connected||r.push(`${ao(a)} input has no conveyor`),c.left?.connected||r.push(`${ao(a)} left output has no conveyor`),c.right?.connected||r.push(`${ao(a)} right output has no conveyor`),!!o)for(let h of["left","right"])c[h]?.connected&&!Ki(e,h,ge(a),a.id)&&r.push(`${ao(a)} ${h} route is incomplete`)}if(o)for(let a of e.eggFarms)Kt(e,a)?.scannerId||r.push(`Egg Farm #${a.id} has no scanner route`);else for(let a of e.eggFarms)(t.farmOutputs.get(a.id)||[]).length||r.push(`Egg Farm #${a.id} has no output conveyor`);return[...new Set(r)]}function fo(e){return gl(e)}var Fn=[{key:"premiumFemaleShipment",target:"female",truckTarget:"female",title:"Premium Female Shipment",route:"Female -> Truck",note:"Today only: ship female chicks to Truck. Meet the export target for a bonus.",objective:"truckTarget"},{key:"contaminantSampleExport",target:"poop",truckTarget:"poop",title:"Contaminant Sample Export",route:"Poop value x2",note:"Today only: poop is valid Truck cargo and all poop income is doubled.",objective:"truckTarget",poopValueMultiplier:2},{key:"healthInspection",title:"Health Inspection",route:"Poop -> Shredder",note:"Today only: poop mistakes are fined double. Shred enough poop for an inspection bonus.",objective:"poopTrash",poopFineMultiplier:2},{key:"conveyorJamWarning",title:"Conveyor Jam Warning",route:"No explosions",note:"Today only: jam explosions trigger at 85% congestion. Finish with zero explosions for a bonus.",objective:"noExplosion",congestionExplosionThreshold:.85,congestionWarningThreshold:.68},{key:"powerSavingOrder",title:"Power Saving Order",route:"Slow factory subsidy",note:"Today only: conveyors -20% speed and Auto Scanners +20% cooldown. A subsidy is paid at settlement.",objective:"flatBonus",conveyorSpeedMultiplier:.8,autoScannerCooldownMultiplier:1.2},{key:"overtimeDay",title:"Overtime Day",route:"80 second day",note:"Today only: the work day is longer. No extra reward or penalty.",objective:"none",daySeconds:80},{key:"shortShift",title:"Short Shift",route:"40 second day",note:"Today only: the work day is shorter. The next Fairies tribute is reduced by 10%.",objective:"none",daySeconds:40,fairiesTributeMultiplier:.9},{key:"chickBoom",title:"Chick Boom",route:"EGG interval x0.65",note:"Today only: eggs hatch faster and poop rate rises by 5 percentage points.",objective:"none",productionMultiplier:1/.65,poopRateAdd:.05},{key:"rottenFeed",title:"Rotten Feed",route:"Poop surge",note:"Today only: poop rate rises by 20 percentage points and Shredder poop income is multiplied by 1.5.",objective:"none",poopRateAdd:.2,shredderIncomeMultiplier:1.5},{key:"maintenanceAudit",title:"Maintenance Audit",route:"Durability check",note:"Today only: machines below 50% remaining durability fail the audit. Passing with none below 50% pays a bonus.",objective:"maintenanceAudit"}];function Ee(e,t=0){let n=Number(e);return Number.isFinite(n)?n:t}function Ml(e,t=0,n=1){let o=Math.max(1,Number(e?.turn||1)+1),r=Math.floor(yr(e)*(.08+Math.random()*.08)),i=Math.floor(Math.max(0,e.cash||0)*.25),a=Math.floor(Math.max(0,t)*Math.max(1,o)*.75);return Math.max(120,Math.ceil(Math.max(r,i,a)*n))}function Yi(e){return Math.max(0,Math.ceil(500+Math.max(1,e?.turn||1)*25))}function yr(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 xl(e,t=1,n=Gt){return e.eggFarms.reduce((o,r)=>{let[i,a]=$n(r),c=Math.max(.1,(i+a)/2);return o+Math.max(1,n)/c*Math.max(.01,t||1)},0)}function Tl(e,t){let n=Ee(t.productionMultiplier,1),o=Ee(t.daySeconds,Gt),r=xl(e,n,o),i=Math.max(0,Number(e?.cardEffects?.hatchingFeed)||0),a=Math.max(0,Number(e?.cardEffects?.poopRateBooster)||0),c=Math.pow(1.03,i),h=zo+Xo*a,g=Math.min(.95,Math.max(0,(h+Ee(t.poopRateAdd,0))*Ee(t.poopMultiplier,1)*c));return{estimate:r,poopRate:g}}function hr(e,t){let n=typeof t=="string"?Fn.find(c=>c.key===t):t;if(!n)return null;let{estimate:o,poopRate:r}=Tl(e,n),i=0;n.objective==="truckTarget"?i=Math.max(1,Math.ceil(o*.3)):n.objective==="poopTrash"&&(i=Math.max(1,Math.ceil(o*r*.8)));let a=0;return["truckTarget","poopTrash","noExplosion"].includes(n.objective)&&(a=Ml(e,o)),n.objective==="flatBonus"&&(a=Yi(e)),n.objective==="maintenanceAudit"&&(a=Yi(e)),{id:`event-${e.turn}-${Date.now()}-${Math.floor(Math.random()*9999)}`,...n,target:n.target||null,truckTarget:n.truckTarget||null,forced:!0,productionMultiplier:Ee(n.productionMultiplier,1),poopMultiplier:Ee(n.poopMultiplier,1),poopRateAdd:Ee(n.poopRateAdd,0),daySeconds:Ee(n.daySeconds,Gt),targetAmount:i,estimatedOutput:Math.round(o),reward:a,createdTurn:e.turn,oneTurnOnly:!0}}function Vi(e){if(e.turn+1ci)return null;let n=Fn[Math.floor(Math.random()*Fn.length)];return hr(e,n)}function zi(e){e.eventActive=e.eventOffer?{...e.eventOffer}:null,e.eventOffer=null}function Xi(e){e.eventActive=null}function ji(e){return Math.max(1,Ee(e.eventActive?.daySeconds,Gt))}function Qi(e){return Ee(e.eventActive?.productionMultiplier,1)}function Ji(e){let t=Math.max(0,Number(e?.cardEffects?.hatchingFeed)||0),n=Math.max(0,Number(e?.cardEffects?.poopRateBooster)||0),o=Math.pow(1.03,t),r=zo+Xo*n,i=Ee(e.eventActive?.poopRateAdd,0);return Math.min(.95,Math.max(0,(r+i)*Ee(e.eventActive?.poopMultiplier,1)*o))}function ln(e){return e.eventActive?.truckTarget||e.eventActive?.target||"female"}function mr(e,t){return t==="male"?e.truckMale||0:t==="poop"?e.poopTruck||0:e.truckFemale||0}function Zi(e,t){return ln(e)===t}function es(e){return ln(e)!=="male"}function br(e){return Math.max(.01,Ee(e.eventActive?.poopValueMultiplier,1))}function po(e){return Math.max(.01,br(e)*Ee(e.eventActive?.shredderIncomeMultiplier,1))}function vr(e){return Math.max(.01,Ee(e.eventActive?.poopFineMultiplier,1))}function wr(e){return Math.max(.1,Math.min(1,Ee(e.eventActive?.congestionExplosionThreshold,1)))}function ts(e){return Math.max(.1,Math.min(1,Ee(e.eventActive?.congestionWarningThreshold,.8)))}function ns(e){return Math.max(.01,Ee(e?.fairiesTributeMultiplier,1))}function El(e){let t=0,n=(o,r)=>{let i=Math.max(1,Ee(r,1)),a=Math.max(0,Ee(o,0));1-Math.min(1,a/i)<.5&&(t+=1)};for(let o of e.conveyorMeta?.values?.()||[])n(o.uses,o.durability);for(let o of e.eggFarms||[])n(o.maintenance?.uses,o.maintenance?.durability);for(let o of e.scanners||[])o.kind==="auto"&&n(o.maintenance?.uses,o.maintenance?.durability);for(let o of Object.values(e.facilities||{})){let r=o?.baseId||o?.id;(r==="mixer"||r==="trash"||r==="sausageMaker")&&n(o.maintenance?.uses,o.maintenance?.durability)}return t}function os(e){let t=e.eventActive;if(!t)return null;let n=0,o=!0,r=0,i="";t.objective==="truckTarget"?(n=mr(e.stats,t.target),o=n>=t.targetAmount,r=o?t.reward:0):t.objective==="poopTrash"?(n=e.stats.poopTrash||0,o=n>=t.targetAmount,r=o?t.reward:0):t.objective==="noExplosion"?(n=e.stats.explosions||0,o=n===0,r=o?t.reward:0,i=`${n} explosion(s)`):t.objective==="flatBonus"?(o=!0,r=t.reward):t.objective==="maintenanceAudit"?(n=El(e),o=n===0,o&&(r=t.reward),i=`${n} machine(s) below 50%`):o=!0,r>0?(je(e,r),e.stats.eventBonus=(e.stats.eventBonus||0)+r,e.totals.eventBonus+=r,e.totals.eventSuccess+=1):t.objective&&t.objective!=="none"&&(e.stats.eventBonus=e.stats.eventBonus||0,e.totals.eventFailed+=o?0:1,o&&(e.totals.eventSuccess+=1));let a={title:t.title,key:t.key,target:t.target,route:t.route,objective:t.objective,targetAmount:t.targetAmount||0,count:n,success:o,bonus:r,reward:t.reward||0,productionMultiplier:t.productionMultiplier,poopMultiplier:t.poopMultiplier,daySeconds:t.daySeconds,detail:i};return e.stats.eventResult=a,a}function Sr(e,t){return Math.max(0,Number(e?.cardEffects?.[t])||0)}function Rl(e){return!!e?.cardEffects?.usedMachineActive}function Pl(e,t){return t?1:Math.min(.8,.5+.1*Math.min(3,Sr(e,"recyclingSubsidy")))}function Al(e){return Math.pow(.95,Sr(e,"safetyCover"))}function yo(e){let t=e?.ref||e||{},n=e?.type||t.type;return n==="conveyor"?t.price||Te[t.kind]?.price||Te.conveyor.price:n==="eggFarm"?t.price||Te.eggFarm.price:n==="scanner"?t.price||(t.kind==="auto"?Te.autoScanner.price:Te.manualScanner.price):n==="facility"?t.price||Te[t.id]?.price||0:n==="maintenanceRoom"?t.price||Te.maintenanceRoom.price:Te[t.id]?.price||0}function He(e,t=null){let n=Te[e]?.price||0;return Rl(t)?Math.ceil(n*.5):n}function Il(e,t){return e?.facilities?Object.values(e.facilities).filter(n=>(n?.baseId||n?.id)===t).reduce((n,o)=>n+Math.max(0,(o.level||1)-1),0):0}function yt(e,t){return fi.includes(t)?Math.pow(xe.incomeUpgradeRate,Il(e,t)):1}function Ve(e){return Math.ceil(xe.income.truck*yt(e,"truck"))}function Ze(e){return Math.ceil((xe.income.sausage||26)*yt(e,"mixer")*yt(e,"sausageMaker"))}function Dn(e){return Math.ceil(xe.poopFine*yt(e,"mixer")*vr(e))}function Bn(e){return Math.ceil(xe.poopFine*yt(e,"truck")*vr(e))}function kr(e){let t=xe.maleTruckFinePerHalfDay*(e.turn/2);return Math.ceil(t*yt(e,"truck"))}function gr(e){return e?.facilities?Object.values(e.facilities).filter(t=>(t?.baseId||t?.id)==="trash").reduce((t,n)=>t+Math.max(0,(n.level||1)-1),0):0}function mt(e){return Math.min(xe.shredderBonus.maxCards,gr(e))}function cn(){return xe.shredderBonus.maxCards}function ho(){return xe.shredderBonus||{}}function rs(e){let t=typeof e=="number"?e:mt(e),n=ho(),o=Math.min(n.maxCards||30,Math.max(0,t));return o>0?Math.ceil((n.payoutCoefficient||1.6)*Math.pow(o,n.payoutExponent||1.25)):0}function qt(e){let t=typeof e=="number"?e:mt(e);if(t<=0)return 0;let n=ho();return Math.min(n.maxHitChance||.82,(n.baseHitChance||.28)+t*(n.hitChancePerUpgrade||.018))}function $l(e){let t=ho(),n=Math.min(t.jackpotMaxChance||.08,(t.jackpotBaseChance||.01)+e*(t.jackpotChancePerUpgrade||.0025)),o=Math.min(t.bigWinMaxChance||.12,(t.bigWinBaseChance||.07)+e*(t.bigWinChancePerUpgrade||.0015)),r=Math.min(1-n-o,t.doubleChance||.2),i=Math.max(0,1-n-o-r);return n*(t.jackpotMultiplier||10)+o*(t.bigWinMultiplier||4)+r*(t.doubleMultiplier||2)+i}function ht(e){let t=typeof e=="number"?e:mt(e);return Math.round(rs(t)*qt(t)*$l(t))}function Fl(e){let t=mt(e),n=qt(t),o=rs(t);if(t<=0||Math.random()>=n)return{cards:t,chance:n,amount:0,expected:ht(t),baseAmount:o,multiplier:0,tier:"miss"};let r=ho(),i=Math.min(r.jackpotMaxChance||.08,(r.jackpotBaseChance||.01)+t*(r.jackpotChancePerUpgrade||.0025)),a=Math.min(r.bigWinMaxChance||.12,(r.bigWinBaseChance||.07)+t*(r.bigWinChancePerUpgrade||.0015)),c=Math.min(1-i-a,r.doubleChance||.2),h=Math.random(),g=1,C="normal";h0&&(e.stats.shredderBonus=(e.stats.shredderBonus||0)+t.amount,e.totals.shredderBonus=(e.totals.shredderBonus||0)+t.amount,je(e,t.amount)),t}function Cr(e,t=e.turn,n=null){let o=Math.max(1,Math.floor(Number(t)||1)),r=xe.fairiesTribute||{perDay:10,profitExemption:300,legalWorkExemptionBonus:250,profitRate:.12},i=Math.ceil(o*(r.perDay||10)),a=Math.max(0,Math.floor(Number(n??e?.lastResult?.profit??e?.stats?.profit??0)||0)),c=Math.max(0,e?.cardEffects?.legalWork||0),h=Math.max(0,(r.profitExemption||0)+c*(r.legalWorkExemptionBonus||0)),g=Math.max(0,a-h),C=Math.ceil(g*Math.max(0,Number(r.profitRate)||0)),P=i+C,L=Math.max(0,e?.cardEffects?.fairiesFlatteryNext||e?.cardEffects?.flattery||0),K=L>0?Math.pow(.5,L):1,j=Math.max(.01,Number(e?.pendingFairiesTributeMultiplier)||1),Z=K*j,Q=Math.max(0,Math.ceil(P*Z)),ke=Math.max(0,P-Q);return{day:o,base:i,profit:a,legalWork:c,profitExemption:h,taxableProfit:g,profitTribute:C,gross:P,flatteryCount:L,eventMultiplier:j,perCardReduction:Math.ceil(P*.5),reduction:ke,amount:Q}}function $t(e,t){let n=e?.ref||e||{},o=e?.type==="conveyor"?t?.conveyorMeta?.get(e.oldKey||n.id):n,r=o?.price||yo(e||n),i=o?.builtSession===t?.buildSession;if(o?.noRefund||n?.noRefund)return{amount:0,sameBuild:i,price:r,rate:0,noRefund:!0};let a=Pl(t,i);return{amount:Math.max(0,Math.ceil(r*a)),sameBuild:i,price:r,rate:a,noRefund:!1}}function Dl(e,t=null){let n=Math.max(1,Math.ceil((Number(e)||0)/xe.explosionDamageDivisor));return Math.max(1,Math.ceil(n*Al(t)))}function Bl(e,t){return Math.max(0,Math.ceil(t))}function ss(e,t){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 je(e,t){let n=Math.max(0,Math.ceil(t));return n>0&&ss(e,n),n}function Ft(e,t){let n=Math.max(0,Math.ceil(t));return n>0&&ss(e,-n),n}function Dt(e,t){Ft(e,t)}function as(e,t){t<=0||(e.cash+=t,e.stats.profit+=t,e.totals.profit+=t,e.totals.maxCash=Math.max(e.totals.maxCash,e.cash))}function ls(e){let t=xe.income.mixer;return e.stats.mixerRevenue+=t,je(e,t),t}function cs(e){let t=Dn(e);return e.stats.mixerPoopFine+=t,e.totals.mixerPoopFine+=t,Ft(e,t),t}function us(e){let t=Bn(e);return e.stats.truckPoopFine+=t,e.totals.truckPoopFine+=t,Ft(e,t),t}function ds(e){let t=kr(e);return e.stats.maleTruckFine+=t,e.totals.maleTruckFine+=t,Ft(e,t),t}function fs(e,t){let n=Dl(t,e);return e.stats.explosionDamage+=n,e.totals.explosionDamage+=n,Ft(e,n),n}function ps(e){let t=ln(e),n=mr(e.stats,t),o=t==="poop"?Math.ceil(Ve(e)*br(e)):Ve(e),r=n*o,i=Math.max(0,e?.stats?.sausageShipped||0),a=Ze(e),c=i*a,h=Bl(e,r+c);return e.stats.pendingTruckRevenue=h,e.stats.truckRevenue=h,e.stats.sausageRevenue=c,t==="poop"?e.stats.poopShipmentIncome=r:e.stats.chickShipmentIncome=r,h>0&&je(e,h),{base:r,adjusted:h,target:t,targetCount:n,unitPrice:o,sausageCount:i,sausageUnitPrice:a,sausageRevenue:c}}function ys(e,t=e.turn,n=null,o={}){let r=Cr(e,t,n);return e.cardEffects&&(e.cardEffects.fairiesFlatteryNext=0,e.cardEffects.flattery!=null&&delete e.cardEffects.flattery),e.pendingFairiesTributeMultiplier!=null&&(e.pendingFairiesTributeMultiplier=1),r.amount<=0||(o.recordStats?(e.stats.fairiesTribute=(e.stats.fairiesTribute||0)+r.amount,e.totals.fairiesTribute=(e.totals.fairiesTribute||0)+r.amount,Ft(e,r.amount)):(e.totals.fairiesTribute=(e.totals.fairiesTribute||0)+r.amount,e.totals.penalty=(e.totals.penalty||0)+r.amount,e.totals.profit=(e.totals.profit||0)-r.amount,e.cash-=r.amount)),r}function hs(e){let t=Sr(e,"chemicalWeaponSubsidy"),n=Math.max(0,e?.stats?.poopTruck||0),o=xe.chemicalWeaponSubsidy||{poopPerBlock:70,yenPerBlock:1200},r=Math.floor(n/Math.max(1,o.poopPerBlock||70)),i=t>0&&r>0?r*Math.max(0,o.yenPerBlock||1200)*t:0;return i>0&&(e.stats.chemicalWeaponSubsidy=(e.stats.chemicalWeaponSubsidy||0)+i,e.totals.chemicalWeaponSubsidy=(e.totals.chemicalWeaponSubsidy||0)+i,je(e,i)),{cards:t,poopTruck:n,blocks:r,amount:i}}function ms(e){let t=yr(e),n=Math.max(1,e.totals.turnsCompleted||e.turn||1),o=Math.max(0,e.cash+e.totals.revenue+t*.5+e.totals.eventBonus*.5),r=Math.ceil(o/n),i=Math.floor(n*35+Math.max(0,n-5)*15),a=e.totals.correct*5,c=e.totals.explosionDamage;return{score:Math.max(0,Math.floor(o+r+a-e.totals.penalty-i)),factoryValue:t,base:o,dailyEarned:r,dayPenalty:i,correctBonus:a,explosionPenalty:c,days:n}}function Ol(e){return{...e,keys:e.keys?{left:{...e.keys.left},right:{...e.keys.right}}:null,queue:[],cooldown:e.cooldown||0}}function On(e){return JSON.stringify({cash:e.cash,stats:e.stats,totals:e.totals,nextId:e.nextId,gridX:f.x,gridY:f.y,gridCols:e.gridCols||f.cols,gridRows:e.gridRows||f.rows,gridExpansionPurchases:e.gridExpansionPurchases||0,gridExpansionPurchasesByDirection:e.gridExpansionPurchasesByDirection||{left:0,right:0,up:0,down:0},gridChunkOriginX:e.gridChunkOriginX||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(Ol),conveyorTiles:[...e.conveyorTiles],conveyorMeta:[...e.conveyorMeta.entries()],branchCounters:[...e.branchCounters.entries()],cardEffects:e.cardEffects,cardDraft:e.cardDraft,cardTargetPick:e.cardTargetPick})}function bs(e,t){let n=JSON.parse(t);e.cash=n.cash,e.stats=n.stats,e.totals=n.totals,e.nextId=n.nextId,f.x=n.gridX??V.grid.x,f.y=n.gridY??V.grid.y,f.cols=n.gridCols||V.grid.cols,f.rows=n.gridRows||V.grid.rows,e.gridCols=f.cols,e.gridRows=f.rows,e.gridExpansionPurchases=n.gridExpansionPurchases||0,e.gridExpansionPurchasesByDirection=n.gridExpansionPurchasesByDirection||{left:0,right:0,up:0,down:0},e.gridExpansionPurchasesByDirection.left==null&&(e.gridExpansionPurchasesByDirection.left=0),e.gridExpansionPurchasesByDirection.down==null&&(e.gridExpansionPurchasesByDirection.down=0),e.gridChunkOriginX=n.gridChunkOriginX||0,e.gridChunkOriginY=n.gridChunkOriginY||0,e.ownedCells=new Set(n.ownedCells||[]),e.ownedChunks=new Set(n.ownedChunks||[]),e.gridOwnedCells=e.ownedCells.size||f.cols*f.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,fairiesFlatteryNext:0,scannerQueueSpacing: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 _e(e){e.phase==="build"&&(e.undoStack.push(On(e)),e.undoStack.length>80&&e.undoStack.shift(),e.redoStack=[])}function Mr(e){return e.phase!=="build"||!e.undoStack.length?!1:(e.redoStack.push(On(e)),bs(e,e.undoStack.pop()),!0)}function xr(e){return e.phase!=="build"||!e.redoStack.length?!1:(e.undoStack.push(On(e)),bs(e,e.redoStack.pop()),!0)}function H(e,t,n,o,r=b.ink){e.floatingTexts.push({x:t,y:n,text:o,color:r,life:1,maxLife:1})}function Yt(e,t,n){e.shake.strength=Math.max(e.shake.strength,t),e.shake.time=Math.max(e.shake.time,n)}function vs(e,t,n){e.effects.push({type:"spawn",priority:Ie.ambient,x:t,y:n,life:.42,maxLife:.42,size:12})}function ws(e,t,n,o=b.green){e.effects.push({type:"scannerPulse",priority:Ie.important,x:t,y:n,color:o,life:.42,maxLife:.42,size:24})}function Ss(e,t,n){for(let o=0;o<12;o+=1)e.effects.push({type:"meat",priority:Ie.important,x:t+z(-18,18),y:n+z(-12,16),vx:z(-60,60),vy:z(-90,-25),life:z(.45,.85),maxLife:.85,size:z(4,8)})}function ks(e,t,n){for(let o=0;o<16;o+=1)e.effects.push({type:"sludge",priority:Ie.important,x:t+z(-18,18),y:n+z(-12,16),vx:z(-46,46),vy:z(-70,-20),life:z(.45,.9),maxLife:.9,size:z(4,8)})}function gs(e,t,n,o){let r=o==="poop"?b.poop:o==="male"?b.male:b.female;for(let i=0;i<22;i+=1)e.effects.push({type:"shred",priority:Ie.important,x:t+z(-16,16),y:n+z(-8,8),vx:z(-70,70),vy:z(40,160),color:i%3===0?b.white:r,life:z(.45,.95),maxLife:.95,w:z(3,7),h:z(10,24),rot:z(-Math.PI,Math.PI)});at(e,t,n,b.green,34,Ie.important)}function Cs(e,t,n,o){let r=o==="poop"?b.poop:o==="male"?b.danger:b.green;for(let i=0;i<8;i+=1)e.effects.push({type:"load",priority:Ie.important,x:t+z(-18,18),y:n+z(-18,18),vx:z(-42,42),vy:z(-90,-25),color:r,life:z(.35,.65),maxLife:.65,size:8})}function at(e,t,n,o=b.danger,r=52,i=Ie.critical){e.effects.push({type:"shockwave",priority:i,x:t,y:n,color:o,life:.55,maxLife:.55,size:8,maxSize:r})}function bt(e,t,n,o=12,r=b.danger,i=Ie.critical){for(let a=0;a0&&(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||Ll(e,r,o);let i=r.x+e.view.x,a=r.y+e.view.y;(i<-100||a<-100||i>n.width+100||a>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 Ll(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=fs(e,o.price);H(e,t.x,t.y-10,`-${x(r)}`,b.danger),at(e,t.x,t.y,b.danger,30),bt(e,t.x,t.y,5,b.danger),Yt(e,5,.18)}}var Qe=V.maintenance;function Er(e,t){return Math.max(0,Number(e?.cardEffects?.[t])||0)}function vo(e){let t=Math.min(1.4,1+.04*Math.min(10,Er(e,"crowdedFarming"))),n=Math.pow(.95,Er(e,"preventiveMaintenance"));return t*n}function Nl(e){return Math.pow(1.03,Er(e,"durabilityCoating"))}function wo(e,t,n=1){let o=Qe.durability[t]||1e3;return Math.max(1,Math.ceil(o*Math.max(.1,n||1)*Nl(e)))}function Ts(e){return Math.max(.1,Number(e?.durabilityBaseMultiplier)||1)}function _l(e,t=0){if(!e)return{x:f.x+f.cell*(.5+t*.55),y:f.y+f.rows*f.cell+84};let n=f.x+e.col*f.cell+f.cell,o=f.y+e.row*f.cell+f.cell;return{x:n,y:o}}function Gl(e,t=0){let n=(e.maintenanceRooms||[])[t];return _l(n,t)}function Re(e){e.repairman||(e.repairman={active:!1,x:f.x+f.cell*.5,y:f.y+f.rows*f.cell+84,target:null,repairedToday:0}),Array.isArray(e.maintenanceRooms)||(e.maintenanceRooms=[]),Array.isArray(e.repairmen)||(e.repairmen=[]);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=wo(e,"conveyor",Ts(n));(n.durability==null||n.durability0&&(n.processingCooldown=Math.max(0,n.processingCooldown-t))}function Gn(e){Re(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:$(re(n).col,re(n).row)});for(let n of e.eggFarms||[])t.push({type:"eggFarm",ref:n,label:`EGG #${n.id}`,center:$(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:$(n.col,n.row)});for(let n of Object.values(e.facilities||{})){let o=Co(n);["mixer","trash","sausageMaker"].includes(o)&&t.push({type:o,ref:n,label:`${n.shortName||n.name||o} #${n.id}`,center:{x:n.x+n.w/2,y:n.y+n.h/2}})}return t}function Vt(e){return e.meta?un(e):un(e.ref)}function Ln(e){return e.meta?Rr(e):Rr(e.ref)}function bo(e){return e.meta?Pr(e):Pr(e.ref)}function Is(e,t){Ul(e.meta?e:e.ref,Math.max(0,Ln(e)-t))}function Dr(e){return e.type==="conveyor"?`conveyor:${e.key}`:`${e.type}:${e.ref?.id||e.ref?.type||e.type}`}function $s(e,t=.02){Re(e);let n=Math.max(0,Number(t)||0),o=0;for(let r of Gn(e)){let i=Ln(r);Is(r,bo(r)*n),o+=Math.max(0,i-Ln(r))/bo(r)*100}return o}function Fs(e){Re(e);let t=e.maintenanceRooms||[];e.repairmen=t.map((o,r)=>{let i=Gl(e,r);return{id:`room-${o.id}`,roomId:o.id,active:!0,x:i.x,y:i.y,target:null,repairedToday:0}});let n=e.repairmen[0];e.repairman.active=!!n,e.repairman.target=n?.target||null,e.repairman.repairedToday=0,n&&(e.repairman.x=n.x,e.repairman.y=n.y)}function Ds(e){Re(e);for(let t of e.repairmen||[])t.active=!1,t.target=null;e.repairmen=[],e.repairman.active=!1,e.repairman.target=null}function Hl(e,t){return t&&Gn(e).find(o=>Dr(o)===t.key)||null}function Kl(e){let t=null;for(let n of Gn(e)){let o=Vt(n);o<=.001||(!t||o>Vt(t))&&(t=n)}return t}function Bs(e,t){Re(e);let n=e.repairmen?.length?e.repairmen:e.repairman?.active?[e.repairman]:[];if(!n.length||e.phase!=="running")return;let o=new Set(n.map(i=>i.target?.key).filter(Boolean));for(let i of n)ql(e,i,t,o);let r=n[0];r&&e.repairman!==r&&(e.repairman.active=!0,e.repairman.x=r.x,e.repairman.y=r.y,e.repairman.target=r.target,e.repairman.repairedToday=r.repairedToday||0)}function ql(e,t,n,o){if(!t.active)return;let r=Hl(e,t.target);if((!r||Vt(r)<=.001)&&(t.target?.key&&o.delete(t.target.key),r=Yl(e,o)),!r){t.target=null;return}let i=Dr(r);o.add(i),t.target={key:i,label:r.label};let a=Qe.repairman.walkSpeed,c=r.center.x-t.x,h=r.center.y-t.y,g=Math.hypot(c,h);if(g>8){let Z=Math.min(g,a*n);t.x+=c/g*Z,t.y+=h/g*Z;return}let C=e?.cardEffects?.laborExploitation?.7:1,P=n/(Qe.repairman.secondsPerOnePercent*C*100),L=Ln(r),K=P*bo(r);Is(r,K);let j=Math.max(0,(L-Ln(r))/bo(r)*100);t.repairedToday=(t.repairedToday||0)+j,j>0&&Math.random()<.02&&H(e,t.x,t.y-28,"REPAIR",b.green),Vt(r)<=.001&&(o.delete(i),t.target=null)}function Yl(e,t=new Set){let n=null;for(let o of Gn(e)){if(t.has(Dr(o)))continue;let r=Vt(o);r<=.001||(!n||r>Vt(n))&&(n=o)}return n||Kl(e)}function Vl(e){let t=0,n="none";for(let o of Gn(e)){let r=Vt(o);r>t&&(t=r,n=o.label)}return{ratio:t,percent:Math.round(t*100),label:n}}function Br(e){let t=Vl(e),n=Ar(e);return{worst:t,avgBeltPerformance:n,repairman:e.repairman||null,repairmen:e.repairmen||[],rooms:e.maintenanceRooms||[]}}var zl=Ue.commonWeight,Xl=Ue.rareWeight,jl=Ue.ultraRareWeight||.45,zt=Ue.baseDraftSize,fn=ui;function dn(e,t){let n=Ke(e);return Math.max(0,Number(n[t])||0)}function Ke(e){e.cardEffects||(e.cardEffects={...jo});for(let[t,n]of Object.entries(jo))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),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 Hs(e){let t=Math.max(.5,1-.05*Math.min(10,dn(e,"crowdedFarming"))),n=Math.pow(.96,dn(e,"hatchingFeed"));return t*n}function Ks(e,t=null){return Math.min(3,Math.max(0,Number(t?.extraEggOutlet)||0))}function qs(e){return Math.pow(.85,dn(e,"scannerQueueSpacing"))}function pn(e,t=null){let n=Ke(e),o=Jn*Math.pow(Zn,Math.max(0,n.bearing||0)),r=t?Math.max(.1,Number(e?.conveyorMeta?.get?.(t)?.speedMultiplier)||1):1,i=t?1:Ar(e),a=Math.max(.01,Number(e?.eventActive?.conveyorSpeedMultiplier)||1);return Math.min(tn,o*i)*r*a}function St(e,t=null){let n=Math.max(0,(e?.level||1)-1),o=t?dn(t,"autoScannerTuning"):0,r=Qn*Math.pow(li,n)*Math.pow(Yo,o),i=Math.max(Vo,r),a=t?Math.max(.01,Number(t?.eventActive?.autoScannerCooldownMultiplier)||1):1;return t?Math.max(Vo,i*ko(e)*a):i}function Os(e){let t=e.cardDraft||{rerolls:0},n=Math.max(1,(t.rerolls||0)+1);return Math.ceil(Math.max(1,e.turn||1)/Ue.rerollCost.dayDivisor*n*Ue.rerollCost.multiplier)}function Xt(e){return fn.find(t=>t.id===e)||null}function Ys(e,t){let n=typeof t=="string"?Xt(t):t;return Ke(e),!n||!(n.type==="equipmentUpgrade"||n.type==="cellAction")?[]:et(e,n).map(o=>({key:wt(o),label:_r(e,o),type:o.type}))}function Nr(e,t,n={}){let o=typeof t=="string"?Xt(t):t;if(Ke(e),!o||o.type==="dud")return{ok:!1,reason:"Card not found."};if(o.id==="extraCards")return e.cardDraft=e.cardDraft||{pending:!1,choices:[],rerolls:0,picksRemaining:0},e.cardDraft.choices.push(...To(e,2)),{ok:!0,reason:`${o.title} added 2 draft cards.`};if(o.type==="instant")return zs(e,o),{ok:!0,reason:`${o.title} granted.`};if(o.type==="equipmentUpgrade"){let r=et(e,o);if(!r.length)return{ok:!1,reason:`No valid target for ${o.title}.`};let i=n.targetKey&&n.targetKey!=="__auto__"?String(n.targetKey):"",a=i?r.find(c=>wt(c)===i):r[0];return a?(Vs(e,a),{ok:!0,reason:`${o.title} applied to ${_r(e,a)}.`}):{ok:!1,reason:`Selected target is no longer valid for ${o.title}.`}}if(o.type==="cellAction"&&o.target==="blockedCell"){let r=et(e,o);if(!r.length)return{ok:!1,reason:"No blocked cells to remove."};let i=new Set((Array.isArray(n.targetKeys)?n.targetKeys:[n.targetKey]).filter(c=>c&&c!=="__auto__").map(String)),a=i.size?r.filter(c=>i.has(wt(c))):r.slice(0,10);if(!a.length)return{ok:!1,reason:`Selected target is no longer valid for ${o.title}.`};for(let c of a.slice(0,3)){e.blockedCells.delete(I(c.col,c.row));let h=$(c.col,c.row);vt(e,h.x,h.y),at(e,h.x,h.y,b.danger,42),bt(e,h.x,h.y,8,b.warn)}return Me(e),{ok:!0,reason:`${o.title} removed ${Math.min(10,a.length)} blocked cell(s).`}}return{ok:!1,reason:`${o.title} cannot be debug-granted.`}}function _r(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 ${St(t,e).toFixed(1)}s -> ${St({...t,level:t.level+1},e).toFixed(1)}s`;if(t.type==="facility"&&["mixer","sausageMaker"].includes(t.baseId||t.id)){let n=t.baseId||t.id,o=t.level||1,r=Ze(e);t.level=o+1;let i=Ze(e);return t.level=o,`${n==="mixer"?"MIXER":"SAUSAGE MACHINE"} L${t.level} -> L${t.level+1} | sausage ${x(r)} -> ${x(i)}`}if(t.type==="facility"&&(t.baseId||t.id)==="truck"){let n=t.level,o=Ve(e);t.level=n+1;let r=Ve(e);return t.level=n,`TRUCK L${t.level} -> L${t.level+1} | ${x(o)} -> ${x(r)}`}if(t.type==="facility"&&(t.baseId||t.id)==="trash"){let n=mt(e),o=Math.min(cn(),n+1),r=ht(n),i=ht(o);return`SHREDDER L${t.level||1} -> L${(t.level||1)+1} | expected ${x(r)} -> ${x(i)} per poop`}return`${t.name||t.id} L${t.level||1} -> L${(t.level||1)+1}`}function Ql(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 et(e,t){let n=typeof t=="string"?Xt(t):t;return Ke(e),n?n.target==="blockedCell"?Ql(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==="sausageMaker"?Object.values(e.facilities||{}).filter(o=>(o.baseId||o.id)==="sausageMaker"):n.target==="trash"?gr(e)(o.baseId||o.id)==="trash"):[]:[]:[]}function Jl(e,t){let n=Ke(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,Ue.dudChancePerCard||0)),r=Math.max(1e-4,Math.min(1,Ue.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 Zl(e){return!!((e.maintenanceRooms||[]).length||(e.repairmen||[]).length||e.repairman?.active)}function Ls(e,t){return t==="conveyor"?(e.conveyorTiles?.size||0)>0:t==="autoScanner"?(e.scanners||[]).some(n=>n.kind==="auto"):t==="manualScanner"?(e.scanners||[]).some(n=>n.kind==="manual"):t==="eggFarm"?(e.eggFarms||[]).length>0:Object.values(e.facilities||{}).some(n=>(n.baseId||n.id)===t)}function ec(e,t){return!(t.requiresFacility&&!Ls(e,t.requiresFacility)||Array.isArray(t.requiresAnyFacility)&&!t.requiresAnyFacility.some(n=>Ls(e,n)))}function tc(e){return fn.filter(t=>!(t.id==="laborExploitation"&&!Zl(e)||!ec(e,t)||Jl(e,t)||(t.type==="equipmentUpgrade"||t.type==="cellAction")&&et(e,t).length<=0))}function Ns(e,t){return t.rarity==="ultraRare"?jl:t.rarity==="rare"?Xl:zl}function nc(e,t){let n=t.reduce((r,i)=>r+Ns(e,i),0);if(n<=0)return t[Math.floor(Math.random()*t.length)];let o=Math.random()*n;for(let r of t)if(o-=Ns(e,r),o<=0)return r;return t[t.length-1]}var _s=0;function oc(){return _s+=1,{id:`dud:${Date.now()}:${_s}`,title:"Dud",rarity:"dud",type:"dud",description:"Click to flick this dud away. It does not consume a pick."}}function rc(e){let t=Ke(e),n=Math.max(0,Math.min(1,Ue.dudChancePerCard||0)),o=Math.max(0,Math.min(1,Ue.dudChanceReductionPerFilter||0));return Math.max(0,n-o*Math.max(0,t.dudFilter||0))}function ic(e,t){let n=[...t],o=0,r=Math.max(0,Ue.maxDudsPerDraft||0),i=rc(e);for(let a=0;ac.id===i.id);a>=0&&r.splice(a,1)}return ic(e,o)}function wt(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 Gs(e){if(!e)return null;if(e.type==="blockedCell"){let t=$(e.col,e.row);return{x:t.x-f.cell*.48,y:t.y-f.cell*.48,w:f.cell*.96,h:f.cell*.96,cx:t.x,cy:t.y}}if(e.type==="eggFarm"||e.type==="eggOutlet"){let t=$(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=ge(e);return{x:t.x-48,y:t.y-48,w:96,h:96,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 Gr(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(wt(o))).map(o=>({key:wt(o),label:"AUTO ROUTE SETTINGS",target:o,bounds:Gs(o)})).filter(o=>o.bounds)}let t=new Set(e.cardTargetPick.targetKeys||[]);return et(e,e.cardTargetPick.cardId).filter(n=>t.has(wt(n))).map(n=>({key:wt(n),label:_r(e,n),target:n,bounds:Gs(n)})).filter(n=>n.bounds)}function sc(e,t){let n=Gr(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 Vs(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=$(n.col,n.row);H(e,o.x,o.y-30,`OUTLET ${1+n.extraEggOutlet}`,b.green),Me(e);return}if(t.level=(t.level||1)+1,t.type==="eggFarm"){t.nextSpawn=Wt(t),t.lastInterval=t.nextSpawn;let n=$(t.col,t.row);H(e,n.x,n.y-30,`LV ${t.level}`,b.green);return}if(t.type==="scanner"){let n=ge(t);H(e,n.x,n.y-34,`LV ${t.level}`,b.green);return}t.type==="facility"&&H(e,t.x+t.w/2,t.y+18,`LV ${t.level}`,b.green)}function zs(e,t){let n=Ke(e),o=r=>{n[r]=Math.max(0,n[r]||0)+1};if(t.id==="bearing"&&o("bearing"),t.id==="autoScannerTuning"&&o("autoScannerTuning"),t.id==="legalWork"&&o("legalWork"),t.id==="crowdedFarming"&&(n.crowdedFarming=Math.min(10,(n.crowdedFarming||0)+1)),t.id==="hatchingFeed"&&o("hatchingFeed"),t.id==="poopRateBooster"&&o("poopRateBooster"),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"),Re(e)),t.id==="sparePartsBin"){o("sparePartsBin");let r=$s(e,.02);e.stats.sparePartsRepair=(e.stats.sparePartsRepair||0)+r,e.totals.sparePartsRepair=(e.totals.sparePartsRepair||0)+r,H(e,f.x+f.cols*f.cell/2,f.y-30,"REPAIR +2%",b.green)}t.id==="dudRefund"&&o("dudRefund"),t.id==="freeReroll"&&o("freeReroll"),t.id==="composter"&&o("composter"),t.id==="scannerQueueSpacing"&&o("scannerQueueSpacing"),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"),H(e,f.x+f.cols*f.cell/2,f.y-30,"RESCUE HELD",b.green))}function ot(e){return`${Math.round(Number(e||0)*100)}%`}function Us(e){let t=Et[Math.max(0,Math.min(Et.length-1,e-1))]||Et[0]||[0,0];return`${t[0].toFixed(2)}-${t[1].toFixed(2)}s`}function Ws(e,t){return Math.ceil(e*Math.pow(xe.incomeUpgradeRate,Math.max(0,t-1)))}function Or(e,t,n){let r=et(e,t==="mixer"?"upgradeMixer":t==="sausageMaker"?"upgradeSausageMaker":"upgradeTruck")[0];if(!r)return{before:Ws(n,1),after:Ws(n,2)};let i=()=>t==="truck"?Ve(e):Ze(e),a=i(),c=r.level||1;r.level=c+1;let h=i();return r.level=c,{before:a,after:h}}function ac(e,t){let n=Ke(e);if(t.id==="upgradeEgg"){let o=et(e,t),r=Math.min(3,Math.max(1,o[0]?.level||1));return`Choose one EGG. Level +1. Production interval ${Us(r)} -> ${Us(r+1)}.`}if(t.id==="upgradeAutoScanner"){let o=et(e,t)[0]||{level:1},r=St(o,e),i=St({...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=Or(e,"mixer",xe.income.sausage);return`Choose one MIXER. Meat quality +10%. Sausage sale value ${x(o.before)} -> ${x(o.after)}.`}if(t.id==="upgradeSausageMaker"){let o=Or(e,"sausageMaker",xe.income.sausage);return`Choose one SAUSAGE MACHINE. Product value +10%. Sausage sale value ${x(o.before)} -> ${x(o.after)}.`}if(t.id==="upgradeTruck"){let o=Or(e,"truck",xe.income.truck);return`Choose one TRUCK. Earning +10%. Correct shipment income ${x(o.before)} -> ${x(o.after)}.`}if(t.id==="upgradeTrash"){let o=mt(e),r=Math.min(cn(),o+1),i=ht(o),a=ht(r),c=Math.round(qt(o)*100),h=Math.round(qt(r)*100);return`SHREDDER level +1. Expected value ${x(i)} -> ${x(a)} per poop; hit chance ${c}% -> ${h}%.`}if(t.id==="bearing"){let o=Math.min(tn,Jn*Math.pow(Zn,Math.max(0,n.bearing||0))),r=Math.min(tn,Jn*Math.pow(Zn,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`Fairies tribute profit exemption +${x(250)}. After pick: exemption bonus ${x(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 ${ot(Math.max(.5,1-.05*o))}, deterioration ${ot(Math.min(1.4,1+.04*o))}.`}if(t.id==="extraEggOutlet"){let o=et(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 ${ot(Math.pow(.96,(n.hatchingFeed||0)+1))}, poop rate multiplier ${ot(Math.pow(1.03,(n.hatchingFeed||0)+1))}.`;if(t.id==="poopRateBooster")return`Held ${n.poopRateBooster||0}. After pick: base poop rate +${Math.round((n.poopRateBooster||0)+1)*4} percentage points.`;if(t.id==="autoScannerTuning")return`Held ${n.autoScannerTuning||0}. After pick: all AUTO SCANNER default cooldown ${ot(Math.pow(Yo,(n.autoScannerTuning||0)+1))}.`;if(t.id==="safetyCover")return`Held ${n.safetyCover||0}. After pick: explosion damage ${ot(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: ${x(1200*((n.chemicalWeaponSubsidy||0)+1))} per 70 truck-shipped poop each day.`;if(t.id==="preventiveMaintenance")return`Held ${n.preventiveMaintenance||0}. After pick: deterioration speed ${ot(Math.pow(.95,(n.preventiveMaintenance||0)+1))}.`;if(t.id==="dudFilter"){let o=Math.max(0,Math.min(1,Ue.dudChancePerCard||0)),r=Math.max(0,Math.min(1,Ue.dudChanceReductionPerFilter||.05)),i=Math.max(0,n.dudFilter||0),a=Math.max(0,o-r*i),c=Math.max(0,o-r*(i+1));return`Held ${i}. DUD chance per card ${ot(a)} -> ${ot(c)}.`}return t.id==="durabilityCoating"?`Held ${n.durabilityCoating||0}. After pick: maximum durability ${ot(Math.pow(1.03,(n.durabilityCoating||0)+1))}.`:t.id==="sparePartsBin"?`Held ${n.sparePartsBin||0}. Immediately restores 2% durability to every maintained machine. Common card weight.`:t.id==="dudRefund"?`Held ${n.dudRefund||0}. After pick: DUD clicks award ${x(50*((n.dudRefund||0)+1))}.`:t.id==="freeReroll"?`Held ${n.freeReroll||0}. After pick: each card screen starts with ${(n.freeReroll||0)+1} free reroll${(n.freeReroll||0)+1===1?"":"s"}.`:t.id==="composter"?`Held ${n.composter||0}. After pick: SHREDDER poop grants ${x(3*((n.composter||0)+1))}.`:t.id==="scannerQueueSpacing"?`Held ${n.scannerQueueSpacing||0}. After pick: Manual/Auto Scanner queue spacing ${ot(Math.pow(.85,(n.scannerQueueSpacing||0)+1))}.`:t.id==="flattery"?`Next Fairies tribute -50%. Held for next tribute: ${n.fairiesFlatteryNext||0}.`:t.id==="usedMachine"?"Enable Used Machines: future equipment costs 50%, has no SELL refund, and starts with 60% durability.":t.id==="newMachine"?"Cancel Used Machines. Future equipment returns to normal price, refund, and durability.":t.id==="laborExploitation"?"Repairman dispatch paperwork is streamlined. Repair rooms keep operating; one time only.":t.id==="rescueLoan"?`At day-end bankruptcy check, gain ${x(500)} if cash is below ${x(0)}. Held: ${n.rescueLoanCharges||0}.`:t.id==="dynamite"?"Choose any blocked tiles on the map and destroy up to 3. Empty-space clicks are ignored; press Esc to finish after at least one blast.":t.description}function Lr(e,t){let n=e.cardDraft?.choices||[],o=n.findIndex(r=>r.id===t.id);o>=0&&n.splice(o,1)}function Xs({game:e,ui:t,onUpdatePanels:n}){function o(){n&&n()}function r(){Ke(e),e.cardTargetPick=null,e.cardDraft={pending:!0,choices:To(e,zt),rerolls:0,freeRerolls:dn(e,"freeReroll"),picksRemaining:1}}function i(){Ke(e),e.cardDraft.pending=!1,e.cardDraft.choices=[],e.cardDraft.picksRemaining=0,e.cardTargetPick=null,t.modal.classList.remove("visible","equipment-popover"),o()}function a(D=zt){Ke(e),e.cardDraft.choices=To(e,D)}function c(){Ke(e),!e.cardDraft.choices.length&&e.cardDraft.picksRemaining>0&&a(zt)}function h(D){Lr(e,D),e.cardDraft.picksRemaining=Math.max(0,(e.cardDraft.picksRemaining||1)-1),e.cardTargetPick=null,c(),e.cardDraft.picksRemaining<=0?i():ue()}function g(D){Lr(e,D),e.cardDraft.picksRemaining=Math.max(0,(e.cardDraft.picksRemaining||1)-1)+Ue.extraCardsAddedPicks,e.cardDraft.choices.push(...To(e,2)),ue()}function C(D,U){let ee=dn(e,"dudRefund")*50;if(ee>0){let O=je(e,ee);e.stats.dudRefundIncome=(e.stats.dudRefundIncome||0)+O,e.totals.dudRefundIncome=(e.totals.dudRefundIncome||0)+O,H(e,f.x+f.cols*f.cell/2,f.y-30,`DUD REFUND +${x(O)}`,b.green)}Yt(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)"}),U&&(U.style.setProperty("--fling-x",`${260+Math.random()*260}px`),U.style.setProperty("--fling-y",`${-260-Math.random()*170}px`),U.style.setProperty("--fling-r",`${(Math.random()<.5?-1:1)*(52+Math.random()*38)}deg`),U.classList.add("flung")),window.setTimeout(()=>{Lr(e,D),c(),ue()},340)}function P(D,U=null){if(D.type==="dud")return C(D,U);if(D.id==="extraCards")return g(D);if(D.type==="equipmentUpgrade"||D.type==="cellAction")return L(D);zs(e,D),h(D)}function L(D){let U=et(e,D);U.length&&(e.cardTargetPick={pending:!0,cardId:D.id,mode:D.target==="blockedCell"?"blockedCell":"equipment",targetKeys:U.map(wt),remaining:D.id==="dynamite"?Math.min(10,U.length):1,used:0},t.modal.classList.remove("visible","equipment-popover"),o())}function K(D){if(!D||D.type!=="blockedCell")return!1;let U=I(D.col,D.row);if(!e.blockedCells?.has(U))return!1;e.blockedCells.delete(U);let ee=$(D.col,D.row);return vt(e,ee.x,ee.y),at(e,ee.x,ee.y,b.danger,62),bt(e,ee.x,ee.y,18,b.warn),mo(e,ee.x,ee.y,8),Yt(e,16,.35),H(e,ee.x,ee.y-18,"BOOM",b.danger),Me(e),e.cardTargetPick.remaining=Math.max(0,(e.cardTargetPick.remaining||1)-1),e.cardTargetPick.used=Math.max(0,e.cardTargetPick.used||0)+1,e.cardTargetPick.targetKeys=et(e,"dynamite").map(wt),o(),!0}function j(D){if(!e.cardTargetPick?.pending)return!1;let U=Xt(e.cardTargetPick.cardId),ee=sc(e,D);return U?U.id==="dynamite"?(!ee||!K(ee)||((e.cardTargetPick.remaining||0)<=0||!et(e,"dynamite").length)&&h(U),!0):ee?(Vs(e,ee),h(U),!0):(Z(),!0):(Z(),!0)}function Z(){if(!e.cardTargetPick?.pending)return;let D=e.cardTargetPick,U=Xt(D.cardId);if(U?.id==="dynamite"&&(D.used||0)>0){h(U);return}e.cardTargetPick=null,ue()}function Q(){let D=Math.max(0,e.cardDraft?.freeRerolls||0);if(D>0){e.cardDraft.freeRerolls=D-1,e.cardDraft.rerolls+=1,a(zt),ue();return}let U=Os(e);e.cash${U.alt||`:``}function le(D,U){let ee=document.createElement("button");ee.type="button";let O=D.type==="dud"?"dud":D.rarity==="ultraRare"?"ultra-rare":D.rarity==="rare"?"rare":"common";ee.className=`card-choice ${O}`;let G=D.type==="dud"?"MISS":D.rarity==="ultraRare"?"ULTRA RARE":D.rarity==="rare"?"RARE":"COMMON",Y=[G,...(D.tags||[]).filter(ie=>ie!==G)].slice(0,5);return ee.innerHTML=`${pe(D)}
${D.title}
${Y.map(ie=>`${ie}`).join("")}
${ac(e,D)}
`,ee.addEventListener("click",()=>P(D,ee)),ee.dataset.index=String(U),ee}function ue(){Ke(e),e.cardTargetPick=null,e.cardDraft?.pending||r(),e.cardDraft.choices?.length||a(zt);let D=e.cardDraft.choices||[],U=Os(e),ee=Math.max(0,e.cardDraft?.freeRerolls||0),O=Math.max(1,e.cardDraft.picksRemaining||1);t.modalTitle.textContent="Choose Upgrade Card",t.modalBody.innerHTML=`

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

`;let G=t.modalBody.querySelector("#cardChoices");D.forEach((De,me)=>G.appendChild(le(De,me))),t.modalActions.innerHTML="";let Y=document.createElement("button");Y.type="button",Y.className="facility-action warn reroll-button",Y.textContent=ee>0?`Reroll (FREE x${ee})`:`Reroll ${x(U)}`,Y.disabled=ee<=0&&e.cash0?`${ee} free reroll${ee===1?"":"s"} remaining.`:Y.disabled?"Not enough cash":"",Y.addEventListener("click",Q),t.modalActions.appendChild(Y);let ie=document.createElement("button");ie.type="button",ie.className="facility-action",ie.textContent="No Card",ie.title="Skip this upgrade pick and enter Build phase.",ie.addEventListener("click",i),t.modalActions.appendChild(ie),t.modal.classList.remove("equipment-popover"),t.modal.classList.add("visible")}return{prepareDraft:r,showDraft:ue,finishDraft:i,chooseTargetAtPoint:j,cancelTargetPick:Z}}var js=[[{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 lc(e){let t=js[e]||js[0];return{left:{...t[0]},right:{...t[1]}}}function Qs(e,t,n){let o={type:"eggFarm",id:e.nextId++,col:t,row:n,level:1,nextSpawn:2,lastInterval:2,extraEggOutlet:0,price:Rt.eggFarm,builtSession:e.buildSession};return o.nextSpawn=Wt(o),o.lastInterval=o.nextSpawn,o}function Js(e,t,n,o){let r=o==="auto"?Te.autoScanner.price:Te.manualScanner.price,i=new Set(e.scanners.filter(c=>c.kind==="manual").map(c=>c.slot)),a=0;for(;i.has(a);)a+=1;return{type:"scanner",id:e.nextId++,kind:o,slot:o==="manual"?a:null,role:o==="manual"?a%2:0,col:t,row:n,level:1,queue:[],cooldown:0,price:r,builtSession:e.buildSession,autoMode:"standard",keys:o==="manual"?lc(a):null}}function cc(e,t,n){let o=n.x-t.x,r=n.y-t.y,i=o*o+r*r,a=i>0?Math.max(0,Math.min(1,((e.x-t.x)*o+(e.y-t.y)*r)/i)):0,c=t.x+o*a,h=t.y+r*a;return Math.hypot(e.x-c,e.y-h)}function uc(e){let t=[],n=e?.ownedCells?.size?[...e.ownedCells].map(o=>{let[r,i]=String(o).split(",").map(Number);return{col:r,row:i}}):Array.from({length:f.cols*f.rows},(o,r)=>({col:r%f.cols,row:Math.floor(r/f.cols)}));for(let o of n){let r=f.x+o.col*f.cell,i=f.y+o.row*f.cell,a=r+f.cell,c=i+f.cell;Ye(e,o.col-1,o.row)||t.push({side:"left",entry:o,a:{x:r,y:i},b:{x:r,y:c}}),Ye(e,o.col+1,o.row)||t.push({side:"right",entry:o,a:{x:a,y:i},b:{x:a,y:c}}),Ye(e,o.col,o.row-1)||t.push({side:"top",entry:o,a:{x:r,y:i},b:{x:a,y:i}}),Ye(e,o.col,o.row+1)||t.push({side:"bottom",entry:o,a:{x:r,y:c},b:{x:a,y:c}})}return t}function Eo(e,t=null){let n=uc(t);if(n.length){let L=n.map(K=>({...K,distance:cc(e,K.a,K.b)})).sort((K,j)=>K.distance-j.distance)[0];return{side:L.side,entry:{...L.entry}}}let o=(e.x-f.x)/f.cell,r=(e.y-f.y)/f.cell,i=Math.max(0,Math.min(f.cols-1,Math.round(o-.5))),a=Math.max(0,Math.min(f.rows-1,Math.round(r-.5))),c=Math.abs(e.x-f.x),h=Math.abs(e.x-(f.x+f.cols*f.cell)),g=Math.abs(e.y-f.y),C=Math.abs(e.y-(f.y+f.rows*f.cell)),P=[["left",c],["right",h],["top",g],["bottom",C]].sort((L,K)=>L[1]-K[1])[0][0];return P==="left"?{side:P,entry:{col:0,row:a}}:P==="right"?{side:P,entry:{col:f.cols-1,row:a}}:P==="top"?{side:P,entry:{col:i,row:0}}:{side:P,entry:{col:i,row:f.rows-1}}}function Ur(e){return e?.baseId||e?.id||""}function yn(e){return e?.gridPlaced&&(e.wCells=Math.max(1,e.wCells||3),e.hCells=Math.max(1,e.hCells||3),e.x=f.x+e.col*f.cell,e.y=f.y+e.row*f.cell,e.w=e.wCells*f.cell,e.h=e.hCells*f.cell,Ur(e)==="sausageMaker"&&(e.entry={col:e.col-1,row:e.row+Math.floor(e.hCells/2)},e.side="grid")),e}function Ro(e){if(!e?.gridPlaced)return[];let t=[];for(let n=0;n<(e.hCells||3);n+=1)for(let o=0;o<(e.wCells||3);o+=1)t.push({col:e.col+o,row:e.row+n});return t}function dc(e,t){if(Ur(e)!=="mixer")return;let n=e.baseW||e.w||168,o=e.baseH||e.h||110;e.baseW=n,e.baseH=o;let r=t==="top"||t==="bottom";e.w=r?Math.max(n,o):Math.min(n,o),e.h=r?Math.min(n,o):Math.max(n,o)}function Po(e,t="input"){if(!e)return null;let n=Ur(e);return n==="mixer"?e.side==="top"||e.side==="bottom"?t==="input"?{x:e.x+e.w/2,y:e.y}:{x:e.x+e.w/2,y:e.y+e.h}:t==="input"?{x:e.x,y:e.y+e.h/2}:{x:e.x+e.w,y:e.y+e.h/2}:n==="sausageMaker"&&e.gridPlaced?t==="input"?{x:e.x,y:e.y+e.h/2}:{x:e.x+e.w,y:e.y+e.h/2}:{x:e.x+e.w/2,y:e.y+e.h/2}}function Ao(e,t,n){dc(e,n);let o={x0:f.x+t.col*f.cell,y0:f.y+t.row*f.cell,x1:f.x+(t.col+1)*f.cell,y1:f.y+(t.row+1)*f.cell},r={x:(o.x0+o.x1)/2,y:(o.y0+o.y1)/2};return e.entry={...t},e.side=n,n==="left"?(e.x=o.x0-e.w-18,e.y=r.y-e.h/2):n==="right"?(e.x=o.x1+18,e.y=r.y-e.h/2):n==="top"?(e.x=r.x-e.w/2,e.y=o.y0-e.h-18):(e.x=r.x-e.w/2,e.y=o.y1+18),e}function Zs(e,t,n,o){let{entry:r,side:i}=Eo(n,e),a=Te[t],c=a?{name:a.name,w:a.body?.w||168,h:a.body?.h||110,price:a.price}:{name:t[0].toUpperCase()+t.slice(1),w:168,h:110,price:o},h={type:"facility",id:t,name:c.name,x:0,y:0,w:c.w,h:c.h,baseW:c.w,baseH:c.h,level:1,price:o||c.price,builtSession:e.buildSession};return Ao(h,r,i)}function ea(e,t,n,o,r){let i=Te[t],a={type:"facility",id:t,name:i?.name||t,baseId:t,col:n,row:o,wCells:t==="sausageMaker"?3:1,hCells:t==="sausageMaker"?3:1,gridPlaced:!0,level:1,price:r||i?.price||0,builtSession:e.buildSession};return yn(a)}function jt(e){return e?[{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 ta(e,t,n){return{type:"maintenanceRoom",id:e.nextId++,col:t,row:n,price:Rt.maintenanceRoom,builtSession:e.buildSession,wCells:2,hCells:2}}function fc(e){return Math.random()({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}}var pc=!0,yc={chickMale:"./assets/images/chick_male.png",chickFemale:"./assets/images/chick_female.png",poop:"./assets/images/poop.png",conveyorStraightH:"./assets/dark_factory/facilities/conveyor/conveyor_straight_h_16.png",conveyorStraightV:"./assets/dark_factory/facilities/conveyor/conveyor_straight_v_16.png",conveyorTurnNe:"./assets/dark_factory/facilities/conveyor/conveyor_turn_ne_16.png",conveyorTurnNw:"./assets/dark_factory/facilities/conveyor/conveyor_turn_nw_16.png",conveyorTurnSe:"./assets/dark_factory/facilities/conveyor/conveyor_turn_se_16.png",conveyorTurnSw:"./assets/dark_factory/facilities/conveyor/conveyor_turn_sw_16.png",boostConveyorStraightH:"./assets/dark_factory/facilities/conveyor/boost_conveyor_straight_h_16.png",boostConveyorStraightV:"./assets/dark_factory/facilities/conveyor/boost_conveyor_straight_v_16.png",boostConveyorTurnNe:"./assets/dark_factory/facilities/conveyor/boost_conveyor_turn_ne_16.png",boostConveyorTurnNw:"./assets/dark_factory/facilities/conveyor/boost_conveyor_turn_nw_16.png",boostConveyorTurnSe:"./assets/dark_factory/facilities/conveyor/boost_conveyor_turn_se_16.png",boostConveyorTurnSw:"./assets/dark_factory/facilities/conveyor/boost_conveyor_turn_sw_16.png",scannerManual:"./assets/dark_factory/facilities/machines/manual_scanner_32x48.png",scannerAuto:"./assets/dark_factory/facilities/machines/auto_scanner_32x48.png",eggFarm:"./assets/dark_factory/facilities/machines/egg_farm_16.png",mixer:"./assets/dark_factory/facilities/machines/mixer_32.png",shredder:"./assets/dark_factory/facilities/machines/shredder_32.png",truck:"./assets/dark_factory/facilities/vehicles/truck_32.png",maintenanceRoom:"./assets/dark_factory/facilities/utility/maintenance_room_32.png",repairman:"./assets/dark_factory/facilities/utility/repairman_16.png",obstacleCrate:"./assets/dark_factory/facilities/utility/obstacle_crate_16.png"},Pe={};for(let[e,t]of Object.entries(yc)){let n=new Image;n.loaded=!1,n.onerror=()=>{n.loaded=!1},pc&&t&&(n.onload=()=>{n.loaded=!0},n.src=t),Pe[e]=n}function qr(e){return e.frameCache||(e.frameCache={}),e.frameCache.draw||(e.frameCache.draw={}),e.frameCache.draw}function Yr(e,t){let n=document.createElement("canvas");return n.width=Math.max(1,Math.ceil(e)),n.height=Math.max(1,Math.ceil(t)),n}function sa(e,t,n,o){e.imageSmoothingEnabled=!1,e.clearRect(0,0,t.width,t.height),Sc(e,t,n),e.save();let r=n.shake.time>0?z(-n.shake.strength,n.shake.strength):0,i=n.shake.time>0?z(-n.shake.strength,n.shake.strength):0,a=n.view?.scale||1;e.translate((n.view?.x||0)+r,(n.view?.y||0)+i),e.scale(a,a),Mc(e,n),xc(e,n),Ic(e,n),Uc(e,n),jc(e,n),Qc(e,n),Wc(e,n),Kc(e,n),mc(e,n),ou(e,n,o.activeQueuedChick),fu(e,n),lu(e,t,n),au(e,n,o.selectedObject,o.selectedTitle),cu(e,n),uu(e,n),du(e,n),e.restore(),yu(e,n,t)}function hc(e){if(e.type==="eggFarm"){let n=$(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 mc(e,t){if(t.phase!=="build")return;let n=an(t);for(let o of n){let r=hc(o);e.save(),e.fillStyle="rgba(196,36,36,.22)",e.strokeStyle=b.danger,e.lineWidth=5,e.setLineDash([10,6]),X(e,r.x,r.y,r.w,r.h,!0,!0),e.setLineDash([]),e.fillStyle=b.danger,e.strokeStyle=b.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 X(e,t,n,o,r,i=!0,a=!0){e.beginPath(),e.rect(t,n,o,r),i&&e.fill(),a&&e.stroke()}function Hr(e,t,n,o,r=b.ink){e.save(),e.font="900 12px ui-monospace, monospace",e.textAlign="center",e.lineWidth=4,e.strokeStyle=b.white,e.fillStyle=r,e.strokeText(o,t,n),e.fillText(o,t,n),e.restore()}function Qt(e,t,n,o,r,i){if(!t?.loaded)return!1;let a=e.imageSmoothingEnabled;return e.imageSmoothingEnabled=!1,e.drawImage(t,Math.round(n),Math.round(o),Math.round(r),Math.round(i)),e.imageSmoothingEnabled=a,!0}function bc(e,t,n,o,r,i,a=null,c=1){e.fillStyle=i,e.fillRect(Math.round(t),Math.round(n),Math.round(o),Math.round(r)),a&&(e.strokeStyle=a,e.lineWidth=c,e.strokeRect(Math.round(t)+.5,Math.round(n)+.5,Math.round(o)-1,Math.round(r)-1))}function vc(e,t){let n=(Math.imul(e+37,73856093)^Math.imul(t+71,19349663))>>>0;return n^=n>>>13,n>>>0}function wc(e,t,n,o,r){let i=vc(o,r);if(i%67!==0)return;let a=10+(i>>>4)%Math.max(1,f.cell-22),c=10+(i>>>9)%Math.max(1,f.cell-22),h=(i>>>14)%16*Math.PI/16;e.save(),e.globalAlpha=.38,e.fillStyle="#4a8d37",e.strokeStyle="rgba(30,66,27,.34)",e.lineWidth=1,e.beginPath();for(let C=0;C<8;C+=1){let P=h+C*Math.PI/4,L=3.5+(i>>>C+2&3),K=t+a+Math.cos(P)*L*1.35,j=n+c+Math.sin(P)*L*.75;C===0?e.moveTo(K,j):e.lineTo(K,j)}e.closePath(),e.fill(),e.stroke();let g=4+(i>>>24)%5;for(let C=0;C>>C+8&3)*.18,L=10+(i>>>C+11&10),K=.9+(i>>>C+17&3)*.35;e.beginPath(),e.arc(t+a+Math.cos(P)*L,n+c+Math.sin(P)*L*.72,K,0,Math.PI*2),e.fill()}e.fillStyle="rgba(151,218,101,.26)",e.beginPath(),e.arc(t+a-2,n+c-2,1.7,0,Math.PI*2),e.fill(),e.restore()}function oa(e,t){e.fillStyle=b.bg,e.fillRect(0,0,t.width,t.height),e.save(),e.globalAlpha=.85;for(let n=0;na.col)),o=Math.max(...t.map(a=>a.col)),r=Math.min(...t.map(a=>a.row)),i=Math.max(...t.map(a=>a.row));return{x:f.x+n*f.cell-6,y:f.y+r*f.cell-6,w:(o-n+1)*f.cell+12,h:(i-r+1)*f.cell+12}}function Mc(e,t){let n=qr(t),o=gc(t);if(!n.grid||n.grid.signature!==o){let a=Cc(t),c=Yr(a.w,a.h),h=c.getContext("2d");h.translate(-a.x,-a.y),kc(h,t),n.grid={signature:o,bounds:a,layer:c}}let{bounds:r,layer:i}=n.grid;e.drawImage(i,r.x,r.y)}function xc(e,t){if(t.phase!=="build"||t.cardDraft?.pending||t.cardTargetPick?.pending)return;let n=ro(t),o=t.cash>=n,r=i=>{let a=dt(i);return Object.values(t.facilities||{}).some(c=>{let h=c?.baseId||c?.id;return["mixer","trash","truck"].includes(h)?!(a.x+a.w<=c.x||c.x+c.w<=a.x||a.y+a.h<=c.y||c.y+c.h<=a.y):!1})};for(let i of Ut(t)){let a=r(i),c=dt(i);e.save(),e.fillStyle=a?"rgba(112,130,118,.34)":o?"rgba(118,226,142,.36)":"rgba(255,255,255,.18)",e.strokeStyle=a?"#708278":o?"#6bd579":b.muted,e.lineWidth=5,e.setLineDash([16,10]),X(e,c.x,c.y,c.w,c.h,!0,!0),e.setLineDash([]);let h=tr(i),g=h.cx,C=h.cy;e.fillStyle="rgba(255,255,255,.94)",e.strokeStyle=b.ink,e.lineWidth=4,X(e,h.x,h.y,h.w,h.h,!0,!0),e.fillStyle=a?"#7c9285":o?b.green:b.danger,e.font="900 16px ui-monospace, monospace",e.textAlign="center";let P=i.direction==="left"?"BUY LEFT LOT":i.direction==="right"?"BUY RIGHT LOT":i.direction==="down"?"BUY LOWER LOT":"BUY UPPER LOT";e.fillText(P,g,C-12),e.fillStyle=b.ink,e.font="900 16px ui-monospace, monospace",e.fillText(`${x(n)} / 10\xD710`,g,C+15),e.restore()}}function kt(e,t,n,o,r,i,a=0){let c=un(i);c<.5||(e.save(),e.globalAlpha=Math.min(.62,.12+c*.54),e.fillStyle="#6b4a23",a>0?(e.beginPath(),e.roundRect?.(t,n,o,r,a),e.roundRect?e.fill():X(e,t,n,o,r,!0,!1)):X(e,t,n,o,r,!0,!1),e.globalAlpha=Math.min(.9,.25+c*.6),e.strokeStyle="#3a2a16",e.lineWidth=Math.max(2,Math.floor(2+c*5)),e.setLineDash([5,6]),X(e,t+3,n+3,o-6,r-6,!1,!0),e.restore())}function Wr(e,t){let n=e.componentLookup?.get(t);return(n?e.congestion.get(n):null)?.ratio||0}function Tc(e,t,n={}){let o=Wn(e,t).sort(),i=n.kind==="boostConveyor"?"boostConveyor":"conveyor",a=c=>o.includes(c);if(o.length===2){if(a("left")&&a("right"))return Pe[`${i}StraightH`];if(a("up")&&a("down"))return Pe[`${i}StraightV`];if(a("up")&&a("right"))return Pe[`${i}TurnNe`];if(a("up")&&a("left"))return Pe[`${i}TurnNw`];if(a("down")&&a("right"))return Pe[`${i}TurnSe`];if(a("down")&&a("left"))return Pe[`${i}TurnSw`]}return o.length===1?a("up")||a("down")?Pe[`${i}StraightV`]:Pe[`${i}StraightH`]:a("up")||a("down")?Pe[`${i}StraightV`]:Pe[`${i}StraightH`]}function Ec(e){let t=Object.entries(Pe).filter(([o])=>o.includes("conveyor")||o.includes("Conveyor")).map(([o,r])=>`${o}:${r.loaded?1:0}`).join("|"),n=[...e.conveyorTiles].sort().map(o=>{let r=e.conveyorMeta.get(o)||{};return`${o}:${r.kind||""}:${r.dir||""}:${(r.outDirs||[]).join(",")}:${r.branchMode||""}`}).join("|");return`${e.routingVersion||0};${t};${n}`}function Rc(e){if(!e.conveyorTiles?.size)return{x:0,y:0,w:1,h:1};let t=[...e.conveyorTiles].map(re),n=Math.min(...t.map(a=>a.col)),o=Math.max(...t.map(a=>a.col)),r=Math.min(...t.map(a=>a.row)),i=Math.max(...t.map(a=>a.row));return{x:f.x+n*f.cell-36,y:f.y+r*f.cell-36,w:(o-n+1)*f.cell+72,h:(i-r+1)*f.cell+72}}function Pc(e){let t=[],n=new Set;for(let i of e.conveyorTiles){let{col:a,row:c}=re(i);for(let h of fe){let g={col:a+h.dc,row:c+h.dr},C=I(g.col,g.row);if(!e.conveyorTiles.has(C))continue;let P=[i,C].sort().join("|");n.has(P)||(n.add(P),t.push({fromKey:i,toKey:C,a:$(a,c),b:$(g.col,g.row)}))}}let o=Nc(e),r=[...e.conveyorTiles].map(i=>{let a=re(i),c=$(a.col,a.row),h=e.conveyorMeta.get(i)||{},g=Wn(e,i);return{k:i,c,meta:h,connectedDirs:g,branchDirs:Dc(e,i,g),markers:o.get(i)||[],sprite:Tc(e,i,h)}});return{edges:t,tiles:r}}function Ac(e,t){e.save(),e.lineCap="round",e.lineJoin="round";for(let n of[{width:31,color:"#0b0c0f",alpha:1},{width:23,color:"#444b56",alpha:1},{width:15,color:"#171a1f",alpha:1}]){e.globalAlpha=n.alpha,e.strokeStyle=n.color,e.lineWidth=n.width;for(let o of t.edges)e.beginPath(),e.moveTo(o.a.x,o.a.y),e.lineTo(o.b.x,o.b.y),e.stroke()}e.globalAlpha=1;for(let n of t.tiles){let{c:o,meta:r,sprite:i}=n;e.fillStyle="rgba(0,0,0,.35)",e.strokeStyle="rgba(255,184,39,.34)",e.lineWidth=1,X(e,o.x-20,o.y-20,40,40,!0,!0),Qt(e,i,o.x-19,o.y-19,38,38)||(e.fillStyle=r.kind==="boostConveyor"?"#315e92":"#454b55",e.strokeStyle=b.ink,e.lineWidth=2,X(e,o.x-18,o.y-18,36,36,!0,!0))}e.restore()}function Ic(e,t){let n=qr(t),o=Ec(t);if(!n.conveyors||n.conveyors.signature!==o){let c=Pc(t),h=Rc(t),g=Yr(h.w,h.h),C=g.getContext("2d");C.translate(-h.x,-h.y),Ac(C,c),n.conveyors={signature:o,bounds:h,layer:g,data:c}}let{bounds:r,layer:i,data:a}=n.conveyors;e.drawImage(i,r.x,r.y),e.save(),e.lineCap="round",e.lineJoin="round";for(let c of a.edges){let h=Math.max(Wr(t,c.fromKey),Wr(t,c.toKey)),g=Math.max(0,(h-.5)/.5);e.strokeStyle=In(b.green,b.danger,g),e.globalAlpha=h>.5?.35+g*.55:.8,e.lineWidth=6,e.beginPath(),e.moveTo(c.a.x,c.a.y),e.lineTo(c.b.x,c.b.y),e.stroke()}e.globalAlpha=1;for(let c of a.tiles){let{k:h,c:g,meta:C}=c,P=Wr(t,h);P>.5&&(e.fillStyle="rgba(196,36,36,.28)",e.strokeStyle=b.danger,e.lineWidth=3,X(e,g.x-20,g.y-20,40,40,!0,!0)),$c(e,t,g,h,C,P),kt(e,g.x-18,g.y-18,36,36,{meta:C}),gt(e,t,"conveyor",h,g.x,g.y,38,38),_c(e,g,c.markers),Oc(e,g,C,c.branchDirs,c.connectedDirs),P>.5&&Hr(e,g.x,g.y-15,`${Math.floor(P*100)}%`,b.danger)}e.restore()}function $c(e,t,n,o,r={},i=0){let a=r.kind==="boostConveyor",h=[...new Set([...Array.isArray(r.outDirs)?r.outDirs:[],r.dir].filter(Boolean))][0]||"right",g=h==="up"||h==="down",C=h==="left"||h==="up",P=pn(t,o)*_n(t,o),K=(typeof performance<"u"?performance.now()/1e3:0)*P,j=((C?-K:K)%10+10)%10;e.save(),e.beginPath(),e.rect(n.x-18,n.y-18,36,36),e.clip();let Z=a?"rgba(20,126,124,.62)":"rgba(42,47,54,.72)";if(e.fillStyle=Z,e.fillRect(n.x-18,n.y-18,36,36),e.strokeStyle=i>.5?"rgba(238,83,61,.90)":"rgba(184,190,198,.92)",e.lineWidth=4,g)for(let Q=n.y-28+j;Qe.conveyorTiles.has(I(n.col+o.dc,n.row+o.dr))).map(o=>o.name)}function la(e,t,n){let o=fe.find(a=>a.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 ca(e,t,n,o=Wn(e,t)){if(o.length<3)return n;let r=re(t),i=new Set(o.filter(h=>la(e,r,h))),a=o.filter(h=>!i.has(h));if(i.size)return a;let c=n.filter(h=>o.includes(h));return c.length>=2?[...new Set(c)]:o}function Fc(e,t,n,o=Wn(e,t),r="random"){let i=ca(e,t,n,o);return o.length>=3&&r&&r!=="random"&&i.includes(r)?[r]:i}function Dc(e,t,n=Wn(e,t)){let o=e.conveyorMeta?.get?.(t)||{},r=[...new Set([...Array.isArray(o.outDirs)?o.outDirs:[],o.dir].filter(Boolean))];return n.length<3?[]:ca(e,t,r,n)}function Bc(e,t,n){let o=re(t),r=fe.filter(c=>e.conveyorTiles.has(I(o.col+c.dc,o.row+c.dr))).map(c=>c.name),i=[...new Set([...Array.isArray(n.outDirs)?n.outDirs:[],n.dir].filter(Boolean))].filter(c=>r.includes(c));if(r.length===1&&(!i.length||i.includes(r[0]))&&la(e,o,r[0]))return[];let a=r;return i.length?i:a}function Oc(e,t,n,o,r=o){if(r.length<3||o.length<2)return;e.save();let i=o.includes(n.branchMode)?n.branchMode:"random",a=i!=="random";if(e.lineJoin="round",e.lineCap="round",e.lineWidth=2,e.fillStyle="rgba(18,21,26,.94)",e.strokeStyle=a?b.warn:b.green,e.lineWidth=4,X(e,t.x-18,t.y-18,36,36,!0,!0),e.strokeStyle="#ffd36a",e.lineWidth=3,e.fillStyle="#020304",i!=="random"){let c=fe.find(h=>h.name===i);Un(e,t.x,t.y-4,c?.angle||0,8,5)}else e.beginPath(),e.moveTo(t.x-10,t.y),e.lineTo(t.x-2,t.y),e.lineTo(t.x+8,t.y-8),e.moveTo(t.x-2,t.y),e.lineTo(t.x+8,t.y+8),e.stroke(),Un(e,t.x+9,t.y-9,-Math.PI/4,4,3),Un(e,t.x+9,t.y+9,Math.PI/4,4,3);i==="random"&&(e.font="900 9px ui-monospace, monospace",e.textAlign="center",e.textBaseline="middle",e.lineWidth=3,e.strokeStyle="#020304",e.fillStyle=b.warn,e.strokeText("RND",t.x,t.y+11),e.fillText("RND",t.x,t.y+11)),e.restore()}function Lc(e,t,n){if(!n)return[];let o=re(t),r=fe.filter(c=>e.conveyorTiles.has(I(o.col+c.dc,o.row+c.dr))).map(c=>c.name),i=[...new Set([...Array.isArray(n.outDirs)?n.outDirs:[],n.dir].filter(Boolean))].filter(c=>r.includes(c));return Fc(e,t,i.length?i:Bc(e,t,n),r,n.branchMode||"random").map(c=>fe.find(h=>h.name===c)).filter(Boolean)}function Nc(e){let t=new Map;for(let[n,o]of e.conveyorMeta||[]){let r=Lc(e,n,o);r.length&&t.set(n,r)}return t}function _c(e,t,n){let o=n.slice(0,4);o.length&&(e.save(),e.globalAlpha=1,o.forEach(r=>{let i=t.x+r.dc*11,a=t.y+r.dr*11;e.fillStyle="rgba(255,205,83,.96)",Un(e,i,a,r.angle,9,7),e.fillStyle="#020304",Un(e,i,a,r.angle,6.5,4.6)}),e.restore())}function Un(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 Gc(e,t,n,o,r){let i=ge(t),a=null;if(n==="inputA"&&(a={x:i.x,y:i.y-40}),n==="left"&&(a={x:i.x-42,y:i.y+10}),n==="right"&&(a={x:i.x+42,y:i.y+10}),!a)return;let c=n==="inputA"?{x:o.x,y:a.y}:{x:a.x,y:o.y};e.save(),e.lineCap="square",e.lineJoin="bevel",e.strokeStyle="#050609",e.lineWidth=15,e.beginPath(),e.moveTo(o.x,o.y),e.lineTo(c.x,c.y),e.lineTo(a.x,a.y),e.stroke(),e.strokeStyle="#596473",e.lineWidth=9,e.beginPath(),e.moveTo(o.x,o.y),e.lineTo(c.x,c.y),e.lineTo(a.x,a.y),e.stroke(),e.strokeStyle=r||"#7b8795",e.lineWidth=3,e.beginPath(),e.moveTo(o.x,o.y),e.lineTo(c.x,c.y),e.lineTo(a.x,a.y),e.stroke(),e.fillStyle=r||"#7b8795",e.strokeStyle="#050609",e.lineWidth=3,X(e,a.x-9,a.y-7,18,14,!0,!0),e.restore()}function Uc(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=ur(n),r=[{type:"inputA",p:Fe(n,"inputA"),label:"IN",color:b.green},{type:"left",p:Fe(n,"left"),label:fr(o.left),color:pr(o.left)},{type:"right",p:Fe(n,"right"),label:fr(o.right),color:pr(o.right)}];for(let i of r){if(!i.p)continue;let a=$(i.p.col,i.p.row);Gc(e,n,i.type,a,i.color);let c=Gi(t,n,i.type||(i.label==="IN"?"inputA":null))||t.conveyorTiles.has(I(i.p.col,i.p.row));e.fillStyle=c?In("#1a1d23",i.color,.24):"#3a1e22",e.strokeStyle=c?i.color:b.danger,X(e,a.x-18,a.y-12,36,24,!0,!0),e.fillStyle=b.white,e.fillText(i.label,a.x,a.y+4)}}e.restore()}function Wc(e,t){for(let n of t.eggFarms)Hc(e,n,t)}function Hc(e,t,n){let o=$(t.col,t.row);e.save(),e.fillStyle="#5a6470",e.strokeStyle=b.ink,e.lineWidth=3,X(e,o.x-27,o.y-27,54,54,!0,!0),e.fillStyle="#6f5130",e.strokeStyle="#2f2112",e.lineWidth=3,e.beginPath(),e.ellipse(o.x,o.y+3,19,12,0,0,Math.PI*2),e.fill(),e.stroke(),e.strokeStyle="#a77b4b",e.lineWidth=2;for(let c=-16;c<=16;c+=6)e.beginPath(),e.moveTo(o.x-18,o.y+c*.1),e.quadraticCurveTo(o.x+c,o.y-10+Math.abs(c)*.2,o.x+18,o.y+c*.08),e.stroke();let r=[{x:-8,y:1},{x:0,y:-3},{x:8,y:2}];for(let c of r)e.fillStyle="#e8e2cf",e.strokeStyle="#8d8475",e.lineWidth=1.5,e.beginPath(),e.ellipse(o.x+c.x,o.y+c.y,4.8,6.2,.2,0,Math.PI*2),e.fill(),e.stroke();let i=1+Math.max(0,Number(t.extraEggOutlet)||0);e.fillStyle="rgba(255,255,255,.92)",e.strokeStyle=b.ink,e.lineWidth=2,X(e,o.x+8,o.y+11,30,18,!0,!0),e.fillStyle=b.ink,e.font="900 10px ui-monospace, monospace",e.textAlign="center",e.fillText(`O${i}`,o.x+23,o.y+23),e.fillStyle="#d5e35a",e.font="900 9px ui-monospace, monospace",e.fillText(`L${t.level}`,o.x-13,o.y-15);let a=Math.max(0,Math.min(1,t.shutterProgress||0));if(a>0){let c=54*a;e.fillStyle="rgba(10,12,15,.86)",X(e,o.x-27,o.y-27,54,c,!0,!1),e.strokeStyle="rgba(230,237,242,.64)",e.lineWidth=2;for(let h=o.y-23;h=1&&(e.fillStyle=b.white,e.font="900 12px ui-monospace, monospace",e.fillText("SHUT",o.x,o.y+4))}kt(e,o.x-28,o.y-28,56,56,t),gt(e,n,"eggFarm",t.id,o.x,o.y,68,68),e.restore()}function Kc(e,t){for(let n of t.scanners)qc(e,n,t)}function qc(e,t,n){let o=ge(t);if(e.save(),t.kind==="manual"&&(n.manualCombo?.count||0)>=10){let h=n.manualCombo.count;e.save(),e.globalAlpha=Math.min(.55,.16+h/120),e.strokeStyle=h>=30?b.warn:b.green,e.lineWidth=Math.min(12,4+Math.floor(h/10)),e.shadowColor=e.strokeStyle,e.shadowBlur=Math.min(24,8+h),X(e,o.x-48,o.y-48,96,96,!1,!0),e.restore()}let r=t.kind==="auto"?Pe.scannerAuto:Pe.scannerManual;if(Qt(e,r,o.x-44,o.y-44,88,88)){t.kind==="auto"?kt(e,o.x-44,o.y-44,88,88,t):ra(e,t,o),gt(e,n,"scanner",t.id,o.x,o.y,96,96),e.restore();return}e.fillStyle=t.kind==="auto"?"#d8ffe2":b.white,e.strokeStyle=b.ink,e.lineWidth=4,X(e,o.x-44,o.y-44,88,88,!0,!0),e.fillStyle=t.kind==="auto"?b.green:b.ink,e.font="900 16px ui-monospace, monospace",e.textAlign="center",e.fillText(t.kind==="auto"?"AUTO":`S${(t.slot??0)+1}`,o.x,o.y-24),e.font="900 9px ui-monospace, monospace";let i=t.keys?.left?.label||(t.slot===0?"A":"Left"),a=t.keys?.right?.label||(t.slot===0?"D":"Right");e.fillText(t.kind==="auto"?"2.1 FARMS":`${i}/${a}`,o.x,o.y-8);let c=t.queue.length;t.kind==="manual"?ra(e,t,o):(e.fillStyle=c>0?b.green:b.muted,e.font="900 10px ui-monospace, monospace",e.fillText(`Q:${c} CD:${t.cooldown.toFixed(1)}`,o.x,o.y+35),kt(e,o.x-44,o.y-44,88,88,t)),gt(e,n,"scanner",t.id,o.x,o.y,96,96),e.restore()}function ra(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,a=n.x-30,c=n.y+8;e.save(),e.lineWidth=3,e.fillStyle="#f7fff5",e.strokeStyle=b.ink,X(e,a-4,c-4,68,25,!0,!0),e.font="900 19px ui-monospace, monospace",e.textAlign="center";for(let h=0;h MEAT","meat","connect OUT to sausage machine"),kt(e,n.x,n.y,n.w,n.h,n),gt(e,t,"facility",n.id,n.x+n.w/2,n.y+n.h/2,n.w+12,n.h+12),e.restore()}function eu(e,t,n=t.facilities.trash){e.save(),zr(e,n),Hn(e,n.x,n.y,n.w,n.h,"#29342d",b.ink,b.wasteGreen),Fo(e,n,b.wasteGreen),e.fillStyle=b.white,e.font="900 14px ui-monospace, monospace",e.textAlign="left",e.fillText(`SHREDDER L${n.level}`,n.x+14,n.y+18),e.fillStyle="#181d20",e.strokeStyle="#9fd473",e.lineWidth=4,X(e,n.x+38,n.y+36,92,22,!0,!0),e.fillStyle="#262d30",e.strokeStyle="#0d1014",X(e,n.x+46,n.y+58,76,18,!0,!0),e.fillStyle="#111418";for(let o=0;o<7;o+=1){let r=n.x+51+o*10;e.beginPath(),e.moveTo(r,n.y+58),e.lineTo(r+5,n.y+68),e.lineTo(r+10,n.y+58),e.closePath(),e.fill()}e.fillStyle="#6f7e8d",X(e,n.x+54,n.y+27,16,9,!0,!0),X(e,n.x+98,n.y+27,16,9,!0,!0),Io(e,n.x+14,n.y+n.h-42,n.w-28,"SEND POOP","poop","shredder"),kt(e,n.x,n.y,n.w,n.h,n),gt(e,t,"facility",n.id,n.x+n.w/2,n.y+n.h/2,n.w+12,n.h+12),e.restore()}function tu(e,t,n){e.save(),Hn(e,n.x,n.y,n.w,n.h,"#3b3029",b.ink,b.sausageOrange),Fo(e,n,b.sausageOrange),e.fillStyle=b.white,e.font="900 14px ui-monospace, monospace",e.textAlign="left",e.fillText(`SAUSAGE L${n.level}`,n.x+14,n.y+18),e.fillStyle=b.muted,e.font="900 10px ui-monospace, monospace",e.fillText(`2 MEAT -> 1 | ${x(Ze(t))}`,n.x+14,n.y+34),e.fillStyle="#211915",e.strokeStyle=b.sausageOrange,e.lineWidth=4,X(e,n.x+20,n.y+43,n.w-40,38,!0,!0);for(let o=0;o<3;o+=1)Do(e,n.x+n.w/2-28+o*28,n.y+62,9);e.fillStyle=b.meatRed,e.fillRect(n.x,n.y+n.h/2-8,8,16),e.fillStyle=b.sausageOrange,e.fillRect(n.x+n.w-8,n.y+n.h/2-8,8,16),Io(e,n.x+10,n.y+n.h-42,n.w-20,"2 MEAT -> SAUSAGE","sausage",`buffer ${n.meatBuffer||0}/2`),kt(e,n.x,n.y,n.w,n.h,n),gt(e,t,"facility",n.id,n.x+n.w/2,n.y+n.h/2,n.w+12,n.h+12),e.restore()}function nu(e,t,n=t.facilities.truck){e.save(),zr(e,n),Hn(e,n.x,n.y,n.w,n.h,"#343128",b.ink,b.truckPink),Fo(e,n,b.truckPink),e.fillStyle=b.white,e.font="900 14px ui-monospace, monospace",e.textAlign="left",e.fillText(`TRUCK L${n.level}`,n.x+14,n.y+18),e.fillStyle=b.muted,e.font="900 10px ui-monospace, monospace",e.fillText(`UNIT ${x(Ve(t))}`,n.x+14,n.y+34),zc(e,Pe.truck,n,2);let o=Kr(t);Io(e,n.x+15,n.y+50,n.w-30,`SEND ${o.toUpperCase()}`,o,t.eventOffer&&!t.eventActive?"next event":"truck cargo"),e.fillStyle="rgba(39,60,36,.9)",X(e,n.x+13,n.y+88,n.w-26,Math.max(18,n.h-103),!0,!1);for(let r of t.truckCargo)r.sex==="poop"?ua(e,n.x+r.x,n.y+r.y):r.sex==="sausage"?Do(e,n.x+r.x,n.y+r.y,7):jr(e,n.x+r.x,n.y+r.y,r.sex,r.sex==="male");kt(e,n.x,n.y,n.w,n.h,n),gt(e,t,"facility",n.id,n.x+n.w/2,n.y+n.h/2,n.w+12,n.h+12),e.restore()}function ou(e,t,n){let o=n(0),r=n(1);for(let i of t.chicks){let a=o&&o.id===i.id||r&&r.id===i.id||i.queueIndex===0;ru(e,i,a,t)}}function ru(e,t,n,o){let r=o.componentLookup&&iu(o,t)>.8,i=t.y+Math.sin(t.bob)*(r?5:1.5);if(e.save(),n&&(e.strokeStyle=b.green,e.lineWidth=5,e.beginPath(),e.arc(t.x,i,t.radius+8,0,Math.PI*2),e.stroke()),r&&(e.strokeStyle=b.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")Xr(e,t.x,i,t.radius);else if(t.sex==="meat")da(e,t.x,i,t.radius);else if(t.sex==="sausage")Do(e,t.x,i,t.radius);else{let a=t.sex==="male"?Pe.chickMale:Pe.chickFemale;if(!Qt(e,a,t.x-t.radius,i-t.radius,t.radius*2,t.radius*2)){let g=t.sex==="male"?b.male:b.female,C=t.sex==="male"?"#164f8f":"#8b2252";e.fillStyle=g,e.strokeStyle=b.ink,e.lineWidth=5,e.beginPath(),e.arc(t.x,i,t.radius,0,Math.PI*2),e.fill(),e.stroke(),e.fillStyle="rgba(255,255,255,.24)",e.beginPath(),e.arc(t.x-5,i-7,t.radius*.36,0,Math.PI*2),e.fill(),e.fillStyle=b.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.strokeStyle=C,e.lineWidth=3,t.sex==="male"?(e.beginPath(),e.moveTo(t.x-8,i-t.radius-2),e.lineTo(t.x+8,i-t.radius-2),e.stroke()):(e.beginPath(),e.moveTo(t.x-9,i-t.radius+2),e.lineTo(t.x,i-t.radius-6),e.lineTo(t.x+9,i-t.radius+2),e.stroke())}e.fillStyle=t.sex==="male"?"#dff1ff":"#fff0f7",e.strokeStyle="#050609",e.lineWidth=3,e.font=`900 ${Math.max(10,Math.round(t.radius*.72))}px ui-monospace, monospace`,e.textAlign="center";let h=t.sex==="male"?"\u2642":"\u2640";e.strokeText(h,t.x+t.radius*.54,i+t.radius*.7),e.fillText(h,t.x+t.radius*.54,i+t.radius*.7)}e.restore()}function iu(e,t){let n=null,o=1/0;for(let[r,i]of e.componentLookup.entries()){let a=re(r),c=$(a.col,a.row),h=Math.hypot(t.x-c.x,t.y-c.y);hia(o.type,o.id)===ia(t,n))}function gt(e,t,n,o,r,i,a,c){su(t,n,o)&&(e.save(),e.fillStyle="rgba(34,185,79,.18)",e.strokeStyle=b.green,e.lineWidth=(t.multiSelected||[]).length>1?6:4,e.setLineDash((t.multiSelected||[]).length>1?[]:[8,5]),X(e,r-a/2,i-c/2,a,c,!0,!0),e.restore())}function au(e,t,n,o){if(t.phase==="build"&&(t.multiSelected||[]).length>1){e.save(),e.fillStyle="rgba(255,255,255,.96)",e.strokeStyle=b.ink,e.lineWidth=3,X(e,54-t.view.x,66-t.view.y,214,42,!0,!0),e.fillStyle=b.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=b.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 lu(e,t,n){if(!n.cardTargetPick?.pending)return;let o=Gr(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 g of o){let C=g.bounds,P=8;e.rect(C.x-P,C.y-P,C.w+P*2,C.h+P*2)}e.fill("evenodd");let i=n.cardTargetPick?.mode==="blockedCell";if(!o.length){e.fillStyle="rgba(255,255,255,.95)",e.strokeStyle=b.ink,e.lineWidth=3,X(e,(260-(n.view?.x||0))/r,(96-(n.view?.y||0))/r,420/r,42/r,!0,!0),e.fillStyle=b.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=b.ink,e.lineWidth=3;let a=Math.max(1,n.cardTargetPick?.remaining||1),c=Math.max(0,n.cardTargetPick?.used||0),h=n.cardTargetPick?.mode==="autoScannerMenu"?"AUTO SCANNER ROUTE SETTINGS. CLICK EMPTY AREA TO CLOSE.":i?`DYNAMITE: CLICK ANY BLOCKED TILE (${a} LEFT). ESC ${c>0?"FINISHES":"CANCELS"}.`:"CLICK AN UPGRADEABLE MACHINE. PRESS ESC TO CANCEL.";X(e,(216-(n.view?.x||0))/r,(96-(n.view?.y||0))/r,560/r,42/r,!0,!0),e.fillStyle=b.ink,e.font="900 12px ui-monospace, monospace",e.textAlign="center",e.fillText(h,(496-(n.view?.x||0))/r,(122-(n.view?.y||0))/r),e.restore()}function cu(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),a=Math.abs(n.y2-n.y1);e.save(),e.fillStyle="rgba(34,185,79,.12)",e.strokeStyle=b.green,e.lineWidth=2,e.setLineDash([7,4]),X(e,o,r,i,a,!0,!0),e.restore()}function uu(e,t){let n=[...t.effects].sort((o,r)=>(o.priority??Ie.ambient)-(r.priority??Ie.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=b.green,X(e,o.x-o.size/2,o.y-o.size/2,o.size*1.5,o.size,!0,!1)):o.type==="sludge"?(e.fillStyle=b.poop,X(e,o.x-o.size/2,o.y-o.size/2,o.size*1.4,o.size,!0,!1)):o.type==="spawn"?(e.strokeStyle=b.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||b.danger,X(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||b.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||b.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||b.ink,e.save(),e.translate(o.x,o.y),e.rotate(o.rot||0),X(e,-o.w/2,-o.h/2,o.w,o.h,!0,!1),e.restore()):o.type==="load"?(e.strokeStyle=o.color||b.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=b.ink,e.lineWidth=3,e.setLineDash([4,4]),X(e,o.x-o.size/2,o.y-o.size/2,o.size,o.size,!1,!0)):o.type==="rage"?(e.fillStyle=o.color||b.danger,e.font="900 16px ui-monospace, monospace",e.textAlign="center",e.fillText("!",o.x,o.y)):o.type==="flyingChick"&&(o.sex==="poop"?Xr(e,o.x,o.y,o.radius||11):jr(e,o.x,o.y,o.sex,o.sex==="male")),e.restore()}}function du(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=b.white,e.fillStyle=n.color,e.strokeText(n.text,n.x,n.y),e.fillText(n.text,n.x,n.y)}e.restore()}function fu(e,t){let n=t.repairmen&&t.repairmen.length?t.repairmen:t.repairman?[t.repairman]:[];for(let o of n)pu(e,t,o)}function pu(e,t,n){if(!!!n?.active)return;if(e.save(),Qt(e,Pe.repairman,n.x-18,n.y-18,36,36)){n.target?.label&&Hr(e,n.x,n.y-24,n.target.label,b.green),e.restore();return}e.lineWidth=3,e.fillStyle="#fff0d1",e.strokeStyle=b.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?b.danger:b.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=b.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=b.green,X(e,n.x-9,n.y+14,18,8,!0,!0),e.strokeStyle=b.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=b.ink,e.font="900 11px ui-monospace, monospace",e.textAlign="center",e.fillText("FIX",n.x,n.y+33),n.target?.label&&Hr(e,n.x,n.y-24,n.target.label,b.green),e.restore()}function yu(e,t,n){e.save(),e.font="900 19px ui-monospace, monospace",e.textAlign="left",e.fillStyle=b.ink,e.font="900 19px ui-monospace, monospace",e.fillStyle=b.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=b.white,e.lineWidth=4,e.strokeText(i,24,r),e.fillText(i,24,r),r+=20;e.restore()}function fa({game:e,canvasPoint:t,updatePanels:n,equipmentAtPoint:o,fail:r,pointInGrid:i,rectOfFacility:a,rectsOverlap:c}){function h(T){return T?T.type==="conveyor"?`conveyor:${T.id||T.oldKey}`:T.type==="facility"?`facility:${T.id||T.ref?.id}`:T.type==="maintenanceRoom"?`maintenanceRoom:${T.id||T.ref?.id}`:`${T.type}:${T.id||T.ref?.id}`:""}function g(T){return T?T.type==="conveyor"?{type:"conveyor",id:T.oldKey}:{type:T.type,id:T.ref.id}:null}function C(T){let M=h(g(T));return(e.multiSelected||[]).some(q=>h(q)===M)}function P(T){return T?T.type==="eggFarm"?e.eggFarms.find(M=>M.id===T.id)||null:T.type==="scanner"?e.scanners.find(M=>M.id===T.id)||null:T.type==="facility"?e.facilities[T.id]||null:T.type==="maintenanceRoom"?(e.maintenanceRooms||[]).find(M=>M.id===T.id)||null:T.type==="conveyor"&&e.conveyorTiles.has(T.id)?{type:"conveyor",id:T.id}:null:null}function L(){let T=[];for(let M of e.conveyorTiles){let q=re(M),ne=$(q.col,q.row);T.push({type:"conveyor",id:M,center:ne,cell:q})}for(let M of e.eggFarms)T.push({type:"eggFarm",id:M.id,ref:M,center:$(M.col,M.row),cell:{col:M.col,row:M.row}});for(let M of e.scanners)T.push({type:"scanner",id:M.id,ref:M,center:ge(M),cell:{col:M.col,row:M.row}});for(let M of e.maintenanceRooms||[]){let q=$(M.col,M.row);T.push({type:"maintenanceRoom",id:M.id,ref:M,center:{x:q.x+f.cell/2,y:q.y+f.cell/2},cell:{col:M.col,row:M.row}})}for(let M of Object.values(e.facilities)){let q={x:M.x+M.w/2,y:M.y+M.h/2},ne=M.entry?$(M.entry.col,M.entry.row):q;T.push({type:"facility",id:M.id,ref:M,center:q,portCenter:ne})}return T}function K(T,M){let q=Math.min(M.x1,M.x2),ne=Math.max(M.x1,M.x2),oe=Math.min(M.y1,M.y2),se=Math.max(M.y1,M.y2);return T.x>=q&&T.x<=ne&&T.y>=oe&&T.y<=se}function j(T){let M=[];for(let q of L())(K(q.center,T)||q.portCenter&&K(q.portCenter,T))&&M.push({type:q.type,id:q.id});return e.multiSelected=M,e.selected=M[0]||null,n(),M.length}function Z(){e.multiSelected=[],e.selected=null,n()}function Q(T){let M=t(T);return e.selectionBox={x1:M.x,y1:M.y,x2:M.x,y2:M.y},e.multiSelected=[],e.selected=null,n(),!0}function ke(T){if(!e.selectionBox)return;let M=t(T);e.selectionBox.x2=M.x,e.selectionBox.y2=M.y}function pe(){if(!e.selectionBox)return 0;let T=e.selectionBox,M=Math.abs(T.x2-T.x1),q=Math.abs(T.y2-T.y1);return e.selectionBox=null,M<6&&q<6?(Z(),0):j(T)}function le(T){let M=g(T);return M?(e.multiSelected||[]).length&&C(T)?[...e.multiSelected]:[M]:[]}function ue(T){let M=P(T);if(!M)return null;if(T.type==="conveyor"){let q=re(T.id);return{...T,obj:M,oldKey:T.id,currentKey:T.id,meta:e.conveyorMeta.get(T.id)||{price:He("conveyor"),builtSession:null},col:q.col,row:q.row}}return T.type==="eggFarm"||T.type==="scanner"||T.type==="maintenanceRoom"?{...T,obj:M,col:M.col,row:M.row}:T.type==="facility"?M.gridPlaced?{...T,obj:M,gridPlaced:!0,col:M.col,row:M.row,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}}:{...T,obj:M,gridPlaced:!1,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 D(T){let M=t(T),q=o(M);if(!q)return!1;let ne=le(q),oe=ne.map(ue).filter(Boolean);return oe.length?(e.groupDrag={start:M,selections:ne,origins:oe,historySnapshot:On(e),committed:!1,lastDCol:0,lastDRow:0},e.multiSelected=ne,e.selected=ne[0]||null,n(),!0):!1}function U(){!e.groupDrag||e.groupDrag.committed||(e.undoStack.push(e.groupDrag.historySnapshot),e.redoStack=[],e.groupDrag.committed=!0)}function ee(T,M,q){let ne=I(T,M),oe=it(e,T,M);if(oe&&!q.has(`eggFarm:${oe.id}`))return!0;let se=st(e,T,M);if(se&&!q.has(`scanner:${se.id}`))return!0;let Se=(e.maintenanceRooms||[]).find(Ce=>jt(Ce).some(B=>B.col===T&&B.row===M));if(Se&&!q.has(`maintenanceRoom:${Se.id}`))return!0;let be=pt(e,T,M);return!!(be&&!q.has(`facility:${be.id}`)||e.conveyorTiles.has(ne)&&!q.has(`conveyor:${ne}`))}function O(T,M,q){for(let ne of Object.values(e.facilities||{})){if(q.has(`facility:${ne.id}`))continue;let oe=nt(e,ne),se=Xe(e,ne);if(oe&&oe.col===T&&oe.row===M||se&&se.col===T&&se.row===M)return!0}return!1}function G(T,M,q,ne){let oe=it(e,M,q);if(oe&&!ne.has(`eggFarm:${oe.id}`))return!0;let se=st(e,M,q);if(se&&!ne.has(`scanner:${se.id}`))return!0;let Se=(e.maintenanceRooms||[]).find(Ce=>jt(Ce).some(B=>B.col===M&&B.row===q));if(Se&&!ne.has(`maintenanceRoom:${Se.id}`))return!0;let be=pt(e,M,q);if(be&&be.id!==T.id&&!ne.has(`facility:${be.id}`))return!0;for(let Ce of Object.values(e.facilities||{})){if(Ce.id===T.id||ne.has(`facility:${Ce.id}`))continue;let B=nt(e,Ce),ye=Xe(e,Ce);if(B&&B.col===M&&B.row===q||ye&&ye.col===M&&ye.row===q)return!0}return!1}function Y(T,M,q){let ne=[];for(let oe of e.groupDrag.origins){if(oe.type!=="facility"||oe.gridPlaced)continue;let se={x:oe.center.x+T,y:oe.center.y+M},{entry:Se,side:be}=Eo(se,e);if(!i(Se.col,Se.row)||e.blockedCells?.has?.(I(Se.col,Se.row)))return!0;let Ce={...oe.obj,entry:{...Se},side:be};Ao(Ce,Se,be),ne.push({id:oe.id,rect:a(Ce)})}for(let oe of ne)for(let se of Object.values(e.facilities))if(!q.has(`facility:${se.id}`)&&c(oe.rect,a(se),12))return!0;for(let oe=0;oeI(q.col,q.row)));for(let q of["left","right"]){let ne=Fe(T,q),oe=I(ne.col,ne.row);if(!e.conveyorTiles.has(oe))continue;let se=e.conveyorMeta.get(oe);if(!se)continue;let Se=fe.find(B=>B.name===q),be=fe.filter(B=>{let ye=I(ne.col+B.dc,ne.row+B.dr);return e.conveyorTiles.has(ye)&&!M.has(ye)}),Ce=be.find(B=>B.name===Se?.name)||be[0]||Se;Ce&&(se.dir=Ce.name,se.outDirs=[Ce.name],se.branchMode="random")}}function De(T){if(!e.groupDrag)return;let M=t(T),q=M.x-e.groupDrag.start.x,ne=M.y-e.groupDrag.start.y;if(Math.hypot(q,ne)<3)return;let oe=Math.round(q/f.cell),se=Math.round(ne/f.cell),Se=new Set(e.groupDrag.selections.map(h)),be=new Set;for(let B of e.groupDrag.origins){if(B.type==="facility"&&!B.gridPlaced)continue;let ye=B.col+oe,Be=B.row+se,de=B.type==="scanner"?ft({col:ye,row:Be}):B.type==="maintenanceRoom"?jt({col:ye,row:Be}):B.type==="facility"?Ro({...B.obj,col:ye,row:Be}):[{col:ye,row:Be}];for(let Oe of de){if(!i(Oe.col,Oe.row))return r("Selection outside grid");if(e.blockedCells?.has?.(I(Oe.col,Oe.row)))return r("Selection hits blocked ground");let ct=I(Oe.col,Oe.row);if(be.has(ct))return r("Selection overlap");if(ee(Oe.col,Oe.row,Se))return r("Cell occupied");if(B.type!=="conveyor"&&O(Oe.col,Oe.row,Se))return r("Machine port must remain open");be.add(ct)}if(B.type==="facility"&&B.gridPlaced){let Oe={...B.obj,col:ye,row:Be},ct={col:ye-1,row:Be+Math.floor((Oe.hCells||3)/2)},xt={col:ye+(Oe.wCells||3),row:Be+Math.floor((Oe.hCells||3)/2)};for(let Le of[ct,xt]){if(!i(Le.col,Le.row)||e.blockedCells?.has?.(I(Le.col,Le.row)))return r("Machine port outside usable grid");if(G(B.obj,Le.col,Le.row,Se))return r("Machine port is occupied")}}}if(Y(q,ne,Se))return r("Facility overlap");U();let Ce=e.groupDrag.origins.filter(B=>B.type==="conveyor");for(let B of Ce){let ye=B.currentKey||B.oldKey;e.conveyorTiles.delete(ye),e.conveyorMeta.delete(ye)}for(let B of Ce){let ye=I(B.col+oe,B.row+se);e.conveyorTiles.add(ye),e.conveyorMeta.set(ye,B.meta||{price:He("conveyor"),builtSession:null,uses:0}),B.currentKey=ye}for(let B of e.groupDrag.origins)if(B.type==="eggFarm")B.obj.col=B.col+oe,B.obj.row=B.row+se;else if(B.type==="scanner")B.obj.col=B.col+oe,B.obj.row=B.row+se,ie(B.obj);else if(B.type==="maintenanceRoom")B.obj.col=B.col+oe,B.obj.row=B.row+se;else if(B.type==="facility"&&B.gridPlaced)B.obj.col=B.col+oe,B.obj.row=B.row+se,yn(B.obj);else if(B.type==="facility"){let ye={x:B.center.x+q,y:B.center.y+ne},{entry:Be,side:de}=Eo(ye,e);Ao(B.obj,Be,de)}e.groupDrag.selections=e.groupDrag.origins.map(B=>B.type==="conveyor"?{type:"conveyor",id:B.currentKey||B.oldKey}:{type:B.type,id:B.id}),e.multiSelected=[...e.groupDrag.selections],e.selected=e.multiSelected[0]||null,Me(e),n()}function me(){e.groupDrag&&(Me(e),e.groupDrag=null,n())}return{selectionFromHit:g,startSelectionBox:Q,updateSelectionBox:ke,finishSelectionBox:pe,startGroupDrag:D,updateGroupDrag:De,finishGroupDrag:me}}function pa({game:e,ui:t,canvas:n,canvasPoint:o,onUpdatePanels:r}){function i(){r&&r()}let a=null,c=!1,h=null,g=!1,C=["random","up","right","down","left"],P={random:"RND",up:"\u25B2",right:"\u25B6",down:"\u25BC",left:"\u25C0"},L={w:40,h:40,y:0};function K(){return ro(e)}function j(){e.gridCols=f.cols,e.gridRows=f.rows,e.gridOwnedCells=Ti(e)}function Z(s){if(s){f.y-=s*f.cell,f.rows+=s,e.gridChunkOriginY=(e.gridChunkOriginY||0)-Math.floor(s/10),e.ownedCells=io(e.ownedCells,s),e.blockedCells=io(e.blockedCells,s),e.conveyorTiles=io(e.conveyorTiles,s),e.conveyorMeta=Ai(e.conveyorMeta,s);for(let l of e.eggFarms||[])l.row+=s;for(let l of e.scanners||[])l.row+=s;for(let l of Object.values(e.facilities||{}))l.gridPlaced?(l.row+=s,yn(l)):l.entry&&(l.entry.row+=s)}}function Q(s){if(s){f.x-=s*f.cell,f.cols+=s,e.gridChunkOriginX=(e.gridChunkOriginX||0)-Math.floor(s/10),e.ownedCells=so(e.ownedCells,s),e.blockedCells=so(e.blockedCells,s),e.conveyorTiles=so(e.conveyorTiles,s),e.conveyorMeta=Ii(e.conveyorMeta,s);for(let l of e.eggFarms||[])l.col+=s;for(let l of e.scanners||[])l.col+=s;for(let l of e.maintenanceRooms||[])l.col+=s;for(let l of Object.values(e.facilities||{}))l.gridPlaced?(l.col+=s,yn(l)):l.entry&&(l.entry.col+=s)}}function ke(s){e.ownedCells=e.ownedCells||new Set;for(let l of Pi(s))e.ownedCells.add(I(l.col,l.row))}function pe(s){return e.phase!=="build"||e.cardDraft?.pending||e.cardTargetPick?.pending?null:Ut(e).map(l=>D(l)).find(l=>l&&Ri(l,s))||null}function le(s){let l=dt(s);return{x:l.x,y:l.y,w:l.w,h:l.h}}function ue(s){let l=le(s);return Object.values(e.facilities||{}).some(y=>{let m=y?.baseId||y?.id;return!eo.includes(m)||y.gridPlaced?!1:ie(l,Y(y),0)})}function D(s){if(!s)return null;let l=ue(s);return{...s,disabled:l,disabledReason:l?"Move edge facilities before buying this lot.":""}}function U(s="right"){if(s&&typeof s=="object"&&Number.isFinite(s.chunkX)&&Number.isFinite(s.chunkY))return s;let l=["left","right","up","down"].includes(s)?s:"right";return Ei(e,l)}function ee(s="right"){let l=U(s);if(e.phase!=="build"||e.cardDraft?.pending||e.cardTargetPick?.pending)return{ok:!1,reason:"Cannot expand now."};if(!l)return{ok:!1,reason:"Invalid expansion lot."};if(ue(l)){let qe="Move edge facilities before buying this lot.";return de(qe),{ok:!1,reason:qe}}let y=l.direction||"right",m=K();if(e.cashf.cols&&(f.cols=N.colStart+N.cols),N.rowStart+N.rows>f.rows&&(f.rows=N.rowStart+N.rows),ke(N),$i(e,N.chunkX,N.chunkY),e.gridExpansionPurchases=(e.gridExpansionPurchases||0)+1,e.gridExpansionPurchasesByDirection[y]=(e.gridExpansionPurchasesByDirection[y]||0)+1,j();let _=wi(e.gridExpansionPurchases,{base:V.map.blockedRatio??0,increment:V.map.blockedExpansionIncrement??.0075,max:V.map.blockedRatioMax??.12}),we=ki({seed:`${e.runSeed}:grid-expansion:${N.chunkX},${N.chunkY}:${e.gridExpansionPurchases}`,colStart:N.colStart,colEnd:N.colStart+N.cols,rowStart:N.rowStart,rowEnd:N.rowStart+N.rows,targetRatio:_});for(let qe of we)e.blockedCells.add(qe);Me(e);let Ne=N.cols*N.rows,Ge=dt(N);return H(e,Ge.x+Ge.w/2,Ge.y+Ge.h/2,`${y.toUpperCase()} +${Ne} CELLS -${x(m)}`,b.green),i(),{ok:!0,direction:y,cost:m,addedCells:Ne,blocked:we.size,blockedRatio:_,chunkX:N.chunkX,chunkY:N.chunkY}}function O(s,l){return s>=0&&s=0&&l!l.has(m.id)&&ie(y,Y(m),12))}function me(s,l,y=null){if(!O(s,l))return!1;if(G(s,l))return!0;let m=I(s,l),v=it(e,s,l);if(v&&!(y?.type==="eggFarm"&&y.ref?.id===v.id))return!0;let R=st(e,s,l);if(R&&!(y?.type==="scanner"&&y.ref?.id===R.id))return!0;let N=M(e,s,l);if(N&&!(y?.type==="maintenanceRoom"&&y.ref?.id===N.id))return!0;let _=pt(e,s,l);return!!(_&&!(y?.type==="facility"&&y.ref?.id===_.id)||e.conveyorTiles.has(m)&&!(y?.type==="conveyor"&&y.oldKey===m))}function T(s,l,y=null){return Object.values(e.facilities||{}).find(m=>{if(y?.type==="facility"&&y.ref?.id===m.id)return!1;let v=nt(e,m),R=Xe(e,m);return v&&v.col===s&&v.row===l||R&&R.col===s&&R.row===l})||null}function M(s,l,y){return(s.maintenanceRooms||[]).find(m=>jt(m).some(v=>v.col===l&&v.row===y))||null}function q(s,l,y=null){let m={col:s,row:l};for(let v of jt(m)){if(!O(v.col,v.row)||G(v.col,v.row))return!0;let R=I(v.col,v.row);if(it(e,v.col,v.row)||st(e,v.col,v.row))return!0;let we=M(e,v.col,v.row);if(we&&!(y?.type==="maintenanceRoom"&&y.ref?.id===we.id)||pt(e,v.col,v.row)||T(v.col,v.row,y)||e.conveyorTiles.has(R))return!0}return!1}function ne(s,l,y=null){let m={col:s,row:l};for(let v of ft(m)){if(!O(v.col,v.row)||G(v.col,v.row))return!0;let R=I(v.col,v.row);if(it(e,v.col,v.row))return!0;let _=st(e,v.col,v.row);if(_&&!(y?.type==="scanner"&&y.ref?.id===_.id)||pt(e,v.col,v.row)||T(v.col,v.row,y)||e.conveyorTiles.has(R))return!0}return!1}function oe(s,l,y,m=null){let v={baseId:s,id:s,gridPlaced:!0,col:l,row:y,wCells:s==="sausageMaker"?3:1,hCells:s==="sausageMaker"?3:1};for(let R of Ro(v))if(!O(R.col,R.row)||G(R.col,R.row)||me(R.col,R.row,m)||T(R.col,R.row,m))return!0;if(s==="sausageMaker"){let R={col:l-1,row:y+1},N={col:l+3,row:y+1};if(!O(R.col,R.row)||!O(N.col,N.row)||G(R.col,R.row)||G(N.col,N.row))return!0;for(let _ of[R,N]){let we=pt(e,_.col,_.row);if(we&&we.id!==m?.ref?.id||it(e,_.col,_.row)||st(e,_.col,_.row)||M(e,_.col,_.row)||T(_.col,_.row,m))return!0}}return!1}function se(){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:e.selected.type==="maintenanceRoom"&&(e.maintenanceRooms||[]).find(s=>s.id===e.selected.id)||null:null}function Se(s){return s?s.type==="eggFarm"?`${Pt("eggFarm")} #${s.id}`:s.type==="scanner"?`${s.kind==="auto"?Pt("autoScanner"):Pt("manualScanner")} #${s.id}`:s.type==="conveyor"?e.conveyorMeta.get(s.id)?.kind==="boostConveyor"?Pt("boostConveyor"):Pt("conveyor"):s.type==="facility"?s.name:s.type==="maintenanceRoom"?`${Pt("maintenanceRoom")} #${s.id}`:"Equipment":"Equipment"}function be(s){return s?.type==="conveyor"?e.conveyorMeta?.get(s.oldKey||s.ref?.id)?.price||yo(s):yo(s)}function Ce(){return!!e.cardEffects?.usedMachineActive}function B(){return Ce()?{usedMachine:!0,noRefund:!0,durabilityBaseMultiplier:.6}:{usedMachine:!1,noRefund:!1,durabilityBaseMultiplier:1}}function ye(s){let l=B();return Object.assign(s,l),s}function Be(s){let l=s?.ref||s||{};if(s?.type==="eggFarm"||l.type==="eggFarm")return e.eggFarms.length<=1?"Cannot sell the last EGG FARM.":"";if((s?.type==="facility"||l.type==="facility")&&["mixer","trash","truck"].includes(l.baseId||l.id)){let y=l.baseId||l.id;if(Object.values(e.facilities).filter(v=>(v.baseId||v.id)===y).length<=1)return`Cannot sell the last ${l.shortName||l.name||l.id}.`}return""}function de(s){t.buildStatus.textContent=s,H(e,n.width/2-e.view.x,86-e.view.y,s.toUpperCase(),b.danger)}function Oe(s){if(!s)return null;let l=I(s.col,s.row);return e.conveyorMeta.get(l)||null}function ct(s){return s?(Array.isArray(s.outDirs)||(s.outDirs=[]),s.outDirs=[...new Set(s.outDirs.filter(l=>["right","left","down","up"].includes(l)))],C.includes(s.branchMode)||(s.branchMode="random"),s.kind||(s.kind="conveyor"),s.speedMultiplier||(s.speedMultiplier=s.kind==="boostConveyor"?2:1),s.outDirs):[]}function xt(s){return s?fe.filter(l=>e.conveyorTiles.has(I(s.col+l.dc,s.row+l.dr))).map(l=>l.name):[]}function Le(s){return xt(s).length>=3}function qn(s,l){let y=fe.find(_=>_.name===l);if(!s||!y)return!1;let m=I(s.col+y.dc,s.row+y.dr),v=e.conveyorMeta.get(m);if(!v)return!1;let R=y.opposite;return[...new Set([...Array.isArray(v.outDirs)?v.outDirs:[],v.dir].filter(Boolean))].includes(R)}function wn(s,l){let y=xt(s);if(y.length!==1)return null;let m=y[0];return l.includes(m)&&qn(s,m)?m:null}function No(s){if(!e.conveyorTiles.has(s))return[];let l=re(s),y=e.conveyorMeta.get(s),m=ct(y),v=xt(l),R=[...new Set([...m,y?.dir].filter(Boolean))].filter(Ge=>v.includes(Ge)),N=v,_=R.length?R:N,we=wn(l,_),Ne=R.length?R.filter(Ge=>Ge!==we):N.filter(Ge=>Ge!==we);return y&&(y.outDirs=m.filter(Ge=>v.includes(Ge)),y.dir&&!v.includes(y.dir)&&(y.dir=y.outDirs[0]||null),y.branchMode!=="random"&&!v.includes(y.branchMode)&&(y.branchMode="random")),Ne}function Yn(s){if(!e.conveyorTiles.has(s))return[];let l=re(s),y=xt(l);return Le(l)?y:No(s)}function Lt(s){let l=re(s),y=Yn(s);if(y.length<3)return[];let m=e.conveyorMeta.get(s)||{},v=new Set(y.filter(_=>qn(l,_))),R=y.filter(_=>!v.has(_));if(v.size)return R.length>=2?R:[];let N=[...new Set([...Array.isArray(m.outDirs)?m.outDirs:[],m.dir].filter(Boolean))].filter(_=>y.includes(_));return N.length>=2?N:y}function Sn(s,l){let y=ut(s,l);if(!y)return!1;let m=I(s.col,s.row),v=I(l.col,l.row),R=e.conveyorMeta.get(m),N=e.conveyorMeta.get(v);if(!R||!N)return!1;let _=ct(R),we=!_.includes(y)||R.dir!==y||!N.dir;return _.includes(y)||_.push(y),R.dir=y,N.dir||(N.dir=y),we}function Tt(s){if(kn(s).length===0)return Vn(s);let l=_o(s);return l.length<2?Vn(s):(_e(e),zn(l),Me(e),i(),!0)}function Vn(s){if(!s)return!1;let l=I(s.col,s.row),y=e.conveyorMeta.get(l);if(!y||Le(s))return!1;let m=wn(s,fe.map(_=>_.name)),v=fe.map(_=>_.name).filter(_=>_!==m),R=y.dir&&v.includes(y.dir)?y.dir:null,N=v[(Math.max(-1,v.indexOf(R))+1)%v.length];return _e(e),y.dir=N,y.outDirs=[N],y.branchMode="random",Me(e),i(),!0}function kn(s){return s?fe.map(l=>({col:s.col+l.dc,row:s.row+l.dr})).filter(l=>e.conveyorTiles.has(I(l.col,l.row))):[]}function _o(s){if(!s||!e.conveyorTiles.has(I(s.col,s.row))||Le(s))return[];let l=kn(s);if(!l.length||l.length>2)return[];let y=R=>{let N=[],_=new Set([I(s.col,s.row)]),we=s,Ne=R,Ge=0;for(;Ne&&Ge<200;){Ge+=1;let qe=I(Ne.col,Ne.row);if(_.has(qe)||(_.add(qe),N.push(Ne),Le(Ne)))break;let ri=kn(Ne).filter(ii=>!(ii.col===we.col&&ii.row===we.row));if(ri.length!==1)break;we=Ne,Ne=ri[0]}return N},m=l[0]?y(l[0]).reverse():[],v=l[1]?y(l[1]):[];return[...m,s,...v]}function gn(s,l){return!!s&&[...new Set([...Array.isArray(s.outDirs)?s.outDirs:[],s.dir].filter(Boolean))].includes(l)}function zn(s){let l=0,y=0;for(let v=0;vy?[...s].reverse():s;for(let v=0;v=2){let v=m[m.length-2],R=m[m.length-1];if(!Le(R)){let N=e.conveyorMeta.get(I(R.col,R.row)),_=ut(v,R);N&&_&&(N.dir=_,N.outDirs=[_],N.branchMode="random")}}}function Cn(){return V.facilities[e.buildTool==="boostConveyor"?"boostConveyor":"conveyor"]}function Xn(s,l=null){if(!s)return{ok:!1,reason:ce.fail.outOfGrid};let{col:y,row:m}=s,v=I(y,m);if(e.conveyorTiles.has(v))return{ok:!1,exists:!0};if(G(y,m))return{ok:!1,reason:"Cannot build on blocked ground."};if(me(y,m))return{ok:!1,reason:ce.fail.cellOccupied};let R=Cn(),N=R.id||"conveyor",_=He(N,e);if(e.cash<_)return{ok:!1,reason:ce.fail.notEnoughCash};_e(e),Dt(e,_),e.conveyorTiles.add(v);let we=B();return e.conveyorMeta.set(v,{price:_,builtSession:e.buildSession,uses:0,durability:wo(e,"conveyor",we.durabilityBaseMultiplier),maintenanceType:"conveyor",kind:N,speedMultiplier:R.speedMultiplier||1,dir:l||null,outDirs:[],branchMode:"random",...we}),e.selected={type:"conveyor",id:v},H(e,$(y,m).x,$(y,m).y,`-${x(_)}`,b.ink),{ok:!0,cell:s,key:v}}function Mn(s){if(!s)return de(ce.fail.outOfGrid);let l=I(s.col,s.row),y=e.conveyorTiles.has(l),m=a;if(m&&m.col===s.col&&m.row===s.row)return;if(m&&(g=!0),y){m&&Sn(m,s)&&(c||(_e(e),c=!0),Me(e)),e.selected={type:"conveyor",id:l},a={col:s.col,row:s.row};return}let v=m?ut(m,s):null,R=Xn(s,v);if(!R.ok)return de(R.reason||ce.fail.cellOccupied);m&&Sn(m,s),a={col:s.col,row:s.row},Me(e)}function Zt(s,l){if(!s||!l)return l;let y=l.col-s.col,m=l.row-s.row;return y===0&&m===0?s:Math.abs(y)>=Math.abs(m)?{col:s.col+Math.sign(y),row:s.row}:{col:s.col,row:s.row+Math.sign(m)}}function Go(s){c=!1,h=s?{col:s.col,row:s.row}:null,g=!1,a=null,Mn(s)}function Uo(s){if(!s)return;if(!a)return Mn(s);let l=a,y=0;for(;(l.col!==s.col||l.row!==s.row)&&y<64;){let m=Zt(l,s);if(Mn(m),l=a||m,y+=1,!a)break}}function Nt(){!g&&h&&e.conveyorTiles.has(I(h.col,h.row))&&Tt(h),h=null,g=!1,a=null,c=!1}function jn(s){if(!s)return de(ce.fail.outOfGrid);let{col:l,row:y}=s;if(e.buildTool==="conveyor"||e.buildTool==="boostConveyor"){let m=Xn(s,null);if(!m.ok)return de(m.reason||ce.fail.cellOccupied);Me(e);return}if(G(l,y))return de("Cannot build on blocked ground.");if(T(l,y)&&!["conveyor","boostConveyor"].includes(e.buildTool))return de("Machine port must remain open for a conveyor.");if(e.buildTool==="maintenanceRoom")return q(l,y)?de(ce.fail.cellOccupied):p(l,y);if(to.includes(e.buildTool))return oe(e.buildTool,l,y)?de("3x3 machine or its ports do not fit here."):k(l,y,e.buildTool);if(e.buildTool==="manualScanner"||e.buildTool==="autoScanner"?ne(l,y):me(l,y))return de(ce.fail.cellOccupied);e.buildTool==="eggFarm"?Wo(l,y):e.buildTool==="manualScanner"?d(l,y,"manual"):e.buildTool==="autoScanner"&&d(l,y,"auto")}function xn(s){let l=tt(s.x,s.y);if(["conveyor","boostConveyor","eggFarm","manualScanner","autoScanner","maintenanceRoom",...to].includes(e.buildTool))return jn(l);if(eo.includes(e.buildTool))return S(s,e.buildTool)}function Wo(s,l){let y=He("eggFarm",e);if(e.cashR.kind==="manual").length>=8)return de("Manual Scanner limit reached (8 max).");let m=y==="auto"?He("autoScanner",e):He("manualScanner",e);if(e.cashv.id!==l.ref.id),vt(e,m.x,m.y),Me(e)}if(l.type==="scanner"){let m=ge(l.ref);A(l,m.x,m.y),e.scanners=e.scanners.filter(v=>v.id!==l.ref.id),vt(e,m.x,m.y),Me(e)}if(l.type==="facility"){let m=l.ref;A(l,m.x+m.w/2,m.y+m.h/2),delete e.facilities[m.id],vt(e,m.x+m.w/2,m.y+m.h/2),Me(e)}if(l.type==="maintenanceRoom"){let m=l.ref,v=w(m);A(l,v.x,v.y),e.maintenanceRooms=(e.maintenanceRooms||[]).filter(R=>R.id!==m.id),vt(e,v.x,v.y)}e.selected=null,e.multiSelected=[],i()}function W(s){let l=Object.values(e.facilities).find(_=>{if(s.x>=_.x&&s.x<=_.x+_.w&&s.y>=_.y&&s.y<=_.y+_.h)return!0;if(!_.entry)return!1;let Ne=$(_.entry.col,_.entry.row);return Math.abs(s.x-Ne.x)<=f.cell*.46&&Math.abs(s.y-Ne.y)<=f.cell*.46});if(l)return{type:"facility",ref:l};let y=tt(s.x,s.y);if(!y)return null;let m=it(e,y.col,y.row);if(m)return{type:"eggFarm",ref:m};let v=st(e,y.col,y.row);if(v)return{type:"scanner",ref:v};let R=M(e,y.col,y.row);if(R)return{type:"maintenanceRoom",ref:R};let N=I(y.col,y.row);return e.conveyorTiles.has(N)?{type:"conveyor",oldKey:N,ref:{type:"conveyor",id:N}}:null}let ae=fa({game:e,canvasPoint:o,updatePanels:i,equipmentAtPoint:W,fail:de,pointInGrid:O,rectOfFacility:Y,rectsOverlap:ie});function ve(s){return null}function en(){let s=se();s&&(s.type==="conveyor"?E($(...Object.values(re(s.id)))):s.type==="eggFarm"?E($(s.col,s.row)):s.type==="scanner"?E(ge(s)):s.type==="facility"?E({x:s.x+s.w/2,y:s.y+s.h/2}):s.type==="maintenanceRoom"&&E(w(s)))}function ze(s,l,y="primary-button"){let m=document.createElement("button");return m.type="button",m.className=y,m.textContent=s,m.addEventListener("click",l),m}function _t(){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 Tn(s){t.modalTitle.textContent="Auto Scanner",e.cardTargetPick={pending:!0,mode:"autoScannerMenu",cardId:null,targetKeys:[`scanner:${s.id}`],remaining:1},t.modalBody.innerHTML=` +var _={version:"v29.00",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:850,costGrowthRate:.75,truncateUnit:50}},map:{blockedRatio:0,blockedExpansionIncrement:.0075,blockedRatioMax:.12},economy:{income:{mixer:5,truck:10,sausage:26},poopFine:10,fairiesTribute:{perDay:10,profitExemption:300,legalWorkExemptionBonus:250,profitRate:.12},incomeUpgradeRate:1.1,explosionDamageDivisor:30,maleTruckFinePerHalfDay:30,shredderBonus:{maxCards:30,payoutCoefficient:1.6,payoutExponent:1.25,baseHitChance:.28,hitChancePerUpgrade:.018,maxHitChance:.82,jackpotBaseChance:.01,jackpotChancePerUpgrade:.0025,jackpotMaxChance:.08,bigWinBaseChance:.07,bigWinChancePerUpgrade:.0015,bigWinMaxChance:.12,doubleChance:.2,jackpotMultiplier:10,bigWinMultiplier:4,doubleMultiplier:2},chemicalWeaponSubsidy:{poopPerBlock:70,yenPerBlock:1200}},production:{poopRate:.08,autoScannerCooldown:1.5,autoScannerUpgradeRate:.95,autoScannerCardUpgradeRate:.95,autoScannerMinCooldown:.5,poopRateCardBonus:.04,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,minimumItemSpacingPx:25},power:{recalcHz:10,maxAssignmentPasses:8,wires:{powerWire:{id:"powerWire",name:"Power Wire",shortName:"WIRE",price:10,lossV:.5},insulatedPowerWire:{id:"insulatedPowerWire",name:"Insulated Wire",shortName:"INS WIRE",price:22,lossV:.15}},exempt:["manualScanner","eggFarm","conveyor","boostConveyor","powerWire","insulatedPowerWire","footbridge","starterGenerator","biomassGenerator","coalGenerator"],machines:{mixer:{minVoltage:10,drawV:4,relayDropV:4,powerPriority:100},trash:{minVoltage:8,drawV:3,relayDropV:3,powerPriority:100},truck:{minVoltage:6,drawV:2,relayDropV:2,powerPriority:100},autoScanner:{minVoltage:8,drawV:2,relayDropV:2,powerPriority:50},sausageMaker:{minVoltage:12,drawV:5,relayDropV:5,powerPriority:50},maintenanceRoom:{minVoltage:6,drawV:1,relayDropV:1,powerPriority:50},inboundTruck:{minVoltage:5,drawV:1,relayDropV:1,powerPriority:50}},generators:{starterGenerator:{outputV:25},biomassGenerator:{baseOutputV:0,pulseVoltage:5,pulseSeconds:2},coalGenerator:{baseOutputV:0,pulseVoltage:15,pulseSeconds:3}}},imports:{combinedLimitPerTruck:80,prices:{coal:2,zundaMochi:4},jitterRatio:.15},farmQuality:{overflowCoefficient:.45,overflowExponent:.85,minMultiplier:1,maxMultiplier:2.5,zundaSeconds:8,zundaMaxSeconds:30,zundaIntervalMultiplier:.55,safeMinIntervalSeconds:.25},effects:{explosionHorizontalVelocityMultiplier:.82},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:"autoScannerTuning",title:"AUTO SCANNER Tuning",rarity:"common",type:"instant",description:"All AUTO SCANNER cooldowns use default cooldown x0.95 per copy.",requiresFacility:"autoScanner",tags:["SCANNER","PASSIVE"]},{id:"upgradeMixer",title:"MIXER Improvement",rarity:"common",type:"equipmentUpgrade",target:"mixer",description:"Choose one MIXER. Each level improves meat quality and raises sausage sale value by 10%.",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:"upgradeSausageMaker",title:"SAUSAGE MACHINE Improvement",rarity:"common",type:"equipmentUpgrade",target:"sausageMaker",description:"Choose one SAUSAGE MACHINE. Each level raises sausage sale value by 10%.",tags:["ECONOMY","SAUSAGE","ACTIVE"]},{id:"upgradeTrash",title:"SHREDDER Improvement",rarity:"common",type:"equipmentUpgrade",target:"trash",description:"Adds a nonlinear scrap-lottery upgrade. More copies raise both payout and win chance; max 30.",tags:["POOP","ACTIVE"]},{id:"crowdedFarming",title:"Crowded Farming",rarity:"common",type:"instant",description:"EGG production interval -5%. Equipment deterioration +4%. Max 10 cards.",requiresFacility:"eggFarm",tags:["EGG","RISK","PASSIVE"]},{id:"hatchingFeed",title:"Hatching Feed",rarity:"common",type:"instant",description:"EGG production interval -4%. Poop rate +3%. No card limit.",requiresFacility:"eggFarm",tags:["EGG","POOP","PASSIVE"]},{id:"poopRateBooster",title:"Poop Culture",rarity:"common",type:"instant",description:"Base poop rate +4 percentage points. No card limit.",tags:["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.",requiresAnyFacility:["conveyor","eggFarm","autoScanner","manualScanner","mixer","trash","sausageMaker"],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.",requiresAnyFacility:["conveyor","eggFarm","autoScanner","manualScanner","mixer","trash","sausageMaker"],tags:["MAINTENANCE","PASSIVE"]},{id:"bearing",title:"High-Quality Bearing",rarity:"common",type:"instant",description:"Conveyor speed +7.5%.",requiresFacility:"conveyor",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.",requiresAnyFacility:["conveyor","eggFarm","autoScanner","manualScanner","mixer","trash","sausageMaker"],tags:["MAINTENANCE","ACTIVE"]},{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.",requiresFacility:"trash",tags:["POOP","ECONOMY","PASSIVE"]},{id:"scannerQueueSpacing",title:"Scanner Queue Spacing",rarity:"common",type:"instant",description:"Manual and Auto Scanner queue spacing -15%. No card limit.",requiresAnyFacility:["manualScanner","autoScanner"],tags:["SCANNER","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:"Choose and remove up to 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:"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:"Fairies tribute profit exemption +JPY 250.",tags:["ECONOMY","RARE","PASSIVE"]},{id:"chemicalWeaponSubsidy",title:"Chemical Weapons Subsidy",rarity:"ultraRare",type:"instant",description:"Each day, gain JPY 1200 per 70 poop shipped by truck. No card limit.",tags:["POOP","ULTRA RARE","PASSIVE"]},{id:"laborExploitation",title:"Motivational Exploitation",rarity:"ultraRare",type:"instant",description:"Repair room work time -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,autoScannerTuning:0,poopRateBooster:0,crowdedFarming:0,hatchingFeed:0,safetyCover:0,recyclingSubsidy:0,chemicalWeaponSubsidy:0,preventiveMaintenance:0,dudFilter:0,durabilityCoating:0,sparePartsBin:0,dudRefund:0,freeReroll:0,composter:0,scannerQueueSpacing:0,laborExploitation:0,usedMachineActive:!1,rescueLoanCharges:0},commonWeight:8,rareWeight:2,ultraRareWeight:.45,baseDraftSize:3,maxDudsPerDraft:2,dudChancePerCard:.3,dudChanceReductionPerFilter:.05,extraCardsAddedPicks:2,rerollCost:{dayDivisor:2,multiplier:100}},events:{eventChance:.1,firstTurn:8},maintenance:{fullPerformanceUntilWear:.5,minimumPerformance:.5,durability:{conveyor:1300,eggFarm:475,autoScanner:850,mixer:775,trash:950,sausageMaker:825,starterGenerator:1e3,biomassGenerator:900,coalGenerator:900,inboundTruck:850},processingDelayMaxSeconds:{mixer:1.2,trash:1,sausageMaker:.9},repairman:{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:75,buildable:!0,upgradeable:!1,speedMultiplier:2},powerWire:{id:"powerWire",type:"powerWire",name:"Power Wire",shortName:"WIRE",price:10,buildable:!0,upgradeable:!1},insulatedPowerWire:{id:"insulatedPowerWire",type:"powerWire",name:"Insulated Power Wire",shortName:"INS WIRE",price:22,buildable:!0,upgradeable:!1},footbridge:{id:"footbridge",type:"footbridge",name:"Footbridge",shortName:"BRIDGE",price:90,buildable:!0,upgradeable:!1,footprint:{cols:1,rows:3}},eggFarm:{id:"eggFarm",type:"eggFarm",name:"Egg Farm",shortName:"EGG",price:250,buildable:!0,upgradeable:!0,maxLevel:4},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:180,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:"Shipping Truck",shortName:"SHIP TRUCK",price:450,buildable:!0,upgradeable:!0,incomeKey:"truck",body:{w:176,h:138}},sausageMaker:{id:"sausageMaker",type:"facility",name:"Sausage Machine",shortName:"SAUSAGE",price:380,buildable:!0,upgradeable:!0,incomeKey:"sausage",footprint:{cols:3,rows:3},body:{w:138,h:138},outputProduct:"sausage"},starterGenerator:{id:"starterGenerator",type:"facility",name:"Starter Generator",shortName:"STARTER",price:0,buildable:!1,upgradeable:!1,footprint:{cols:2,rows:2},body:{w:92,h:92}},biomassGenerator:{id:"biomassGenerator",type:"facility",name:"Biomass Generator",shortName:"BIOMASS",price:520,buildable:!0,upgradeable:!1,footprint:{cols:3,rows:3},body:{w:138,h:138}},coalGenerator:{id:"coalGenerator",type:"facility",name:"Coal Generator",shortName:"COAL GEN",price:680,buildable:!0,upgradeable:!1,footprint:{cols:2,rows:3},body:{w:92,h:138}},inboundTruck:{id:"inboundTruck",type:"facility",name:"Inbound Truck",shortName:"INBOUND",price:350,buildable:!0,upgradeable:!1,body:{w:176,h:120}},maintenanceRoom:{id:"maintenanceRoom",type:"maintenanceRoom",name:"Maintenance Room",shortName:"REPAIR ROOM",price:220,buildable:!0,upgradeable:!1,footprint:{cols:2,rows:2}}}};var Ai=_.version,yt=_.time.daySeconds,Un=_.cash.starting,Ie=_.economy,$i=_.time.farmShutdownGraceSeconds,bo=_.production.autoScannerCooldown,Fi=_.production.autoScannerUpgradeRate,wr=_.production.autoScannerCardUpgradeRate||_.production.autoScannerUpgradeRate,mr=_.production.autoScannerMinCooldown,Oi=_.events.eventChance,Vn=_.events.firstTurn,vr=_.production.poopRate,Sr=_.production.poopRateCardBonus||0,wo=_.conveyor.baseSpeed,yn=_.conveyor.maxSpeed,mo=_.conveyor.bearingSpeedMultiplier,qe=_.cards,Bi=_.cards.definitions,kr=_.cards.defaultEffects,Gt=_.production.eggSpawnRanges,p=_.grid,Te=_.facilities,Mr=["conveyor","boostConveyor","powerWire","insulatedPowerWire","footbridge","eggFarm","autoScanner","manualScanner","mixer","trash","truck","inboundTruck","sausageMaker","biomassGenerator","coalGenerator","maintenanceRoom"],vo=["mixer","trash","truck","inboundTruck"],So=["sausageMaker","starterGenerator","biomassGenerator","coalGenerator"],Cr=[...vo,...So],Di=["mixer","trash","truck"],Li=["mixer","truck","sausageMaker"],_t=Object.fromEntries(Object.entries(Te).map(([e,t])=>[e,t.price])),fe=[{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"}],w={bg:"#111318",ink:"#090a0d",muted:"#8f9bab",green:"#7bd84f",greenSoft:"#273c24",mixerBlue:"#4aa3ff",truckPink:"#ffb33e",wasteGreen:"#88d35a",meatRed:"#d96b72",sausageOrange:"#d78b3f",danger:"#e34a36",warn:"#ffb827",white:"#e6edf2",poop:"#7b4a23",male:"#4aa3ff",female:"#ff6fae"},_e={ambient:0,important:1,critical:2};function R(e){let t=Number(e)||0;return`${t<0?"-":""}\uFFE5${Math.ceil(Math.abs(t)).toLocaleString("ja-JP")}`}function E(e,t){return`${e},${t}`}function X(e){let[t,n]=String(e).split(",").map(Number);return{col:t,row:n}}function Kn(e,t,n){return Math.max(t,Math.min(n,e))}function ne(e,t){return e+Math.random()*(t-e)}function Re(e,t){return e>=0&&e=0&&tMath.round(Kn(r,0,255)).toString(16).padStart(2,"0");return`#${o(e)}${o(t)}${o(n)}`}function qn(e,t,n){let o=Ni(e),r=Ni(t);return Tl(gr(o.r,r.r,n),gr(o.g,r.g,n),gr(o.b,r.b,n))}var pe={appTitle:"Chick Sorter",versionTitle:`Chick Sorter ${Ai}`,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."},routeLabels:{mixer:"MIXER",truck:"TRUCK",inboundTruck:"INBOUND",trash:"WASTE",sausageMaker:"SAUSAGE",biomassGenerator:"BIOMASS",coalGenerator:"COAL GEN",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 ht(e){return Te[e]?.name||String(e||"").replace(/^[a-z]/,t=>t.toUpperCase())}function El(e){let t=Te[e];return t?e==="conveyor"||e==="boostConveyor"?`${R(t.price)} / tile`:e==="autoScanner"?`${R(t.price)} / ${bo.toFixed(1)}s cooldown`:e==="trash"?R(t.price):R(t.price):""}function Pl(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:"Processes about 40 items per day at Lv1, roughly 2.1 Lv1 farms.",manualScanner:"A manual checkpoint. The operator is the algorithm.",mixer:"Makes meat when OUT is clear; otherwise falls back to direct sale.",trash:"A polite shredder for poop and other regrets.",truck:"Shipping Truck. Ships target chicks and all sausages.",powerWire:"Overlay wire. Loses 0.5V per tile.",insulatedPowerWire:"Overlay wire. Loses 0.15V per tile.",footbridge:"Lets repair workers cross conveyor cells.",inboundTruck:"Imports coal and zunda mochi during the day.",biomassGenerator:"Fuel generator. Chicks and poop add timed voltage.",coalGenerator:"Fuel generator. Coal adds timed voltage.",sausageMaker:"A 3\xD73 grid machine. Left inlet takes 2 meat; right outlet emits 1 sausage.",maintenanceRoom:"2\xD72 repair bay. Each room dispatches one repairman during the day."}[e]||""}function Rl(e){return{conveyor:"./assets/dark_factory/facilities/conveyor/conveyor_straight_h_16.png",boostConveyor:"./assets/dark_factory/facilities/conveyor/boost_conveyor_straight_h_16.png",eggFarm:"./assets/dark_factory/facilities/machines/egg_farm_16.png?v=dark_factory",autoScanner:"./assets/dark_factory/facilities/machines/auto_scanner_32x48.png",manualScanner:"./assets/dark_factory/facilities/machines/manual_scanner_32x48.png",mixer:"./assets/dark_factory/facilities/machines/mixer_32.png",trash:"./assets/dark_factory/facilities/machines/shredder_32.png",truck:"./assets/dark_factory/facilities/vehicles/truck_32.png",sausageMaker:"./assets/dark_factory/facilities/machines/mixer_32.png",maintenanceRoom:"./assets/dark_factory/facilities/utility/maintenance_room_32.png"}[e]||""}function _i(e){let t=Pl(e),n=Rl(e);return`${n?``:""}${ht(e)}${El(e)}${t?`${t}`:""}`}function Wi(e){return pe.routeLabels[e]||String(e).toUpperCase()}function Ui(e){let t=2166136261;for(let n=0;n>>0}function Vi(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 Ki(e,{base:t=0,increment:n=.0075,max:o=.12}={}){let r=Math.max(0,Math.floor(Number(e)||0));return Number(Math.min(o,t+r*n).toFixed(6))}function Mo(){return`RUN-${Date.now().toString(36)}-${Math.floor(Math.random()*1e6).toString(36)}`.toUpperCase()}function qi({seed:e,targetRatio:t=.2,reserved:n=[]}={}){let o=Vi(Ui(e||Mo())),r=new Set(n.map(c=>typeof c=="string"?c:E(c.col,c.row))),i=[];for(let c=0;c0;c-=1){let b=Math.floor(o()*(c+1));[i[c],i[b]]=[i[b],i[c]]}let a=Math.round(p.cols*p.rows*t+1e-9);return new Set(i.slice(0,Math.min(a,i.length)).map(c=>c.k))}function zi({seed:e,colStart:t=0,colEnd:n=p.cols,rowStart:o=0,rowEnd:r=p.rows,targetRatio:i=.2,reserved:a=[]}={}){let c=Vi(Ui(e||Mo())),b=new Set(a.map(z=>typeof z=="string"?z:E(z.col,z.row))),M=[],C=Math.max(0,Math.floor(t)),$=Math.max(C,Math.floor(n)),W=Math.max(0,Math.floor(o)),K=Math.max(W,Math.floor(r));for(let z=W;z0;z-=1){let Q=Math.floor(c()*(z+1));[M[z],M[Q]]=[M[Q],M[z]]}let G=Math.round(($-C)*(K-W)*i+1e-9);return new Set(M.slice(0,Math.min(G,M.length)).map(z=>z.k))}var hn=10,Hi=2,Yi=1;function bn(e,t){return`${e},${t}`}function Il(e){let[t,n]=String(e).split(",").map(Number);return{chunkX:t,chunkY:n}}function Co(e){let t=_.grid.expansion||{},n=Math.max(1,(e.gridExpansionPurchases||0)+1),o=Math.max(0,Number(t.costBase)||850),r=Math.max(0,Number(t.costGrowthRate)||.75),i=o*(1+r*(n-1)),a=Math.max(1,t.truncateUnit||50);return Math.floor(i/a)*a}function Qi(e){e.ownedCells=new Set,e.ownedChunks=new Set,e.gridChunkOriginX=0,e.gridChunkOriginY=0,e.gridExpansionPurchases=0,e.gridExpansionPurchasesByDirection={left:0,right:0,up:0,down:0};for(let t=0;t=0&&t=0&&n{let a=(o[r.direction]??9)-(o[i.direction]??9);return a||(r.chunkY!==i.chunkY?r.chunkY-i.chunkY:r.chunkX-i.chunkX)})}function Ji(e,t="right"){let n=jt(e);return n.find(o=>o.direction===t)||n[0]||null}function Et(e){return{x:p.x+e.colStart*p.cell,y:p.y+e.rowStart*p.cell,w:e.cols*p.cell,h:e.rows*p.cell}}function Tr(e){let t=Et(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 Zi(e,t){let n=Tr(e);return t.x>=n.x&&t.x<=n.x+n.w&&t.y>=n.y&&t.y<=n.y+n.h}function es(e){let t=[];for(let n=e.rowStart;nRe(t.col,t.row))}function Nl(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}].filter(t=>Re(t.col,t.row))}function Gl(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=>Re(t.col,t.row))}function ss(e){let t=new Set([...e.conveyorTiles]),n=o=>{o&&Re(o.col,o.row)&&t.add(E(o.col,o.row))};for(let o of e.scanners){for(let r of Nl(o))n(r);for(let r of Ll(o))n(r)}for(let o of e.eggFarms){n(o);for(let r of Gl(o))n(r)}for(let o of Object.values(e.facilities))n(o.entry);for(let o of Object.values(e.facilities))if(o.gridPlaced)for(let r=0;r<(o.hCells||1);r+=1)for(let i=0;i<(o.wCells||1);i+=1)n({col:o.col+i,row:o.row+r});return t}function _l(e,t,n,o="powerWire",r=!1){Re(t,n)&&e.powerWires.set(E(t,n),{kind:o,price:r?0:_.power.wires[o]?.price||0,builtSession:r?null:e.buildSession})}function Wl(e){let t=Te.starterGenerator,n={type:"facility",id:"starterGenerator",baseId:"starterGenerator",name:t.name,col:6,row:6,wCells:t.footprint.cols,hCells:t.footprint.rows,gridPlaced:!0,level:1,price:0,builtSession:null,unsellable:!0};n.x=p.x+n.col*p.cell,n.y=p.y+n.row*p.cell,n.w=n.wCells*p.cell,n.h=n.hCells*p.cell,e.facilities.starterGenerator=n}function os(e){e.powerWires=new Map;let t=[[5,6],[4,6],[3,6],[2,6],[1,6],[0,6],[0,5],[0,4],[8,7],[8,8],[9,8],[10,8],[11,8],[12,8],[12,9],[13,8],[14,8],[15,8],[16,8],[17,8],[18,8],[18,9]];for(let[n,o]of t)_l(e,n,o,"powerWire",!0)}function Ul(e){let t=new Set;for(let n of Object.values(e.facilities||{}))if(n.gridPlaced)for(let o=0;o<(n.hCells||1);o+=1)for(let r=0;r<(n.wCells||1);r+=1)t.add(E(n.col+r,n.row+o));for(let n of e.powerWires.keys())if(e.blockedCells.has(n)||t.has(n))throw new Error(`Invalid starter power wire at ${n}`);for(let n of["starterGenerator","mixer","trash","truck"])if(!e.facilities[n])throw new Error(`Missing starter power facility: ${n}`)}function rs(e){let t=Fl.map(([n,o])=>({col:n,row:o}));for(let{col:n,row:o}of t)is(e,n,o);for(let n=0;n({...n,keys:n.keys?{left:{...n.keys.left},right:{...n.keys.right}}:null,queue:[]})),e.eggFarms=Bl.map(n=>{let o=Jt(n);return{...n,nextSpawn:o,lastInterval:o}}),os(e);let t=ss(e);for(let n of e.powerWires.keys())t.add(n);e.blockedCells=qi({seed:e.runSeed,targetRatio:_.map.blockedRatio,reserved:[...t]}),Vl(e),rs(e),os(e),Ul(e),e.power={dirty:!0,elapsed:0,routes:new Map,assignments:new Map,sources:new Map,underVoltage:new Set},e.walkabilityVersion=(e.walkabilityVersion||0)+1,e.routingVersion=(e.routingVersion||0)+1,e.factoryGraph=null,e.factoryGraphCommittedVersion=-1}function Pt(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}]:[]}function ge(e){let t=B(e.col,e.row);return{x:t.x+p.cell/2,y:t.y-p.cell/2}}function it(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=>Pt(o).some(r=>r.col===t&&r.row===n))||null}function Zt(e,t){return e.scanners.find(n=>n.id===t)||null}function $r(e,t){return e.scanners.find(n=>n.kind==="manual"&&n.slot===t)||null}function mn(e){return e?.baseId||e?.id||""}function Fr(e,t){return Object.entries(e.facilities||{}).filter(([,n])=>mn(n)===t)}function Ro(e,t){return!!(t&&Re(t.col,t.row)&&(!e?.ownedCells?.size||e.ownedCells.has(E(t.col,t.row))))}function bt(e,t,n){return Object.values(e?.facilities||{}).find(o=>o?.gridPlaced?t>=o.col&&t=o.row&&n({col:e.col+t.dc,row:e.row+t.dr})).filter(t=>Re(t.col,t.row)):[]}function cs(e,t,n=[],o=[]){if(!t||!Re(t.col,t.row))return[];let r=[],i=a=>{!a||!Re(a.col,a.row)||!e.conveyorTiles.has(E(a.col,a.row))||n.some(c=>c&&ko(a,c))||r.some(c=>ko(c,a))||r.push({...a,viaTolerance:!ko(a,t)})};i(t);for(let a of o)i(a);for(let a of ls(t))i(a);return r}function Ar(e,t,n=[],o=[]){let r=cs(e,t,n,o),i=r.filter(a=>!a.viaTolerance);return i.length?i:r}function Kl(e,t){let n=We(t,"inputA");return!n||!Re(n.col,n.row)?[]:Ar(e,n,Pt(t))}function ql(e,t,n){let o=We(t,n);return cs(e,o,Pt(t))}function zl(e,t){let n=e.facilities[t]||Fr(e,t)[0]?.[1];if(!n)return null;let o=wt(e,n);if(o)return B(o.col,o.row);let r=mn(n)||t;return r==="mixer"?{x:n.x+n.w,y:n.y+n.h*.52}:r==="truck"?{x:n.x,y:n.y+n.h*.52}:r==="trash"?{x:n.x+n.w/2,y:n.y+8}:{x:n.x+n.w/2,y:n.y+n.h/2}}function us(e,t,n){return n==="inputA"?Kl(e,t).length>0:ql(e,t,n).length>0}function Ee(e){e.branchCounters?.clear?.(),e.routeCache=null,e.routingVersion=(e.routingVersion||0)+1,e.factoryGraph=null,e.factoryGraphCommittedVersion=-1,e.frameCache&&(e.frameCache.routingVersion=-1,e.frameCache.congestionFrame=-1,e.frameCache.draw={})}function ze(e,t){return E(e,t)}function wn(e){return X(e)}function Hl(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=>fe.some(i=>i.name===r))}function as(e,t,n){let o=fe.find(a=>a.name===n);if(!o)return!1;let r=e.conveyorMeta?.get?.(ze(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 Yl(e,t){let n=wn(t),o=e.conveyorMeta?.get?.(t)||{},r=fe.filter(C=>e.conveyorTiles.has(ze(n.col+C.dc,n.row+C.dr))).map(C=>C.name),i=Hl(e,t).filter(C=>r.includes(C)),a=i.length?i:r;if(r.length===1&&(!i.length||i.includes(r[0]))&&as(e,n,r[0]))return[];if(r.length<3)return a;let c=new Set(r.filter(C=>as(e,n,C))),b=r.filter(C=>!c.has(C)),M=c.size?b:[...new Set(a)];return!M.length&&!c.size&&(M=r),o.branchMode&&o.branchMode!=="random"&&M.length>=2&&M.includes(o.branchMode)?[o.branchMode]:M}function Ql(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 Xl(e){let t=Ql(e);for(let o of e.conveyorTiles)t.cells.add(o),t.adjacency.set(o,[]);for(let o of t.cells){let r=wn(o),i=Yl(e,o),a=fe.filter(c=>i.includes(c.name));for(let c of a){let b=ze(r.col+c.dc,r.row+c.dr);t.cells.has(b)&&t.adjacency.get(o).push({key:b,dir:c.name})}}let n=0;for(let[o,r]of t.adjacency.entries()){n+=r.length;let i=new Set(r.map(a=>a.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),jl(t),Jl(e,t),t.issues=uc(e,t,{includeRoutes:!1}),t}function jl(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 a=r.shift();i.push(a);for(let c of e.adjacency.get(a)||[])e.cellToComponent.has(c.key)||(e.cellToComponent.set(c.key,o),r.push(c.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 Jl(e,t){for(let n of e.scanners){let o={};for(let r of["inputA","left","right"]){let i=We(n,r),a=Ar(e,i,Pt(n)).filter(M=>t.cells.has(ze(M.col,M.row))),c=a.map(M=>ze(M.col,M.row)),b=c.length>0;if(o[r]={type:r,cell:i,cells:a,keys:c,key:c[0]||null,connected:b},t.metrics.scannerPorts+=1,b&&(t.metrics.connectedScannerPorts+=1),r==="inputA")for(let M of c)t.scannerInputs.set(M,n.id)}t.scannerPorts.set(n.id,o)}for(let[n,o]of Object.entries(e.facilities)){let r=wt(e,o);if(!r)continue;let i=Ar(e,r).filter(b=>t.cells.has(ze(b.col,b.row))),a=i.map(b=>ze(b.col,b.row)),c=a.length>0;t.facilityPorts.set(n,{id:n,cell:{...r},cells:i,keys:a,key:a[0]||null,connected:c}),t.metrics.facilityPorts+=1,c&&(t.metrics.connectedFacilityPorts+=1)}for(let n of e.eggFarms){let o=ls({col:n.col,row:n.row}).map(r=>ze(r.col,r.row)).filter(r=>t.cells.has(r));t.farmOutputs.set(n.id,o),t.metrics.farmOutputs+=o.length}}function at(e){let t=e.routingVersion||0;return(!e.factoryGraph||e.factoryGraph.version!==t)&&(e.factoryGraph=Xl(e)),e.factoryGraph}function ds(e){let t=at(e);return e.factoryGraphCommittedVersion=t.version,e.factoryGraphSnapshot=Zl(t),t}function Zl(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 ec(e,t){let n=t.col-e.col,o=t.row-e.row;return fe.find(r=>r.dc===n&&r.dr===o)?.name||null}function Or(e,t,n){let o=at(e),r=ze(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(ae=>ae.map(ye=>({...ye})));let a=`${r}|none`,c=[{key:r,col:t.col,row:t.row,incoming:"none",depth:0}],b=new Map([[a,0]]),M=new Map,C=[],$=1/0,W=8,K=24,G=80;for(;c.length;){let ae=c.shift();if(ae.depth>$)break;let ye=`${ae.key}|${ae.incoming}`;if(n(ae)){if(C.push(ye),$=ae.depth,C.length>=K)break;continue}for(let L of o.adjacency.get(ae.key)||[]){let V=wn(L.key),se=ec(ae,V),O=ae.depth+1;if(O>$)continue;let H=`${L.key}|${se}`,oe=b.get(H);if(oe==null)b.set(H,O),M.set(H,[ye]),c.push({key:L.key,col:V.col,row:V.row,incoming:se,depth:O});else if(oe===O){let ue=M.get(H)||[];ue.length{if(z.length>=G)return;let[L]=ae.split("|"),V=[wn(L),...ye];if(ae===a||L===r){z.push(V);return}for(let se of M.get(ae)||[])Q(se,V)};for(let ae of C)Q(ae,[]);let ie=[],J=new Set;for(let ae of z){let ye=ae.map(L=>ze(L.col,L.row)).join(">");J.has(ye)||(J.add(ye),ie.push(ae))}return n.cacheKey&&o.pathCache.set(i,ie.map(ae=>ae.map(ye=>({...ye})))),ie}function fs(e){let t=at(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 Ao(e,t=null){let n=e.map(o=>B(o.col,o.row));return t&&n.push(t),n}function Wt(e,t){t&&(!e.length||Gi(e[e.length-1],t)>1)&&e.push(t)}function tc(e,t,n,o=null){let r=[{x:e.x,y:e.y}];Wt(r,B(t.col,t.row));for(let i of Ao(n,o))Wt(r,i);return r}function nc(e,t){let n=Ao(e),o=We(t,"inputA");return o&&Wt(n,B(o.col,o.row)),Wt(n,ge(t)),n}function oc(e,t,n,o){let r=[{x:e.x,y:e.y}];Wt(r,B(t.col,t.row));for(let a of Ao(n))Wt(r,a);let i=We(o,"inputA");return i&&Wt(r,B(i.col,i.row)),Wt(r,ge(o)),r}function rc(e,t,n=0){let o=B(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 W=C.roleBias??0,K=$.roleBias??0;if(W!==K)return W-K;let G=C.cells?.length??C.route?.length??Number.POSITIVE_INFINITY,z=$.cells?.length??$.route?.length??Number.POSITIVE_INFINITY;if(G!==z)return G-z;let Q=C.key||JSON.stringify(C.route||C.cells||C),ie=$.key||JSON.stringify($.route||$.cells||$);return Q.localeCompare(ie)});if(!o||r.length===1)return r[0];let i=r[0].roleBias??0,a=r.filter(C=>(C.roleBias??0)===i),c=a.map(C=>({item:C,score:ic(e,C)})),b=Math.min(...c.map(C=>C.score));a=c.filter(C=>C.score===b).map(C=>C.item);let M=e.branchCounters.get(t)||0;return e.branchCounters.set(t,M+1),a[Math.floor(Math.random()*a.length)]}function ps(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 Br(e){let t=ps(e);return{left:t.left.destination,right:t.right.destination}}function Dr(e,t){return ps(e).left.match===t.sex?"left":"right"}function Lr(e){return Wi(e)}function Nr(e){return{mixer:"#2477ff",truck:"#ff6aa8",inboundTruck:"#8bd0ff",trash:"#22b94f",sausageMaker:"#d78b3f",biomassGenerator:"#7bd84f",coalGenerator:"#d0d5dc","scanner-role-1":"#526456",scanner:"#526456",input:"#526456"}[e]||"#102015"}function en(e,t,n=!1){let o=at(e),r=(o.farmOutputs.get(t.id)||[]).map(wn),i=[];for(let b of r){let M=$=>o.scannerInputs.has(ze($.col,$.row));M.cacheKey=`farmInput:${b.col},${b.row}:v${o.version}`;let C=Or(e,b,M);for(let $ of C){let W=$[$.length-1],K=o.scannerInputs.get(ze(W.col,W.row)),G=Zt(e,K);G&&i.push({key:`${G.id}:${W.col},${W.row}:${$.length}`,scanner:G,cells:$})}}let a=Io(e,`farm:${t.id}`,i,n);if(a)return{scannerId:a.scanner.id,route:nc(a.cells,a.scanner)};let c=Io(e,`farm:${t.id}:output`,r.map(b=>({key:E(b.col,b.row),cells:[b]})),n);return c?{scannerId:null,route:Ao(c.cells)}:null}function ys(e,t,n,o,r=!1){let i=Zt(e,o);if(!i)return null;let c=at(e).scannerPorts.get(i.id)?.[t],b=We(i,t),M=c?.connected?(c.keys||[c.key]).filter(Boolean).map(wn):[];if(!b||!M.length)return null;let C=Br(i)[t];return C==="scanner-role-1"?sc(e,i,t,n,b,M,r):ac(e,i,t,n,b,M,C,r)}function sc(e,t,n,o,r,i,a){let c=at(e),b=e.scanners.filter($=>$.id!==t.id&&c.scannerPorts.get($.id)?.inputA?.connected),M=[];for(let $ of i)for(let W of b){let K=c.scannerPorts.get(W.id)?.inputA?.key;if(!K)continue;let G=(c.scannerPorts.get(W.id)?.inputA?.keys||[K]).filter(Boolean),z=J=>G.includes(ze(J.col,J.row));z.cacheKey=`toScanner:${$.col},${$.row}:${W.id}:v${c.version}`;let Q=Or(e,$,z),ie=W.role===1?0:1;for(let J of Q)M.push({key:`${ie}:start${E($.col,$.row)}:s${W.id}:${J.length}:${E(J[J.length-1].col,J[J.length-1].row)}`,target:W,cells:J,roleBias:ie})}let C=Io(e,`scanner:${t.id}:${n}:toRole1`,M,a);return C?{destination:"scanner",nextScannerId:C.target.id,route:oc(o,r,C.cells,C.target)}:null}function ac(e,t,n,o,r,i,a,c){let b=at(e),M=[...b.facilityPorts.entries()].filter(([W,K])=>K?.connected&&mn(e.facilities?.[W])===a);if(!M.length)return null;let C=[];for(let W of i)for(let[K,G]of M){let z=(G.keys||[G.key]).filter(Boolean);if(!z.length)continue;let Q=J=>z.includes(ze(J.col,J.row));Q.cacheKey=`toFacility:${W.col},${W.row}:${K}:v${b.version}`;let ie=Or(e,W,Q);for(let J of ie)C.push({key:`start${E(W.col,W.row)}:${K}:${J.length}:${E(J[J.length-1].col,J[J.length-1].row)}`,cells:J,facilityId:K})}let $=Io(e,`scanner:${t.id}:${n}:${a}`,C,c);return $?{destination:a,facilityId:$.facilityId,route:tc(o,r,$.cells,zl(e,$.facilityId))}:null}function hs(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=ys(e,o,ge(t),t.id,!1);if(r){if(["mixer","trash","truck","sausageMaker"].includes(r.destination))return!0;if(r.destination==="scanner"){let i=Zt(e,r.nextScannerId);if(hs(e,i,n))return!0}}}return!1}function lc(e,t){return!!en(e,t,!1)?.scannerId}function vn(e){if(e.phase!=="build")return[];let t=[];for(let n of e.eggFarms||[])lc(e,n)||t.push({type:"eggFarm",id:n.id,ref:n,message:"This Egg will not produce chicks"});for(let n of Cr)for(let[o,r]of Fr(e,n))at(e).facilityPorts.get(o)?.connected||t.push({type:"facility",id:o,ref:r,message:"This facility cannot receive items"});return t}function cc(e){let t=at(e);for(let r of e.eggFarms||[]){let i=en(e,r,!1);if(!i?.scannerId)continue;let a=Zt(e,i.scannerId);if(hs(e,a))return[]}let n=e.eggFarms?.length||0,o=[...t.facilityPorts.entries()].filter(([r,i])=>i?.connected&&["mixer","trash","truck"].includes(mn(e.facilities?.[r]))).length;return n?t.metrics.farmOutputs?e.scanners?.length?o?["No route from an EGG scanner to a receiver."]:["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 Po(e){return e.kind==="manual"?`S${(e.slot??0)+1}`:`Auto Scanner #${e.id}`}function uc(e,t=at(e),n={}){let o=n.includeRoutes!==!1,r=[],i={mixer:"Mixer",trash:"Shredder",truck:"Truck",sausageMaker:"Sausage Machine"};for(let a of Cr){let c=Fr(e,a);if(!c.length){Di.includes(a)&&r.push(`${i[a]||a} is missing`);continue}for(let[b]of c)t.facilityPorts.get(b)?.connected||r.push(`${i[a]||a} receiver has no conveyor`)}for(let a of e.scanners){let c=t.scannerPorts.get(a.id)||{};if(c.inputA?.connected||r.push(`${Po(a)} input has no conveyor`),c.left?.connected||r.push(`${Po(a)} left output has no conveyor`),c.right?.connected||r.push(`${Po(a)} right output has no conveyor`),!!o)for(let b of["left","right"])c[b]?.connected&&!ys(e,b,ge(a),a.id)&&r.push(`${Po(a)} ${b} route is incomplete`)}if(o)for(let a of e.eggFarms)en(e,a)?.scannerId||r.push(`Egg Farm #${a.id} has no scanner route`);else for(let a of e.eggFarms)(t.farmOutputs.get(a.id)||[]).length||r.push(`Egg Farm #${a.id} has no output conveyor`);return[...new Set(r)]}function $o(e){return cc(e)}var Hn=[{key:"premiumFemaleShipment",target:"female",truckTarget:"female",title:"Premium Female Shipment",route:"Female -> Truck",note:"Today only: ship female chicks to Truck. Meet the export target for a bonus.",objective:"truckTarget"},{key:"contaminantSampleExport",target:"poop",truckTarget:"poop",title:"Contaminant Sample Export",route:"Poop value x2",note:"Today only: poop is valid Truck cargo and all poop income is doubled.",objective:"truckTarget",poopValueMultiplier:2},{key:"healthInspection",title:"Health Inspection",route:"Poop -> Shredder",note:"Today only: poop mistakes are fined double. Shred enough poop for an inspection bonus.",objective:"poopTrash",poopFineMultiplier:2},{key:"conveyorJamWarning",title:"Conveyor Jam Warning",route:"No explosions",note:"Today only: jam explosions trigger at 85% congestion. Finish with zero explosions for a bonus.",objective:"noExplosion",congestionExplosionThreshold:.85,congestionWarningThreshold:.68},{key:"powerSavingOrder",title:"Power Saving Order",route:"Slow factory subsidy",note:"Today only: conveyors -20% speed and Auto Scanners +20% cooldown. A subsidy is paid at settlement.",objective:"flatBonus",conveyorSpeedMultiplier:.8,autoScannerCooldownMultiplier:1.2},{key:"overtimeDay",title:"Overtime Day",route:"80 second day",note:"Today only: the work day is longer. No extra reward or penalty.",objective:"none",daySeconds:80},{key:"shortShift",title:"Short Shift",route:"40 second day",note:"Today only: the work day is shorter. The next Fairies tribute is reduced by 10%.",objective:"none",daySeconds:40,fairiesTributeMultiplier:.9},{key:"chickBoom",title:"Chick Boom",route:"EGG interval x0.65",note:"Today only: eggs hatch faster and poop rate rises by 5 percentage points.",objective:"none",productionMultiplier:1/.65,poopRateAdd:.05},{key:"rottenFeed",title:"Rotten Feed",route:"Poop surge",note:"Today only: poop rate rises by 20 percentage points and Shredder poop income is multiplied by 1.5.",objective:"none",poopRateAdd:.2,shredderIncomeMultiplier:1.5},{key:"maintenanceAudit",title:"Maintenance Audit",route:"Durability check",note:"Today only: machines below 50% remaining durability fail the audit. Passing with none below 50% pays a bonus.",objective:"maintenanceAudit"}];function Fe(e,t=0){let n=Number(e);return Number.isFinite(n)?n:t}function dc(e,t=0,n=1){let o=Math.max(1,Number(e?.turn||1)+1),r=Math.floor(Gr(e)*(.08+Math.random()*.08)),i=Math.floor(Math.max(0,e.cash||0)*.25),a=Math.floor(Math.max(0,t)*Math.max(1,o)*.75);return Math.max(120,Math.ceil(Math.max(r,i,a)*n))}function bs(e){return Math.max(0,Math.ceil(500+Math.max(1,e?.turn||1)*25))}function Gr(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 fc(e,t=1,n=yt){return e.eggFarms.reduce((o,r)=>{let[i,a]=zn(r),c=Math.max(.1,(i+a)/2);return o+Math.max(1,n)/c*Math.max(.01,t||1)},0)}function pc(e,t){let n=Fe(t.productionMultiplier,1),o=Fe(t.daySeconds,yt),r=fc(e,n,o),i=Math.max(0,Number(e?.cardEffects?.hatchingFeed)||0),a=Math.max(0,Number(e?.cardEffects?.poopRateBooster)||0),c=Math.pow(1.03,i),b=vr+Sr*a,M=Math.min(.95,Math.max(0,(b+Fe(t.poopRateAdd,0))*Fe(t.poopMultiplier,1)*c));return{estimate:r,poopRate:M}}function _r(e,t){let n=typeof t=="string"?Hn.find(c=>c.key===t):t;if(!n)return null;let{estimate:o,poopRate:r}=pc(e,n),i=0;n.objective==="truckTarget"?i=Math.max(1,Math.ceil(o*.3)):n.objective==="poopTrash"&&(i=Math.max(1,Math.ceil(o*r*.8)));let a=0;return["truckTarget","poopTrash","noExplosion"].includes(n.objective)&&(a=dc(e,o)),n.objective==="flatBonus"&&(a=bs(e)),n.objective==="maintenanceAudit"&&(a=bs(e)),{id:`event-${e.turn}-${Date.now()}-${Math.floor(Math.random()*9999)}`,...n,target:n.target||null,truckTarget:n.truckTarget||null,forced:!0,productionMultiplier:Fe(n.productionMultiplier,1),poopMultiplier:Fe(n.poopMultiplier,1),poopRateAdd:Fe(n.poopRateAdd,0),daySeconds:Fe(n.daySeconds,yt),targetAmount:i,estimatedOutput:Math.round(o),reward:a,createdTurn:e.turn,oneTurnOnly:!0}}function ws(e){if(e.turn+1Oi)return null;let n=Hn[Math.floor(Math.random()*Hn.length)];return _r(e,n)}function ms(e){e.eventActive=e.eventOffer?{...e.eventOffer}:null,e.eventOffer=null}function vs(e){e.eventActive=null}function Ss(e){return Math.max(1,Fe(e.eventActive?.daySeconds,yt))}function ks(e){return Fe(e.eventActive?.productionMultiplier,1)}function Ms(e){let t=Math.max(0,Number(e?.cardEffects?.hatchingFeed)||0),n=Math.max(0,Number(e?.cardEffects?.poopRateBooster)||0),o=Math.pow(1.03,t),r=vr+Sr*n,i=Fe(e.eventActive?.poopRateAdd,0);return Math.min(.95,Math.max(0,(r+i)*Fe(e.eventActive?.poopMultiplier,1)*o))}function Sn(e){return e.eventActive?.truckTarget||e.eventActive?.target||"female"}function Wr(e,t){return t==="male"?e.truckMale||0:t==="poop"?e.poopTruck||0:e.truckFemale||0}function Cs(e,t){return Sn(e)===t}function gs(e){return Sn(e)!=="male"}function Ur(e){return Math.max(.01,Fe(e.eventActive?.poopValueMultiplier,1))}function Fo(e){return Math.max(.01,Ur(e)*Fe(e.eventActive?.shredderIncomeMultiplier,1))}function Vr(e){return Math.max(.01,Fe(e.eventActive?.poopFineMultiplier,1))}function Kr(e){return Math.max(.1,Math.min(1,Fe(e.eventActive?.congestionExplosionThreshold,1)))}function xs(e){return Math.max(.1,Math.min(1,Fe(e.eventActive?.congestionWarningThreshold,.8)))}function Ts(e){return Math.max(.01,Fe(e?.fairiesTributeMultiplier,1))}function yc(e){let t=0,n=(o,r)=>{let i=Math.max(1,Fe(r,1)),a=Math.max(0,Fe(o,0));1-Math.min(1,a/i)<.5&&(t+=1)};for(let o of e.conveyorMeta?.values?.()||[])n(o.uses,o.durability);for(let o of e.eggFarms||[])n(o.maintenance?.uses,o.maintenance?.durability);for(let o of e.scanners||[])o.kind==="auto"&&n(o.maintenance?.uses,o.maintenance?.durability);for(let o of Object.values(e.facilities||{})){let r=o?.baseId||o?.id;(r==="mixer"||r==="trash"||r==="sausageMaker")&&n(o.maintenance?.uses,o.maintenance?.durability)}return t}function Es(e){let t=e.eventActive;if(!t)return null;let n=0,o=!0,r=0,i="";t.objective==="truckTarget"?(n=Wr(e.stats,t.target),o=n>=t.targetAmount,r=o?t.reward:0):t.objective==="poopTrash"?(n=e.stats.poopTrash||0,o=n>=t.targetAmount,r=o?t.reward:0):t.objective==="noExplosion"?(n=e.stats.explosions||0,o=n===0,r=o?t.reward:0,i=`${n} explosion(s)`):t.objective==="flatBonus"?(o=!0,r=t.reward):t.objective==="maintenanceAudit"?(n=yc(e),o=n===0,o&&(r=t.reward),i=`${n} machine(s) below 50%`):o=!0,r>0?(tt(e,r),e.stats.eventBonus=(e.stats.eventBonus||0)+r,e.totals.eventBonus+=r,e.totals.eventSuccess+=1):t.objective&&t.objective!=="none"&&(e.stats.eventBonus=e.stats.eventBonus||0,e.totals.eventFailed+=o?0:1,o&&(e.totals.eventSuccess+=1));let a={title:t.title,key:t.key,target:t.target,route:t.route,objective:t.objective,targetAmount:t.targetAmount||0,count:n,success:o,bonus:r,reward:t.reward||0,productionMultiplier:t.productionMultiplier,poopMultiplier:t.poopMultiplier,daySeconds:t.daySeconds,detail:i};return e.stats.eventResult=a,a}function qr(e,t){return Math.max(0,Number(e?.cardEffects?.[t])||0)}function hc(e){return!!e?.cardEffects?.usedMachineActive}function bc(e,t){return t?1:Math.min(.8,.5+.1*Math.min(3,qr(e,"recyclingSubsidy")))}function wc(e){return Math.pow(.95,qr(e,"safetyCover"))}function Oo(e){let t=e?.ref||e||{},n=e?.type||t.type;return n==="conveyor"?t.price||Te[t.kind]?.price||Te.conveyor.price:n==="eggFarm"?t.price||Te.eggFarm.price:n==="scanner"?t.price||(t.kind==="auto"?Te.autoScanner.price:Te.manualScanner.price):n==="facility"?t.price||Te[t.id]?.price||0:n==="maintenanceRoom"?t.price||Te.maintenanceRoom.price:Te[t.id]?.price||0}function Le(e,t=null){let n=Te[e]?.price||0;return hc(t)?Math.ceil(n*.5):n}function mc(e,t){return e?.facilities?Object.values(e.facilities).filter(n=>(n?.baseId||n?.id)===t).reduce((n,o)=>n+Math.max(0,(o.level||1)-1),0):0}function Rt(e,t){return Li.includes(t)?Math.pow(Ie.incomeUpgradeRate,mc(e,t)):1}function Je(e){return Math.ceil(Ie.income.truck*Rt(e,"truck"))}function lt(e){return Math.ceil((Ie.income.sausage||26)*Rt(e,"mixer")*Rt(e,"sausageMaker"))}function Yn(e){return Math.ceil(Ie.poopFine*Rt(e,"mixer")*Vr(e))}function Qn(e){return Math.ceil(Ie.poopFine*Rt(e,"truck")*Vr(e))}function zr(e){let t=Ie.maleTruckFinePerHalfDay*(e.turn/2);return Math.ceil(t*Rt(e,"truck"))}function Hr(e){return e?.facilities?Object.values(e.facilities).filter(t=>(t?.baseId||t?.id)==="trash").reduce((t,n)=>t+Math.max(0,(n.level||1)-1),0):0}function At(e){return Math.min(Ie.shredderBonus.maxCards,Hr(e))}function kn(){return Ie.shredderBonus.maxCards}function Bo(){return Ie.shredderBonus||{}}function Ps(e){let t=typeof e=="number"?e:At(e),n=Bo(),o=Math.min(n.maxCards||30,Math.max(0,t));return o>0?Math.ceil((n.payoutCoefficient||1.6)*Math.pow(o,n.payoutExponent||1.25)):0}function tn(e){let t=typeof e=="number"?e:At(e);if(t<=0)return 0;let n=Bo();return Math.min(n.maxHitChance||.82,(n.baseHitChance||.28)+t*(n.hitChancePerUpgrade||.018))}function vc(e){let t=Bo(),n=Math.min(t.jackpotMaxChance||.08,(t.jackpotBaseChance||.01)+e*(t.jackpotChancePerUpgrade||.0025)),o=Math.min(t.bigWinMaxChance||.12,(t.bigWinBaseChance||.07)+e*(t.bigWinChancePerUpgrade||.0015)),r=Math.min(1-n-o,t.doubleChance||.2),i=Math.max(0,1-n-o-r);return n*(t.jackpotMultiplier||10)+o*(t.bigWinMultiplier||4)+r*(t.doubleMultiplier||2)+i}function It(e){let t=typeof e=="number"?e:At(e);return Math.round(Ps(t)*tn(t)*vc(t))}function Sc(e){let t=At(e),n=tn(t),o=Ps(t);if(t<=0||Math.random()>=n)return{cards:t,chance:n,amount:0,expected:It(t),baseAmount:o,multiplier:0,tier:"miss"};let r=Bo(),i=Math.min(r.jackpotMaxChance||.08,(r.jackpotBaseChance||.01)+t*(r.jackpotChancePerUpgrade||.0025)),a=Math.min(r.bigWinMaxChance||.12,(r.bigWinBaseChance||.07)+t*(r.bigWinChancePerUpgrade||.0015)),c=Math.min(1-i-a,r.doubleChance||.2),b=Math.random(),M=1,C="normal";b0&&(e.stats.shredderBonus=(e.stats.shredderBonus||0)+t.amount,e.totals.shredderBonus=(e.totals.shredderBonus||0)+t.amount,tt(e,t.amount)),t}function Yr(e,t=e.turn,n=null){let o=Math.max(1,Math.floor(Number(t)||1)),r=Ie.fairiesTribute||{perDay:10,profitExemption:300,legalWorkExemptionBonus:250,profitRate:.12},i=Math.ceil(o*(r.perDay||10)),a=Math.max(0,Math.floor(Number(n??e?.lastResult?.profit??e?.stats?.profit??0)||0)),c=Math.max(0,e?.cardEffects?.legalWork||0),b=Math.max(0,(r.profitExemption||0)+c*(r.legalWorkExemptionBonus||0)),M=Math.max(0,a-b),C=Math.ceil(M*Math.max(0,Number(r.profitRate)||0)),$=i+C,W=Math.max(0,e?.cardEffects?.fairiesFlatteryNext||e?.cardEffects?.flattery||0),K=W>0?Math.pow(.5,W):1,G=Math.max(.01,Number(e?.pendingFairiesTributeMultiplier)||1),z=K*G,Q=Math.max(0,Math.ceil($*z)),ie=Math.max(0,$-Q);return{day:o,base:i,profit:a,legalWork:c,profitExemption:b,taxableProfit:M,profitTribute:C,gross:$,flatteryCount:W,eventMultiplier:G,perCardReduction:Math.ceil($*.5),reduction:ie,amount:Q}}function Ut(e,t){let n=e?.ref||e||{},o=e?.type==="conveyor"?t?.conveyorMeta?.get(e.oldKey||n.id):n,r=o?.price||Oo(e||n),i=o?.builtSession===t?.buildSession;if(o?.noRefund||n?.noRefund)return{amount:0,sameBuild:i,price:r,rate:0,noRefund:!0};let a=bc(t,i);return{amount:Math.max(0,Math.ceil(r*a)),sameBuild:i,price:r,rate:a,noRefund:!1}}function kc(e,t=null){let n=Math.max(1,Math.ceil((Number(e)||0)/Ie.explosionDamageDivisor));return Math.max(1,Math.ceil(n*wc(t)))}function Mc(e,t){return Math.max(0,Math.ceil(t))}function Is(e,t){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 tt(e,t){let n=Math.max(0,Math.ceil(t));return n>0&&Is(e,n),n}function Mt(e,t){let n=Math.max(0,Math.ceil(t));return n>0&&Is(e,-n),n}function Ct(e,t){Mt(e,t)}function Qr(e,t){t<=0||(e.cash+=t,e.stats.profit+=t,e.totals.profit+=t,e.totals.maxCash=Math.max(e.totals.maxCash,e.cash))}function As(e){let t=Ie.income.mixer;return e.stats.mixerRevenue+=t,tt(e,t),t}function $s(e){let t=Yn(e);return e.stats.mixerPoopFine+=t,e.totals.mixerPoopFine+=t,Mt(e,t),t}function Fs(e){let t=Qn(e);return e.stats.truckPoopFine+=t,e.totals.truckPoopFine+=t,Mt(e,t),t}function Os(e){let t=zr(e);return e.stats.maleTruckFine+=t,e.totals.maleTruckFine+=t,Mt(e,t),t}function Bs(e,t){let n=kc(t,e);return e.stats.explosionDamage+=n,e.totals.explosionDamage+=n,Mt(e,n),n}function Ds(e){let t=Sn(e),n=t==="poop"?Math.ceil(Je(e)*Ur(e)):Je(e),o=Array.isArray(e.shippedCargo)?e.shippedCargo:Array.isArray(e.truckCargo)?e.truckCargo:[],r=o.filter(G=>G.sex===t),i=r.length||Wr(e.stats,t),a=r.length?r.reduce((G,z)=>G+Math.ceil(n*(t==="female"?Math.max(1,z.sourceQuality||1):1)),0):i*n,c=o.filter(G=>G.sex==="sausage"),b=c.length||Math.max(0,e?.stats?.sausageShipped||0),M=lt(e),C=c.length?c.reduce((G,z)=>G+Math.ceil(M*Math.max(1,z.sourceQuality||1)),0):b*M,$=i*n+b*M,W=Math.max(0,a+C-$);e.stats.qualityRevenue=(e.stats.qualityRevenue||0)+W,e.totals.qualityRevenue=(e.totals.qualityRevenue||0)+W;let K=Mc(e,a+C);return e.stats.pendingTruckRevenue=K,e.stats.truckRevenue=K,e.stats.sausageRevenue=C,t==="poop"?e.stats.poopShipmentIncome=a:e.stats.chickShipmentIncome=a,K>0&&tt(e,K),{base:a,adjusted:K,target:t,targetCount:i,unitPrice:n,sausageCount:b,sausageUnitPrice:M,sausageRevenue:C}}function Ls(e,t=e.turn,n=null,o={}){let r=Yr(e,t,n);return e.cardEffects&&(e.cardEffects.fairiesFlatteryNext=0,e.cardEffects.flattery!=null&&delete e.cardEffects.flattery),e.pendingFairiesTributeMultiplier!=null&&(e.pendingFairiesTributeMultiplier=1),r.amount<=0||(o.recordStats?(e.stats.fairiesTribute=(e.stats.fairiesTribute||0)+r.amount,e.totals.fairiesTribute=(e.totals.fairiesTribute||0)+r.amount,Mt(e,r.amount)):(e.totals.fairiesTribute=(e.totals.fairiesTribute||0)+r.amount,e.totals.penalty=(e.totals.penalty||0)+r.amount,e.totals.profit=(e.totals.profit||0)-r.amount,e.cash-=r.amount)),r}function Ns(e){let t=qr(e,"chemicalWeaponSubsidy"),n=Math.max(0,e?.stats?.poopTruck||0),o=Ie.chemicalWeaponSubsidy||{poopPerBlock:70,yenPerBlock:1200},r=Math.floor(n/Math.max(1,o.poopPerBlock||70)),i=t>0&&r>0?r*Math.max(0,o.yenPerBlock||1200)*t:0;return i>0&&(e.stats.chemicalWeaponSubsidy=(e.stats.chemicalWeaponSubsidy||0)+i,e.totals.chemicalWeaponSubsidy=(e.totals.chemicalWeaponSubsidy||0)+i,tt(e,i)),{cards:t,poopTruck:n,blocks:r,amount:i}}function Gs(e){let t=Gr(e),n=Math.max(1,e.totals.turnsCompleted||e.turn||1),o=Math.max(0,e.cash+e.totals.revenue+t*.5+e.totals.eventBonus*.5),r=Math.ceil(o/n),i=Math.floor(n*35+Math.max(0,n-5)*15),a=e.totals.correct*5,c=e.totals.explosionDamage;return{score:Math.max(0,Math.floor(o+r+a-e.totals.penalty-i)),factoryValue:t,base:o,dailyEarned:r,dayPenalty:i,correctBonus:a,explosionPenalty:c,days:n}}function Cc(e){return{...e,keys:e.keys?{left:{...e.keys.left},right:{...e.keys.right}}:null,queue:[],cooldown:e.cooldown||0}}function Xn(e){return JSON.stringify({cash:e.cash,stats:e.stats,totals:e.totals,nextId:e.nextId,gridX:p.x,gridY:p.y,gridCols:e.gridCols||p.cols,gridRows:e.gridRows||p.rows,gridExpansionPurchases:e.gridExpansionPurchases||0,gridExpansionPurchasesByDirection:e.gridExpansionPurchasesByDirection||{left:0,right:0,up:0,down:0},gridChunkOriginX:e.gridChunkOriginX||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(Cc),conveyorTiles:[...e.conveyorTiles],conveyorMeta:[...e.conveyorMeta.entries()],powerWires:[...(e.powerWires||new Map).entries()],footbridges:[...(e.footbridges||new Map).entries()],footbridgeOrientation:e.footbridgeOrientation||"vertical",powerOverlay:!!e.powerOverlay,imports:{schedules:[...(e.imports?.schedules||new Map).entries()]},branchCounters:[...e.branchCounters.entries()],cardEffects:e.cardEffects,cardDraft:e.cardDraft,cardTargetPick:e.cardTargetPick})}function _s(e,t){let n=JSON.parse(t);e.cash=n.cash,e.stats=n.stats,e.totals=n.totals,e.nextId=n.nextId,p.x=n.gridX??_.grid.x,p.y=n.gridY??_.grid.y,p.cols=n.gridCols||_.grid.cols,p.rows=n.gridRows||_.grid.rows,e.gridCols=p.cols,e.gridRows=p.rows,e.gridExpansionPurchases=n.gridExpansionPurchases||0,e.gridExpansionPurchasesByDirection=n.gridExpansionPurchasesByDirection||{left:0,right:0,up:0,down:0},e.gridExpansionPurchasesByDirection.left==null&&(e.gridExpansionPurchasesByDirection.left=0),e.gridExpansionPurchasesByDirection.down==null&&(e.gridExpansionPurchasesByDirection.down=0),e.gridChunkOriginX=n.gridChunkOriginX||0,e.gridChunkOriginY=n.gridChunkOriginY||0,e.ownedCells=new Set(n.ownedCells||[]),e.ownedChunks=new Set(n.ownedChunks||[]),e.gridOwnedCells=e.ownedCells.size||p.cols*p.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.powerWires=new Map(n.powerWires||[]),e.footbridges=new Map(n.footbridges||[]),e.footbridgeOrientation=n.footbridgeOrientation||"vertical",e.powerOverlay=!!n.powerOverlay,e.imports={schedules:new Map(n.imports?.schedules||[])},e.branchCounters=new Map(n.branchCounters||[]),e.cardEffects=n.cardEffects||{bearing:0,legalWork:0,fairiesFlatteryNext:0,scannerQueueSpacing: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.walkabilityVersion=(e.walkabilityVersion||0)+1,e.power={...e.power||{},dirty:!0},e.factoryGraph=null,e.factoryGraphCommittedVersion=-1}function Oe(e){e.phase==="build"&&(e.undoStack.push(Xn(e)),e.undoStack.length>80&&e.undoStack.shift(),e.redoStack=[])}function Xr(e){return e.phase!=="build"||!e.undoStack.length?!1:(e.redoStack.push(Xn(e)),_s(e,e.undoStack.pop()),!0)}function jr(e){return e.phase!=="build"||!e.redoStack.length?!1:(e.undoStack.push(Xn(e)),_s(e,e.redoStack.pop()),!0)}function Y(e,t,n,o,r=w.ink){e.floatingTexts.push({x:t,y:n,text:o,color:r,life:1,maxLife:1})}function nn(e,t,n){e.shake.strength=Math.max(e.shake.strength,t),e.shake.time=Math.max(e.shake.time,n)}function Ws(e,t,n){e.effects.push({type:"spawn",priority:_e.ambient,x:t,y:n,life:.42,maxLife:.42,size:12})}function Us(e,t,n,o=w.green){e.effects.push({type:"scannerPulse",priority:_e.important,x:t,y:n,color:o,life:.42,maxLife:.42,size:24})}function Vs(e,t,n){for(let o=0;o<12;o+=1)e.effects.push({type:"meat",priority:_e.important,x:t+ne(-18,18),y:n+ne(-12,16),vx:ne(-60,60),vy:ne(-90,-25),life:ne(.45,.85),maxLife:.85,size:ne(4,8)})}function Ks(e,t,n){for(let o=0;o<16;o+=1)e.effects.push({type:"sludge",priority:_e.important,x:t+ne(-18,18),y:n+ne(-12,16),vx:ne(-46,46),vy:ne(-70,-20),life:ne(.45,.9),maxLife:.9,size:ne(4,8)})}function qs(e,t,n,o){let r=o==="poop"?w.poop:o==="male"?w.male:w.female;for(let i=0;i<22;i+=1)e.effects.push({type:"shred",priority:_e.important,x:t+ne(-16,16),y:n+ne(-8,8),vx:ne(-70,70),vy:ne(40,160),color:i%3===0?w.white:r,life:ne(.45,.95),maxLife:.95,w:ne(3,7),h:ne(10,24),rot:ne(-Math.PI,Math.PI)});gt(e,t,n,w.green,34,_e.important)}function zs(e,t,n,o){let r=o==="poop"?w.poop:o==="male"?w.danger:w.green;for(let i=0;i<8;i+=1)e.effects.push({type:"load",priority:_e.important,x:t+ne(-18,18),y:n+ne(-18,18),vx:ne(-42,42),vy:ne(-90,-25),color:r,life:ne(.35,.65),maxLife:.65,size:8})}function gt(e,t,n,o=w.danger,r=52,i=_e.critical){e.effects.push({type:"shockwave",priority:i,x:t,y:n,color:o,life:.55,maxLife:.55,size:8,maxSize:r})}function $t(e,t,n,o=12,r=w.danger,i=_e.critical){for(let a=0;a0&&(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||gc(e,r,o);let i=r.x+e.view.x,a=r.y+e.view.y;(i<-100||a<-100||i>n.width+100||a>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 gc(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=Bs(e,o.price);Y(e,t.x,t.y-10,`-${R(r)}`,w.danger),gt(e,t.x,t.y,w.danger,30),$t(e,t.x,t.y,5,w.danger),nn(e,5,.18)}}var Qs=[[{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 xc(e){let t=Qs[e]||Qs[0];return{left:{...t[0]},right:{...t[1]}}}function Xs(e,t,n){let o={type:"eggFarm",id:e.nextId++,col:t,row:n,level:1,nextSpawn:2,lastInterval:2,extraEggOutlet:0,price:_t.eggFarm,builtSession:e.buildSession};return o.nextSpawn=Jt(o),o.lastInterval=o.nextSpawn,o}function js(e,t,n,o){let r=o==="auto"?Te.autoScanner.price:Te.manualScanner.price,i=new Set(e.scanners.filter(c=>c.kind==="manual").map(c=>c.slot)),a=0;for(;i.has(a);)a+=1;return{type:"scanner",id:e.nextId++,kind:o,slot:o==="manual"?a:null,role:o==="manual"?a%2:0,col:t,row:n,level:1,queue:[],cooldown:0,price:r,builtSession:e.buildSession,autoMode:"standard",keys:o==="manual"?xc(a):null}}function Tc(e,t,n){let o=n.x-t.x,r=n.y-t.y,i=o*o+r*r,a=i>0?Math.max(0,Math.min(1,((e.x-t.x)*o+(e.y-t.y)*r)/i)):0,c=t.x+o*a,b=t.y+r*a;return Math.hypot(e.x-c,e.y-b)}function Ec(e){let t=[],n=e?.ownedCells?.size?[...e.ownedCells].map(o=>{let[r,i]=String(o).split(",").map(Number);return{col:r,row:i}}):Array.from({length:p.cols*p.rows},(o,r)=>({col:r%p.cols,row:Math.floor(r/p.cols)}));for(let o of n){let r=p.x+o.col*p.cell,i=p.y+o.row*p.cell,a=r+p.cell,c=i+p.cell;je(e,o.col-1,o.row)||t.push({side:"left",entry:o,a:{x:r,y:i},b:{x:r,y:c}}),je(e,o.col+1,o.row)||t.push({side:"right",entry:o,a:{x:a,y:i},b:{x:a,y:c}}),je(e,o.col,o.row-1)||t.push({side:"top",entry:o,a:{x:r,y:i},b:{x:a,y:i}}),je(e,o.col,o.row+1)||t.push({side:"bottom",entry:o,a:{x:r,y:c},b:{x:a,y:c}})}return t}function Lo(e,t=null){let n=Ec(t);if(n.length){let W=n.map(K=>({...K,distance:Tc(e,K.a,K.b)})).sort((K,G)=>K.distance-G.distance)[0];return{side:W.side,entry:{...W.entry}}}let o=(e.x-p.x)/p.cell,r=(e.y-p.y)/p.cell,i=Math.max(0,Math.min(p.cols-1,Math.round(o-.5))),a=Math.max(0,Math.min(p.rows-1,Math.round(r-.5))),c=Math.abs(e.x-p.x),b=Math.abs(e.x-(p.x+p.cols*p.cell)),M=Math.abs(e.y-p.y),C=Math.abs(e.y-(p.y+p.rows*p.cell)),$=[["left",c],["right",b],["top",M],["bottom",C]].sort((W,K)=>W[1]-K[1])[0][0];return $==="left"?{side:$,entry:{col:0,row:a}}:$==="right"?{side:$,entry:{col:p.cols-1,row:a}}:$==="top"?{side:$,entry:{col:i,row:0}}:{side:$,entry:{col:i,row:p.rows-1}}}function Mn(e){return e?.baseId||e?.id||""}function Cn(e){if(!e?.gridPlaced)return e;let t=Te[Mn(e)]||{};return e.wCells=Math.max(1,e.wCells||t.footprint?.cols||3),e.hCells=Math.max(1,e.hCells||t.footprint?.rows||3),e.x=p.x+e.col*p.cell,e.y=p.y+e.row*p.cell,e.w=e.wCells*p.cell,e.h=e.hCells*p.cell,Mn(e)==="sausageMaker"&&(e.entry={col:e.col-1,row:e.row+Math.floor(e.hCells/2)},e.side="grid"),(Mn(e)==="biomassGenerator"||Mn(e)==="coalGenerator")&&(e.entry={col:e.col-1,row:e.row+Math.floor(e.hCells/2)},e.side="grid"),e}function gn(e){if(!e?.gridPlaced)return[];let t=[];for(let n=0;n<(e.hCells||3);n+=1)for(let o=0;o<(e.wCells||3);o+=1)t.push({col:e.col+o,row:e.row+n});return t}function Pc(e,t){if(Mn(e)!=="mixer")return;let n=e.baseW||e.w||168,o=e.baseH||e.h||110;e.baseW=n,e.baseH=o;let r=t==="top"||t==="bottom";e.w=r?Math.max(n,o):Math.min(n,o),e.h=r?Math.min(n,o):Math.max(n,o)}function No(e,t="input"){if(!e)return null;let n=Mn(e);return n==="mixer"?e.side==="top"||e.side==="bottom"?t==="input"?{x:e.x+e.w/2,y:e.y}:{x:e.x+e.w/2,y:e.y+e.h}:t==="input"?{x:e.x,y:e.y+e.h/2}:{x:e.x+e.w,y:e.y+e.h/2}:n==="sausageMaker"&&e.gridPlaced?t==="input"?{x:e.x,y:e.y+e.h/2}:{x:e.x+e.w,y:e.y+e.h/2}:{x:e.x+e.w/2,y:e.y+e.h/2}}function Go(e,t,n){Pc(e,n);let o={x0:p.x+t.col*p.cell,y0:p.y+t.row*p.cell,x1:p.x+(t.col+1)*p.cell,y1:p.y+(t.row+1)*p.cell},r={x:(o.x0+o.x1)/2,y:(o.y0+o.y1)/2};return e.entry={...t},e.side=n,n==="left"?(e.x=o.x0-e.w-18,e.y=r.y-e.h/2):n==="right"?(e.x=o.x1+18,e.y=r.y-e.h/2):n==="top"?(e.x=r.x-e.w/2,e.y=o.y0-e.h-18):(e.x=r.x-e.w/2,e.y=o.y1+18),e}function Js(e,t,n,o){let{entry:r,side:i}=Lo(n,e),a=Te[t],c=a?{name:a.name,w:a.body?.w||168,h:a.body?.h||110,price:a.price}:{name:t[0].toUpperCase()+t.slice(1),w:168,h:110,price:o},b={type:"facility",id:t,name:c.name,x:0,y:0,w:c.w,h:c.h,baseW:c.w,baseH:c.h,level:1,price:o||c.price,builtSession:e.buildSession};return t==="inboundTruck"&&(b.inboundOrder={coalPerDay:0,zundaMochiPerDay:0}),Go(b,r,i)}function Zs(e,t,n,o,r){let i=Te[t],a={type:"facility",id:t,name:i?.name||t,baseId:t,col:n,row:o,wCells:i?.footprint?.cols||(t==="sausageMaker"?3:1),hCells:i?.footprint?.rows||(t==="sausageMaker"?3:1),gridPlaced:!0,level:1,price:r||i?.price||0,builtSession:e.buildSession};return(t==="biomassGenerator"||t==="coalGenerator")&&(a.fuelPulses=[]),Cn(a)}function xt(e){return e?[{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 ea(e,t,n){return{type:"maintenanceRoom",id:e.nextId++,col:t,row:n,price:_t.maintenanceRoom,builtSession:e.buildSession,wCells:2,hCells:2}}function Rc(e){return Math.random()({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 jn(e){return e?.baseId||e?.id||""}function xn(e){if(!e)return null;let t=e.type==="scanner"?`${e.kind}Scanner`:jn(e)||e.type;return _.power.exempt.includes(t)?null:_.power.machines[t]||null}function ra(e){return _.power.generators[jn(e)]||null}function Tn(e){let t=ra(e);if(!t)return 0;let n=t.outputV||t.baseOutputV||0,o=(e.fuelPulses||[]).reduce((r,i)=>r+Math.max(0,i.voltage||0),0);return n+o}function Ic(e){return e?.gridPlaced?gn(e):[]}function Ac(e){if(!e)return[];if(e.type==="scanner")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}];if(e.type==="maintenanceRoom")return xt(e);if(e.gridPlaced){let t=jn(e),n=e.row+Math.floor((e.hCells||1)/2);return t==="sausageMaker"?[{col:e.col-1,row:n},{col:e.col+(e.wCells||3),row:n}]:[{col:e.col-1,row:n},{col:e.col+(e.wCells||1),row:n},{col:e.col+Math.floor((e.wCells||1)/2),row:e.row-1},{col:e.col+Math.floor((e.wCells||1)/2),row:e.row+(e.hCells||1)}]}return e.entry?[e.entry]:[]}function $c(e,t,n){let o=1/0;for(let r of t){for(let a of fe){let c=E(r.col+a.dc,r.row+a.dr),b=n.get(c);b!=null&&(o=Math.min(o,b))}let i=n.get(E(r.col,r.row));i!=null&&(o=Math.min(o,i))}return Number.isFinite(o)?o:null}function Fc(e,t){let n=new Map,o=[],r=(i,a)=>{a<0||a>=Tn(t)||n.has(i)&&n.get(i)<=a||(n.set(i,a),o.push({k:i,loss:a}))};for(let i of Ic(t))for(let a of fe){let c=E(i.col+a.dc,i.row+a.dr);e.powerWires?.has(c)&&r(c,_.power.wires[e.powerWires.get(c).kind]?.lossV||0)}for(;o.length;){o.sort((c,b)=>c.loss-b.loss);let i=o.shift();if(n.get(i.k)!==i.loss)continue;let a=X(i.k);for(let c of fe){let b=E(a.col+c.dc,a.row+c.dr),M=e.powerWires?.get(b);if(!M)continue;let C=_.power.wires[M.kind]?.lossV??.5;r(b,i.loss+C)}}return n}function na(e){let t=[];for(let n of Object.values(e.facilities||{}))xn(n)&&t.push(n);for(let n of e.scanners||[])xn(n)&&t.push(n);for(let n of e.maintenanceRooms||[])xn(n)&&t.push(n);return t}function ia(e){return Object.values(e.facilities||{}).filter(ra)}function Ft(e){return String(e.id??`${e.type}:${e.col},${e.row}`)}function oa(e){return e.builtSession==null?-1:e.builtSession}function _o(e){if(!e)return{dirty:!1,routes:new Map,assignments:new Map,sources:new Map,underVoltage:new Set};let t=new Map,n=ia(e).map(G=>{let z=Fc(e,G);return t.set(G.id,z),{generator:G,outputV:Tn(G),admitted:[],busVoltage:Tn(G)}}),o=[];for(let G of na(e)){let z=xn(G),Q=[];for(let ie of n){let J=$c(e,Ac(G),t.get(ie.generator.id));J!=null&&Q.push({source:ie,loss:J,openCircuit:ie.outputV-J})}Q.sort((ie,J)=>J.openCircuit-ie.openCircuit||ie.loss-J.loss||Ft(ie.source.generator).localeCompare(Ft(J.source.generator))),Q[0]&&o.push({item:G,spec:z,routes:Q})}o.sort((G,z)=>(z.spec.powerPriority||50)-(G.spec.powerPriority||50)||G.routes[0].loss-z.routes[0].loss||oa(G.item)-oa(z.item)||Ft(G.item).localeCompare(Ft(z.item)));let r=new Map,i="",a=0,c=Math.max(1,_.power.maxAssignmentPasses||8);for(let G=0;GO+H.spec.drawV,0)+ie.spec.drawV,L=ae.outputV-ye,V=ae.admitted.every(O=>L-O.loss>=O.spec.minVoltage),se=L-J.loss>=ie.spec.minVoltage;if(!(!V||!se)){ae.admitted.push({item:ie.item,spec:ie.spec,loss:J.loss}),ae.busVoltage=L,z.set(Ft(ie.item),{sourceId:ae.generator.id,sourceName:ae.generator.name||ae.generator.shortName||ae.generator.id,sourceBusV:L,routeLossV:J.loss,receivedV:L-J.loss,minVoltage:ie.spec.minVoltage,drawV:ie.spec.drawV});break}}let Q=[...z.entries()].map(([ie,J])=>`${ie}:${J.sourceId}:${J.receivedV.toFixed(3)}`).join("|");if(r=z,Q===i)break;i=Q}let b=new Map,M=new Map;for(let G of n)M.set(G.generator.id,{outputV:G.outputV,busVoltage:G.busVoltage,admitted:G.admitted.length,stablePasses:a});for(let[G,z]of r)b.set(G,z);let C=e.power?.underVoltage||new Set,$=new Set;for(let G of na(e))r.has(Ft(G))||$.add(Ft(G));let W=e.power?.assignments||new Map,K=0;for(let[G,z]of r)W.get(G)?.sourceId!==z.sourceId&&(K+=1);return e.phase==="running"&&e.stats&&(e.stats.generatorAssignmentChanges=(e.stats.generatorAssignmentChanges||0)+K,e.totals&&(e.totals.generatorAssignmentChanges=(e.totals.generatorAssignmentChanges||0)+K)),e.power={...e.power||{},dirty:!1,routes:b,assignments:r,sources:M,underVoltage:$,previousUnder:C},e.power}function Wo(e){return(!e.power||e.power.dirty)&&_o(e),e.power}function Ye(e){e.power||(e.power={}),e.power.dirty=!0}function sa(e,t){let n=!1;for(let i of ia(e)){if(e.stats){e.stats.maxGeneratorOutput=e.stats.maxGeneratorOutput||{};let c=i.id||jn(i);e.stats.maxGeneratorOutput[c]=Math.max(e.stats.maxGeneratorOutput[c]||0,Tn(i))}if(!i.fuelPulses?.length)continue;for(let c of i.fuelPulses)c.timeLeft-=t;let a=i.fuelPulses.length;i.fuelPulses=i.fuelPulses.filter(c=>c.timeLeft>0),i.fuelPulses.length!==a&&(n=!0)}n&&Ye(e),e.power||(e.power={dirty:!0,elapsed:0}),e.power.elapsed=(e.power.elapsed||0)+t;let o=1/Math.max(1,_.power.recalcHz||10);(e.power.dirty||e.power.elapsed>=o)&&(e.power.elapsed=0,_o(e));let r=Wo(e);if(e.phase==="running"){let i=r.underVoltage?.size||0;if(i){e.stats.underVoltageSeconds=(e.stats.underVoltageSeconds||0)+i*t,e.stats.underVoltageSecondsByMachine=e.stats.underVoltageSecondsByMachine||{};for(let a of r.underVoltage)e.stats.underVoltageSecondsByMachine[a]=(e.stats.underVoltageSecondsByMachine[a]||0)+t;e.totals.underVoltageSeconds=(e.totals.underVoltageSeconds||0)+i*t}}}function nt(e,t){return xn(t)?Wo(e).assignments.has(Ft(t)):!0}function En(e,t){let n=xn(t);return n?Wo(e).assignments.get(Ft(t))||{sourceName:"None",sourceBusV:0,routeLossV:0,receivedV:0,minVoltage:n.minVoltage,drawV:n.drawV}:null}function aa(e){let t=Wo(e);return`POWER: ${[...(t.sources||new Map).entries()].map(([o,r])=>`${o} ${r.outputV.toFixed(0)}V / ${r.admitted} powered`).join(" | ")||"none"} / ${t.underVoltage?.size||0} under-voltage`}function la(e,t){let n=jn(e),o=_.power.generators[n];return!o||n==="biomassGenerator"&&!["male","female","poop"].includes(t)||n==="coalGenerator"&&t!=="coal"?!1:(e.fuelPulses=e.fuelPulses||[],e.fuelPulses.push({voltage:o.pulseVoltage,timeLeft:o.pulseSeconds}),!0)}var Ze=_.maintenance;function Zr(e,t){return Math.max(0,Number(e?.cardEffects?.[t])||0)}function Ko(e){let t=Math.min(1.4,1+.04*Math.min(10,Zr(e,"crowdedFarming"))),n=Math.pow(.95,Zr(e,"preventiveMaintenance"));return t*n}function Oc(e){return Math.pow(1.03,Zr(e,"durabilityCoating"))}function qo(e,t,n=1){let o=Ze.durability[t]||1e3;return Math.max(1,Math.ceil(o*Math.max(.1,n||1)*Oc(e)))}function ca(e){return Math.max(.1,Number(e?.durabilityBaseMultiplier)||1)}function Bc(e,t=0){if(!e)return{x:p.x+p.cell*(.5+t*.55),y:p.y+p.rows*p.cell+84};let n=p.x+e.col*p.cell+p.cell,o=p.y+e.row*p.cell+p.cell;return{x:n,y:o}}function Dc(e,t=0){let n=(e.maintenanceRooms||[])[t];return Bc(n,t)}function Be(e){e.repairman||(e.repairman={active:!1,x:p.x+p.cell*.5,y:p.y+p.rows*p.cell+84,target:null,repairedToday:0}),Array.isArray(e.maintenanceRooms)||(e.maintenanceRooms=[]),Array.isArray(e.repairmen)||(e.repairmen=[]);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=qo(e,"conveyor",ca(n));(n.durability==null||n.durability0&&(n.processingCooldown=Math.max(0,n.processingCooldown-t))}function to(e){Be(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:B(X(n).col,X(n).row)});for(let n of e.eggFarms||[])t.push({type:"eggFarm",ref:n,label:`EGG #${n.id}`,center:B(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:B(n.col,n.row)});for(let n of Object.values(e.facilities||{})){let o=Qo(n);["mixer","trash","sausageMaker","starterGenerator","biomassGenerator","coalGenerator","inboundTruck"].includes(o)&&t.push({type:o,ref:n,label:`${n.shortName||n.name||o} #${n.id}`,center:{x:n.x+n.w/2,y:n.y+n.h/2}})}return t}function on(e){return e.meta?Pn(e):Pn(e.ref)}function Jn(e){return e.meta?ei(e):ei(e.ref)}function Uo(e){return e.meta?ti(e):ti(e.ref)}function ya(e,t){Lc(e.meta?e:e.ref,Math.max(0,Jn(e)-t))}function si(e){return e.type==="conveyor"?`conveyor:${e.key}`:`${e.type}:${e.ref?.id||e.ref?.type||e.type}`}function ha(e,t=.02){Be(e);let n=Math.max(0,Number(t)||0),o=0;for(let r of to(e)){let i=Jn(r);ya(r,Uo(r)*n),o+=Math.max(0,i-Jn(r))/Uo(r)*100}return o}function ba(e){Be(e);let t=e.maintenanceRooms||[];e.repairmen=t.map((o,r)=>{let i=ai(e,o),a=i?B(i.col,i.row):Dc(e,r);return{id:`room-${o.id}`,roomId:o.id,active:!0,x:a.x,y:a.y,target:null,repairedToday:0}});let n=e.repairmen[0];e.repairman.active=!!n,e.repairman.target=n?.target||null,e.repairman.repairedToday=0,n&&(e.repairman.x=n.x,e.repairman.y=n.y)}function wa(e){Be(e);for(let t of e.repairmen||[])t.active=!1,t.target=null;e.repairmen=[],e.repairman.active=!1,e.repairman.target=null}function Gc(e,t){return t&&to(e).find(o=>si(o)===t.key)||null}function _c(e){let t=null;for(let n of to(e)){if(!Vo(e,n))continue;let o=on(n);o<=.001||(!t||o>on(t))&&(t=n)}return t}function ma(e,t){Be(e);let n=e.repairmen?.length?e.repairmen:e.repairman?.active?[e.repairman]:[];if(!n.length||e.phase!=="running")return;let o=new Set(n.map(i=>i.target?.key).filter(Boolean));for(let i of n)Wc(e,i,t,o);let r=n[0];r&&e.repairman!==r&&(e.repairman.active=!0,e.repairman.x=r.x,e.repairman.y=r.y,e.repairman.target=r.target,e.repairman.repairedToday=r.repairedToday||0)}function Wc(e,t,n,o){if(!t.active)return;let r=(e.maintenanceRooms||[]).find(J=>J.id===t.roomId);if(r&&!nt(e,r))return;let i=Gc(e,t.target);if((!i||on(i)<=.001||!Vo(e,i))&&(t.target?.key&&o.delete(t.target.key),i=Uc(e,o)),!i){t.target=null;return}let a=si(i);o.add(a);let c=Vo(e,i,t);if(!c){e.stats.unreachableRepairTargets=(e.stats.unreachableRepairTargets||0)+1,e.totals.unreachableRepairTargets=(e.totals.unreachableRepairTargets||0)+1,o.delete(a),t.target=null;return}if(t.target={key:a,label:i.label,workKey:E(c.col,c.row)},zc(e,t,c),t.path?.length&&t.pathIndex4){let V=Math.min(L,Ze.repairman.walkSpeed*n);t.x+=ae/L*V,t.y+=ye/L*V;return}t.pathIndex+=1;return}let b=Ze.repairman.walkSpeed,M=B(c.col,c.row),C=M.x-t.x,$=M.y-t.y,W=Math.hypot(C,$);if(W>8){let J=Math.min(W,b*n);t.x+=C/W*J,t.y+=$/W*J;return}let K=e?.cardEffects?.laborExploitation?.7:1,G=n/(Ze.repairman.secondsPerOnePercent*K*100),z=Jn(i),Q=G*Uo(i);ya(i,Q);let ie=Math.max(0,(z-Jn(i))/Uo(i)*100);t.repairedToday=(t.repairedToday||0)+ie,ie>0&&Math.random()<.02&&Y(e,t.x,t.y-28,"REPAIR",w.green),on(i)<=.001&&(o.delete(a),t.target=null)}function Uc(e,t=new Set){let n=null;for(let o of to(e)){if(t.has(si(o))||!Vo(e,o))continue;let r=on(o);r<=.001||(!n||r>on(n))&&(n=o)}return n||_c(e)}function Vc(e){if(e.type==="conveyor")return[X(e.key)];if(e.type==="eggFarm")return[{col:e.ref.col,row:e.ref.row}];if(e.type==="autoScanner"){let n=e.ref;return[{col:n.col,row:n.row-1},{col:n.col+1,row:n.row-1},{col:n.col,row:n.row},{col:n.col+1,row:n.row}]}let t=e.ref;if(t?.gridPlaced){let n=[];for(let o=0;o<(t.hCells||1);o+=1)for(let r=0;r<(t.wCells||1);r+=1)n.push({col:t.col+r,row:t.row+o});return n}return t?.entry?[t.entry]:[]}function Kc(e){let t=new Set;for(let n of e.eggFarms||[])t.add(E(n.col,n.row));for(let n of e.scanners||[])for(let o of[{col:n.col,row:n.row-1},{col:n.col+1,row:n.row-1},{col:n.col,row:n.row},{col:n.col+1,row:n.row}])t.add(E(o.col,o.row));for(let n of e.maintenanceRooms||[])for(let o=0;o<2;o+=1)for(let r=0;r<2;r+=1)t.add(E(n.col+r,n.row+o));for(let n of Object.values(e.facilities||{}))if(n.gridPlaced)for(let o=0;o<(n.hCells||1);o+=1)for(let r=0;r<(n.wCells||1);r+=1)t.add(E(n.col+r,n.row+o));return t}function Rn(e,t,n){if(!Re(t,n)||!e.ownedCells?.has?.(E(t,n))||e.blockedCells?.has?.(E(t,n)))return!1;let o=E(t,n);return!(Kc(e).has(o)||e.conveyorTiles?.has(o)&&!e.footbridges?.has(o))}function ai(e,t){if(!t)return{col:0,row:Math.max(0,p.rows-1)};let n=[];for(let o=0;o<2;o+=1)n.push({col:t.col+o,row:t.row-1},{col:t.col+o,row:t.row+2});for(let o=0;o<2;o+=1)n.push({col:t.col-1,row:t.row+o},{col:t.col+2,row:t.row+o});return n.find(o=>Rn(e,o.col,o.row))||null}function va(e,t,n){if(!t)return null;let o=new Set(n.map(c=>E(c.col,c.row))),r=E(t.col,t.row),i=[t],a=new Map([[r,null]]);for(;i.length;){let c=i.shift(),b=E(c.col,c.row);if(o.has(b)){let M=[],C=b;for(;C;){let $=X(C);M.push($),C=a.get(C)}return M.reverse()}for(let M of fe){let C={col:c.col+M.dc,row:c.row+M.dr},$=E(C.col,C.row);a.has($)||!Rn(e,C.col,C.row)||(a.set($,b),i.push(C))}}return null}function qc(e,t){let n=[];for(let r of Vc(t)){t.type==="conveyor"&&e.footbridges?.has(E(r.col,r.row))&&Rn(e,r.col,r.row)&&n.push(r);for(let i of fe){let a={col:r.col+i.dc,row:r.row+i.dr};Rn(e,a.col,a.row)&&n.push(a)}}let o=new Set;return n.filter(r=>{let i=E(r.col,r.row);return o.has(i)?!1:(o.add(i),!0)})}function Vo(e,t,n=null){let o=qc(e,t);if(!o.length)return null;let r=(e.maintenanceRooms||[]).find(c=>c.id===n?.roomId)||(e.maintenanceRooms||[])[0],i=n?ka(e,n,r):ai(e,r),a=va(e,i,o);return a?.[a.length-1]||null}function Sa(e,t){let n=Math.floor((e-p.x)/p.cell),o=Math.floor((t-p.y)/p.cell);return Re(n,o)?{col:n,row:o}:null}function ka(e,t,n){let o=Sa(t.x,t.y);return o&&Rn(e,o.col,o.row)?o:ai(e,n)}function zc(e,t,n){let o=E(n.col,n.row);if(t.path&&t.pathVersion===e.walkabilityVersion&&t.target?.workKey===o&&t.pathIndexb.id===t.roomId),i=ka(e,t,r);t.path=va(e,i,[n])||[];let a=Sa(t.x,t.y),c=a&&i&&a.col===i.col&&a.row===i.row&&Rn(e,a.col,a.row);t.pathIndex=c&&t.path.length>1?1:0,t.pathVersion=e.walkabilityVersion||0}function Hc(e){let t=0,n="none";for(let o of to(e)){let r=on(o);r>t&&(t=r,n=o.label)}return{ratio:t,percent:Math.round(t*100),label:n}}function li(e){let t=Hc(e),n=ni(e);return{worst:t,avgBeltPerformance:n,repairman:e.repairman||null,repairmen:e.repairmen||[],rooms:e.maintenanceRooms||[]}}var Yc=qe.commonWeight,Qc=qe.rareWeight,Xc=qe.ultraRareWeight||.45,sn=qe.baseDraftSize,An=Bi;function In(e,t){let n=Qe(e);return Math.max(0,Number(n[t])||0)}function Qe(e){e.cardEffects||(e.cardEffects={...kr});for(let[t,n]of Object.entries(kr))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),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 Ra(e){let t=Math.max(.5,1-.05*Math.min(10,In(e,"crowdedFarming"))),n=Math.pow(.96,In(e,"hatchingFeed"));return t*n}function Ia(e,t=null){return Math.min(3,Math.max(0,Number(t?.extraEggOutlet)||0))}function Aa(e){return Math.pow(.85,In(e,"scannerQueueSpacing"))}function Vt(e,t=null){let n=Qe(e),o=wo*Math.pow(mo,Math.max(0,n.bearing||0)),r=t?Math.max(.1,Number(e?.conveyorMeta?.get?.(t)?.speedMultiplier)||1):1,i=t?1:ni(e),a=Math.max(.01,Number(e?.eventActive?.conveyorSpeedMultiplier)||1);return Math.min(yn,o*i)*r*a}function Bt(e,t=null){let n=Math.max(0,(e?.level||1)-1),o=t?In(t,"autoScannerTuning"):0,r=bo*Math.pow(Fi,n)*Math.pow(wr,o),i=Math.max(mr,r),a=t?Math.max(.01,Number(t?.eventActive?.autoScannerCooldownMultiplier)||1):1;return t?Math.max(mr,i*Ho(e)*a):i}function Ma(e){let t=e.cardDraft||{rerolls:0},n=Math.max(1,(t.rerolls||0)+1);return Math.ceil(Math.max(1,e.turn||1)/qe.rerollCost.dayDivisor*n*qe.rerollCost.multiplier)}function an(e){return An.find(t=>t.id===e)||null}function $a(e,t){let n=typeof t=="string"?an(t):t;return Qe(e),!n||!(n.type==="equipmentUpgrade"||n.type==="cellAction")?[]:dt(e,n).map(o=>({key:Ot(o),label:fi(e,o),type:o.type}))}function di(e,t,n={}){let o=typeof t=="string"?an(t):t;if(Qe(e),!o||o.type==="dud")return{ok:!1,reason:"Card not found."};if(o.id==="extraCards")return e.cardDraft=e.cardDraft||{pending:!1,choices:[],rerolls:0,picksRemaining:0},e.cardDraft.choices.push(...jo(e,2)),{ok:!0,reason:`${o.title} added 2 draft cards.`};if(o.type==="instant")return Oa(e,o),{ok:!0,reason:`${o.title} granted.`};if(o.type==="equipmentUpgrade"){let r=dt(e,o);if(!r.length)return{ok:!1,reason:`No valid target for ${o.title}.`};let i=n.targetKey&&n.targetKey!=="__auto__"?String(n.targetKey):"",a=i?r.find(c=>Ot(c)===i):r[0];return a?(Fa(e,a),{ok:!0,reason:`${o.title} applied to ${fi(e,a)}.`}):{ok:!1,reason:`Selected target is no longer valid for ${o.title}.`}}if(o.type==="cellAction"&&o.target==="blockedCell"){let r=dt(e,o);if(!r.length)return{ok:!1,reason:"No blocked cells to remove."};let i=new Set((Array.isArray(n.targetKeys)?n.targetKeys:[n.targetKey]).filter(c=>c&&c!=="__auto__").map(String)),a=i.size?r.filter(c=>i.has(Ot(c))):r.slice(0,10);if(!a.length)return{ok:!1,reason:`Selected target is no longer valid for ${o.title}.`};for(let c of a.slice(0,3)){e.blockedCells.delete(E(c.col,c.row));let b=B(c.col,c.row);ct(e,b.x,b.y),gt(e,b.x,b.y,w.danger,42),$t(e,b.x,b.y,8,w.warn)}return Ee(e),{ok:!0,reason:`${o.title} removed ${Math.min(10,a.length)} blocked cell(s).`}}return{ok:!1,reason:`${o.title} cannot be debug-granted.`}}function fi(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 ${Bt(t,e).toFixed(1)}s -> ${Bt({...t,level:t.level+1},e).toFixed(1)}s`;if(t.type==="facility"&&["mixer","sausageMaker"].includes(t.baseId||t.id)){let n=t.baseId||t.id,o=t.level||1,r=lt(e);t.level=o+1;let i=lt(e);return t.level=o,`${n==="mixer"?"MIXER":"SAUSAGE MACHINE"} L${t.level} -> L${t.level+1} | sausage ${R(r)} -> ${R(i)}`}if(t.type==="facility"&&(t.baseId||t.id)==="truck"){let n=t.level,o=Je(e);t.level=n+1;let r=Je(e);return t.level=n,`TRUCK L${t.level} -> L${t.level+1} | ${R(o)} -> ${R(r)}`}if(t.type==="facility"&&(t.baseId||t.id)==="trash"){let n=At(e),o=Math.min(kn(),n+1),r=It(n),i=It(o);return`SHREDDER L${t.level||1} -> L${(t.level||1)+1} | expected ${R(r)} -> ${R(i)} per poop`}return`${t.name||t.id} L${t.level||1} -> L${(t.level||1)+1}`}function jc(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 dt(e,t){let n=typeof t=="string"?an(t):t;return Qe(e),n?n.target==="blockedCell"?jc(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==="sausageMaker"?Object.values(e.facilities||{}).filter(o=>(o.baseId||o.id)==="sausageMaker"):n.target==="trash"?Hr(e)(o.baseId||o.id)==="trash"):[]:[]:[]}function Jc(e,t){let n=Qe(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,qe.dudChancePerCard||0)),r=Math.max(1e-4,Math.min(1,qe.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 Zc(e){return!!((e.maintenanceRooms||[]).length||(e.repairmen||[]).length||e.repairman?.active)}function Ca(e,t){return t==="conveyor"?(e.conveyorTiles?.size||0)>0:t==="autoScanner"?(e.scanners||[]).some(n=>n.kind==="auto"):t==="manualScanner"?(e.scanners||[]).some(n=>n.kind==="manual"):t==="eggFarm"?(e.eggFarms||[]).length>0:Object.values(e.facilities||{}).some(n=>(n.baseId||n.id)===t)}function eu(e,t){return!(t.requiresFacility&&!Ca(e,t.requiresFacility)||Array.isArray(t.requiresAnyFacility)&&!t.requiresAnyFacility.some(n=>Ca(e,n)))}function tu(e){return An.filter(t=>!(t.id==="laborExploitation"&&!Zc(e)||!eu(e,t)||Jc(e,t)||(t.type==="equipmentUpgrade"||t.type==="cellAction")&&dt(e,t).length<=0))}function ga(e,t){return t.rarity==="ultraRare"?Xc:t.rarity==="rare"?Qc:Yc}function nu(e,t){let n=t.reduce((r,i)=>r+ga(e,i),0);if(n<=0)return t[Math.floor(Math.random()*t.length)];let o=Math.random()*n;for(let r of t)if(o-=ga(e,r),o<=0)return r;return t[t.length-1]}var xa=0;function ou(){return xa+=1,{id:`dud:${Date.now()}:${xa}`,title:"Dud",rarity:"dud",type:"dud",description:"Click to flick this dud away. It does not consume a pick."}}function ru(e){let t=Qe(e),n=Math.max(0,Math.min(1,qe.dudChancePerCard||0)),o=Math.max(0,Math.min(1,qe.dudChanceReductionPerFilter||0));return Math.max(0,n-o*Math.max(0,t.dudFilter||0))}function iu(e,t){let n=[...t],o=0,r=Math.max(0,qe.maxDudsPerDraft||0),i=ru(e);for(let a=0;ac.id===i.id);a>=0&&r.splice(a,1)}return iu(e,o)}function Ot(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 Ta(e){if(!e)return null;if(e.type==="blockedCell"){let t=B(e.col,e.row);return{x:t.x-p.cell*.48,y:t.y-p.cell*.48,w:p.cell*.96,h:p.cell*.96,cx:t.x,cy:t.y}}if(e.type==="eggFarm"||e.type==="eggOutlet"){let t=B(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=ge(e);return{x:t.x-48,y:t.y-48,w:96,h:96,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 pi(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(Ot(o))).map(o=>({key:Ot(o),label:"AUTO ROUTE SETTINGS",target:o,bounds:Ta(o)})).filter(o=>o.bounds)}let t=new Set(e.cardTargetPick.targetKeys||[]);return dt(e,e.cardTargetPick.cardId).filter(n=>t.has(Ot(n))).map(n=>({key:Ot(n),label:fi(e,n),target:n,bounds:Ta(n)})).filter(n=>n.bounds)}function su(e,t){let n=pi(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 Fa(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=B(n.col,n.row);Y(e,o.x,o.y-30,`OUTLET ${1+n.extraEggOutlet}`,w.green),Ee(e);return}if(t.level=(t.level||1)+1,t.type==="eggFarm"){t.nextSpawn=Jt(t),t.lastInterval=t.nextSpawn;let n=B(t.col,t.row);Y(e,n.x,n.y-30,`LV ${t.level}`,w.green);return}if(t.type==="scanner"){let n=ge(t);Y(e,n.x,n.y-34,`LV ${t.level}`,w.green);return}t.type==="facility"&&Y(e,t.x+t.w/2,t.y+18,`LV ${t.level}`,w.green)}function Oa(e,t){let n=Qe(e),o=r=>{n[r]=Math.max(0,n[r]||0)+1};if(t.id==="bearing"&&o("bearing"),t.id==="autoScannerTuning"&&o("autoScannerTuning"),t.id==="legalWork"&&o("legalWork"),t.id==="crowdedFarming"&&(n.crowdedFarming=Math.min(10,(n.crowdedFarming||0)+1)),t.id==="hatchingFeed"&&o("hatchingFeed"),t.id==="poopRateBooster"&&o("poopRateBooster"),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"),Be(e)),t.id==="sparePartsBin"){o("sparePartsBin");let r=ha(e,.02);e.stats.sparePartsRepair=(e.stats.sparePartsRepair||0)+r,e.totals.sparePartsRepair=(e.totals.sparePartsRepair||0)+r,Y(e,p.x+p.cols*p.cell/2,p.y-30,"REPAIR +2%",w.green)}t.id==="dudRefund"&&o("dudRefund"),t.id==="freeReroll"&&o("freeReroll"),t.id==="composter"&&o("composter"),t.id==="scannerQueueSpacing"&&o("scannerQueueSpacing"),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"),Y(e,p.x+p.cols*p.cell/2,p.y-30,"RESCUE HELD",w.green))}function mt(e){return`${Math.round(Number(e||0)*100)}%`}function Ea(e){let t=Gt[Math.max(0,Math.min(Gt.length-1,e-1))]||Gt[0]||[0,0];return`${t[0].toFixed(2)}-${t[1].toFixed(2)}s`}function Pa(e,t){return Math.ceil(e*Math.pow(Ie.incomeUpgradeRate,Math.max(0,t-1)))}function ci(e,t,n){let r=dt(e,t==="mixer"?"upgradeMixer":t==="sausageMaker"?"upgradeSausageMaker":"upgradeTruck")[0];if(!r)return{before:Pa(n,1),after:Pa(n,2)};let i=()=>t==="truck"?Je(e):lt(e),a=i(),c=r.level||1;r.level=c+1;let b=i();return r.level=c,{before:a,after:b}}function au(e,t){let n=Qe(e);if(t.id==="upgradeEgg"){let o=dt(e,t),r=Math.min(3,Math.max(1,o[0]?.level||1));return`Choose one EGG. Level +1. Production interval ${Ea(r)} -> ${Ea(r+1)}.`}if(t.id==="upgradeAutoScanner"){let o=dt(e,t)[0]||{level:1},r=Bt(o,e),i=Bt({...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=ci(e,"mixer",Ie.income.sausage);return`Choose one MIXER. Meat quality +10%. Sausage sale value ${R(o.before)} -> ${R(o.after)}.`}if(t.id==="upgradeSausageMaker"){let o=ci(e,"sausageMaker",Ie.income.sausage);return`Choose one SAUSAGE MACHINE. Product value +10%. Sausage sale value ${R(o.before)} -> ${R(o.after)}.`}if(t.id==="upgradeTruck"){let o=ci(e,"truck",Ie.income.truck);return`Choose one TRUCK. Earning +10%. Correct shipment income ${R(o.before)} -> ${R(o.after)}.`}if(t.id==="upgradeTrash"){let o=At(e),r=Math.min(kn(),o+1),i=It(o),a=It(r),c=Math.round(tn(o)*100),b=Math.round(tn(r)*100);return`SHREDDER level +1. Expected value ${R(i)} -> ${R(a)} per poop; hit chance ${c}% -> ${b}%.`}if(t.id==="bearing"){let o=Math.min(yn,wo*Math.pow(mo,Math.max(0,n.bearing||0))),r=Math.min(yn,wo*Math.pow(mo,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`Fairies tribute profit exemption +${R(250)}. After pick: exemption bonus ${R(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 ${mt(Math.max(.5,1-.05*o))}, deterioration ${mt(Math.min(1.4,1+.04*o))}.`}if(t.id==="extraEggOutlet"){let o=dt(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 ${mt(Math.pow(.96,(n.hatchingFeed||0)+1))}, poop rate multiplier ${mt(Math.pow(1.03,(n.hatchingFeed||0)+1))}.`;if(t.id==="poopRateBooster")return`Held ${n.poopRateBooster||0}. After pick: base poop rate +${Math.round((n.poopRateBooster||0)+1)*4} percentage points.`;if(t.id==="autoScannerTuning")return`Held ${n.autoScannerTuning||0}. After pick: all AUTO SCANNER default cooldown ${mt(Math.pow(wr,(n.autoScannerTuning||0)+1))}.`;if(t.id==="safetyCover")return`Held ${n.safetyCover||0}. After pick: explosion damage ${mt(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: ${R(1200*((n.chemicalWeaponSubsidy||0)+1))} per 70 truck-shipped poop each day.`;if(t.id==="preventiveMaintenance")return`Held ${n.preventiveMaintenance||0}. After pick: deterioration speed ${mt(Math.pow(.95,(n.preventiveMaintenance||0)+1))}.`;if(t.id==="dudFilter"){let o=Math.max(0,Math.min(1,qe.dudChancePerCard||0)),r=Math.max(0,Math.min(1,qe.dudChanceReductionPerFilter||.05)),i=Math.max(0,n.dudFilter||0),a=Math.max(0,o-r*i),c=Math.max(0,o-r*(i+1));return`Held ${i}. DUD chance per card ${mt(a)} -> ${mt(c)}.`}return t.id==="durabilityCoating"?`Held ${n.durabilityCoating||0}. After pick: maximum durability ${mt(Math.pow(1.03,(n.durabilityCoating||0)+1))}.`:t.id==="sparePartsBin"?`Held ${n.sparePartsBin||0}. Immediately restores 2% durability to every maintained machine. Common card weight.`:t.id==="dudRefund"?`Held ${n.dudRefund||0}. After pick: DUD clicks award ${R(50*((n.dudRefund||0)+1))}.`:t.id==="freeReroll"?`Held ${n.freeReroll||0}. After pick: each card screen starts with ${(n.freeReroll||0)+1} free reroll${(n.freeReroll||0)+1===1?"":"s"}.`:t.id==="composter"?`Held ${n.composter||0}. After pick: SHREDDER poop grants ${R(3*((n.composter||0)+1))}.`:t.id==="scannerQueueSpacing"?`Held ${n.scannerQueueSpacing||0}. After pick: Manual/Auto Scanner queue spacing ${mt(Math.pow(.85,(n.scannerQueueSpacing||0)+1))}.`:t.id==="flattery"?`Next Fairies tribute -50%. Held for next tribute: ${n.fairiesFlatteryNext||0}.`:t.id==="usedMachine"?"Enable Used Machines: future equipment costs 50%, has no SELL refund, and starts with 60% durability.":t.id==="newMachine"?"Cancel Used Machines. Future equipment returns to normal price, refund, and durability.":t.id==="laborExploitation"?"Repairman dispatch paperwork is streamlined. Repair rooms keep operating; one time only.":t.id==="rescueLoan"?`At day-end bankruptcy check, gain ${R(500)} if cash is below ${R(0)}. Held: ${n.rescueLoanCharges||0}.`:t.id==="dynamite"?"Choose any blocked tiles on the map and destroy up to 3. Empty-space clicks are ignored; press Esc to finish after at least one blast.":t.description}function ui(e,t){let n=e.cardDraft?.choices||[],o=n.findIndex(r=>r.id===t.id);o>=0&&n.splice(o,1)}function Ba({game:e,ui:t,onUpdatePanels:n}){function o(){n&&n()}function r(){Qe(e),e.cardTargetPick=null,e.cardDraft={pending:!0,choices:jo(e,sn),rerolls:0,freeRerolls:In(e,"freeReroll"),picksRemaining:1}}function i(){Qe(e),e.cardDraft.pending=!1,e.cardDraft.choices=[],e.cardDraft.picksRemaining=0,e.cardTargetPick=null,t.modal.classList.remove("visible","equipment-popover"),o()}function a(L=sn){Qe(e),e.cardDraft.choices=jo(e,L)}function c(){Qe(e),!e.cardDraft.choices.length&&e.cardDraft.picksRemaining>0&&a(sn)}function b(L){ui(e,L),e.cardDraft.picksRemaining=Math.max(0,(e.cardDraft.picksRemaining||1)-1),e.cardTargetPick=null,c(),e.cardDraft.picksRemaining<=0?i():ye()}function M(L){ui(e,L),e.cardDraft.picksRemaining=Math.max(0,(e.cardDraft.picksRemaining||1)-1)+qe.extraCardsAddedPicks,e.cardDraft.choices.push(...jo(e,2)),ye()}function C(L,V){let se=In(e,"dudRefund")*50;if(se>0){let O=tt(e,se);e.stats.dudRefundIncome=(e.stats.dudRefundIncome||0)+O,e.totals.dudRefundIncome=(e.totals.dudRefundIncome||0)+O,Y(e,p.x+p.cols*p.cell/2,p.y-30,`DUD REFUND +${R(O)}`,w.green)}nn(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)"}),V&&(V.style.setProperty("--fling-x",`${260+Math.random()*260}px`),V.style.setProperty("--fling-y",`${-260-Math.random()*170}px`),V.style.setProperty("--fling-r",`${(Math.random()<.5?-1:1)*(52+Math.random()*38)}deg`),V.classList.add("flung")),window.setTimeout(()=>{ui(e,L),c(),ye()},340)}function $(L,V=null){if(L.type==="dud")return C(L,V);if(L.id==="extraCards")return M(L);if(L.type==="equipmentUpgrade"||L.type==="cellAction")return W(L);Oa(e,L),b(L)}function W(L){let V=dt(e,L);V.length&&(e.cardTargetPick={pending:!0,cardId:L.id,mode:L.target==="blockedCell"?"blockedCell":"equipment",targetKeys:V.map(Ot),remaining:L.id==="dynamite"?Math.min(10,V.length):1,used:0},t.modal.classList.remove("visible","equipment-popover"),o())}function K(L){if(!L||L.type!=="blockedCell")return!1;let V=E(L.col,L.row);if(!e.blockedCells?.has(V))return!1;e.blockedCells.delete(V);let se=B(L.col,L.row);return ct(e,se.x,se.y),gt(e,se.x,se.y,w.danger,62),$t(e,se.x,se.y,18,w.warn),Do(e,se.x,se.y,8),nn(e,16,.35),Y(e,se.x,se.y-18,"BOOM",w.danger),Ee(e),e.cardTargetPick.remaining=Math.max(0,(e.cardTargetPick.remaining||1)-1),e.cardTargetPick.used=Math.max(0,e.cardTargetPick.used||0)+1,e.cardTargetPick.targetKeys=dt(e,"dynamite").map(Ot),o(),!0}function G(L){if(!e.cardTargetPick?.pending)return!1;let V=an(e.cardTargetPick.cardId),se=su(e,L);return V?V.id==="dynamite"?(!se||!K(se)||((e.cardTargetPick.remaining||0)<=0||!dt(e,"dynamite").length)&&b(V),!0):se?(Fa(e,se),b(V),!0):(z(),!0):(z(),!0)}function z(){if(!e.cardTargetPick?.pending)return;let L=e.cardTargetPick,V=an(L.cardId);if(V?.id==="dynamite"&&(L.used||0)>0){b(V);return}e.cardTargetPick=null,ye()}function Q(){let L=Math.max(0,e.cardDraft?.freeRerolls||0);if(L>0){e.cardDraft.freeRerolls=L-1,e.cardDraft.rerolls+=1,a(sn),ye();return}let V=Ma(e);e.cash${V.alt||`:``}function ae(L,V){let se=document.createElement("button");se.type="button";let O=L.type==="dud"?"dud":L.rarity==="ultraRare"?"ultra-rare":L.rarity==="rare"?"rare":"common";se.className=`card-choice ${O}`;let H=L.type==="dud"?"MISS":L.rarity==="ultraRare"?"ULTRA RARE":L.rarity==="rare"?"RARE":"COMMON",oe=[H,...(L.tags||[]).filter(ue=>ue!==H)].slice(0,5);return se.innerHTML=`${J(L)}
${L.title}
${oe.map(ue=>`${ue}`).join("")}
${au(e,L)}
`,se.addEventListener("click",()=>$(L,se)),se.dataset.index=String(V),se}function ye(){Qe(e),e.cardTargetPick=null,e.cardDraft?.pending||r(),e.cardDraft.choices?.length||a(sn);let L=e.cardDraft.choices||[],V=Ma(e),se=Math.max(0,e.cardDraft?.freeRerolls||0),O=Math.max(1,e.cardDraft.picksRemaining||1);t.modalTitle.textContent="Choose Upgrade Card",t.modalBody.innerHTML=`

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

`;let H=t.modalBody.querySelector("#cardChoices");L.forEach((Ve,xe)=>H.appendChild(ae(Ve,xe))),t.modalActions.innerHTML="";let oe=document.createElement("button");oe.type="button",oe.className="facility-action warn reroll-button",oe.textContent=se>0?`Reroll (FREE x${se})`:`Reroll ${R(V)}`,oe.disabled=se<=0&&e.cash0?`${se} free reroll${se===1?"":"s"} remaining.`:oe.disabled?"Not enough cash":"",oe.addEventListener("click",Q),t.modalActions.appendChild(oe);let ue=document.createElement("button");ue.type="button",ue.className="facility-action",ue.textContent="No Card",ue.title="Skip this upgrade pick and enter Build phase.",ue.addEventListener("click",i),t.modalActions.appendChild(ue),t.modal.classList.remove("equipment-popover"),t.modal.classList.add("visible")}return{prepareDraft:r,showDraft:ye,finishDraft:i,chooseTargetAtPoint:G,cancelTargetPick:z}}var lu=!0,cu={chickMale:"./assets/images/chick_male.png",chickFemale:"./assets/images/chick_female.png",poop:"./assets/images/poop.png",conveyorStraightH:"./assets/dark_factory/facilities/conveyor/conveyor_straight_h_16.png",conveyorStraightV:"./assets/dark_factory/facilities/conveyor/conveyor_straight_v_16.png",conveyorTurnNe:"./assets/dark_factory/facilities/conveyor/conveyor_turn_ne_16.png",conveyorTurnNw:"./assets/dark_factory/facilities/conveyor/conveyor_turn_nw_16.png",conveyorTurnSe:"./assets/dark_factory/facilities/conveyor/conveyor_turn_se_16.png",conveyorTurnSw:"./assets/dark_factory/facilities/conveyor/conveyor_turn_sw_16.png",boostConveyorStraightH:"./assets/dark_factory/facilities/conveyor/boost_conveyor_straight_h_16.png",boostConveyorStraightV:"./assets/dark_factory/facilities/conveyor/boost_conveyor_straight_v_16.png",boostConveyorTurnNe:"./assets/dark_factory/facilities/conveyor/boost_conveyor_turn_ne_16.png",boostConveyorTurnNw:"./assets/dark_factory/facilities/conveyor/boost_conveyor_turn_nw_16.png",boostConveyorTurnSe:"./assets/dark_factory/facilities/conveyor/boost_conveyor_turn_se_16.png",boostConveyorTurnSw:"./assets/dark_factory/facilities/conveyor/boost_conveyor_turn_sw_16.png",scannerManual:"./assets/dark_factory/facilities/machines/manual_scanner_32x48.png",scannerAuto:"./assets/dark_factory/facilities/machines/auto_scanner_32x48.png",eggFarm:"./assets/dark_factory/facilities/machines/egg_farm_16.png",mixer:"./assets/dark_factory/facilities/machines/mixer_32.png",shredder:"./assets/dark_factory/facilities/machines/shredder_32.png",truck:"./assets/dark_factory/facilities/vehicles/truck_32.png",maintenanceRoom:"./assets/dark_factory/facilities/utility/maintenance_room_32.png",repairman:"./assets/dark_factory/facilities/utility/repairman_16.png",obstacleCrate:"./assets/dark_factory/facilities/utility/obstacle_crate_16.png"},De={};for(let[e,t]of Object.entries(cu)){let n=new Image;n.loaded=!1,n.onerror=()=>{n.loaded=!1},lu&&t&&(n.onload=()=>{n.loaded=!0},n.src=t),De[e]=n}function bi(e){return e.frameCache||(e.frameCache={}),e.frameCache.draw||(e.frameCache.draw={}),e.frameCache.draw}function wi(e,t){let n=document.createElement("canvas");return n.width=Math.max(1,Math.ceil(e)),n.height=Math.max(1,Math.ceil(t)),n}function Ga(e,t,n,o){e.__gameRef=n,e.imageSmoothingEnabled=!1,e.clearRect(0,0,t.width,t.height),hu(e,t,n),e.save();let r=n.shake.time>0?ne(-n.shake.strength,n.shake.strength):0,i=n.shake.time>0?ne(-n.shake.strength,n.shake.strength):0,a=n.view?.scale||1;e.translate((n.view?.x||0)+r,(n.view?.y||0)+i),e.scale(a,a),vu(e,n),Su(e,n),Tu(e,n),Eu(e,n),Pu(e,n),Nu(e,n),Yu(e,n),Qu(e,n),Gu(e,n),Wu(e,n),du(e,n),od(e,n,o.activeQueuedChick),yd(e,n),ud(e,t,n),cd(e,n,o.selectedObject,o.selectedTitle),dd(e,n),fd(e,n),pd(e,n),e.restore(),bd(e,n,t)}function uu(e){if(e.type==="eggFarm"){let n=B(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 du(e,t){if(t.phase!=="build")return;let n=vn(t);for(let o of n){let r=uu(o);e.save(),e.fillStyle="rgba(196,36,36,.22)",e.strokeStyle=w.danger,e.lineWidth=5,e.setLineDash([10,6]),ee(e,r.x,r.y,r.w,r.h,!0,!0),e.setLineDash([]),e.fillStyle=w.danger,e.strokeStyle=w.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 ee(e,t,n,o,r,i=!0,a=!0){e.beginPath(),e.rect(t,n,o,r),i&&e.fill(),a&&e.stroke()}function oo(e,t,n,o,r=w.ink){e.save(),e.font="900 12px ui-monospace, monospace",e.textAlign="center",e.lineWidth=4,e.strokeStyle=w.white,e.fillStyle=r,e.strokeText(o,t,n),e.fillText(o,t,n),e.restore()}function ln(e,t,n,o,r,i){if(!t?.loaded)return!1;let a=e.imageSmoothingEnabled;return e.imageSmoothingEnabled=!1,e.drawImage(t,Math.round(n),Math.round(o),Math.round(r),Math.round(i)),e.imageSmoothingEnabled=a,!0}function fu(e,t,n,o,r,i,a=null,c=1){e.fillStyle=i,e.fillRect(Math.round(t),Math.round(n),Math.round(o),Math.round(r)),a&&(e.strokeStyle=a,e.lineWidth=c,e.strokeRect(Math.round(t)+.5,Math.round(n)+.5,Math.round(o)-1,Math.round(r)-1))}function pu(e,t){let n=(Math.imul(e+37,73856093)^Math.imul(t+71,19349663))>>>0;return n^=n>>>13,n>>>0}function yu(e,t,n,o,r){let i=pu(o,r);if(i%67!==0)return;let a=10+(i>>>4)%Math.max(1,p.cell-22),c=10+(i>>>9)%Math.max(1,p.cell-22),b=(i>>>14)%16*Math.PI/16;e.save(),e.globalAlpha=.38,e.fillStyle="#4a8d37",e.strokeStyle="rgba(30,66,27,.34)",e.lineWidth=1,e.beginPath();for(let C=0;C<8;C+=1){let $=b+C*Math.PI/4,W=3.5+(i>>>C+2&3),K=t+a+Math.cos($)*W*1.35,G=n+c+Math.sin($)*W*.75;C===0?e.moveTo(K,G):e.lineTo(K,G)}e.closePath(),e.fill(),e.stroke();let M=4+(i>>>24)%5;for(let C=0;C>>C+8&3)*.18,W=10+(i>>>C+11&10),K=.9+(i>>>C+17&3)*.35;e.beginPath(),e.arc(t+a+Math.cos($)*W,n+c+Math.sin($)*W*.72,K,0,Math.PI*2),e.fill()}e.fillStyle="rgba(151,218,101,.26)",e.beginPath(),e.arc(t+a-2,n+c-2,1.7,0,Math.PI*2),e.fill(),e.restore()}function Da(e,t){e.fillStyle=w.bg,e.fillRect(0,0,t.width,t.height),e.save(),e.globalAlpha=.85;for(let n=0;na.col)),o=Math.max(...t.map(a=>a.col)),r=Math.min(...t.map(a=>a.row)),i=Math.max(...t.map(a=>a.row));return{x:p.x+n*p.cell-6,y:p.y+r*p.cell-6,w:(o-n+1)*p.cell+12,h:(i-r+1)*p.cell+12}}function vu(e,t){let n=bi(t),o=wu(t);if(!n.grid||n.grid.signature!==o){let a=mu(t),c=wi(a.w,a.h),b=c.getContext("2d");b.translate(-a.x,-a.y),bu(b,t),n.grid={signature:o,bounds:a,layer:c}}let{bounds:r,layer:i}=n.grid;e.drawImage(i,r.x,r.y)}function Su(e,t){if(t.phase!=="build"||t.cardDraft?.pending||t.cardTargetPick?.pending)return;let n=Co(t),o=t.cash>=n,r=i=>{let a=Et(i);return Object.values(t.facilities||{}).some(c=>{let b=c?.baseId||c?.id;return["mixer","trash","truck"].includes(b)?!(a.x+a.w<=c.x||c.x+c.w<=a.x||a.y+a.h<=c.y||c.y+c.h<=a.y):!1})};for(let i of jt(t)){let a=r(i),c=Et(i);e.save(),e.fillStyle=a?"rgba(112,130,118,.34)":o?"rgba(118,226,142,.36)":"rgba(255,255,255,.18)",e.strokeStyle=a?"#708278":o?"#6bd579":w.muted,e.lineWidth=5,e.setLineDash([16,10]),ee(e,c.x,c.y,c.w,c.h,!0,!0),e.setLineDash([]);let b=Tr(i),M=b.cx,C=b.cy;e.fillStyle="rgba(255,255,255,.94)",e.strokeStyle=w.ink,e.lineWidth=4,ee(e,b.x,b.y,b.w,b.h,!0,!0),e.fillStyle=a?"#7c9285":o?w.green:w.danger,e.font="900 16px ui-monospace, monospace",e.textAlign="center";let $=i.direction==="left"?"BUY LEFT LOT":i.direction==="right"?"BUY RIGHT LOT":i.direction==="down"?"BUY LOWER LOT":"BUY UPPER LOT";e.fillText($,M,C-12),e.fillStyle=w.ink,e.font="900 16px ui-monospace, monospace",e.fillText(`${R(n)} / 10\xD710`,M,C+15),e.restore()}}function Dt(e,t,n,o,r,i,a=0){let c=Pn(i);c<.5||(e.save(),e.globalAlpha=Math.min(.62,.12+c*.54),e.fillStyle="#6b4a23",a>0?(e.beginPath(),e.roundRect?.(t,n,o,r,a),e.roundRect?e.fill():ee(e,t,n,o,r,!0,!1)):ee(e,t,n,o,r,!0,!1),e.globalAlpha=Math.min(.9,.25+c*.6),e.strokeStyle="#3a2a16",e.lineWidth=Math.max(2,Math.floor(2+c*5)),e.setLineDash([5,6]),ee(e,t+3,n+3,o-6,r-6,!1,!0),e.restore())}function yi(e,t){let n=e.componentLookup?.get(t);return(n?e.congestion.get(n):null)?.ratio||0}function ku(e,t,n={}){let o=ro(e,t).sort(),i=n.kind==="boostConveyor"?"boostConveyor":"conveyor",a=c=>o.includes(c);if(o.length===2){if(a("left")&&a("right"))return De[`${i}StraightH`];if(a("up")&&a("down"))return De[`${i}StraightV`];if(a("up")&&a("right"))return De[`${i}TurnNe`];if(a("up")&&a("left"))return De[`${i}TurnNw`];if(a("down")&&a("right"))return De[`${i}TurnSe`];if(a("down")&&a("left"))return De[`${i}TurnSw`]}return o.length===1?a("up")||a("down")?De[`${i}StraightV`]:De[`${i}StraightH`]:a("up")||a("down")?De[`${i}StraightV`]:De[`${i}StraightH`]}function Mu(e){let t=Object.entries(De).filter(([o])=>o.includes("conveyor")||o.includes("Conveyor")).map(([o,r])=>`${o}:${r.loaded?1:0}`).join("|"),n=[...e.conveyorTiles].sort().map(o=>{let r=e.conveyorMeta.get(o)||{};return`${o}:${r.kind||""}:${r.dir||""}:${(r.outDirs||[]).join(",")}:${r.branchMode||""}`}).join("|");return`${e.routingVersion||0};${t};${n}`}function Cu(e){if(!e.conveyorTiles?.size)return{x:0,y:0,w:1,h:1};let t=[...e.conveyorTiles].map(X),n=Math.min(...t.map(a=>a.col)),o=Math.max(...t.map(a=>a.col)),r=Math.min(...t.map(a=>a.row)),i=Math.max(...t.map(a=>a.row));return{x:p.x+n*p.cell-36,y:p.y+r*p.cell-36,w:(o-n+1)*p.cell+72,h:(i-r+1)*p.cell+72}}function gu(e){let t=[],n=new Set;for(let i of e.conveyorTiles){let{col:a,row:c}=X(i);for(let b of fe){let M={col:a+b.dc,row:c+b.dr},C=E(M.col,M.row);if(!e.conveyorTiles.has(C))continue;let $=[i,C].sort().join("|");n.has($)||(n.add($),t.push({fromKey:i,toKey:C,a:B(a,c),b:B(M.col,M.row)}))}}let o=Bu(e),r=[...e.conveyorTiles].map(i=>{let a=X(i),c=B(a.col,a.row),b=e.conveyorMeta.get(i)||{},M=ro(e,i);return{k:i,c,meta:b,connectedDirs:M,branchDirs:Au(e,i,M),markers:o.get(i)||[],sprite:ku(e,i,b)}});return{edges:t,tiles:r}}function xu(e,t){e.save(),e.lineCap="round",e.lineJoin="round";for(let n of[{width:31,color:"#0b0c0f",alpha:1},{width:23,color:"#444b56",alpha:1},{width:15,color:"#171a1f",alpha:1}]){e.globalAlpha=n.alpha,e.strokeStyle=n.color,e.lineWidth=n.width;for(let o of t.edges)e.beginPath(),e.moveTo(o.a.x,o.a.y),e.lineTo(o.b.x,o.b.y),e.stroke()}e.globalAlpha=1;for(let n of t.tiles){let{c:o,meta:r,sprite:i}=n;e.fillStyle="rgba(0,0,0,.35)",e.strokeStyle="rgba(255,184,39,.34)",e.lineWidth=1,ee(e,o.x-20,o.y-20,40,40,!0,!0),ln(e,i,o.x-19,o.y-19,38,38)||(e.fillStyle=r.kind==="boostConveyor"?"#315e92":"#454b55",e.strokeStyle=w.ink,e.lineWidth=2,ee(e,o.x-18,o.y-18,36,36,!0,!0))}e.restore()}function Tu(e,t){let n=bi(t),o=Mu(t);if(!n.conveyors||n.conveyors.signature!==o){let c=gu(t),b=Cu(t),M=wi(b.w,b.h),C=M.getContext("2d");C.translate(-b.x,-b.y),xu(C,c),n.conveyors={signature:o,bounds:b,layer:M,data:c}}let{bounds:r,layer:i,data:a}=n.conveyors;e.drawImage(i,r.x,r.y),e.save(),e.lineCap="round",e.lineJoin="round";for(let c of a.edges){let b=Math.max(yi(t,c.fromKey),yi(t,c.toKey)),M=Math.max(0,(b-.5)/.5);e.strokeStyle=qn(w.green,w.danger,M),e.globalAlpha=b>.5?.35+M*.55:.8,e.lineWidth=6,e.beginPath(),e.moveTo(c.a.x,c.a.y),e.lineTo(c.b.x,c.b.y),e.stroke()}e.globalAlpha=1;for(let c of a.tiles){let{k:b,c:M,meta:C}=c,$=yi(t,b);$>.5&&(e.fillStyle="rgba(196,36,36,.28)",e.strokeStyle=w.danger,e.lineWidth=3,ee(e,M.x-20,M.y-20,40,40,!0,!0)),Ru(e,t,M,b,C,$),Dt(e,M.x-18,M.y-18,36,36,{meta:C}),ft(e,t,"conveyor",b,M.x,M.y,38,38),Du(e,M,c.markers),Fu(e,M,C,c.branchDirs,c.connectedDirs),$>.5&&oo(e,M.x,M.y-15,`${Math.floor($*100)}%`,w.danger)}e.restore()}function Eu(e,t){if(t.powerWires?.size){e.save();for(let[n,o]of t.powerWires){let r=X(n),i=B(r.col,r.row),a=o.kind==="insulatedPowerWire";e.globalAlpha=t.powerOverlay||t.phase==="build"?.95:.32,e.strokeStyle=a?"#d7eef7":"#1e252b",e.lineWidth=a?8:5,e.lineCap="round",e.beginPath(),e.moveTo(i.x-15,i.y),e.lineTo(i.x+15,i.y),e.moveTo(i.x,i.y-15),e.lineTo(i.x,i.y+15),e.stroke(),e.strokeStyle=a?"#2c8ec4":"#f3c33d",e.lineWidth=a?3:2,e.stroke(),t.powerOverlay&&oo(e,i.x,i.y-14,a?"I":"V",a?"#2c8ec4":w.warn)}e.restore()}}function Pu(e,t){if(t.footbridges?.size){e.save();for(let n of t.footbridges.keys()){let o=X(n),r=B(o.col,o.row);e.fillStyle="rgba(210,222,228,.52)",e.strokeStyle="#e6edf2",e.lineWidth=3,ee(e,r.x-20,r.y-14,40,28,!0,!0),e.strokeStyle="#34404a",e.lineWidth=2,e.beginPath(),e.moveTo(r.x-18,r.y-9),e.lineTo(r.x+18,r.y-9),e.moveTo(r.x-18,r.y+9),e.lineTo(r.x+18,r.y+9),e.stroke(),ft(e,t,"footbridge",n,r.x,r.y,44,34)}e.restore()}}function Ru(e,t,n,o,r={},i=0){let a=r.kind==="boostConveyor",b=[...new Set([...Array.isArray(r.outDirs)?r.outDirs:[],r.dir].filter(Boolean))][0]||"right",M=b==="up"||b==="down",C=b==="left"||b==="up",$=Vt(t,o)*eo(t,o),K=(typeof performance<"u"?performance.now()/1e3:0)*$,G=((C?-K:K)%10+10)%10;e.save(),e.beginPath(),e.rect(n.x-18,n.y-18,36,36),e.clip();let z=a?"rgba(20,126,124,.62)":"rgba(42,47,54,.72)";if(e.fillStyle=z,e.fillRect(n.x-18,n.y-18,36,36),e.strokeStyle=i>.5?"rgba(238,83,61,.90)":"rgba(184,190,198,.92)",e.lineWidth=4,M)for(let Q=n.y-28+G;Qe.conveyorTiles.has(E(n.col+o.dc,n.row+o.dr))).map(o=>o.name)}function Wa(e,t,n){let o=fe.find(a=>a.name===n);if(!o)return!1;let r=e.conveyorMeta?.get?.(E(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 Ua(e,t,n,o=ro(e,t)){if(o.length<3)return n;let r=X(t),i=new Set(o.filter(b=>Wa(e,r,b))),a=o.filter(b=>!i.has(b));if(i.size)return a;let c=n.filter(b=>o.includes(b));return c.length>=2?[...new Set(c)]:o}function Iu(e,t,n,o=ro(e,t),r="random"){let i=Ua(e,t,n,o);return o.length>=3&&r&&r!=="random"&&i.includes(r)?[r]:i}function Au(e,t,n=ro(e,t)){let o=e.conveyorMeta?.get?.(t)||{},r=[...new Set([...Array.isArray(o.outDirs)?o.outDirs:[],o.dir].filter(Boolean))];return n.length<3?[]:Ua(e,t,r,n)}function $u(e,t,n){let o=X(t),r=fe.filter(c=>e.conveyorTiles.has(E(o.col+c.dc,o.row+c.dr))).map(c=>c.name),i=[...new Set([...Array.isArray(n.outDirs)?n.outDirs:[],n.dir].filter(Boolean))].filter(c=>r.includes(c));if(r.length===1&&(!i.length||i.includes(r[0]))&&Wa(e,o,r[0]))return[];let a=r;return i.length?i:a}function Fu(e,t,n,o,r=o){if(r.length<3||o.length<2)return;e.save();let i=o.includes(n.branchMode)?n.branchMode:"random",a=i!=="random";if(e.lineJoin="round",e.lineCap="round",e.lineWidth=2,e.fillStyle="rgba(18,21,26,.94)",e.strokeStyle=a?w.warn:w.green,e.lineWidth=4,ee(e,t.x-18,t.y-18,36,36,!0,!0),e.strokeStyle="#ffd36a",e.lineWidth=3,e.fillStyle="#020304",i!=="random"){let c=fe.find(b=>b.name===i);no(e,t.x,t.y-4,c?.angle||0,8,5)}else e.beginPath(),e.moveTo(t.x-10,t.y),e.lineTo(t.x-2,t.y),e.lineTo(t.x+8,t.y-8),e.moveTo(t.x-2,t.y),e.lineTo(t.x+8,t.y+8),e.stroke(),no(e,t.x+9,t.y-9,-Math.PI/4,4,3),no(e,t.x+9,t.y+9,Math.PI/4,4,3);i==="random"&&(e.font="900 9px ui-monospace, monospace",e.textAlign="center",e.textBaseline="middle",e.lineWidth=3,e.strokeStyle="#020304",e.fillStyle=w.warn,e.strokeText("RND",t.x,t.y+11),e.fillText("RND",t.x,t.y+11)),e.restore()}function Ou(e,t,n){if(!n)return[];let o=X(t),r=fe.filter(c=>e.conveyorTiles.has(E(o.col+c.dc,o.row+c.dr))).map(c=>c.name),i=[...new Set([...Array.isArray(n.outDirs)?n.outDirs:[],n.dir].filter(Boolean))].filter(c=>r.includes(c));return Iu(e,t,i.length?i:$u(e,t,n),r,n.branchMode||"random").map(c=>fe.find(b=>b.name===c)).filter(Boolean)}function Bu(e){let t=new Map;for(let[n,o]of e.conveyorMeta||[]){let r=Ou(e,n,o);r.length&&t.set(n,r)}return t}function Du(e,t,n){let o=n.slice(0,4);o.length&&(e.save(),e.globalAlpha=1,o.forEach(r=>{let i=t.x+r.dc*11,a=t.y+r.dr*11;e.fillStyle="rgba(255,205,83,.96)",no(e,i,a,r.angle,9,7),e.fillStyle="#020304",no(e,i,a,r.angle,6.5,4.6)}),e.restore())}function no(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 Lu(e,t,n,o,r){let i=ge(t),a=null;if(n==="inputA"&&(a={x:i.x,y:i.y-40}),n==="left"&&(a={x:i.x-42,y:i.y+10}),n==="right"&&(a={x:i.x+42,y:i.y+10}),!a)return;let c=n==="inputA"?{x:o.x,y:a.y}:{x:a.x,y:o.y};e.save(),e.lineCap="square",e.lineJoin="bevel",e.strokeStyle="#050609",e.lineWidth=15,e.beginPath(),e.moveTo(o.x,o.y),e.lineTo(c.x,c.y),e.lineTo(a.x,a.y),e.stroke(),e.strokeStyle="#596473",e.lineWidth=9,e.beginPath(),e.moveTo(o.x,o.y),e.lineTo(c.x,c.y),e.lineTo(a.x,a.y),e.stroke(),e.strokeStyle=r||"#7b8795",e.lineWidth=3,e.beginPath(),e.moveTo(o.x,o.y),e.lineTo(c.x,c.y),e.lineTo(a.x,a.y),e.stroke(),e.fillStyle=r||"#7b8795",e.strokeStyle="#050609",e.lineWidth=3,ee(e,a.x-9,a.y-7,18,14,!0,!0),e.restore()}function Nu(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=Br(n),r=[{type:"inputA",p:We(n,"inputA"),label:"IN",color:w.green},{type:"left",p:We(n,"left"),label:Lr(o.left),color:Nr(o.left)},{type:"right",p:We(n,"right"),label:Lr(o.right),color:Nr(o.right)}];for(let i of r){if(!i.p)continue;let a=B(i.p.col,i.p.row);Lu(e,n,i.type,a,i.color);let c=us(t,n,i.type||(i.label==="IN"?"inputA":null))||t.conveyorTiles.has(E(i.p.col,i.p.row));e.fillStyle=c?qn("#1a1d23",i.color,.24):"#3a1e22",e.strokeStyle=c?i.color:w.danger,ee(e,a.x-18,a.y-12,36,24,!0,!0),e.fillStyle=w.white,e.fillText(i.label,a.x,a.y+4)}}e.restore()}function Gu(e,t){for(let n of t.eggFarms)_u(e,n,t)}function _u(e,t,n){let o=B(t.col,t.row);e.save(),e.fillStyle="#5a6470",e.strokeStyle=w.ink,e.lineWidth=3,ee(e,o.x-27,o.y-27,54,54,!0,!0),e.fillStyle="#6f5130",e.strokeStyle="#2f2112",e.lineWidth=3,e.beginPath(),e.ellipse(o.x,o.y+3,19,12,0,0,Math.PI*2),e.fill(),e.stroke(),e.strokeStyle="#a77b4b",e.lineWidth=2;for(let c=-16;c<=16;c+=6)e.beginPath(),e.moveTo(o.x-18,o.y+c*.1),e.quadraticCurveTo(o.x+c,o.y-10+Math.abs(c)*.2,o.x+18,o.y+c*.08),e.stroke();let r=[{x:-8,y:1},{x:0,y:-3},{x:8,y:2}];for(let c of r)e.fillStyle="#e8e2cf",e.strokeStyle="#8d8475",e.lineWidth=1.5,e.beginPath(),e.ellipse(o.x+c.x,o.y+c.y,4.8,6.2,.2,0,Math.PI*2),e.fill(),e.stroke();let i=1+Math.max(0,Number(t.extraEggOutlet)||0);e.fillStyle="rgba(255,255,255,.92)",e.strokeStyle=w.ink,e.lineWidth=2,ee(e,o.x+8,o.y+11,30,18,!0,!0),e.fillStyle=w.ink,e.font="900 10px ui-monospace, monospace",e.textAlign="center",e.fillText(`O${i}`,o.x+23,o.y+23),e.fillStyle="#d5e35a",e.font="900 9px ui-monospace, monospace",e.fillText(`L${t.level}`,o.x-13,o.y-15);let a=Math.max(0,Math.min(1,t.shutterProgress||0));if(a>0){let c=54*a;e.fillStyle="rgba(10,12,15,.86)",ee(e,o.x-27,o.y-27,54,c,!0,!1),e.strokeStyle="rgba(230,237,242,.64)",e.lineWidth=2;for(let b=o.y-23;b=1&&(e.fillStyle=w.white,e.font="900 12px ui-monospace, monospace",e.fillText("SHUT",o.x,o.y+4))}Dt(e,o.x-28,o.y-28,56,56,t),ft(e,n,"eggFarm",t.id,o.x,o.y,68,68),e.restore()}function Wu(e,t){for(let n of t.scanners)Uu(e,n,t)}function Uu(e,t,n){let o=ge(t);if(e.save(),t.kind==="manual"&&(n.manualCombo?.count||0)>=10){let b=n.manualCombo.count;e.save(),e.globalAlpha=Math.min(.55,.16+b/120),e.strokeStyle=b>=30?w.warn:w.green,e.lineWidth=Math.min(12,4+Math.floor(b/10)),e.shadowColor=e.strokeStyle,e.shadowBlur=Math.min(24,8+b),ee(e,o.x-48,o.y-48,96,96,!1,!0),e.restore()}let r=t.kind==="auto"?De.scannerAuto:De.scannerManual;if(ln(e,r,o.x-44,o.y-44,88,88)){t.kind==="auto"?Dt(e,o.x-44,o.y-44,88,88,t):La(e,t,o),ft(e,n,"scanner",t.id,o.x,o.y,96,96),e.restore();return}e.fillStyle=t.kind==="auto"?"#d8ffe2":w.white,e.strokeStyle=w.ink,e.lineWidth=4,ee(e,o.x-44,o.y-44,88,88,!0,!0),e.fillStyle=t.kind==="auto"?w.green:w.ink,e.font="900 16px ui-monospace, monospace",e.textAlign="center",e.fillText(t.kind==="auto"?"AUTO":`S${(t.slot??0)+1}`,o.x,o.y-24),e.font="900 9px ui-monospace, monospace";let i=t.keys?.left?.label||(t.slot===0?"A":"Left"),a=t.keys?.right?.label||(t.slot===0?"D":"Right");e.fillText(t.kind==="auto"?"2.1 FARMS":`${i}/${a}`,o.x,o.y-8);let c=t.queue.length;t.kind==="manual"?La(e,t,o):(e.fillStyle=c>0?w.green:w.muted,e.font="900 10px ui-monospace, monospace",e.fillText(`Q:${c} CD:${t.cooldown.toFixed(1)}`,o.x,o.y+35),Dt(e,o.x-44,o.y-44,88,88,t)),ft(e,n,"scanner",t.id,o.x,o.y,96,96),e.restore()}function La(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,a=n.x-30,c=n.y+8;e.save(),e.lineWidth=3,e.fillStyle="#f7fff5",e.strokeStyle=w.ink,ee(e,a-4,c-4,68,25,!0,!0),e.font="900 19px ui-monospace, monospace",e.textAlign="center";for(let b=0;b MEAT","meat","connect OUT to sausage machine"),Dt(e,n.x,n.y,n.w,n.h,n),ft(e,t,"facility",n.id,n.x+n.w/2,n.y+n.h/2,n.w+12,n.h+12),e.restore()}function ed(e,t,n=t.facilities.trash){e.save(),er(e,n),cn(e,n.x,n.y,n.w,n.h,"#29342d",w.ink,w.wasteGreen),$n(e,n,w.wasteGreen),e.fillStyle=w.white,e.font="900 14px ui-monospace, monospace",e.textAlign="left",e.fillText(`SHREDDER L${n.level}`,n.x+14,n.y+18),e.fillStyle="#181d20",e.strokeStyle="#9fd473",e.lineWidth=4,ee(e,n.x+38,n.y+36,92,22,!0,!0),e.fillStyle="#262d30",e.strokeStyle="#0d1014",ee(e,n.x+46,n.y+58,76,18,!0,!0),e.fillStyle="#111418";for(let o=0;o<7;o+=1){let r=n.x+51+o*10;e.beginPath(),e.moveTo(r,n.y+58),e.lineTo(r+5,n.y+68),e.lineTo(r+10,n.y+58),e.closePath(),e.fill()}e.fillStyle="#6f7e8d",ee(e,n.x+54,n.y+27,16,9,!0,!0),ee(e,n.x+98,n.y+27,16,9,!0,!0),Jo(e,n.x+14,n.y+n.h-42,n.w-28,"SEND POOP","poop","shredder"),Dt(e,n.x,n.y,n.w,n.h,n),ft(e,t,"facility",n.id,n.x+n.w/2,n.y+n.h/2,n.w+12,n.h+12),e.restore()}function td(e,t,n){e.save(),cn(e,n.x,n.y,n.w,n.h,"#3b3029",w.ink,w.sausageOrange),$n(e,n,w.sausageOrange),e.fillStyle=w.white,e.font="900 14px ui-monospace, monospace",e.textAlign="left",e.fillText(`SAUSAGE L${n.level}`,n.x+14,n.y+18),e.fillStyle=w.muted,e.font="900 10px ui-monospace, monospace",e.fillText(`2 MEAT -> 1 | ${R(lt(t))}`,n.x+14,n.y+34),e.fillStyle="#211915",e.strokeStyle=w.sausageOrange,e.lineWidth=4,ee(e,n.x+20,n.y+43,n.w-40,38,!0,!0);for(let o=0;o<3;o+=1)tr(e,n.x+n.w/2-28+o*28,n.y+62,9);e.fillStyle=w.meatRed,e.fillRect(n.x,n.y+n.h/2-8,8,16),e.fillStyle=w.sausageOrange,e.fillRect(n.x+n.w-8,n.y+n.h/2-8,8,16),Jo(e,n.x+10,n.y+n.h-42,n.w-20,"2 MEAT -> SAUSAGE","sausage",`buffer ${n.meatBuffer||0}/2`),Dt(e,n.x,n.y,n.w,n.h,n),ft(e,t,"facility",n.id,n.x+n.w/2,n.y+n.h/2,n.w+12,n.h+12),e.restore()}function nd(e,t,n=t.facilities.truck){e.save(),er(e,n),cn(e,n.x,n.y,n.w,n.h,"#343128",w.ink,w.truckPink),$n(e,n,w.truckPink),e.fillStyle=w.white,e.font="900 14px ui-monospace, monospace",e.textAlign="left",e.fillText(`SHIP TRUCK L${n.level}`,n.x+14,n.y+18),e.fillStyle=w.muted,e.font="900 10px ui-monospace, monospace",e.fillText(`UNIT ${R(Je(t))}`,n.x+14,n.y+34),zu(e,De.truck,n,2);let o=hi(t);Jo(e,n.x+15,n.y+50,n.w-30,`SEND ${o.toUpperCase()}`,o,t.eventOffer&&!t.eventActive?"next event":"truck cargo"),e.fillStyle="rgba(39,60,36,.9)",ee(e,n.x+13,n.y+88,n.w-26,Math.max(18,n.h-103),!0,!1);for(let r of t.truckCargo)r.sex==="poop"?Va(e,n.x+r.x,n.y+r.y):r.sex==="sausage"?tr(e,n.x+r.x,n.y+r.y,7):Si(e,n.x+r.x,n.y+r.y,r.sex,r.sex==="male");Dt(e,n.x,n.y,n.w,n.h,n),ft(e,t,"facility",n.id,n.x+n.w/2,n.y+n.h/2,n.w+12,n.h+12),e.restore()}function od(e,t,n){let o=n(0),r=n(1);for(let i of t.chicks){let a=o&&o.id===i.id||r&&r.id===i.id||i.queueIndex===0;rd(e,i,a,t)}}function rd(e,t,n,o){let r=o.componentLookup&&id(o,t)>.8,i=t.y+Math.sin(t.bob)*(r?5:1.5);if(e.save(),n&&(e.strokeStyle=w.green,e.lineWidth=5,e.beginPath(),e.arc(t.x,i,t.radius+8,0,Math.PI*2),e.stroke()),r&&(e.strokeStyle=w.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")vi(e,t.x,i,t.radius);else if(t.sex==="meat")Ka(e,t.x,i,t.radius);else if(t.sex==="sausage")tr(e,t.x,i,t.radius);else if(t.sex==="coal")sd(e,t.x,i,t.radius);else if(t.sex==="zundaMochi")ad(e,t.x,i,t.radius);else{let a=t.sex==="male"?De.chickMale:De.chickFemale;if(!ln(e,a,t.x-t.radius,i-t.radius,t.radius*2,t.radius*2)){let M=t.sex==="male"?w.male:w.female,C=t.sex==="male"?"#164f8f":"#8b2252";e.fillStyle=M,e.strokeStyle=w.ink,e.lineWidth=5,e.beginPath(),e.arc(t.x,i,t.radius,0,Math.PI*2),e.fill(),e.stroke(),e.fillStyle="rgba(255,255,255,.24)",e.beginPath(),e.arc(t.x-5,i-7,t.radius*.36,0,Math.PI*2),e.fill(),e.fillStyle=w.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.strokeStyle=C,e.lineWidth=3,t.sex==="male"?(e.beginPath(),e.moveTo(t.x-8,i-t.radius-2),e.lineTo(t.x+8,i-t.radius-2),e.stroke()):(e.beginPath(),e.moveTo(t.x-9,i-t.radius+2),e.lineTo(t.x,i-t.radius-6),e.lineTo(t.x+9,i-t.radius+2),e.stroke())}e.fillStyle=t.sex==="male"?"#dff1ff":"#fff0f7",e.strokeStyle="#050609",e.lineWidth=3,e.font=`900 ${Math.max(10,Math.round(t.radius*.72))}px ui-monospace, monospace`,e.textAlign="center";let b=t.sex==="male"?"\u2642":"\u2640";e.strokeText(b,t.x+t.radius*.54,i+t.radius*.7),e.fillText(b,t.x+t.radius*.54,i+t.radius*.7)}e.restore()}function id(e,t){let n=null,o=1/0;for(let[r,i]of e.componentLookup.entries()){let a=X(r),c=B(a.col,a.row),b=Math.hypot(t.x-c.x,t.y-c.y);bNa(o.type,o.id)===Na(t,n))}function ft(e,t,n,o,r,i,a,c){ld(t,n,o)&&(e.save(),e.fillStyle="rgba(34,185,79,.18)",e.strokeStyle=w.green,e.lineWidth=(t.multiSelected||[]).length>1?6:4,e.setLineDash((t.multiSelected||[]).length>1?[]:[8,5]),ee(e,r-a/2,i-c/2,a,c,!0,!0),e.restore())}function cd(e,t,n,o){if(t.phase==="build"&&(t.multiSelected||[]).length>1){e.save(),e.fillStyle="rgba(255,255,255,.96)",e.strokeStyle=w.ink,e.lineWidth=3,ee(e,54-t.view.x,66-t.view.y,214,42,!0,!0),e.fillStyle=w.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=w.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 ud(e,t,n){if(!n.cardTargetPick?.pending)return;let o=pi(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 M of o){let C=M.bounds,$=8;e.rect(C.x-$,C.y-$,C.w+$*2,C.h+$*2)}e.fill("evenodd");let i=n.cardTargetPick?.mode==="blockedCell";if(!o.length){e.fillStyle="rgba(255,255,255,.95)",e.strokeStyle=w.ink,e.lineWidth=3,ee(e,(260-(n.view?.x||0))/r,(96-(n.view?.y||0))/r,420/r,42/r,!0,!0),e.fillStyle=w.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=w.ink,e.lineWidth=3;let a=Math.max(1,n.cardTargetPick?.remaining||1),c=Math.max(0,n.cardTargetPick?.used||0),b=n.cardTargetPick?.mode==="autoScannerMenu"?"AUTO SCANNER ROUTE SETTINGS. CLICK EMPTY AREA TO CLOSE.":i?`DYNAMITE: CLICK ANY BLOCKED TILE (${a} LEFT). ESC ${c>0?"FINISHES":"CANCELS"}.`:"CLICK AN UPGRADEABLE MACHINE. PRESS ESC TO CANCEL.";ee(e,(216-(n.view?.x||0))/r,(96-(n.view?.y||0))/r,560/r,42/r,!0,!0),e.fillStyle=w.ink,e.font="900 12px ui-monospace, monospace",e.textAlign="center",e.fillText(b,(496-(n.view?.x||0))/r,(122-(n.view?.y||0))/r),e.restore()}function dd(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),a=Math.abs(n.y2-n.y1);e.save(),e.fillStyle="rgba(34,185,79,.12)",e.strokeStyle=w.green,e.lineWidth=2,e.setLineDash([7,4]),ee(e,o,r,i,a,!0,!0),e.restore()}function fd(e,t){let n=[...t.effects].sort((o,r)=>(o.priority??_e.ambient)-(r.priority??_e.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=w.green,ee(e,o.x-o.size/2,o.y-o.size/2,o.size*1.5,o.size,!0,!1)):o.type==="sludge"?(e.fillStyle=w.poop,ee(e,o.x-o.size/2,o.y-o.size/2,o.size*1.4,o.size,!0,!1)):o.type==="spawn"?(e.strokeStyle=w.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||w.danger,ee(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||w.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||w.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||w.ink,e.save(),e.translate(o.x,o.y),e.rotate(o.rot||0),ee(e,-o.w/2,-o.h/2,o.w,o.h,!0,!1),e.restore()):o.type==="load"?(e.strokeStyle=o.color||w.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=w.ink,e.lineWidth=3,e.setLineDash([4,4]),ee(e,o.x-o.size/2,o.y-o.size/2,o.size,o.size,!1,!0)):o.type==="rage"?(e.fillStyle=o.color||w.danger,e.font="900 16px ui-monospace, monospace",e.textAlign="center",e.fillText("!",o.x,o.y)):o.type==="flyingChick"&&(o.sex==="poop"?vi(e,o.x,o.y,o.radius||11):Si(e,o.x,o.y,o.sex,o.sex==="male")),e.restore()}}function pd(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=w.white,e.fillStyle=n.color,e.strokeText(n.text,n.x,n.y),e.fillText(n.text,n.x,n.y)}e.restore()}function yd(e,t){let n=t.repairmen&&t.repairmen.length?t.repairmen:t.repairman?[t.repairman]:[];for(let o of n)hd(e,t,o)}function hd(e,t,n){if(!!!n?.active)return;if(e.save(),ln(e,De.repairman,n.x-18,n.y-18,36,36)){n.target?.label&&oo(e,n.x,n.y-24,n.target.label,w.green),e.restore();return}e.lineWidth=3,e.fillStyle="#fff0d1",e.strokeStyle=w.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?w.danger:w.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=w.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=w.green,ee(e,n.x-9,n.y+14,18,8,!0,!0),e.strokeStyle=w.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=w.ink,e.font="900 11px ui-monospace, monospace",e.textAlign="center",e.fillText("FIX",n.x,n.y+33),n.target?.label&&oo(e,n.x,n.y-24,n.target.label,w.green),e.restore()}function bd(e,t,n){e.save(),e.font="900 19px ui-monospace, monospace",e.textAlign="left",e.fillStyle=w.ink,e.font="900 19px ui-monospace, monospace",e.fillStyle=w.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=w.white,e.lineWidth=4,e.strokeText(i,24,r),e.fillText(i,24,r),r+=20;e.restore()}function qa({game:e,canvasPoint:t,updatePanels:n,equipmentAtPoint:o,fail:r,pointInGrid:i,rectOfFacility:a,rectsOverlap:c}){function b(x){return x?x.type==="conveyor"?`conveyor:${x.id||x.oldKey}`:x.type==="facility"?`facility:${x.id||x.ref?.id}`:x.type==="maintenanceRoom"?`maintenanceRoom:${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 C(x){let g=b(M(x));return(e.multiSelected||[]).some(D=>b(D)===g)}function $(x){return x?x.type==="eggFarm"?e.eggFarms.find(g=>g.id===x.id)||null:x.type==="scanner"?e.scanners.find(g=>g.id===x.id)||null:x.type==="facility"?e.facilities[x.id]||null:x.type==="maintenanceRoom"?(e.maintenanceRooms||[]).find(g=>g.id===x.id)||null:x.type==="conveyor"?e.conveyorTiles.has(x.id)?{type:"conveyor",id:x.id}:null:x.type==="powerWire"?e.powerWires?.has(x.id)?{type:"powerWire",id:x.id}:null:x.type==="footbridge"&&e.footbridges?.has(x.id)?{type:"footbridge",id:x.id}:null:null}function W(){let x=[];for(let g of e.conveyorTiles){let D=X(g),Z=B(D.col,D.row);x.push({type:"conveyor",id:g,center:Z,cell:D})}if(e.powerOverlay)for(let[g]of e.powerWires||new Map){let D=X(g);x.push({type:"powerWire",id:g,center:B(D.col,D.row),cell:D})}for(let[g]of e.footbridges||new Map){let D=X(g);x.push({type:"footbridge",id:g,center:B(D.col,D.row),cell:D})}for(let g of e.eggFarms)x.push({type:"eggFarm",id:g.id,ref:g,center:B(g.col,g.row),cell:{col:g.col,row:g.row}});for(let g of e.scanners)x.push({type:"scanner",id:g.id,ref:g,center:ge(g),cell:{col:g.col,row:g.row}});for(let g of e.maintenanceRooms||[]){let D=B(g.col,g.row);x.push({type:"maintenanceRoom",id:g.id,ref:g,center:{x:D.x+p.cell/2,y:D.y+p.cell/2},cell:{col:g.col,row:g.row}})}for(let g of Object.values(e.facilities)){let D={x:g.x+g.w/2,y:g.y+g.h/2},Z=g.entry?B(g.entry.col,g.entry.row):D;x.push({type:"facility",id:g.id,ref:g,center:D,portCenter:Z})}return x}function K(x,g){let D=Math.min(g.x1,g.x2),Z=Math.max(g.x1,g.x2),j=Math.min(g.y1,g.y2),le=Math.max(g.y1,g.y2);return x.x>=D&&x.x<=Z&&x.y>=j&&x.y<=le}function G(x){let g=[];for(let D of W())(K(D.center,x)||D.portCenter&&K(D.portCenter,x))&&g.push({type:D.type,id:D.id});return e.multiSelected=g,e.selected=g[0]||null,n(),g.length}function z(){e.multiSelected=[],e.selected=null,n()}function Q(x){let g=t(x);return e.selectionBox={x1:g.x,y1:g.y,x2:g.x,y2:g.y},e.multiSelected=[],e.selected=null,n(),!0}function ie(x){if(!e.selectionBox)return;let g=t(x);e.selectionBox.x2=g.x,e.selectionBox.y2=g.y}function J(){if(!e.selectionBox)return 0;let x=e.selectionBox,g=Math.abs(x.x2-x.x1),D=Math.abs(x.y2-x.y1);return e.selectionBox=null,g<6&&D<6?(z(),0):G(x)}function ae(x){let g=M(x);return g?(e.multiSelected||[]).length&&C(x)?[...e.multiSelected]:[g]:[]}function ye(x){let g=$(x);if(!g)return null;if(x.type==="conveyor"){let D=X(x.id);return{...x,obj:g,oldKey:x.id,currentKey:x.id,meta:e.conveyorMeta.get(x.id)||{price:Le("conveyor"),builtSession:null},col:D.col,row:D.row}}if(x.type==="powerWire"){let D=X(x.id);return{...x,obj:g,oldKey:x.id,currentKey:x.id,meta:{...e.powerWires?.get(x.id)||{}},col:D.col,row:D.row}}if(x.type==="footbridge"){let D=X(x.id),Z=e.footbridges?.get(x.id)||{},j=[];for(let[le,ce]of e.footbridges||new Map)if(Z.bridgeId&&ce.bridgeId===Z.bridgeId||le===x.id){let ve=X(le);j.push({key:le,dc:ve.col-D.col,dr:ve.row-D.row,meta:{...ce}})}return{...x,obj:g,oldKey:x.id,currentKey:x.id,meta:{...Z},cells:j,dragToken:`footbridge:${Z.bridgeId||x.id}`,col:D.col,row:D.row}}return x.type==="eggFarm"||x.type==="scanner"||x.type==="maintenanceRoom"?{...x,obj:g,col:g.col,row:g.row}:x.type==="facility"?g.gridPlaced?{...x,obj:g,gridPlaced:!0,col:g.col,row:g.row,x:g.x,y:g.y,w:g.w,h:g.h,entry:g.entry?{...g.entry}:null,side:g.side,center:{x:g.x+g.w/2,y:g.y+g.h/2}}:{...x,obj:g,gridPlaced:!1,x:g.x,y:g.y,w:g.w,h:g.h,entry:g.entry?{...g.entry}:null,side:g.side,center:{x:g.x+g.w/2,y:g.y+g.h/2}}:null}function L(x){let g=t(x),D=o(g);if(!D)return!1;let Z=ae(D),j=[],le=new Set;for(let ce of Z.map(ye).filter(Boolean)){let ve=ce.dragToken||b(ce);le.has(ve)||(le.add(ve),j.push(ce))}return j.length?(e.groupDrag={start:g,selections:Z,origins:j,historySnapshot:Xn(e),committed:!1,lastDCol:0,lastDRow:0},e.multiSelected=Z,e.selected=Z[0]||null,n(),!0):!1}function V(){!e.groupDrag||e.groupDrag.committed||(e.undoStack.push(e.groupDrag.historySnapshot),e.redoStack=[],e.groupDrag.committed=!0)}function se(x,g,D){let Z=E(x,g),j=it(e,x,g);if(j&&!D.has(`eggFarm:${j.id}`))return!0;let le=st(e,x,g);if(le&&!D.has(`scanner:${le.id}`))return!0;let ce=(e.maintenanceRooms||[]).find(Me=>xt(Me).some(Se=>Se.col===x&&Se.row===g));if(ce&&!D.has(`maintenanceRoom:${ce.id}`))return!0;let ve=bt(e,x,g);return!!(ve&&!D.has(`facility:${ve.id}`)||e.conveyorTiles.has(Z)&&!D.has(`conveyor:${Z}`))}function O(x,g,D){for(let Z of Object.values(e.facilities||{})){if(D.has(`facility:${Z.id}`))continue;let j=wt(e,Z),le=He(e,Z);if(j&&j.col===x&&j.row===g||le&&le.col===x&&le.row===g)return!0}return!1}function H(x,g,D,Z){let j=it(e,g,D);if(j&&!Z.has(`eggFarm:${j.id}`))return!0;let le=st(e,g,D);if(le&&!Z.has(`scanner:${le.id}`))return!0;let ce=(e.maintenanceRooms||[]).find(Me=>xt(Me).some(Se=>Se.col===g&&Se.row===D));if(ce&&!Z.has(`maintenanceRoom:${ce.id}`))return!0;let ve=bt(e,g,D);if(ve&&ve.id!==x.id&&!Z.has(`facility:${ve.id}`))return!0;for(let Me of Object.values(e.facilities||{})){if(Me.id===x.id||Z.has(`facility:${Me.id}`))continue;let Se=wt(e,Me),Ke=He(e,Me);if(Se&&Se.col===g&&Se.row===D||Ke&&Ke.col===g&&Ke.row===D)return!0}return!1}function oe(x,g,D,Z){let j=E(g,D);if(!i(g,D)||e.blockedCells?.has?.(j))return"Selection hits blocked ground";if(it(e,g,D)||st(e,g,D))return"Cell occupied";let le=(e.maintenanceRooms||[]).find(Me=>xt(Me).some(Se=>Se.col===g&&Se.row===D));if(le&&!Z.has(`maintenanceRoom:${le.id}`))return"Cell occupied";let ce=bt(e,g,D);if(ce&&!Z.has(`facility:${ce.id}`))return"Cell occupied";let ve=x.cells?.some(Me=>Me.key===j);return x.type==="powerWire"&&e.powerWires?.has(j)&&!Z.has(`powerWire:${j}`)?"Power wire already covers that cell":x.type==="footbridge"&&e.footbridges?.has(j)&&!Z.has(`footbridge:${j}`)&&!ve?"Footbridge already covers that cell":""}function ue(x,g,D){let Z=[];for(let j of e.groupDrag.origins){if(j.type!=="facility"||j.gridPlaced)continue;let le={x:j.center.x+x,y:j.center.y+g},{entry:ce,side:ve}=Lo(le,e);if(!i(ce.col,ce.row)||e.blockedCells?.has?.(E(ce.col,ce.row)))return!0;let Me={...j.obj,entry:{...ce},side:ve};Go(Me,ce,ve),Z.push({id:j.id,rect:a(Me)})}for(let j of Z)for(let le of Object.values(e.facilities))if(!D.has(`facility:${le.id}`)&&c(j.rect,a(le),12))return!0;for(let j=0;jE(D.col,D.row)));for(let D of["left","right"]){let Z=We(x,D),j=E(Z.col,Z.row);if(!e.conveyorTiles.has(j))continue;let le=e.conveyorMeta.get(j);if(!le)continue;let ce=fe.find(Se=>Se.name===D),ve=fe.filter(Se=>{let Ke=E(Z.col+Se.dc,Z.row+Se.dr);return e.conveyorTiles.has(Ke)&&!g.has(Ke)}),Me=ve.find(Se=>Se.name===ce?.name)||ve[0]||ce;Me&&(le.dir=Me.name,le.outDirs=[Me.name],le.branchMode="random")}}function xe(x){if(!e.groupDrag)return;let g=t(x),D=g.x-e.groupDrag.start.x,Z=g.y-e.groupDrag.start.y;if(Math.hypot(D,Z)<3)return;let j=Math.round(D/p.cell),le=Math.round(Z/p.cell),ce=new Set(e.groupDrag.selections.map(b)),ve=new Set;for(let T of e.groupDrag.origins){if(T.type==="facility"&&!T.gridPlaced)continue;let ke=T.col+j,Ae=T.row+le;if(T.type==="powerWire"||T.type==="footbridge"){let Ce=T.type==="footbridge"&&T.cells?.length?T.cells.map(Ue=>({col:ke+Ue.dc,row:Ae+Ue.dr})):[{col:ke,row:Ae}];for(let Ue of Ce){let St=oe(T,Ue.col,Ue.row,ce);if(St)return r(St);let et=`${T.type}:${E(Ue.col,Ue.row)}`;if(ve.has(et))return r("Selection overlap");ve.add(et)}continue}let ot=T.type==="scanner"?Pt({col:ke,row:Ae}):T.type==="maintenanceRoom"?xt({col:ke,row:Ae}):T.type==="facility"?gn({...T.obj,col:ke,row:Ae}):[{col:ke,row:Ae}];for(let Ce of ot){if(!i(Ce.col,Ce.row))return r("Selection outside grid");if(e.blockedCells?.has?.(E(Ce.col,Ce.row)))return r("Selection hits blocked ground");let Ue=E(Ce.col,Ce.row);if(ve.has(Ue))return r("Selection overlap");if(se(Ce.col,Ce.row,ce))return r("Cell occupied");if(T.type!=="conveyor"&&O(Ce.col,Ce.row,ce))return r("Machine port must remain open");ve.add(Ue)}if(T.type==="facility"&&T.gridPlaced){let Ce={...T.obj,col:ke,row:Ae},Ue={col:ke-1,row:Ae+Math.floor((Ce.hCells||3)/2)},St={col:ke+(Ce.wCells||3),row:Ae+Math.floor((Ce.hCells||3)/2)};for(let et of[Ue,St]){if(!i(et.col,et.row)||e.blockedCells?.has?.(E(et.col,et.row)))return r("Machine port outside usable grid");if(H(T.obj,et.col,et.row,ce))return r("Machine port is occupied")}}}if(ue(D,Z,ce))return r("Facility overlap");V();let Me=e.groupDrag.origins.filter(T=>T.type==="conveyor");for(let T of Me){let ke=T.currentKey||T.oldKey;e.conveyorTiles.delete(ke),e.conveyorMeta.delete(ke)}for(let T of Me){let ke=E(T.col+j,T.row+le);e.conveyorTiles.add(ke),e.conveyorMeta.set(ke,T.meta||{price:Le("conveyor"),builtSession:null,uses:0}),T.currentKey=ke}let Se=e.groupDrag.origins.filter(T=>T.type==="powerWire");for(let T of Se)e.powerWires.delete(T.currentKey||T.oldKey);for(let T of Se){let ke=E(T.col+j,T.row+le);e.powerWires.set(ke,T.meta||{}),T.currentKey=ke}let Ke=e.groupDrag.origins.filter(T=>T.type==="footbridge");for(let T of Ke)for(let ke of T.cells||[{key:T.currentKey||T.oldKey}])e.footbridges.delete(ke.key);for(let T of Ke){let ke=E(T.col+j,T.row+le);for(let Ae of T.cells||[{dc:0,dr:0,meta:T.meta||{}}]){let ot=E(T.col+j+(Ae.dc||0),T.row+le+(Ae.dr||0));e.footbridges.set(ot,{...Ae.meta||T.meta||{}})}T.currentKey=ke}for(let T of e.groupDrag.origins)if(T.type==="eggFarm")T.obj.col=T.col+j,T.obj.row=T.row+le;else if(T.type==="scanner")T.obj.col=T.col+j,T.obj.row=T.row+le,Ve(T.obj);else if(T.type==="maintenanceRoom")T.obj.col=T.col+j,T.obj.row=T.row+le;else if(T.type==="facility"&&T.gridPlaced)T.obj.col=T.col+j,T.obj.row=T.row+le,Cn(T.obj);else if(T.type==="facility"){let ke={x:T.center.x+D,y:T.center.y+Z},{entry:Ae,side:ot}=Lo(ke,e);Go(T.obj,Ae,ot)}e.groupDrag.selections=e.groupDrag.origins.map(T=>["conveyor","powerWire","footbridge"].includes(T.type)?{type:T.type,id:T.currentKey||T.oldKey}:{type:T.type,id:T.id}),e.multiSelected=[...e.groupDrag.selections],e.selected=e.multiSelected[0]||null,Ee(e),e.groupDrag.origins.some(T=>T.type!=="powerWire")&&(e.walkabilityVersion=(e.walkabilityVersion||0)+1),e.groupDrag.origins.some(T=>T.type==="powerWire"||T.type!=="conveyor"&&T.type!=="footbridge")&&Ye(e),n()}function Ge(){e.groupDrag&&(Ee(e),e.groupDrag=null,n())}return{selectionFromHit:M,startSelectionBox:Q,updateSelectionBox:ie,finishSelectionBox:J,startGroupDrag:L,updateGroupDrag:xe,finishGroupDrag:Ge}}function Kt(e){return Vt(e)*(_.facilities.boostConveyor.speedMultiplier||2)/Math.max(1,_.conveyor.minimumItemSpacingPx||25)}function io(e,t,n){let o=Math.max(_.farmQuality.safeMinIntervalSeconds,Number(n)||1),r=(t.zundaBoostTime||0)>0?_.farmQuality.zundaIntervalMultiplier:1;return 1/Math.max(_.farmQuality.safeMinIntervalSeconds,o*r)}function so(e,t){let n=_.farmQuality,o=Math.max(1e-4,t),r=Math.max(0,e/o-1);return Math.max(n.minMultiplier,Math.min(n.maxMultiplier,1+n.overflowCoefficient*Math.pow(r,n.overflowExponent)))}function za(e,t){return 1/Math.max(1e-4,Math.min(e,t))}function Ha(e){return e>=1.75?"Luxury":e>=1.35?"Premium":e>=1.15?"Select":"Standard"}function Ya(e,t){let n=t.zundaBoostTime||0;t.zundaBoostTime=Math.min(_.farmQuality.zundaMaxSeconds,n+_.farmQuality.zundaSeconds);let o=Math.max(0,t.zundaBoostTime-n);e.stats.zundaConsumed=(e.stats.zundaConsumed||0)+1,e.stats.zundaBoostSeconds=(e.stats.zundaBoostSeconds||0)+o,e.totals.zundaConsumed=(e.totals.zundaConsumed||0)+1,e.totals.zundaBoostSeconds=(e.totals.zundaBoostSeconds||0)+o,e.stats.zundaBoostSecondsByFarm=e.stats.zundaBoostSecondsByFarm||{},e.stats.zundaBoostSecondsByFarm[t.id]=(e.stats.zundaBoostSecondsByFarm[t.id]||0)+o}function Qa({game:e,ui:t,canvas:n,canvasPoint:o,onUpdatePanels:r}){function i(){r&&r()}let a=null,c=!1,b=null,M=!1,C=["random","up","right","down","left"],$={random:"RND",up:"\u25B2",right:"\u25B6",down:"\u25BC",left:"\u25C0"},W={w:40,h:40,y:0};function K(){return Co(e)}function G(){e.gridCols=p.cols,e.gridRows=p.rows,e.gridOwnedCells=ji(e)}function z(s){if(s){p.y-=s*p.cell,p.rows+=s,e.gridChunkOriginY=(e.gridChunkOriginY||0)-Math.floor(s/10),e.ownedCells=go(e.ownedCells,s),e.blockedCells=go(e.blockedCells,s),e.conveyorTiles=go(e.conveyorTiles,s),e.conveyorMeta=xo(e.conveyorMeta,s),e.powerWires=xo(e.powerWires||new Map,s),e.footbridges=xo(e.footbridges||new Map,s);for(let l of e.eggFarms||[])l.row+=s;for(let l of e.scanners||[])l.row+=s;for(let l of Object.values(e.facilities||{}))l.gridPlaced?(l.row+=s,Cn(l)):l.entry&&(l.entry.row+=s)}}function Q(s){if(s){p.x-=s*p.cell,p.cols+=s,e.gridChunkOriginX=(e.gridChunkOriginX||0)-Math.floor(s/10),e.ownedCells=To(e.ownedCells,s),e.blockedCells=To(e.blockedCells,s),e.conveyorTiles=To(e.conveyorTiles,s),e.conveyorMeta=Eo(e.conveyorMeta,s),e.powerWires=Eo(e.powerWires||new Map,s),e.footbridges=Eo(e.footbridges||new Map,s);for(let l of e.eggFarms||[])l.col+=s;for(let l of e.scanners||[])l.col+=s;for(let l of e.maintenanceRooms||[])l.col+=s;for(let l of Object.values(e.facilities||{}))l.gridPlaced?(l.col+=s,Cn(l)):l.entry&&(l.entry.col+=s)}}function ie(s){e.ownedCells=e.ownedCells||new Set;for(let l of es(s))e.ownedCells.add(E(l.col,l.row))}function J(s){return e.phase!=="build"||e.cardDraft?.pending||e.cardTargetPick?.pending?null:jt(e).map(l=>L(l)).find(l=>l&&Zi(l,s))||null}function ae(s){let l=Et(s);return{x:l.x,y:l.y,w:l.w,h:l.h}}function ye(s){let l=ae(s);return Object.values(e.facilities||{}).some(y=>{let h=y?.baseId||y?.id;return!vo.includes(h)||y.gridPlaced?!1:ue(l,oe(y),0)})}function L(s){if(!s)return null;let l=ye(s);return{...s,disabled:l,disabledReason:l?"Move edge facilities before buying this lot.":""}}function V(s="right"){if(s&&typeof s=="object"&&Number.isFinite(s.chunkX)&&Number.isFinite(s.chunkY))return s;let l=["left","right","up","down"].includes(s)?s:"right";return Ji(e,l)}function se(s="right"){let l=V(s);if(e.phase!=="build"||e.cardDraft?.pending||e.cardTargetPick?.pending)return{ok:!1,reason:"Cannot expand now."};if(!l)return{ok:!1,reason:"Invalid expansion lot."};if(ye(l)){let $e="Move edge facilities before buying this lot.";return T($e),{ok:!1,reason:$e}}let y=l.direction||"right",h=K();if(e.cashp.cols&&(p.cols=F.colStart+F.cols),F.rowStart+F.rows>p.rows&&(p.rows=F.rowStart+F.rows),ie(F),ts(e,F.chunkX,F.chunkY),e.gridExpansionPurchases=(e.gridExpansionPurchases||0)+1,e.gridExpansionPurchasesByDirection[y]=(e.gridExpansionPurchasesByDirection[y]||0)+1,G();let q=Ki(e.gridExpansionPurchases,{base:_.map.blockedRatio??0,increment:_.map.blockedExpansionIncrement??.0075,max:_.map.blockedRatioMax??.12}),be=zi({seed:`${e.runSeed}:grid-expansion:${F.chunkX},${F.chunkY}:${e.gridExpansionPurchases}`,colStart:F.colStart,colEnd:F.colStart+F.cols,rowStart:F.rowStart,rowEnd:F.rowStart+F.rows,targetRatio:q});for(let $e of be)e.blockedCells.add($e);Ee(e);let Pe=F.cols*F.rows,we=Et(F);return Y(e,we.x+we.w/2,we.y+we.h/2,`${y.toUpperCase()} +${Pe} CELLS -${R(h)}`,w.green),i(),{ok:!0,direction:y,cost:h,addedCells:Pe,blocked:be.size,blockedRatio:q,chunkX:F.chunkX,chunkY:F.chunkY}}function O(s,l){return s>=0&&s=0&&l!l.has(h.id)&&ue(y,oe(h),12))}function xe(s,l,y=null){if(!O(s,l))return!1;if(H(s,l))return!0;let h=E(s,l),m=it(e,s,l);if(m&&!(y?.type==="eggFarm"&&y.ref?.id===m.id))return!0;let P=st(e,s,l);if(P&&!(y?.type==="scanner"&&y.ref?.id===P.id))return!0;let F=x(e,s,l);if(F&&!(y?.type==="maintenanceRoom"&&y.ref?.id===F.id))return!0;let q=bt(e,s,l);return!!(q&&!(y?.type==="facility"&&y.ref?.id===q.id)||e.conveyorTiles.has(h)&&!(y?.type==="conveyor"&&y.oldKey===h))}function Ge(s,l,y=null){return Object.values(e.facilities||{}).find(h=>{if(y?.type==="facility"&&y.ref?.id===h.id)return!1;let m=wt(e,h),P=He(e,h);return m&&m.col===s&&m.row===l||P&&P.col===s&&P.row===l})||null}function x(s,l,y){return(s.maintenanceRooms||[]).find(h=>xt(h).some(m=>m.col===l&&m.row===y))||null}function g(s,l,y=null){let h={col:s,row:l};for(let m of xt(h)){if(!O(m.col,m.row)||H(m.col,m.row))return!0;let P=E(m.col,m.row);if(it(e,m.col,m.row)||st(e,m.col,m.row))return!0;let be=x(e,m.col,m.row);if(be&&!(y?.type==="maintenanceRoom"&&y.ref?.id===be.id)||bt(e,m.col,m.row)||Ge(m.col,m.row,y)||e.conveyorTiles.has(P))return!0}return!1}function D(s,l,y=null){let h={col:s,row:l};for(let m of Pt(h)){if(!O(m.col,m.row)||H(m.col,m.row))return!0;let P=E(m.col,m.row);if(it(e,m.col,m.row))return!0;let q=st(e,m.col,m.row);if(q&&!(y?.type==="scanner"&&y.ref?.id===q.id)||bt(e,m.col,m.row)||Ge(m.col,m.row,y)||e.conveyorTiles.has(P))return!0}return!1}function Z(s,l,y,h=null){let m=_.facilities[s]||{},P={baseId:s,id:s,gridPlaced:!0,col:l,row:y,wCells:m.footprint?.cols||(s==="sausageMaker"?3:1),hCells:m.footprint?.rows||(s==="sausageMaker"?3:1)};for(let F of gn(P))if(!O(F.col,F.row)||H(F.col,F.row)||xe(F.col,F.row,h)||Ge(F.col,F.row,h))return!0;if(s==="sausageMaker"||s==="biomassGenerator"||s==="coalGenerator"){let F={col:l-1,row:y+1},q={col:l+(P.wCells||1),row:y+1},be=[F,{col:l+1,row:y-1},{col:l+1,row:y+(P.hCells||3)}],Pe=s==="sausageMaker"?[F,q]:s==="biomassGenerator"?be:[F];for(let we of Pe){if(!O(we.col,we.row)||H(we.col,we.row))return!0;let $e=bt(e,we.col,we.row);if($e&&$e.id!==h?.ref?.id||it(e,we.col,we.row)||st(e,we.col,we.row)||x(e,we.col,we.row)||Ge(we.col,we.row,h))return!0}}return!1}function j(){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==="powerWire"?e.powerWires?.has(e.selected.id)?{type:"powerWire",id:e.selected.id}:null:e.selected.type==="footbridge"?e.footbridges?.has(e.selected.id)?{type:"footbridge",id:e.selected.id}:null:e.selected.type==="facility"?e.facilities[e.selected.id]||null:e.selected.type==="maintenanceRoom"&&(e.maintenanceRooms||[]).find(s=>s.id===e.selected.id)||null:null}function le(s){return s?s.type==="eggFarm"?`${ht("eggFarm")} #${s.id}`:s.type==="scanner"?`${s.kind==="auto"?ht("autoScanner"):ht("manualScanner")} #${s.id}`:s.type==="conveyor"?e.conveyorMeta.get(s.id)?.kind==="boostConveyor"?ht("boostConveyor"):ht("conveyor"):s.type==="powerWire"?e.powerWires?.get(s.id)?.kind==="insulatedPowerWire"?ht("insulatedPowerWire"):ht("powerWire"):s.type==="footbridge"?ht("footbridge"):s.type==="facility"?s.name:s.type==="maintenanceRoom"?`${ht("maintenanceRoom")} #${s.id}`:"Equipment":"Equipment"}function ce(s){return s?.type==="conveyor"?e.conveyorMeta?.get(s.oldKey||s.ref?.id)?.price||Oo(s):s?.type==="powerWire"?e.powerWires?.get(s.oldKey||s.ref?.id)?.price||0:s?.type==="footbridge"?e.footbridges?.get(s.oldKey||s.ref?.id)?.price||_.facilities.footbridge.price:Oo(s)}function ve(){return!!e.cardEffects?.usedMachineActive}function Me(){return ve()?{usedMachine:!0,noRefund:!0,durabilityBaseMultiplier:.6}:{usedMachine:!1,noRefund:!1,durabilityBaseMultiplier:1}}function Se(s){let l=Me();return Object.assign(s,l),s}function Ke(s){let l=s?.ref||s||{};if(l.unsellable||(l.baseId||l.id)==="starterGenerator")return"Starter Generator cannot be sold.";if(s?.type==="eggFarm"||l.type==="eggFarm")return e.eggFarms.length<=1?"Cannot sell the last EGG FARM.":"";if((s?.type==="facility"||l.type==="facility")&&["mixer","trash","truck"].includes(l.baseId||l.id)){let y=l.baseId||l.id;if(Object.values(e.facilities).filter(m=>(m.baseId||m.id)===y).length<=1)return`Cannot sell the last ${l.shortName||l.name||l.id}.`}return""}function T(s){t.buildStatus.textContent=s,Y(e,n.width/2-e.view.x,86-e.view.y,s.toUpperCase(),w.danger)}function ke(s){if(!s)return null;let l=E(s.col,s.row);return e.conveyorMeta.get(l)||null}function Ae(s){return s?(Array.isArray(s.outDirs)||(s.outDirs=[]),s.outDirs=[...new Set(s.outDirs.filter(l=>["right","left","down","up"].includes(l)))],C.includes(s.branchMode)||(s.branchMode="random"),s.kind||(s.kind="conveyor"),s.speedMultiplier||(s.speedMultiplier=s.kind==="boostConveyor"?2:1),s.outDirs):[]}function ot(s){return s?fe.filter(l=>e.conveyorTiles.has(E(s.col+l.dc,s.row+l.dr))).map(l=>l.name):[]}function Ce(s){return ot(s).length>=3}function Ue(s,l){let y=fe.find(q=>q.name===l);if(!s||!y)return!1;let h=E(s.col+y.dc,s.row+y.dr),m=e.conveyorMeta.get(h);if(!m)return!1;let P=y.opposite;return[...new Set([...Array.isArray(m.outDirs)?m.outDirs:[],m.dir].filter(Boolean))].includes(P)}function St(s,l){let y=ot(s);if(y.length!==1)return null;let h=y[0];return l.includes(h)&&Ue(s,h)?h:null}function et(s){if(!e.conveyorTiles.has(s))return[];let l=X(s),y=e.conveyorMeta.get(s),h=Ae(y),m=ot(l),P=[...new Set([...h,y?.dir].filter(Boolean))].filter(we=>m.includes(we)),F=m,q=P.length?P:F,be=St(l,q),Pe=P.length?P.filter(we=>we!==be):F.filter(we=>we!==be);return y&&(y.outDirs=h.filter(we=>m.includes(we)),y.dir&&!m.includes(y.dir)&&(y.dir=y.outDirs[0]||null),y.branchMode!=="random"&&!m.includes(y.branchMode)&&(y.branchMode="random")),Pe}function Ln(s){if(!e.conveyorTiles.has(s))return[];let l=X(s),y=ot(l);return Ce(l)?y:et(s)}function Ht(s){let l=X(s),y=Ln(s);if(y.length<3)return[];let h=e.conveyorMeta.get(s)||{},m=new Set(y.filter(q=>Ue(l,q))),P=y.filter(q=>!m.has(q));if(m.size)return P.length>=2?P:[];let F=[...new Set([...Array.isArray(h.outDirs)?h.outDirs:[],h.dir].filter(Boolean))].filter(q=>y.includes(q));return F.length>=2?F:y}function lo(s,l){let y=Tt(s,l);if(!y)return!1;let h=E(s.col,s.row),m=E(l.col,l.row),P=e.conveyorMeta.get(h),F=e.conveyorMeta.get(m);if(!P||!F)return!1;let q=Ae(P),be=!q.includes(y)||P.dir!==y||!F.dir;return q.includes(y)||q.push(y),P.dir=y,F.dir||(F.dir=y),be}function co(s){if(pt(s).length===0)return Nn(s);let l=ir(s);return l.length<2?Nn(s):(Oe(e),sr(l),Ee(e),i(),!0)}function Nn(s){if(!s)return!1;let l=E(s.col,s.row),y=e.conveyorMeta.get(l);if(!y||Ce(s))return!1;let h=St(s,fe.map(q=>q.name)),m=fe.map(q=>q.name).filter(q=>q!==h),P=y.dir&&m.includes(y.dir)?y.dir:null,F=m[(Math.max(-1,m.indexOf(P))+1)%m.length];return Oe(e),y.dir=F,y.outDirs=[F],y.branchMode="random",Ee(e),i(),!0}function pt(s){return s?fe.map(l=>({col:s.col+l.dc,row:s.row+l.dr})).filter(l=>e.conveyorTiles.has(E(l.col,l.row))):[]}function ir(s){if(!s||!e.conveyorTiles.has(E(s.col,s.row))||Ce(s))return[];let l=pt(s);if(!l.length||l.length>2)return[];let y=P=>{let F=[],q=new Set([E(s.col,s.row)]),be=s,Pe=P,we=0;for(;Pe&&we<200;){we+=1;let $e=E(Pe.col,Pe.row);if(q.has($e)||(q.add($e),F.push(Pe),Ce(Pe)))break;let Ri=pt(Pe).filter(Ii=>!(Ii.col===be.col&&Ii.row===be.row));if(Ri.length!==1)break;be=Pe,Pe=Ri[0]}return F},h=l[0]?y(l[0]).reverse():[],m=l[1]?y(l[1]):[];return[...h,s,...m]}function uo(s,l){return!!s&&[...new Set([...Array.isArray(s.outDirs)?s.outDirs:[],s.dir].filter(Boolean))].includes(l)}function sr(s){let l=0,y=0;for(let m=0;my?[...s].reverse():s;for(let m=0;m=2){let m=h[h.length-2],P=h[h.length-1];if(!Ce(P)){let F=e.conveyorMeta.get(E(P.col,P.row)),q=Tt(m,P);F&&q&&(F.dir=q,F.outDirs=[q],F.branchMode="random")}}}function fo(){return _.facilities[e.buildTool==="boostConveyor"?"boostConveyor":"conveyor"]}function Gn(s,l=null){if(!s)return{ok:!1,reason:pe.fail.outOfGrid};let{col:y,row:h}=s,m=E(y,h);if(e.conveyorTiles.has(m))return{ok:!1,exists:!0};if(H(y,h))return{ok:!1,reason:"Cannot build on blocked ground."};if(xe(y,h))return{ok:!1,reason:pe.fail.cellOccupied};let P=fo(),F=P.id||"conveyor",q=Le(F,e);if(e.cash=Math.abs(h)?{col:s.col+Math.sign(y),row:s.row}:{col:s.col,row:s.row+Math.sign(h)}}function lr(s){c=!1,b=s?{col:s.col,row:s.row}:null,M=!1,a=null,Yt(s)}function cr(s){if(!s)return;if(!a)return Yt(s);let l=a,y=0;for(;(l.col!==s.col||l.row!==s.row)&&y<64;){let h=ar(l,s);if(Yt(h),l=a||h,y+=1,!a)break}}function Qt(){!M&&b&&e.conveyorTiles.has(E(b.col,b.row))&&co(b),b=null,M=!1,a=null,c=!1}function Xt(s,l){return!O(s,l)||H(s,l)||it(e,s,l)||st(e,s,l)||x(e,s,l)||bt(e,s,l)}function dn(s,l){if(!s)return{ok:!1,reason:pe.fail.outOfGrid};if(Xt(s.col,s.row))return{ok:!1,reason:"Power wire cannot cross equipment or blocked ground."};let y=E(s.col,s.row),h=_.power.wires[l]?.price||Le(l,e),m=e.powerWires?.get(y),P=m&&_.power.wires[m.kind]?.price||0,F=h-P;return F>0&&e.cash0&&Ct(e,F),F<0&&Qr(e,-F),e.powerWires=e.powerWires||new Map,e.powerWires.set(y,{kind:l,price:h,builtSession:e.buildSession}),Ye(e),e.selected={type:"powerWire",id:y},i(),{ok:!0})}function ur(s){if(!s)return T(pe.fail.outOfGrid);let l=(e.footbridgeOrientation||"vertical")!=="horizontal",y=l?[s.row-1,s.row,s.row+1].map(P=>({col:s.col,row:P})):[s.col-1,s.col,s.col+1].map(P=>({col:P,row:s.row}));if(y.some(P=>Xt(P.col,P.row)))return T("Footbridge cannot cross equipment or blocked ground.");if(y.some(P=>e.footbridges?.has(E(P.col,P.row))))return T("Footbridge already covers that cell.");let h=Le("footbridge",e);if(e.cashy!=="starterGenerator")].includes(e.buildTool))return po(l);if(vo.includes(e.buildTool))return v(s,e.buildTool)}function dr(s,l){let y=Le("eggFarm",e);if(e.cashP.kind==="manual").length>=8)return T("Manual Scanner limit reached (8 max).");let h=y==="auto"?Le("autoScanner",e):Le("manualScanner",e);if(e.cashm.id!==l.ref.id),ct(e,h.x,h.y),Ee(e),e.walkabilityVersion=(e.walkabilityVersion||0)+1}else if(l.type==="scanner"){let h=ge(l.ref);k(l,h.x,h.y),e.scanners=e.scanners.filter(m=>m.id!==l.ref.id),ct(e,h.x,h.y),Ee(e),Ye(e),e.walkabilityVersion=(e.walkabilityVersion||0)+1}else if(l.type==="facility"){let h=l.ref;k(l,h.x+h.w/2,h.y+h.h/2),delete e.facilities[h.id],ct(e,h.x+h.w/2,h.y+h.h/2),Ee(e),Ye(e),e.walkabilityVersion=(e.walkabilityVersion||0)+1}else if(l.type==="maintenanceRoom"){let h=l.ref,m=u(h);k(l,m.x,m.y),e.maintenanceRooms=(e.maintenanceRooms||[]).filter(P=>P.id!==h.id),ct(e,m.x,m.y),Ye(e),e.walkabilityVersion=(e.walkabilityVersion||0)+1}e.selected=null,e.multiSelected=[],i()}function I(s){let l=Object.values(e.facilities).find(q=>{if(s.x>=q.x&&s.x<=q.x+q.w&&s.y>=q.y&&s.y<=q.y+q.h)return!0;if(!q.entry)return!1;let Pe=B(q.entry.col,q.entry.row);return Math.abs(s.x-Pe.x)<=p.cell*.46&&Math.abs(s.y-Pe.y)<=p.cell*.46});if(l)return{type:"facility",ref:l};let y=rt(s.x,s.y);if(!y)return null;let h=it(e,y.col,y.row);if(h)return{type:"eggFarm",ref:h};let m=st(e,y.col,y.row);if(m)return{type:"scanner",ref:m};let P=x(e,y.col,y.row);if(P)return{type:"maintenanceRoom",ref:P};let F=E(y.col,y.row);return e.footbridges?.has(F)?{type:"footbridge",oldKey:F,ref:{type:"footbridge",id:F}}:e.powerOverlay&&e.powerWires?.has(F)?{type:"powerWire",oldKey:F,ref:{type:"powerWire",id:F}}:e.conveyorTiles.has(F)?{type:"conveyor",oldKey:F,ref:{type:"conveyor",id:F}}:null}let A=qa({game:e,canvasPoint:o,updatePanels:i,equipmentAtPoint:I,fail:T,pointInGrid:O,rectOfFacility:oe,rectsOverlap:ue});function U(s){return null}function he(){let s=j();if(s)if(s.type==="conveyor")S(B(...Object.values(X(s.id))));else if(s.type==="powerWire"){let l=X(s.id),y=e.powerWires?.get(s.id);if(!y)return;Oe(e);let h=B(l.col,l.row);k({type:"powerWire",oldKey:s.id,ref:{id:s.id,price:y.price||0,builtSession:y.builtSession}},h.x,h.y),e.powerWires.delete(s.id),Ye(e),ct(e,h.x,h.y),e.selected=null,e.multiSelected=[],i()}else if(s.type==="footbridge"){let l=X(s.id);S(B(l.col,l.row))}else s.type==="eggFarm"?S(B(s.col,s.row)):s.type==="scanner"?S(ge(s)):s.type==="facility"?S({x:s.x+s.w/2,y:s.y+s.h/2}):s.type==="maintenanceRoom"&&S(u(s))}function de(s,l,y="primary-button"){let h=document.createElement("button");return h.type="button",h.className=y,h.textContent=s,h.addEventListener("click",l),h}function Xe(){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 _n(s){t.modalTitle.textContent="Auto Scanner",e.cardTargetPick={pending:!0,mode:"autoScannerMenu",cardId:null,targetKeys:[`scanner:${s.id}`],remaining:1},t.modalBody.innerHTML=`

Left / right output routing for this AUTO scanner.

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

-

Cooldown: ${St(s).toFixed(1)}s.

-
`,t.modalActions.innerHTML="";let l=ze("Male Left / Others Right",()=>{_e(e),s.role=0,_t(),i()},s.role===0?"facility-action warn":"facility-action"),y=ze("Poop Left / Others Right",()=>{_e(e),s.role=1,_t(),i()},s.role===1?"facility-action warn":"facility-action");t.modalActions.append(l,y,ze("Close",_t,"facility-action")),Ho(s),t.modal.classList.add("visible","equipment-popover"),i()}function Jr(s){return s?.label||s?.key||"?"}function $a(s){let l=s.key===" "?"Space":s.key.length===1?s.key.toUpperCase():s.key.replace(/^Arrow/,"");return{key:s.key.toLowerCase(),code:s.code,label:l}}function Fa(s,l,y){for(let m of e.scanners.filter(v=>v.kind==="manual"))for(let v of["left","right"]){if(m.id===s.id&&v===l)continue;let R=m.keys?.[v];if(R&&(R.code===y.code||R.key===y.key))return!0}return!1}function En(s,l=""){if(!s||s.kind!=="manual")return;t.modalTitle.textContent=`Manual Scanner #${s.id}`;let y=Jr(s.keys?.left),m=Jr(s.keys?.right),v=Da(s);t.modalBody.innerHTML=` +

Cooldown: ${Bt(s).toFixed(1)}s.

+ `,t.modalActions.innerHTML="";let l=de("Male Left / Others Right",()=>{Oe(e),s.role=0,Xe(),i()},s.role===0?"facility-action warn":"facility-action"),y=de("Poop Left / Others Right",()=>{Oe(e),s.role=1,Xe(),i()},s.role===1?"facility-action warn":"facility-action");t.modalActions.append(l,y,de("Close",Xe,"facility-action")),ho(s),t.modal.classList.add("visible","equipment-popover"),i()}function yo(s){return s?.label||s?.key||"?"}function pr(s){let l=s.key===" "?"Space":s.key.length===1?s.key.toUpperCase():s.key.replace(/^Arrow/,"");return{key:s.key.toLowerCase(),code:s.code,label:l}}function wl(s,l,y){for(let h of e.scanners.filter(m=>m.kind==="manual"))for(let m of["left","right"]){if(h.id===s.id&&m===l)continue;let P=h.keys?.[m];if(P&&(P.code===y.code||P.key===y.key))return!0}return!1}function Wn(s,l=""){if(!s||s.kind!=="manual")return;t.modalTitle.textContent=`Manual Scanner #${s.id}`;let y=yo(s.keys?.left),h=yo(s.keys?.right),m=ml(s);t.modalBody.innerHTML=`

Left route key: ${y}

-

Right route key: ${m}

+

Right route key: ${h}

${l?`

${l}

`:""} - ${v?`

${v}

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

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

Esc cancels.

`,t.modalActions.innerHTML="";let y=m=>{if(m.preventDefault(),m.stopPropagation(),window.removeEventListener("keydown",y,!0),m.key==="Escape")return En(s);let v=$a(m),R=Fa(s,l,v);_e(e),s.keys=s.keys||{left:null,right:null},s.keys[l]=v,i(),En(s,R?`${v.label} assigned, but another scanner already uses it.`:`${l.toUpperCase()} set to ${v.label}.`)};window.addEventListener("keydown",y,!0)}function Ba(){let s=se();if(!(!s||s.type!=="scanner"))return s.kind==="auto"?Tn(s):En(s)}function Oa(s){if(!s)return{x:80,y:80};if(s.type==="conveyor"){let l=re(s.id);return $(l.col,l.row)}return s.type==="eggFarm"?$(s.col,s.row):s.type==="scanner"?ge(s):s.type==="facility"?{x:s.x+s.w/2,y:s.y+10}:{x:80,y:80}}function Ho(s){let l=n.getBoundingClientRect(),y=Oa(s),m=e.view?.scale||1,v=l.left+(y.x*m+(e.view?.x||0))/n.width*l.width+18,R=l.top+(y.y*m+(e.view?.y||0))/n.height*l.height-12,N=Math.max(12,window.innerWidth-274),_=Math.max(12,window.innerHeight-220);t.modal.style.setProperty("--popover-x",`${Math.max(12,Math.min(N,v))}px`),t.modal.style.setProperty("--popover-y",`${Math.max(12,Math.min(_,R))}px`)}function ei(){let s=se();if(!s)return;let l=oi(s);if(t.modalTitle.textContent=Se(s),t.modalBody.innerHTML=` -
${l.map(v=>`

${v}

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

Income/Fine = ceil(base x 1.10^upgrades)

':""}`,t.modalActions.innerHTML="",s.type==="scanner"&&s.kind==="auto"&&t.modalActions.appendChild(ze("Auto Menu",()=>Tn(s),"facility-action warn")),s.type==="scanner"&&s.kind==="manual"&&t.modalActions.appendChild(ze("Set Keys",()=>En(s),"facility-action warn")),s.type==="conveyor"&&Ko(s.id)&&Lt(s.id).length>=2){let v=e.conveyorMeta.get(s.id)?.branchMode||"random";t.modalActions.appendChild(ze(`Branch: ${ti(v)}`,()=>{qo(s.id),e.selected={type:"conveyor",id:s.id},ei()},"facility-action warn branch-action"))}let y=s.type==="conveyor"?{type:"conveyor",oldKey:s.id,ref:s}:{type:s.type,ref:s},m=Be(y);if(m){let v=ze(m,()=>{},"facility-action danger disabled");v.disabled=!0,t.modalActions.appendChild(v)}else{let v=$t(y,e),R=v.sameBuild?`Remove / Refund ${x(v.amount)}`:`Sell ${x(v.amount)}`;t.modalActions.appendChild(ze(R,()=>{en(),_t()},"facility-action danger"))}Ho(s),t.modal.classList.add("visible","equipment-popover")}function La(s){e.phase==="build"&&(Nt(),e.buildTool=e.buildTool===s?null:s,e.selected=null,e.multiSelected=[],e.selectionBox=null,i())}function Na(){let s=[];for(let[l,y]of Object.entries(e.facilities))if(s.push({id:`facility:${l}`,price:be({type:"facility",ref:y}),x:y.x,y:y.y,w:y.w,h:y.h}),y.entry){let m=$(y.entry.col,y.entry.row);s.push({id:`facilityPort:${l}`,price:be({type:"facility",ref:y}),x:m.x-18,y:m.y-18,w:36,h:36})}for(let l of e.maintenanceRooms||[]){let y=w(l);s.push({id:`maintenanceRoom:${l.id}`,price:be({type:"maintenanceRoom",ref:l}),x:y.x-f.cell,y:y.y-f.cell,w:f.cell*2,h:f.cell*2})}for(let l of e.eggFarms){let y=$(l.col,l.row);s.push({id:`eggFarm:${l.id}`,price:be({type:"eggFarm",ref:l}),x:y.x-24,y:y.y-24,w:48,h:48})}for(let l of e.scanners){let y=ge(l);s.push({id:`scanner:${l.id}`,price:be({type:"scanner",ref:l}),x:y.x-50,y:y.y-73,w:100,h:146})}for(let l of e.conveyorTiles){let y=re(l),m=$(y.col,y.row),v=e.conveyorMeta.get(l)||{};s.push({id:`conveyor:${l}`,price:v.price||He(v.kind==="boostConveyor"?"boostConveyor":"conveyor",e),x:m.x-22,y:m.y-22,w:44,h:44})}return s}function ti(s){return P[s]||P.random}function Vu(s){return Yn(s)}function Ko(s){return e.conveyorTiles.has(s)?Le(re(s)):!1}function ni(s){if(e.phase!=="build"||e.cardDraft?.pending||e.cardTargetPick?.pending)return null;for(let l of e.conveyorTiles){if(!Ko(l)||Lt(l).length<2)continue;let y=re(l),m=$(y.col,y.row),v=m.x-L.w/2,R=m.y+L.y-L.h/2;if(s.x>=v&&s.x<=v+L.w&&s.y>=R&&s.y<=R+L.h)return l}return null}function _a(s){let l=ni(s);return l?qo(l):!1}function qo(s){let l=e.conveyorMeta.get(s);if(!l)return!1;let y=Lt(s);if(y.length<2)return!1;_e(e);let m=C.includes(l.branchMode)?l.branchMode:"random",v=["random",...fe.map(R=>R.name).filter(R=>y.includes(R))];return l.branchMode=v[(Math.max(0,v.indexOf(m))+1)%v.length],Me(e),e.selected=null,e.multiSelected=[],i(),!0}function oi(s){Re(e);let l=[],y=s?.type==="facility"?s.baseId||s.id:"";if(s.type==="eggFarm"){let[m,v]=$n(s);l.push(`Price: ${x(be(s))}`);{let R=Be({type:s.type,ref:s});l.push(R?`Sale: blocked (${R})`:`Sale value: ${x($t({type:s.type,ref:s},e).amount)}`)}l.push(`Level: ${s.level}/4`),l.push(`Random interval: ${m.toFixed(2)}-${v.toFixed(2)}s`),l.push(`Durability: ${Nn(s)}% | Spawn delay x${go(s).toFixed(2)}`)}else if(s.type==="scanner")l.push(`Price: ${x(be(s))}`),l.push(`Sale value: ${x($t({type:s.type,ref:s},e).amount)}`),l.push(`Type: ${s.kind.toUpperCase()} / Standard`),s.kind==="auto"&&l.push(`Role: ${s.role===0?"Male left / Others right":"Poop left / Others right"}`),l.push(`Queue: ${s.queue.length}`),s.kind==="auto"&&l.push(`Cooldown: ${s.cooldown.toFixed(1)}s / ${St(s,e).toFixed(1)}s`),s.kind==="auto"&&l.push(`Durability: ${Nn(s)}% | Delay x${ko(s).toFixed(2)}`),s.kind==="manual"&&l.push("Durability: none | Always normal");else if(s.type==="conveyor"){let m=e.conveyorMeta.get(s.id),v=e.componentLookup?.get(s.id),R=v?e.congestion.get(v):null;l.push(`Price: ${x(be({type:"conveyor",oldKey:s.id,ref:s}))}`),l.push(`Cell: ${s.id}`),l.push(`Type: ${m?.kind==="boostConveyor"?"BOOST BELT":"Normal Belt"} | Speed x${Number(m?.speedMultiplier||1).toFixed(1)}`),l.push(`Congestion: ${R?Math.floor(R.ratio*100):0}%`),l.push(`Durability: ${Nn({meta:m})}% | Speed x${So({meta:m}).toFixed(2)}`),Ko(s.id)?Lt(s.id).length>=2?(l.push(`Branch mode: ${ti(m?.branchMode||"random")}`),l.push("Click the Branch button or the on-belt triangle to cycle RANDOM / fixed exit.")):l.push("Branch mode: unavailable \u2014 fewer than two outgoing conveyors."):l.push("Branch mode: needs 3+ conveyor connections."),l.push(`Sale value: ${x($t({type:"conveyor",oldKey:s.id,ref:s},e).amount)}`)}else if(s.type==="maintenanceRoom")l.push(`Price: ${x(be({type:"maintenanceRoom",ref:s}))}`),l.push(`Sale value: ${x($t({type:"maintenanceRoom",ref:s},e).amount)}`),l.push("Size: 2\xD72 grid cells"),l.push("Dispatch: 1 repairman during each production day");else if(s.type==="facility"){l.push(`Price: ${x(be(s))}`);{let m=Be({type:s.type,ref:s});l.push(m?`Sale: blocked (${m})`:`Sale value: ${x($t({type:s.type,ref:s},e).amount)}`)}if(l.push(["mixer","truck","trash","sausageMaker"].includes(y)?`Level: ${s.level} / no cap`:`Level: ${s.level}`),y==="truck"&&l.push("Durability: none | Always normal"),["mixer","trash","sausageMaker"].includes(y)&&l.push(`Durability: ${Nn(s)}% | Extra delay ${Fr(e,s).toFixed(2)}s`),y==="mixer"){l.push(`Output: 1 meat per non-poop chick | meat buffer ${s.meatBuffer||0}`),l.push(`Meat quality: x${yt(e,"mixer").toFixed(3)} to sausage value`);let m=Xe(e,s);l.push(`OUT: ${m?`${m.col},${m.row}`:"none"} | conveyor ${It(e,s)?"connected":"missing"}`),l.push(`Fallback sale: ${x(xe.income.mixer)} when OUT is missing or blocked`),l.push(`Poop fine: -${x(Dn(e))}`)}if(y==="truck"&&(l.push(`Income: ${x(Ve(e))} per target cargo | upgrade x${yt(e,"truck").toFixed(3)}`),l.push(`Poop shipment fine: -${x(Bn(e))}`)),y==="trash"){let m=mt(e),v=ht(m),R=qt(m);l.push(`Bonus cards: ${m}/${cn()}`),l.push(`Expected poop bonus: ${x(v)} | hit chance ${Math.round(R*100)}%`),l.push("Winning payouts roll 1x / 2x / 4x / 10x, with jackpot odds rising per card."),l.push("With enough upgrades, poop can out-earn male/female routes.")}if(y==="sausageMaker"){let m=nt(e,s),v=Xe(e,s);l.push("Size: 3\xD73 grid cells | IN left / OUT right"),l.push(`Recipe: 2 meat -> 1 sausage | meat buffer ${s.meatBuffer||0} | sausage buffer ${s.sausageBuffer||0}`),l.push(`Sausage sale value: ${x(Ze(e))}`),l.push(`IN: ${m?`${m.col},${m.row}`:"none"} | OUT: ${v?`${v.col},${v.row}`:"none"} | conveyor ${It(e,s)?"connected":"missing"}`)}s.entry&&l.push(`Receiver: edge cell ${s.entry.col},${s.entry.row} (${s.side})`)}return l}function Ga(s){if(!s||e.phase!=="build")return"";let l=an(e).find(y=>y.type===s.type&&y.id===s.id);return l?l.message||"Not connected to conveyor route":""}function Ua(s){return s?s.type==="conveyor"&&e.conveyorMeta.get(s.id)?.kind==="boostConveyor"?"Moves chicks twice as fast. Wears down like a normal belt.":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"?"A compact 2\xD72 scanner sized for roughly 2.1 level-1 farms.":s.type==="scanner"?"A manual checkpoint. The operator is the algorithm.":s.type==="facility"&&(s.baseId||s.id)==="mixer"?"Chicks become meat when OUT is clear; a blocked OUT falls back to direct sale.":s.type==="facility"&&(s.baseId||s.id)==="trash"?"Poop goes in. Sometimes coins come out, for reasons best left unaudited.":s.type==="facility"&&(s.baseId||s.id)==="truck"?"The shipping endpoint. Correct cargo and sausages pay; wrong cargo complains.":s.type==="facility"&&(s.baseId||s.id)==="sausageMaker"?"A 3\xD73 in-grid machine with one meat inlet on the left and one sausage outlet on the right.":s.type==="maintenanceRoom"?"A cramped 2\xD72 storage and break room with lockers, spare parts, a bench and one dispatched repairman.":"Factory equipment.":""}return{buildAtCell:jn,buildAtPoint:xn,eraseAtPoint:E,equipmentAtPoint:W,startSelectionBox:ae.startSelectionBox,updateSelectionBox:ae.updateSelectionBox,finishSelectionBox:ae.finishSelectionBox,startGroupDrag:ae.startGroupDrag,updateGroupDrag:ae.updateGroupDrag,finishGroupDrag:ae.finishGroupDrag,selectedObject:se,selectedTitle:Se,equipmentPrice:be,selectedUpgradeCost:ve,selectedInfoLines:oi,flavorText:Ua,disconnectedWarningFor:Ga,gridExpansionCost:K,buyGridExpansion:ee,expansionOfferAtPoint:pe,beginConveyorDrag:Go,continueConveyorDrag:Uo,endConveyorDrag:Nt,cycleSingleConveyorDirection:Tt,branchSwitcherAtPoint:ni,cycleBranchModeAtPoint:_a,cycleBranchModeForKey:qo,branchSelectableExitDirsForKey:Lt,removeSelected:en,switchScannerRole:Ba,showManualScannerMenu:En,showAutoScannerMenu:Tn,showSelectedMenu:ei,setBuildTool:La,fail:de,isEquipmentCell:me,equipmentHitBoxes:Na,routeFromFarmToScanner:Kt}}function ya({game:e,ui:t,build:n,startGame:o,beginCardDraft:r,activeQueuedChick:i}){function a(O){return String(O).replace(/[&<>"']/g,G=>({"&":"&","<":"<",">":">",'"':""","'":"'"})[G])}function c(){return Math.round(pn(e))}function h(){if(e.phase!=="build"||e.cardDraft?.pending||e.cardTargetPick?.pending)return!1;let O=!1;for(let G of Qo)if(O=!0,e.cash>=He(G,e))return!1;return O}function g(){C(),P(),K(),L()}function C(){for(let[G,Y]of Object.entries(t.buttons))Y?.classList?.contains("tool-button")&&Y.classList.toggle("active",e.buildTool===G);for(let G of Qo){let Y=t.buttons[G];if(!Y)continue;let ie=He(G,e),De=Y.querySelector("span");De&&(De.textContent=G==="conveyor"||G==="boostConveyor"?`${x(ie)} / tile`:x(ie));let me=G==="manualScanner"&&e.scanners.filter(M=>M.kind==="manual").length>=8,T=e.cashKt(e,Y)?.scannerId).length,G=fo(e);t.turnSummary.innerHTML=[`Seed: ${a(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`,`Penalties: wrong shipment -${x(kr(e))} | Mixer poop -${x(Dn(e))} | Shipment poop -${x(Bn(e))}`,`Maintenance: worst ${Br(e).worst.percent}% dirty (${Br(e).worst.label}) | Rooms: ${(e.maintenanceRooms||[]).length} | Repairmen: ${(e.repairmen||[]).length}`,`Card draft: ${e.cardDraft?.pending?Math.max(1,e.cardDraft.picksRemaining||1)+" pick(s) left":"ready after each day"} | Farms with scanner route: ${O}/${e.eggFarms.length}`,e.cardEffects?.usedMachineActive?'Procurement: USED MACHINE MODE / no refunds / 60% durability':"",G.length?`Blocked: ${G[0]}`:`${ce.status.allPortsConnected}`].filter(Boolean).join("
")}function j(){C(),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=x(e.cash),t.money.classList.toggle("cash-negative",e.cash<0),t.money.classList.toggle("cash-positive",e.cash>Rn),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=Q(),t.comboCount&&(t.comboCount.textContent=e.manualCombo?.count||0),Z(),t.turnProfit.textContent=x(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 O=e.scanners.find(me=>me.kind==="manual"&&me.slot===0),G=e.scanners.find(me=>me.kind==="manual"&&me.slot===1);O&&(t.buttons.s1Left.textContent=`S1 ${O.keys?.left?.label||"A"} -> Mixer`,t.buttons.s1Right.textContent=`S1 ${O.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 Y=Math.max(0,Math.floor(e.lastResult?.profit??e.stats?.profit??0)),ie=Cr(e,Math.max(1,Number(e.turn)||1),Y).amount,De=e.phase==="build"&&ie>0?`NEXT DAYFAIRIES -${x(ie)}`:`${ce.actions.nextDay}`;t.buttons.nextTurn.dataset.renderedHtml!==De&&(t.buttons.nextTurn.innerHTML=De,t.buttons.nextTurn.dataset.renderedHtml=De),t.buttons.nextTurn.disabled=e.phase!=="build"||!!e.cardDraft?.pending||!!e.cardTargetPick?.pending}function Z(){if(!t.eventBrief||!t.speedBrief)return;t.speedBrief.textContent=`BELT SPEED: ${c()} px/s`,t.speedBrief.title=`Conveyor speed does not increase by day. Degraded belts reduce speed. Cap: ${tn}px/s.`;let O=e.eventActive,G=e.eventOffer;O?(t.eventBrief.textContent=`ACTIVE EVENT: ${O.title} | ${O.route}`,t.eventBrief.className="event-brief active"):e.phase==="build"&&G?(t.eventBrief.textContent=`NEXT DAY EVENT: ${G.title} | ${G.route}${G.targetAmount?` | Target ${G.targetAmount}`:""}${G.reward?` | Reward ${x(G.reward)}`:""}`,t.eventBrief.className="event-brief next"):e.phase==="build"&&e.turn+1
\u2642Male->Mixer -> MeatS1: 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(D(ce.actions.startGame,o,"primary-button")),t.modal.classList.remove("equipment-popover","gameover-modal"),t.modal.classList.add("visible")}function pe(O){let G=me=>`${me>0?"+":""}${x(me)}`,Y=me=>`${me>0?"-":""}${x(me)}`,ie=O.profit>=0?"cash-positive":"cash-negative",De=(O.mixerPoopFine||0)+(O.truckPoopFine||0)+(O.maleTruckFine||0)+(O.explosionDamage||0)+(O.cardRerollCost||0);t.modalTitle.textContent=`Day ${O.turn} Settlement`,t.modalBody.innerHTML=` -
Net result${x(O.profit)}
+ ${m?`

${m}

`:""} + `,t.modalActions.innerHTML="",t.modalActions.appendChild(de("Set Left Key",()=>gi(s,"left"),"facility-action warn")),t.modalActions.appendChild(de("Set Right Key",()=>gi(s,"right"),"facility-action warn")),t.modalActions.appendChild(de("Close",Xe,"facility-action")),ho(s),t.modal.classList.add("visible","equipment-popover")}function Ci(s){if(!s||(s.baseId||s.id)!=="inboundTruck")return;s.inboundOrder=s.inboundOrder||{coalPerDay:0,zundaMochiPerDay:0};let l=_.imports.combinedLimitPerTruck,y=Math.max(0,Math.floor(s.inboundOrder.coalPerDay||0)),h=Math.max(0,Math.floor(s.inboundOrder.zundaMochiPerDay||0)),m=y*_.imports.prices.coal+h*_.imports.prices.zundaMochi,P=y+h,F=P>0?(_.time.daySeconds/P).toFixed(1):"none";t.modalTitle.textContent=`Inbound Truck #${s.id}`,t.modalBody.innerHTML=` +
+ + +

Combined total: ${P}/${l}

+

Daily purchase cost: ${R(m)}

+

Average release interval: ${F}${P>0?"s":""}

+

Output queue: ${s.inboundQueue?.length||0}

+
`,t.modalActions.innerHTML="";let q=de("Apply",()=>{if(e.phase!=="build")return;let be=Math.max(0,Math.floor(Number(document.getElementById("inboundCoalInput")?.value)||0)),Pe=Math.max(0,Math.floor(Number(document.getElementById("inboundZundaInput")?.value)||0)),we=be+Pe,$e=we>l?l/we:1;Oe(e),s.inboundOrder={coalPerDay:Math.floor(be*$e),zundaMochiPerDay:Math.floor(Pe*$e)},Ci(s),i()},"primary-button");q.disabled=e.phase!=="build",t.modalActions.appendChild(q),t.modalActions.appendChild(de("Close",Xe,"facility-action")),ho(s),t.modal.classList.add("visible","equipment-popover")}function ml(s){let l=[{side:"left",binding:s.keys?.left},{side:"right",binding:s.keys?.right}];if(l[0].binding&&l[1].binding&&(l[0].binding.code===l[1].binding.code||l[0].binding.key===l[1].binding.key))return"Left and right keys conflict.";for(let y of l)if(y.binding)for(let h of e.scanners.filter(m=>m.kind==="manual"&&m.id!==s.id)){if(h.keys?.left&&(h.keys.left.code===y.binding.code||h.keys.left.key===y.binding.key))return`Key conflicts with Manual Scanner #${h.id} left.`;if(h.keys?.right&&(h.keys.right.code===y.binding.code||h.keys.right.key===y.binding.key))return`Key conflicts with Manual Scanner #${h.id} right.`}return""}function gi(s,l){t.modalBody.innerHTML=`

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

Esc cancels.

`,t.modalActions.innerHTML="";let y=h=>{if(h.preventDefault(),h.stopPropagation(),window.removeEventListener("keydown",y,!0),h.key==="Escape")return Wn(s);let m=pr(h),P=wl(s,l,m);Oe(e),s.keys=s.keys||{left:null,right:null},s.keys[l]=m,i(),Wn(s,P?`${m.label} assigned, but another scanner already uses it.`:`${l.toUpperCase()} set to ${m.label}.`)};window.addEventListener("keydown",y,!0)}function vl(){let s=j();if(!(!s||s.type!=="scanner"))return s.kind==="auto"?_n(s):Wn(s)}function Sl(s){if(!s)return{x:80,y:80};if(s.type==="conveyor"){let l=X(s.id);return B(l.col,l.row)}return s.type==="eggFarm"?B(s.col,s.row):s.type==="scanner"?ge(s):s.type==="facility"?{x:s.x+s.w/2,y:s.y+10}:{x:80,y:80}}function ho(s){let l=n.getBoundingClientRect(),y=Sl(s),h=e.view?.scale||1,m=l.left+(y.x*h+(e.view?.x||0))/n.width*l.width+18,P=l.top+(y.y*h+(e.view?.y||0))/n.height*l.height-12,F=Math.max(12,window.innerWidth-274),q=Math.max(12,window.innerHeight-220);t.modal.style.setProperty("--popover-x",`${Math.max(12,Math.min(F,m))}px`),t.modal.style.setProperty("--popover-y",`${Math.max(12,Math.min(q,P))}px`)}function xi(){let s=j();if(!s)return;let l=Pi(s);if(t.modalTitle.textContent=le(s),t.modalBody.innerHTML=` +
${l.map(m=>`

${m}

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

Income/Fine = ceil(base x 1.10^upgrades)

':""}`,t.modalActions.innerHTML="",s.type==="scanner"&&s.kind==="auto"&&t.modalActions.appendChild(de("Auto Menu",()=>_n(s),"facility-action warn")),s.type==="scanner"&&s.kind==="manual"&&t.modalActions.appendChild(de("Set Keys",()=>Wn(s),"facility-action warn")),s.type==="conveyor"&&yr(s.id)&&Ht(s.id).length>=2){let m=e.conveyorMeta.get(s.id)?.branchMode||"random";t.modalActions.appendChild(de(`Branch: ${Ti(m)}`,()=>{hr(s.id),e.selected={type:"conveyor",id:s.id},xi()},"facility-action warn branch-action"))}let y=s.type==="conveyor"?{type:"conveyor",oldKey:s.id,ref:s}:{type:s.type,ref:s},h=Ke(y);if(h){let m=de(h,()=>{},"facility-action danger disabled");m.disabled=!0,t.modalActions.appendChild(m)}else{let m=Ut(y,e),P=m.sameBuild?`Remove / Refund ${R(m.amount)}`:`Sell ${R(m.amount)}`;t.modalActions.appendChild(de(P,()=>{he(),Xe()},"facility-action danger"))}ho(s),t.modal.classList.add("visible","equipment-popover")}function kl(s){e.phase==="build"&&(Qt(),e.buildTool=e.buildTool===s?null:s,e.selected=null,e.multiSelected=[],e.selectionBox=null,i())}function Ml(){let s=[];for(let[l,y]of Object.entries(e.facilities))if(s.push({id:`facility:${l}`,price:ce({type:"facility",ref:y}),x:y.x,y:y.y,w:y.w,h:y.h}),y.entry){let h=B(y.entry.col,y.entry.row);s.push({id:`facilityPort:${l}`,price:ce({type:"facility",ref:y}),x:h.x-18,y:h.y-18,w:36,h:36})}for(let l of e.maintenanceRooms||[]){let y=u(l);s.push({id:`maintenanceRoom:${l.id}`,price:ce({type:"maintenanceRoom",ref:l}),x:y.x-p.cell,y:y.y-p.cell,w:p.cell*2,h:p.cell*2})}for(let l of e.eggFarms){let y=B(l.col,l.row);s.push({id:`eggFarm:${l.id}`,price:ce({type:"eggFarm",ref:l}),x:y.x-24,y:y.y-24,w:48,h:48})}for(let l of e.scanners){let y=ge(l);s.push({id:`scanner:${l.id}`,price:ce({type:"scanner",ref:l}),x:y.x-50,y:y.y-73,w:100,h:146})}for(let l of e.conveyorTiles){let y=X(l),h=B(y.col,y.row),m=e.conveyorMeta.get(l)||{};s.push({id:`conveyor:${l}`,price:m.price||Le(m.kind==="boostConveyor"?"boostConveyor":"conveyor",e),x:h.x-22,y:h.y-22,w:44,h:44})}if(e.powerOverlay)for(let[l,y]of e.powerWires||new Map){let h=X(l),m=B(h.col,h.row);s.push({id:`powerWire:${l}`,price:y.price||0,x:m.x-18,y:m.y-18,w:36,h:36})}for(let[l,y]of e.footbridges||new Map){let h=X(l),m=B(h.col,h.row);s.push({id:`footbridge:${l}`,price:y.price||Le("footbridge",e),x:m.x-22,y:m.y-22,w:44,h:44})}return s}function Ti(s){return $[s]||$.random}function ef(s){return Ln(s)}function yr(s){return e.conveyorTiles.has(s)?Ce(X(s)):!1}function Ei(s){if(e.phase!=="build"||e.cardDraft?.pending||e.cardTargetPick?.pending)return null;for(let l of e.conveyorTiles){if(!yr(l)||Ht(l).length<2)continue;let y=X(l),h=B(y.col,y.row),m=h.x-W.w/2,P=h.y+W.y-W.h/2;if(s.x>=m&&s.x<=m+W.w&&s.y>=P&&s.y<=P+W.h)return l}return null}function Cl(s){let l=Ei(s);return l?hr(l):!1}function hr(s){let l=e.conveyorMeta.get(s);if(!l)return!1;let y=Ht(s);if(y.length<2)return!1;Oe(e);let h=C.includes(l.branchMode)?l.branchMode:"random",m=["random",...fe.map(P=>P.name).filter(P=>y.includes(P))];return l.branchMode=m[(Math.max(0,m.indexOf(h))+1)%m.length],Ee(e),e.selected=null,e.multiSelected=[],i(),!0}function Pi(s){Be(e);let l=[],y=s?.type==="facility"?s.baseId||s.id:"";if(s.type==="eggFarm"){let[h,m]=zn(s);l.push(`Price: ${R(ce(s))}`);{let be=Ke({type:s.type,ref:s});l.push(be?`Sale: blocked (${be})`:`Sale value: ${R(Ut({type:s.type,ref:s},e).amount)}`)}l.push(`Level: ${s.level}/4`),l.push(`Random interval: ${h.toFixed(2)}-${m.toFixed(2)}s`),l.push(`Durability: ${Zn(s)}% | Spawn delay x${Yo(s).toFixed(2)}`);let P=Kt(e),F=s.theoreticalRate||io(e,s,s.lastInterval||1),q=so(F,P);l.push(`Rate: ${F.toFixed(2)}/s theoretical | ${Math.min(F,P).toFixed(2)}/s actual`),l.push(`Boost cap: ${P.toFixed(2)}/s | Quality: x${q.toFixed(2)} ${Ha(q)}`),s.zundaBoostTime>0&&l.push(`Zunda boost: ${s.zundaBoostTime.toFixed(1)}s`)}else if(s.type==="scanner"){l.push(`Price: ${R(ce(s))}`),l.push(`Sale value: ${R(Ut({type:s.type,ref:s},e).amount)}`),l.push(`Type: ${s.kind.toUpperCase()} / Standard`),s.kind==="auto"&&l.push(`Role: ${s.role===0?"Male left / Others right":"Poop left / Others right"}`),l.push(`Queue: ${s.queue.length}`),s.kind==="auto"&&l.push(`Cooldown: ${s.cooldown.toFixed(1)}s / ${Bt(s,e).toFixed(1)}s`),s.kind==="auto"&&l.push(`Durability: ${Zn(s)}% | Delay x${Ho(s).toFixed(2)}`),s.kind==="manual"&&l.push("Durability: none | Always normal");let h=En(e,s);h&&br(l,h)}else if(s.type==="conveyor"){let h=e.conveyorMeta.get(s.id),m=e.componentLookup?.get(s.id),P=m?e.congestion.get(m):null;l.push(`Price: ${R(ce({type:"conveyor",oldKey:s.id,ref:s}))}`),l.push(`Cell: ${s.id}`),l.push(`Type: ${h?.kind==="boostConveyor"?"BOOST BELT":"Normal Belt"} | Speed x${Number(h?.speedMultiplier||1).toFixed(1)}`),l.push(`Congestion: ${P?Math.floor(P.ratio*100):0}%`),l.push(`Durability: ${Zn({meta:h})}% | Speed x${zo({meta:h}).toFixed(2)}`),yr(s.id)?Ht(s.id).length>=2?(l.push(`Branch mode: ${Ti(h?.branchMode||"random")}`),l.push("Click the Branch button or the on-belt triangle to cycle RANDOM / fixed exit.")):l.push("Branch mode: unavailable \u2014 fewer than two outgoing conveyors."):l.push("Branch mode: needs 3+ conveyor connections."),l.push(`Sale value: ${R(Ut({type:"conveyor",oldKey:s.id,ref:s},e).amount)}`)}else if(s.type==="powerWire"){let h=e.powerWires?.get(s.id),m=_.power.wires[h?.kind||"powerWire"];l.push(`Price: ${R(h?.price||m.price)}`),l.push(`Cell: ${s.id}`),l.push(`Type: ${m.name} | Loss ${m.lossV}V/tile`)}else if(s.type==="footbridge"){let h=e.footbridges?.get(s.id);l.push(`Price: ${R(h?.price||Le("footbridge",e))}`),l.push(`Cell: ${s.id}`),l.push(`Orientation: ${h?.orientation||"vertical"}`),l.push("Repair workers may cross this conveyor cell.")}else if(s.type==="maintenanceRoom"){l.push(`Price: ${R(ce({type:"maintenanceRoom",ref:s}))}`),l.push(`Sale value: ${R(Ut({type:"maintenanceRoom",ref:s},e).amount)}`),l.push("Size: 2\xD72 grid cells"),l.push("Dispatch: 1 repairman during each production day");let h=En(e,s);h&&br(l,h)}else if(s.type==="facility"){l.push(`Price: ${R(ce(s))}`);{let m=Ke({type:s.type,ref:s});l.push(m?`Sale: blocked (${m})`:`Sale value: ${R(Ut({type:s.type,ref:s},e).amount)}`)}if(l.push(["mixer","truck","trash","sausageMaker"].includes(y)?`Level: ${s.level} / no cap`:`Level: ${s.level}`),y==="truck"&&l.push("Durability: none | Always normal"),["mixer","trash","sausageMaker"].includes(y)&&l.push(`Durability: ${Zn(s)}% | Extra delay ${ii(e,s).toFixed(2)}s`),y==="mixer"){l.push(`Output: 1 meat per non-poop chick | meat buffer ${s.meatBuffer||0}`),l.push(`Meat quality: x${Rt(e,"mixer").toFixed(3)} to sausage value`);let m=He(e,s);l.push(`OUT: ${m?`${m.col},${m.row}`:"none"} | conveyor ${kt(e,s)?"connected":"missing"}`),l.push(`Fallback sale: ${R(Ie.income.mixer)} when OUT is missing or blocked`),l.push(`Poop fine: -${R(Yn(e))}`)}if(y==="truck"&&(l.push(`Income: ${R(Je(e))} per target cargo | upgrade x${Rt(e,"truck").toFixed(3)}`),l.push(`Poop shipment fine: -${R(Qn(e))}`)),y==="trash"){let m=At(e),P=It(m),F=tn(m);l.push(`Bonus cards: ${m}/${kn()}`),l.push(`Expected poop bonus: ${R(P)} | hit chance ${Math.round(F*100)}%`),l.push("Winning payouts roll 1x / 2x / 4x / 10x, with jackpot odds rising per card."),l.push("With enough upgrades, poop can out-earn male/female routes.")}if(y==="sausageMaker"){let m=wt(e,s),P=He(e,s);l.push("Size: 3\xD73 grid cells | IN left / OUT right"),l.push(`Recipe: 2 meat -> 1 sausage | meat buffer ${s.meatBuffer||0} | sausage buffer ${s.sausageBuffer||0}`),l.push(`Sausage sale value: ${R(lt(e))}`),l.push(`IN: ${m?`${m.col},${m.row}`:"none"} | OUT: ${P?`${P.col},${P.row}`:"none"} | conveyor ${kt(e,s)?"connected":"missing"}`)}s.entry&&l.push(`Receiver: edge cell ${s.entry.col},${s.entry.row} (${s.side})`);let h=En(e,s);h&&br(l,h)}return l}function br(s,l){s.push(`Power: ${l.receivedV.toFixed(1)}V / ${l.minVoltage}V`),s.push(`Source: ${l.sourceName} | Source bus: ${l.sourceBusV.toFixed(1)}V`),s.push(`Route loss: ${l.routeLossV.toFixed(1)}V | Voltage Load: ${l.drawV}V`),l.receivedVy.type===s.type&&y.id===s.id);return l?l.message||"Not connected to conveyor route":""}function xl(s){return s?s.type==="conveyor"&&e.conveyorMeta.get(s.id)?.kind==="boostConveyor"?"Moves chicks twice as fast. Wears down like a normal belt.":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"?"A compact 2\xD72 scanner sized for roughly 2.1 level-1 farms.":s.type==="scanner"?"A manual checkpoint. The operator is the algorithm.":s.type==="facility"&&(s.baseId||s.id)==="mixer"?"Chicks become meat when OUT is clear; a blocked OUT falls back to direct sale.":s.type==="facility"&&(s.baseId||s.id)==="trash"?"Poop goes in. Sometimes coins come out, for reasons best left unaudited.":s.type==="facility"&&(s.baseId||s.id)==="truck"?"The shipping endpoint. Correct cargo and sausages pay; wrong cargo complains.":s.type==="facility"&&(s.baseId||s.id)==="sausageMaker"?"A 3\xD73 in-grid machine with one meat inlet on the left and one sausage outlet on the right.":s.type==="maintenanceRoom"?"A cramped 2\xD72 storage and break room with lockers, spare parts, a bench and one dispatched repairman.":"Factory equipment.":""}return{buildAtCell:po,buildAtPoint:fn,eraseAtPoint:S,equipmentAtPoint:I,startSelectionBox:A.startSelectionBox,updateSelectionBox:A.updateSelectionBox,finishSelectionBox:A.finishSelectionBox,startGroupDrag:A.startGroupDrag,updateGroupDrag:A.updateGroupDrag,finishGroupDrag:A.finishGroupDrag,selectedObject:j,selectedTitle:le,equipmentPrice:ce,selectedUpgradeCost:U,selectedInfoLines:Pi,flavorText:xl,disconnectedWarningFor:gl,gridExpansionCost:K,buyGridExpansion:se,expansionOfferAtPoint:J,beginConveyorDrag:lr,continueConveyorDrag:cr,endConveyorDrag:Qt,cycleSingleConveyorDirection:co,branchSwitcherAtPoint:Ei,cycleBranchModeAtPoint:Cl,cycleBranchModeForKey:hr,branchSelectableExitDirsForKey:Ht,removeSelected:he,switchScannerRole:vl,showManualScannerMenu:Wn,showInboundTruckMenu:Ci,showAutoScannerMenu:_n,showSelectedMenu:xi,setBuildTool:kl,fail:T,isEquipmentCell:xe,equipmentHitBoxes:Ml,routeFromFarmToScanner:en}}function Xa({game:e,ui:t,build:n,startGame:o,beginCardDraft:r,activeQueuedChick:i}){function a(O){return String(O).replace(/[&<>"']/g,H=>({"&":"&","<":"<",">":">",'"':""","'":"'"})[H])}function c(){return Math.round(Vt(e))}function b(){if(e.phase!=="build"||e.cardDraft?.pending||e.cardTargetPick?.pending)return!1;let O=!1;for(let H of Mr)if(O=!0,e.cash>=Le(H,e))return!1;return O}function M(){C(),$(),K(),W()}function C(){for(let[H,oe]of Object.entries(t.buttons))oe?.classList?.contains("tool-button")&&oe.classList.toggle("active",e.buildTool===H);t.buttons.powerOverlay?.classList.toggle("active",!!e.powerOverlay),t.buttons.powerOverlay&&(t.buttons.powerOverlay.disabled=e.phase!=="build"||!!e.cardDraft?.pending||!!e.cardTargetPick?.pending);for(let H of Mr){let oe=t.buttons[H];if(!oe)continue;let ue=Le(H,e),Ve=oe.querySelector("span");Ve&&(Ve.textContent=H==="conveyor"||H==="boostConveyor"?`${R(ue)} / tile`:R(ue));let xe=H==="manualScanner"&&e.scanners.filter(x=>x.kind==="manual").length>=8,Ge=e.cashen(e,oe)?.scannerId).length,H=$o(e);t.turnSummary.innerHTML=[`Seed: ${a(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`,`Penalties: wrong shipment -${R(zr(e))} | Mixer poop -${R(Yn(e))} | Shipment poop -${R(Qn(e))}`,aa(e),`Maintenance: worst ${li(e).worst.percent}% dirty (${li(e).worst.label}) | Rooms: ${(e.maintenanceRooms||[]).length} | Repairmen: ${(e.repairmen||[]).length}`,`Card draft: ${e.cardDraft?.pending?Math.max(1,e.cardDraft.picksRemaining||1)+" pick(s) left":"ready after each day"} | Farms with scanner route: ${O}/${e.eggFarms.length}`,e.cardEffects?.usedMachineActive?'Procurement: USED MACHINE MODE / no refunds / 60% durability':"",H.length?`Blocked: ${H[0]}`:`${pe.status.allPortsConnected}`].filter(Boolean).join("
")}function G(){C(),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=R(e.cash),t.money.classList.toggle("cash-negative",e.cash<0),t.money.classList.toggle("cash-positive",e.cash>Un),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=Q(),t.comboCount&&(t.comboCount.textContent=e.manualCombo?.count||0),z(),t.turnProfit.textContent=R(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 O=e.scanners.find(xe=>xe.kind==="manual"&&xe.slot===0),H=e.scanners.find(xe=>xe.kind==="manual"&&xe.slot===1);O&&(t.buttons.s1Left.textContent=`S1 ${O.keys?.left?.label||"A"} -> Mixer`,t.buttons.s1Right.textContent=`S1 ${O.keys?.right?.label||"D"} -> S2`),H&&(t.buttons.s2Left.textContent=`S2 ${H.keys?.left?.label||"Left"} -> Waste`,t.buttons.s2Right.textContent=`S2 ${H.keys?.right?.label||"Right"} -> Truck`);let oe=Math.max(0,Math.floor(e.lastResult?.profit??e.stats?.profit??0)),ue=Yr(e,Math.max(1,Number(e.turn)||1),oe).amount,Ve=e.phase==="build"&&ue>0?`NEXT DAYFAIRIES -${R(ue)}`:`${pe.actions.nextDay}`;t.buttons.nextTurn.dataset.renderedHtml!==Ve&&(t.buttons.nextTurn.innerHTML=Ve,t.buttons.nextTurn.dataset.renderedHtml=Ve),t.buttons.nextTurn.disabled=e.phase!=="build"||!!e.cardDraft?.pending||!!e.cardTargetPick?.pending}function z(){if(!t.eventBrief||!t.speedBrief)return;t.speedBrief.textContent=`BELT SPEED: ${c()} px/s`,t.speedBrief.title=`Conveyor speed does not increase by day. Degraded belts reduce speed. Cap: ${yn}px/s.`;let O=e.eventActive,H=e.eventOffer;O?(t.eventBrief.textContent=`ACTIVE EVENT: ${O.title} | ${O.route}`,t.eventBrief.className="event-brief active"):e.phase==="build"&&H?(t.eventBrief.textContent=`NEXT DAY EVENT: ${H.title} | ${H.route}${H.targetAmount?` | Target ${H.targetAmount}`:""}${H.reward?` | Reward ${R(H.reward)}`:""}`,t.eventBrief.className="event-brief next"):e.phase==="build"&&e.turn+1
\u2642Male->Mixer -> MeatS1: 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(L(pe.actions.startGame,o,"primary-button")),t.modal.classList.remove("equipment-popover","gameover-modal"),t.modal.classList.add("visible")}function J(O){let H=D=>`${D>0?"+":""}${R(D)}`,oe=D=>`${D>0?"-":""}${R(D)}`,ue=O.profit>=0?"cash-positive":"cash-negative",Ve=(O.mixerPoopFine||0)+(O.truckPoopFine||0)+(O.maleTruckFine||0)+(O.explosionDamage||0)+(O.cardRerollCost||0),xe=O.qualityMultiplierCount?(O.qualityMultiplierSum||0)/O.qualityMultiplierCount:1,Ge=O.qualityRevenue||0,x=Ge>0?"cash-positive":"",g=Object.values(O.maxGeneratorOutput||{}).reduce((D,Z)=>Math.max(D,Number(Z)||0),0);t.modalTitle.textContent=`Day ${O.turn} Settlement`,t.modalBody.innerHTML=` +
Net result${R(O.profit)}
-
Chick shipment income${G(O.chickShipmentIncome||0)}
-
Poop shipment income${G(O.poopShipmentIncome||0)}
-
Sausage shipment income${G(O.sausageRevenue||0)}
+
Chick shipment income${H(O.chickShipmentIncome||0)}
+
Poop shipment income${H(O.poopShipmentIncome||0)}
+
Sausage shipment income${H(O.sausageRevenue||0)}
Meat / sausages${O.meatProduced||0} meat -> ${O.sausageProduced||0} made / ${O.sausageShipped||0} shipped
-
Composter income${G(O.composterIncome||0)}
-
DUD refund income${G(O.dudRefundIncome||0)}
-
Manual combo bonus${G(O.manualComboBonus||0)}
-
Chemical subsidy${G(O.chemicalWeaponSubsidy||0)}
-
Event bonus${G(O.eventBonus||0)}
-
Penalties${Y(De)}
-
Fairies fee${Y(O.fairiesTribute?.amount||O.fairiesTribute||0)}
+
Importscoal ${O.coalReleased||0}/${O.coalOrdered||0}, zunda ${O.zundaReleased||0}/${O.zundaOrdered||0}
+
Import usecoal fuel ${O.coalConsumed||0}, zunda used ${O.zundaConsumed||0}, undelivered ${O.coalUndelivered||0}/${O.zundaUndelivered||0}
+
Import cost${oe(O.importPurchaseCost||0)}
+
Generator fuelbiomass ${O.biomassFuelConsumed||0}, max output ${g.toFixed(0)}V
+
Quality revenue${Ge>0?"+":""}${R(Ge)} avg x${xe.toFixed(2)} / max x${(O.maxQualityMultiplier||1).toFixed(2)}
+
Zunda boost${(O.zundaBoostSeconds||0).toFixed(1)} farm-seconds
+
Power trouble${(O.underVoltageSeconds||0).toFixed(1)} machine-seconds / ${O.generatorAssignmentChanges||0} source changes
+
Repair access${O.unreachableRepairTargets||0} unreachable target checks
+
Composter income${H(O.composterIncome||0)}
+
DUD refund income${H(O.dudRefundIncome||0)}
+
Manual combo bonus${H(O.manualComboBonus||0)}
+
Chemical subsidy${H(O.chemicalWeaponSubsidy||0)}
+
Event bonus${H(O.eventBonus||0)}
+
Penalties${oe(Ve)}
+
Fairies fee${oe(O.fairiesTribute?.amount||O.fairiesTribute||0)}
-

Income ${x(O.revenue)} - losses ${x(O.penalty)} = ${x(O.profit)}

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

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

+

Income ${R(O.revenue)} - losses ${R(O.penalty)} = ${R(O.profit)}

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

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

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

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

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

Your cash went negative.

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

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

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

Your cash went negative.

+
Final Score${oe.score}
Survival${e.totals.turnsCompleted} days
Final Cash${R(e.cash)}
Accuracy${H}%
Score breakdown -
Formula
Base = Cash + Total revenue + Factory value \xD70.5 + Event bonus \xD70.5 = ${x(Y.base)}
Daily earned = ceil(Base \xF7 Days) = ${x(Y.dailyEarned)}
Score = max(0, floor(Base + Daily earned + Correct bonus - Total outflow - Day penalty)) = ${Y.score}
-
Factory Value${x(Y.factoryValue)}
Total Revenue${x(e.totals.revenue)}
Daily Earned${x(Y.dailyEarned)} / day
Total Outflow${x(e.totals.penalty)}
Fairies Tribute${x(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${x(e.totals.explosionDamage)}
Event Bonus${x(e.totals.eventBonus)}
Day Penalty-${Y.dayPenalty}
Events${e.totals.eventSuccess} success / ${e.totals.eventFailed} failed
-
`,t.modalActions.innerHTML="",t.modalActions.appendChild(D(ce.actions.restart||"Restart",o,"primary-button restart-button")),t.modal.classList.remove("equipment-popover"),t.modal.classList.add("visible","gameover-modal")}function D(O,G,Y="primary-button"){let ie=document.createElement("button");return ie.type="button",ie.className=Y,ie.textContent=O,ie.addEventListener("click",G),ie}function U(){t.modal.classList.remove("visible","equipment-popover","gameover-modal"),t.modal.style.removeProperty("--popover-x"),t.modal.style.removeProperty("--popover-y")}function ee(){}return{updatePanels:g,updateUI:j,showTitle:ke,showTurnResult:pe,showEmergencyLoanDecision:le,showGameOver:ue,hideModal:U,checkGameOver:ee}}function Qr(e){e.totals.processed+=1}function hn(e){e.stats.correct+=1,e.totals.correct+=1}function rt(e){e.stats.mistakes+=1,e.totals.mistakes+=1}function ha(e){e.stats.autoSorted+=1,e.totals.autoSorted+=1}function ma(e){e.stats.poopSpawned+=1}function ba(e){e.stats.mixerCount+=1,Qr(e)}function va(e){e.stats.trashCount+=1,Qr(e)}function wa(e,t){t==="female"?e.stats.truckFemale+=1:t==="male"&&(e.stats.truckMale+=1),Qr(e)}function Bo(e,t){let n=t==="truck"?"poopTruck":t==="trash"?"poopTrash":t==="mixer"?"poopMixer":null;n&&(e.stats[n]+=1,e.totals[n]+=1)}function Sa({game:e,currentConveyorSpeed:t,onGameOverCheck:n}){function o(){return e.frameCache||(e.frameCache={frameId:-1,routingVersion:-1,chickRevision:0,spatialRevision:-1,spatial:new Map,congestionFrame:-1,componentChicks:new Map,manualMonitorSignature:"",draw:{}}),e.frameCache}function r(){let d=o();d.chickRevision=(d.chickRevision||0)+1,d.congestionFrame=-1}function i(){o().congestionFrame=-1}function a(d){let p=o();p.frameId!==d&&(p.frameId=d,p.spatialRevision=-1,p.congestionFrame=-1)}function c(d,p){return`${Math.floor(d/f.cell)},${Math.floor(p/f.cell)}`}function h(){let d=o();if(d.spatialRevision===d.chickRevision)return d.spatial;let p=new Map;for(let w of e.chicks){if(w.stage==="flying")continue;let k=c(w.x,w.y);p.has(k)||p.set(k,[]),p.get(k).push(w)}return d.spatial=p,d.spatialRevision=d.chickRevision,p}function g(d){let p=h(),w=Math.floor(d.x/f.cell),k=Math.floor(d.y/f.cell),S=[];for(let A=k-1;A<=k+1;A+=1)for(let E=w-1;E<=w+1;E+=1){let W=p.get(`${E},${A}`);W&&S.push(...W)}return S}function C(d){return Wt(d)*go(d)*Hs(e)/Qi(e)}function P(d){let p=$(d.col,d.row),w=1+Ks(e,d),k=K(d,w);if(!k.length)return L(d,p),!0;let S=0,A=0;for(let E of k){let W=$(E.col,E.row),ae=j(W);if(ae){ae.stoppedTimer=.85,A+=1;continue}let ve=na(e,{stage:"belt",scannerId:null,route:[W]});ve.sex==="poop"&&ma(e),e.chicks.push(ve),r(),Rs(e,d),S+=1}return S>0?(vs(e,p.x,p.y),S>1&&H(e,p.x,p.y-28,`x${S}`,b.green),!0):A===0}function L(d,p){(d.noRouteNoticeCooldown||0)<=0&&(H(e,p.x,p.y-24,"NO BELT: PAUSED",b.warn),d.noRouteNoticeCooldown=2.5)}function K(d,p){let k=fe.map(S=>({col:d.col+S.dc,row:d.row+S.dr})).filter(S=>$e(S.col,S.row)&&e.conveyorTiles.has(I(S.col,S.row))).map(S=>({p:S,blocked:le(null,$(S.col,S.row))?1:0,roll:Math.random()}));return k.sort((S,A)=>S.blocked-A.blocked||S.roll-A.roll),k.slice(0,Math.max(1,p||1)).map(S=>S.p)}function j(d){return g(d).find(p=>Math.hypot(p.x-d.x,p.y-d.y)0;if(e.timeLeft=Math.max(0,e.timeLeft-d),k&&e.timeLeft<=0&&p(),e.timeLeft<=0){e.shutdownTimeLeft>0&&(e.shutdownTimeLeft=Math.max(0,e.shutdownTimeLeft-d));for(let S of e.eggFarms)S.shutterProgress=Math.min(1,(S.shutterProgress||0)+d*2.8)}if(k&&e.timeLeft>0){for(let S of e.eggFarms)if(S.noRouteNoticeCooldown>0&&(S.noRouteNoticeCooldown=Math.max(0,S.noRouteNoticeCooldown-d)),S.nextSpawn-=d,S.nextSpawn<=0){let A=P(S);S.nextSpawn=A?C(S):.15,S.lastInterval=S.nextSpawn}}for(let S=e.chicks.length-1;S>=0;S-=1){let A=e.chicks[S];A.bob+=d*7,A.stoppedTimer>0&&(A.stoppedTimer=Math.max(0,A.stoppedTimer-d)),A.stage!=="queued"&&ke(A,S,d)}Oe(d),Nt(e.frameId),jn(),e.timeLeft<=0&&(e.chicks.length===0?w():e.shutdownTimeLeft<=0&&Q())}function Q(){if(e.cleanupBlown)return;e.cleanupBlown=!0,e.cleanupTimer=1;let d=[...e.chicks];for(let p of d)Tr(e,p.sex,p.x,p.y,-Math.PI/2,!0);e.chicks=[];for(let p of e.scanners)p.queue=[];if(r(),Nt(e.frameId),d.length){let p=d.reduce((k,S)=>k+S.x,0)/d.length,w=d.reduce((k,S)=>k+S.y,0)/d.length;at(e,p,w,b.warn,58),H(e,p,w-24,"CLEANUP",b.warn)}}function ke(d,p,w){if(pe(d)){d.stoppedTimer=.35;return}if(B(d,w),!(d.route&&d.targetIndex=d.route.length)return!1;let p=d.route[d.targetIndex];return le(d,p)}function le(d,p){if(!p)return!1;for(let w of g(p))if(!(d&&w.id===d.id)&&Math.hypot(w.x-p.x,w.y-p.y)1)&&d.push({x:p.x,y:p.y})}function D(d){let p=e.conveyorMeta?.get?.(d)||{},w=[];Array.isArray(p.outDirs)&&w.push(...p.outDirs),p.dir&&w.push(p.dir);let k=[...new Set(w)].filter(ve=>fe.some(en=>en.name===ve)),S=re(d),A=fe.filter(ve=>De(S,ve.name,null)).map(ve=>ve.name),E=k.filter(ve=>A.includes(ve)),W=fe.filter(ve=>e.conveyorTiles.has(I(S.col+ve.dc,S.row+ve.dr))).map(ve=>ve.name);if(W.length===1&&(!E.length||E.includes(W[0]))&&me(S,W[0]))return E.filter(ve=>ve!==W[0]);let ae=A;return E.length?E:ae}function U(d){return fe.find(p=>p.name===d)||null}function ee(d,p){return e.scanners.find(w=>w.col<=d&&d<=w.col+1&&w.row-1<=p&&p<=w.row)||null}function O(d,p){let w=U(p);if(!w)return null;let k=ee(d.col+w.dc,d.row+w.dr);if(!k)return null;let S=Fe(k,"inputA");return S&&S.col===d.col&&S.row===d.row?k:null}function G(d){return Object.entries(e.facilities||{}).find(([,p])=>p?.entry&&p.entry.col===d.col&&p.entry.row===d.row)||null}function Y(d){return d?.baseId||d?.id||""}function ie(d,p){return d?.gridPlaced&&Y(d)==="sausageMaker"?p==="right":d.side==="left"&&p==="left"||d.side==="right"&&p==="right"||d.side==="top"&&p==="up"||d.side==="bottom"&&p==="down"}function De(d,p,w=null){let k=U(p);if(!k)return null;let S={col:d.col+k.dc,row:d.row+k.dr};if($e(S.col,S.row)&&e.conveyorTiles.has(I(S.col,S.row)))return{type:"conveyor",dirName:p,target:$(S.col,S.row),nextCell:S};let A=O(d,p);if(A&&!["meat","sausage"].includes(w?.sex))return{type:"scanner",dirName:p,target:ge(A),scanner:A};let E=G(d);return E&&ie(E[1],p)?{type:"facility",dirName:p,facilityId:E[0],facility:E[1],target:$(d.col,d.row)}:null}function me(d,p){let w=U(p);if(!w)return!1;let k=e.conveyorMeta?.get?.(I(d.col+w.dc,d.row+w.dr));return k?[...new Set([...Array.isArray(k.outDirs)?k.outDirs:[],k.dir].filter(Boolean))].includes(w.opposite):!1}function T(d){return fe.filter(p=>$e(d.col+p.dc,d.row+p.dr)&&e.conveyorTiles.has(I(d.col+p.dc,d.row+p.dr))).map(p=>p.name)}function M(d,p,w="random"){let k=T(d);if(k.length<3)return p;let S=new Set(k.filter(W=>me(d,W))),A=k.filter(W=>!S.has(W)),E=S.size?A:[...new Set(p.filter(W=>k.includes(W)))];return!E.length&&!S.size&&(E=k),w&&w!=="random"&&w!=="pass"&&E.length>=2&&E.includes(w)?[w]:E}function q(d,p){return p&&(p.type==="facility"||!le(d,p.target))}function ne(d,p,w,k="random"){let S=w.map(ae=>De(p,ae,d)).filter(Boolean);if(!S.length)return null;let E=S.filter(ae=>ae.type!=="facility").filter(ae=>!le(d,ae.target)),W=E.length?E:S.filter(ae=>ae.type==="facility");return W.length?W[Math.floor(Math.random()*W.length)]:null}function oe(d,p){let w=[];ue(w,{x:d.x,y:d.y}),ue(w,p),d.route=w.length?w:[{x:d.x,y:d.y}],d.targetIndex=d.route.length>1?1:d.route.length}function se(d,p){let w=tt(d.x,d.y);if(!w)return Zt(p,"OFF GRID");let k=I(w.col,w.row);if(!e.conveyorTiles.has(k))return Zt(p,"OFF BELT");let S=e.conveyorMeta?.get(k)||{},A=M(w,D(k),S.branchMode||"random");if(!A.length){d.stoppedTimer=.25;return}let E=ne(d,w,A,S.branchMode||"random");if(!E){d.stoppedTimer=.25;return}if(E.type==="facility"){let W=Y(E.facility);W==="mixer"?Vn(p,E.facility):W==="truck"?kn(p,E.facility):W==="trash"?Mn(p,E.facility):W==="sausageMaker"?Xn(p,E.facility):Zt(p,"DONE");return}E.type==="scanner"&&(d.pendingScannerId=E.scanner.id),d.prevConveyorCell=k,oe(d,E.target)}function Se(d){let p=tt(d.x,d.y);if(!p)return null;let w=I(p.col,p.row);return e.conveyorTiles.has(w)?w:null}function be(d){let p=Se(d)||Se(d.route?.[d.targetIndex]||d);return t(p)}function Ce(d,p){let w=Se(p);!w||d.lastWearKey===w||(d.lastWearKey=w,Es(e,w))}function B(d,p){let w=be(d)*p;for(;w>0&&d.targetIndex=E?(d.x=k.x,d.y=k.y,Ce(d,k),d.targetIndex+=1,w-=E,i()):(d.x+=S/E*w,d.y+=A/E*w,w=0,i())}}function ye(d,p,w){d.stage="queued",d.route=null,d.queueRoute=w?w.map(k=>({x:k.x,y:k.y})):[ge(p)],d.scannerId=p.id,p.queue.includes(d.id)||p.queue.push(d.id),r(),Be(p)}function Be(d){d.queue=d.queue.filter(w=>e.chicks.some(k=>k.id===w&&k.stage==="queued"));let p=f.cell*.86*qs(e);d.queue.forEach((w,k)=>{let S=e.chicks.find(A=>A.id===w);S&&(de(S,S.queueRoute||[ge(d)],k*p),S.queueIndex=k)})}function de(d,p,w){if(!p||!p.length)return;if(p.length===1||w<=0){let A=p[p.length-1];d.x=A.x,d.y=A.y,i();return}let k=w;for(let A=p.length-1;A>0;A-=1){let E=p[A-1],W=p[A],ae=Math.hypot(W.x-E.x,W.y-E.y);if(k<=ae){let ve=1-k/ae;d.x=E.x+(W.x-E.x)*ve,d.y=E.y+(W.y-E.y)*ve,i();return}k-=ae}let S=p[0];d.x=S.x,d.y=S.y,i()}function Oe(d){for(let p of e.scanners){if(p.cooldown>0&&(p.cooldown=Math.max(0,p.cooldown-d)),p.keyPressTime>0&&(p.keyPressTime=Math.max(0,p.keyPressTime-d)),Be(p),p.kind!=="auto"||p.cooldown>0||!p.queue.length)continue;let w=p.queue[0],k=e.chicks.findIndex(S=>S.id===w);if(k<0){p.queue.shift();continue}Sn(k,dr(p,e.chicks[k]),!0)&&(Ps(e,p),p.cooldown=St(p,e),ha(e))}}function ct(d){let p=ar(e,d);return!p||!p.queue.length?null:e.chicks.find(w=>w.id===p.queue[0])||null}function xt(d,p){if(e.phase!=="running")return;let w=ar(e,d);Le(w,p)}function Le(d,p){if(e.phase!=="running"||!d||d.kind!=="manual"||(d.keyPressTime=.18,d.keyPressSide=p,!d.queue.length))return;let w=e.chicks.findIndex(k=>k.id===d.queue[0]);w>=0&&Sn(w,p,!1)}function qn(d,p,w){return d.kind==="manual"&&dr(d,p)===w}function wn(d){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 w=ge(d);Yt(e,10,.25),H(e,w.x,w.y-36,"COMBO RESET",b.danger)}}function No(d){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 w=Math.max(1,Math.floor(p/10)),k=e.scanners.filter(W=>W.kind==="manual").length,S=Math.min(1,(20+k*10)/100),A=0;for(let W=0;W0){je(e,E),e.stats.manualComboBonus=(e.stats.manualComboBonus||0)+E,e.totals.manualComboBonus=(e.totals.manualComboBonus||0)+E,e.manualCombo.lastBonus=E;let W=ge(d);H(e,W.x,W.y-42,`COMBO +${x(E)}`,b.green)}return E}function Yn(d,p){let w=Fe(d,p);if(!w)return null;let k=I(w.col,w.row);if(e.conveyorTiles.has(k))return{...w};let A=(Je(e).scannerPorts.get(d.id)?.[p]?.keys||[]).filter(Boolean);if(!A.length)return null;let E=A.map(re).sort((ae,ve)=>{let en=$(ae.col,ae.row),ze=$(ve.col,ve.row),_t=le(null,en)?1:0,Tn=le(null,ze)?1:0;return _t-Tn});return E.find(ae=>ae.col===w.col&&ae.row===w.row)||E[0]||null}function Lt(d,p,w,k){let S=[];ue(S,{x:d.x,y:d.y});let A=Fe(p,w);return A&&ue(S,$(A.col,A.row)),ue(S,$(k.col,k.row)),S}function Sn(d,p,w){let k=e.chicks[d];if(!k||k.stage!=="queued")return!1;let S=Ht(e,k.scannerId);if(!S)return!1;let A=Yn(S,p);if(!A)return!w&&S.kind==="manual"&&wn(S),H(e,k.x,k.y-16,p==="left"?"NO LEFT BELT":"NO RIGHT BELT",b.danger),!1;!w&&S.kind==="manual"&&(qn(S,k,p)?No(S):wn(S)),S.queue=S.queue.filter(W=>W!==k.id),k.stage="belt",k.route=Lt(k,S,p,A),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,r();let E=w?b.green:p==="left"?b.mixerBlue:b.truckPink;return ws(e,ge(S).x,ge(S).y,E),H(e,k.x,k.y-20,w?"AUTO":p.toUpperCase(),E),!0}function Tt(d){let p=e.chicks[d];return p?(e.chicks.splice(d,1),r(),p):null}function Vn(d,p=e.facilities.mixer){if(p?.processingCooldown>0){let E=e.chicks[d];E&&(E.stoppedTimer=.25);return}let w=Tt(d);if(!w)return;let{x:k,y:S}=w;if(ba(e),Mo(e,p),xo(e,p),w.sex==="meat"||w.sex==="sausage"){rt(e),H(e,k,S-18,"CHICKS ONLY",b.warn);return}if(w.sex==="poop"){let E=cs(e);Bo(e,"mixer"),rt(e),ks(e,k,S),H(e,k,S-18,`-${x(E)}`,b.danger);return}w.sex==="male"?hn(e):rt(e);let A=Xe(e,p);if(!A||!It(e,p)||!gn(A)){let E=ls(e);H(e,k,S-18,`DIRECT +${x(E)}`,b.green);return}p.meatBuffer=Math.max(0,p.meatBuffer||0)+1,e.stats.meatProduced=(e.stats.meatProduced||0)+1,e.totals.meatProduced=(e.totals.meatProduced||0)+1,Ss(e,k,S),H(e,k,S-18,"MEAT +1",b.meatRed),Cn()}function kn(d,p=e.facilities.truck){let w=Tt(d);if(!w)return;let{x:k,y:S}=w;Go(w.sex,p),Cs(e,k,S,w.sex),wa(e,w.sex);let A=ln(e),E=Zi(e,w.sex);if(w.sex==="sausage"){e.stats.sausageShipped=(e.stats.sausageShipped||0)+1,e.totals.sausageShipped=(e.totals.sausageShipped||0)+1,H(e,k,S-18,`SAUSAGE +${x(Ze(e))}`,b.sausageOrange);return}if(w.sex==="meat"){rt(e),H(e,k,S-18,"RAW MEAT REJECT",b.warn);return}if(w.sex==="poop"){if(Bo(e,"truck"),E)hn(e),H(e,k,S-18,`+${x(Ve(e))}`,b.green);else{let W=us(e);rt(e),H(e,k,S-18,`-${x(W)}`,b.danger)}return}if(w.sex==="female"){E?(hn(e),H(e,k,S-18,`+${x(Ve(e))}`,b.green)):(rt(e),H(e,k,S-18,A==="male"?"REJECT":"NO SALE",b.warn));return}if(E){hn(e),H(e,k,S-18,`+${x(Ve(e))}`,b.green);return}if(es(e)){let W=ds(e);rt(e),H(e,k,S-18,`-${x(W)}`,b.danger)}else rt(e),H(e,k,S-18,"REJECT",b.warn)}function _o(d,p){let w=$(p.col,p.row);return{id:e.nextId++,sex:d,x:w.x,y:w.y,route:[{x:w.x,y:w.y}],targetIndex:1,stage:"belt",scannerId:null,nextScannerId:null,pendingScannerId:null,radius:d==="sausage"?11:10,bob:Math.random()*Math.PI*2,queueIndex:-1,queueRoute:null,stoppedTimer:0}}function gn(d){if(!d||!e.conveyorTiles.has(I(d.col,d.row)))return!1;let p=$(d.col,d.row);return!le(null,p)}function zn(d,p,w){let k=Xe(e,d);if(!k||!It(e,d)||!gn(k)||Math.max(0,d[w]||0)<=0)return!1;d[w]-=1,e.chicks.push(_o(p,k)),r();let S=$(k.col,k.row);return H(e,S.x,S.y-16,p==="meat"?"MEAT OUT":"SAUSAGE OUT",p==="meat"?b.meatRed:b.sausageOrange),!0}function Cn(){for(let d of Object.values(e.facilities||{})){let p=Y(d);p==="mixer"&&zn(d,"meat","meatBuffer"),p==="sausageMaker"&&zn(d,"sausage","sausageBuffer")}}function Xn(d,p){if(p?.processingCooldown>0){let A=e.chicks[d];A&&(A.stoppedTimer=.25);return}let w=Tt(d);if(!w)return;let{x:k,y:S}=w;if(w.sex!=="meat"){rt(e),H(e,k,S-18,"MEAT ONLY",b.warn);return}Mo(e,p),xo(e,p),p.meatBuffer=Math.max(0,p.meatBuffer||0)+1,p.meatBuffer>=2?(p.meatBuffer-=2,p.sausageBuffer=Math.max(0,p.sausageBuffer||0)+1,e.stats.sausageProduced=(e.stats.sausageProduced||0)+1,e.totals.sausageProduced=(e.totals.sausageProduced||0)+1,H(e,k,S-18,"SAUSAGE +1",b.sausageOrange)):H(e,k,S-18,"MEAT 1/2",b.meatRed),Cn()}function Mn(d,p=e.facilities.trash){if(p?.processingCooldown>0){let W=e.chicks[d];W&&(W.stoppedTimer=.25);return}let w=Tt(d);if(!w)return;let{x:k,y:S}=w;gs(e,k,S,w.sex),va(e),Mo(e,p),xo(e,p);let A={amount:0},E="";if(w.sex==="poop"){A=is(e),E=A.amount>0?` ${A.tier==="jackpot"?"JACKPOT ":"+"}${x(A.amount)}`:" MISS",Bo(e,"trash"),hn(e);let W=Math.ceil(Math.max(0,Number(e.cardEffects?.composter)||0)*3*po(e));if(W>0){let ae=je(e,W);e.stats.composterIncome=(e.stats.composterIncome||0)+ae,e.totals.composterIncome=(e.totals.composterIncome||0)+ae,H(e,k,S-36,`COMPOST +${x(ae)}`,b.green)}H(e,k,S-18,`CLEAN${E}`,A.amount>0?b.green:b.green)}else rt(e),H(e,k,S-18,A.amount>0?`WASTE${E}`:"WASTE",A.amount>0?b.green:b.ink)}function Zt(d,p){let w=Tt(d);w&&H(e,w.x,w.y-12,p,b.muted)}function Go(d,p=e.facilities.truck){let w=p||e.facilities.truck;w&&(e.truckCargo.push({sex:d,x:z(22,w.w-22),y:z(66,w.h-24)}),e.truckCargo.length>45&&e.truckCargo.shift())}function Uo(d){let p=tt(d.x,d.y);if(p){let S=I(p.col,p.row);if(e.conveyorTiles.has(S))return S}if(!p)return null;let w=null,k=1/0;for(let S of[p,...fe.map(A=>({col:p.col+A.dc,row:p.row+A.dr}))]){if(!$e(S.col,S.row))continue;let A=I(S.col,S.row);if(!e.conveyorTiles.has(A))continue;let E=$(S.col,S.row),W=Math.hypot(d.x-E.x,d.y-E.y);Ww&&d.ratio=p){let k=xn(d.id);if(!k.length||k.some(E=>E.stage!=="queued"&&E.stoppedTimer<=.2))continue;let S=e.lastExplodedComponent.get(d.id)||0,A=performance.now();A-S>900&&(e.lastExplodedComponent.set(d.id,A),Wo(d))}}}function xn(d){return Nt(e.frameId||0),e.frameCache?.componentChicks?.get(d)||[]}function Wo(d){let p=xn(d.id);if(!p.length)return;for(let S of p)Tr(e,S.sex,S.x,S.y);e.chicks=e.chicks.filter(S=>!p.some(A=>A.id===S.id));for(let S of e.scanners)S.queue=S.queue.filter(A=>e.chicks.some(E=>E.id===A));r();for(let S of d.cells){let A=re(S),E=$(A.col,A.row);at(e,E.x,E.y,b.danger),bt(e,E.x,E.y,6),mo(e,E.x,E.y,4)}Yt(e,18,.75);let w=re(d.cells[0]),k=$(w.col,w.row);e.stats.explosions=(e.stats.explosions||0)+1,e.totals.explosions=(e.totals.explosions||0)+1,H(e,k.x,k.y-22,`${Math.round(wr(e)*100)}% JAM EXPLOSION`,b.danger)}return{activeQueuedChick:ct,beginFrame:a,currentSpawnDelay:C,sortSlot:xt,updateCongestion:Nt,sortScanner:Le,updateRunning:Z}}var he=document.getElementById("gameCanvas"),hu=he.getContext("2d"),F={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"),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"),modal:document.getElementById("modal"),modalTitle:document.getElementById("modalTitle"),modalBody:document.getElementById("modalBody"),modalActions:document.getElementById("modalActions"),hoverTooltip:document.getElementById("hoverTooltip"),manualScannerMonitor:document.getElementById("manualScannerMonitor"),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"),targetSelect:document.getElementById("debugTargetSelect"),eventSelect:document.getElementById("debugEventSelect"),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"),sausageMaker:document.getElementById("buildSausageMakerButton"),maintenanceRoom:document.getElementById("buildMaintenanceRoomButton"),erase:document.getElementById("eraseButton"),undo:document.getElementById("undoButton"),redo:document.getElementById("redoButton")}};function mu(){let e={conveyor:"conveyor",boostConveyor:"boostConveyor",eggFarm:"eggFarm",autoScanner:"autoScanner",manualScanner:"manualScanner",mixer:"mixer",trash:"trash",truck:"truck",sausageMaker:"sausageMaker",maintenanceRoom:"maintenanceRoom"};for(let[t,n]of Object.entries(e))F.buttons[t]&&(F.buttons[t].innerHTML=hi(n))}function bu(e){let t=new Set(e.tags||[]);return["upgradeEgg","crowdedFarming","hatchingFeed","extraEggOutlet"].includes(e.id)||t.has("EGG")?"Production / EGG":["upgradeTrash","poopRateBooster","composter","chemicalWeaponSubsidy"].includes(e.id)||t.has("POOP")?"Poop / Shredder":["upgradeAutoScanner","autoScannerTuning","scannerQueueSpacing"].includes(e.id)||t.has("SCANNER")?"Scanner":["bearing","dynamite"].includes(e.id)||t.has("CONVEYOR")?"Conveyor / Map":["safetyCover","preventiveMaintenance","durabilityCoating","sparePartsBin","laborExploitation"].includes(e.id)||t.has("MAINTENANCE")?"Maintenance / Risk":["dudFilter","dudRefund","freeReroll","extraCards"].includes(e.id)||t.has("CARD")?"Card Engine":t.has("ULTRA RARE")?"Ultra Rare":t.has("ECONOMY")||["upgradeMixer","upgradeTruck","upgradeSausageMaker","recyclingSubsidy","usedMachine","newMachine","flattery","legalWork","rescueLoan"].includes(e.id)?"Economy / Shipment":"Other"}function vu(){if(!F.debug?.panel||!F.debug.cardSelect)return;let e=["Production / EGG","Poop / Shredder","Scanner","Conveyor / Map","Maintenance / Risk","Card Engine","Economy / Shipment","Ultra Rare","Other"],t=new Map(e.map(n=>[n,[]]));for(let n of fn){let o=bu(n);t.has(o)||t.set(o,[]),t.get(o).push(n)}F.debug.cardSelect.innerHTML="";for(let n of e){let o=t.get(n)||[];if(!o.length)continue;let r=document.createElement("optgroup");r.label=n;for(let i of o){let a=document.createElement("option");a.value=i.id;let c=i.rarity==="ultraRare"?"UR":i.rarity==="rare"?"R":"C";a.textContent=`[${c}] ${i.title}`,r.appendChild(a)}F.debug.cardSelect.appendChild(r)}F.debug.cardSelect.addEventListener("change",Jt),wu(),Jt()}function wu(){if(!F.debug?.eventSelect)return;F.debug.eventSelect.innerHTML="";let e=document.createElement("option");e.value="__roll__",e.textContent="Roll normally",F.debug.eventSelect.appendChild(e);let t=document.createElement("option");t.value="__none__",t.textContent="No event",F.debug.eventSelect.appendChild(t);for(let n of Fn){let o=document.createElement("option");o.value=n.key,o.textContent=n.title,F.debug.eventSelect.appendChild(o)}F.debug.eventSelect.value=u.debugNextEventKey||"__roll__",F.debug.eventSelect.addEventListener("change",ku)}function Ma(){let e=u.debugNextEventKey||"__roll__";return e==="__none__"?null:e==="__roll__"?Vi(u):hr(u,e)}function Jt(){if(!F.debug?.targetSelect)return;let e=F.debug.cardSelect?.value,t=Xt(e),n=t?Ys(u,t):[];if(F.debug.targetSelect.innerHTML="",!n.length){let r=document.createElement("option");r.value="__none__",r.textContent=t?"No target required":"No card selected",F.debug.targetSelect.appendChild(r),F.debug.targetSelect.disabled=!0;return}let o=document.createElement("option");o.value="__auto__",o.textContent=t?.id==="dynamite"?"Auto: first 10 blocked cells":"Auto: first valid target",F.debug.targetSelect.appendChild(o);for(let r of n){let i=document.createElement("option");i.value=r.key,i.textContent=r.label,F.debug.targetSelect.appendChild(i)}F.debug.targetSelect.disabled=!1}function Su(){let e=Math.max(1,Math.floor(Number(F.debug.dayInput?.value)||1));u.turn=e,F.debug.dayInput&&(F.debug.dayInput.value=String(e)),H(u,he.width/2-u.view.x,86-u.view.y,`DAY ${e}`,b.green),J.updatePanels(),J.updateUI(),Jt(),Ot(`Day set to ${e}.`)}function ku(){u.debugNextEventKey=F.debug.eventSelect?.value||"__roll__",u.phase==="build"&&(u.eventOffer=Ma(),J.updatePanels(),J.updateUI());let e=F.debug.eventSelect?.selectedOptions?.[0]?.textContent||"Roll normally";Ot(`Next event: ${e}.`)}function gu(){let e=u.phase==="running"&&(u.timeLeft<=0||u.shutdownTimeLeft>0||u.cleanupTimer!==null);u.timeLeft=0,u.cleanupTimer!==null&&(u.cleanupTimer=0),e?u.shutdownTimeLeft=0:u.phase==="running"&&Ea(),H(u,he.width/2-u.view.x,86-u.view.y,"TIMER 0",b.warn),J.updatePanels(),J.updateUI(),Ot("Timer set to 0.")}function Cu(){let e=F.debug.cardSelect?.value,t=F.debug.targetSelect?.disabled?void 0:F.debug.targetSelect?.value,n=Nr(u,e,{targetKey:t});F.debug.grantCard&&(F.debug.grantCard.textContent=n.ok?"Granted":"Failed",window.setTimeout(()=>{F.debug.grantCard&&(F.debug.grantCard.textContent="Grant Selected")},900)),Ae?.updateCongestion?.(u.frameId||0),J.updatePanels(),J.updateUI(),Jt(),Ot(n.reason)}function Mu(){let t=fn.map(n=>Nr(u,n,{targetKey:"__auto__"})).filter(n=>n.ok).length;Ae?.updateCongestion?.(u.frameId||0),J.updatePanels(),J.updateUI(),Jt(),Ot(`Granted ${t}/${fn.length} cards.`)}function xu(){F.debug?.infiniteCash?.checked&&u.cash<999999&&(u.cash=999999,u.totals.maxCash=Math.max(u.totals.maxCash,u.cash))}function Ot(e=""){F.debug?.readout&&(F.debug.readout.textContent=[e,`D${u.turn}`,x(u.cash),`Combo ${u.manualCombo?.count||0}`,`Event ${u.eventActive?.title||u.eventOffer?.title||"none"}`,`Next ${F.debug?.eventSelect?.selectedOptions?.[0]?.textContent||"Roll normally"}`,`Rescue ${u.cardEffects?.rescueLoanCharges||0}`,`Chem ${u.cardEffects?.chemicalWeaponSubsidy||0}`].filter(Boolean).join(" | "))}var u=rr(),te,J,Ae,bn;function Tu(e=null){let t=pn(u,e);return e?t*_n(u,e):t}function Eu(){Object.assign(u,rr()),u.phase="running",u.view={x:0,y:0,scale:1},ir(u),Pa(),Re(u),J.hideModal(),Ae.updateCongestion(u.frameId||0),J.updatePanels(),J.updateUI(),Ot()}function Ru(){if(u.phase!=="build")return;if(u.cardDraft?.pending||u.cardTargetPick?.pending){te.fail(u.cardTargetPick?.pending?"Choose upgrade target first.":"Choose an upgrade card first."),J.updatePanels();return}let e=fo(u);if(e.length){Du(),te.fail(`Cannot start: ${e[0]}`),J.updatePanels();return}Re(u),Ui(u),Fs(u);let t=Math.max(0,Math.floor(u.lastResult?.profit??u.stats?.profit??0)),n=ys(u,Math.max(1,u.turn||1),t);u.lastStartFees={fairiesTribute:n.amount||0,tribute:n},n.amount>0&&H(u,he.width/2-u.view.x,116-u.view.y,`FAIRIES -${x(n.amount)}`,b.danger),zi(u),u.phase="running",u.turn+=1,u.timeLeft=ji(u),u.shutdownTimeLeft=0,u.cleanupTimer=null,u.cleanupBlown=!1,u.chicks=[],u.effects=[],u.floatingTexts=[],u.shake={time:0,strength:0},u.truckCargo=[],Pa(),u.stats=or(),u.stats.fairiesTribute=u.lastStartFees?.fairiesTribute||0,u.stats.fairiesTribute>0&&(u.stats.penalty+=u.stats.fairiesTribute,u.stats.profit-=u.stats.fairiesTribute);for(let o of u.scanners)o.queue=[],o.cooldown=0;for(let o of u.eggFarms)o.nextSpawn=Ae.currentSpawnDelay(o),o.lastInterval=o.nextSpawn,o.shutterProgress=0,o.shutterSparked=!1;u.buildTool=null,u.selected=null,u.groupDrag=null,u.selectionBox=null,u.pan=null,Ae.updateCongestion(u.frameId||0),J.updatePanels(),J.updateUI()}function Pu(){if(u.phase!=="running")return;let e=ps(u),t=u.eventActive?{...u.eventActive}:null,n=os(u),o=hs(u);if(e.adjusted>0){let i=u.facilities.truck,a=i?i.x+i.w/2:he.width/2-u.view.x,c=i?i.y+26:96-u.view.y;H(u,a,c,`${e.target==="poop"?"POOP SHIP":"SHIP"} +${x(e.adjusted)}`,b.green)}n?.bonus>0&&H(u,he.width/2-u.view.x,96-u.view.y,`EVENT +${x(n.bonus)}`,b.green),o.amount>0&&H(u,he.width/2-u.view.x,116-u.view.y,`CHEM SUBSIDY +${x(o.amount)}`,b.green),Xi(u),u.totals.turnsCompleted+=1;let r=ns(t);if(r!==1&&(u.pendingFairiesTributeMultiplier=r),u.lastResult={...u.stats,ship:e,event:n,chemical:o,rescue:{amount:0,used:0,remaining:u.cardEffects?.rescueLoanCharges||0},fairiesTribute:{amount:u.stats.fairiesTribute||0},tributeMultiplier:r,turn:u.turn,cash:u.cash},u.cash<0){if((u.cardEffects?.rescueLoanCharges||0)>0){u.phase="loanDecision",J.showEmergencyLoanDecision(Ta,Oo);return}u.phase="gameover",J.showGameOver();return}xa()}function xa(){u.phase="build",u.truckCargo=[],u.chicks=[],u.shutdownTimeLeft=0,u.cleanupTimer=null,u.cleanupBlown=!1,u.buildSession+=1,u.undoStack=[],u.redoStack=[],u.timeLeft=0;for(let e of u.scanners)e.queue=[];for(let e of u.eggFarms)e.shutterProgress=0,e.shutterSparked=!1;Ds(u),u.eventOffer=Ma(),bn&&bn.prepareDraft(),u.cardTargetPick=null,u.buildTool=null,u.groupDrag=null,u.selectionBox=null,u.pan=null,J.updatePanels(),J.showTurnResult(u.lastResult)}function Ta(){if((u.cardEffects?.rescueLoanCharges||0)<=0)return Oo();u.cardEffects.rescueLoanCharges-=1,u.stats.rescueLoan=(u.stats.rescueLoan||0)+500,u.totals.rescueLoan=(u.totals.rescueLoan||0)+500;let e=je(u,500);u.lastResult={...u.lastResult||{},...u.stats,rescue:{amount:(u.lastResult?.rescue?.amount||0)+e,used:(u.lastResult?.rescue?.used||0)+1,remaining:u.cardEffects.rescueLoanCharges},cash:u.cash},H(u,he.width/2-u.view.x,176-u.view.y,`RESCUE +${x(e)}`,b.green),u.cash>=0?xa():(u.cardEffects?.rescueLoanCharges||0)>0?(u.phase="loanDecision",J.showEmergencyLoanDecision(Ta,Oo)):Oo()}function Oo(){u.phase="gameover",J.showGameOver()}function Ea(){u.shutdownTimeLeft<=0&&(u.shutdownTimeLeft=u.shutdownGraceSeconds||10);for(let e of u.eggFarms){if(e.shutterSparked)continue;e.shutterProgress=.02,e.shutterSparked=!0;let t=$(e.col,e.row);at(u,t.x,t.y,b.ink),bt(u,t.x,t.y+10,10),H(u,t.x,t.y-34,"SHUT +10s",b.ink)}}function Ra(e){u.lastTimestamp||(u.lastTimestamp=e);let t=Math.min((e-u.lastTimestamp)/1e3,.05);u.lastTimestamp=e,u.frameId=(u.frameId||0)+1,Ae.beginFrame?.(u.frameId),u.phase==="running"&&Ae.updateRunning(t,{closeFarmShutters:Ea,completeTurn:Pu}),xs(u,t,he,te.equipmentHitBoxes),Ae.updateCongestion(u.frameId),sa(hu,he,u,{activeQueuedChick:Ae.activeQueuedChick,selectedObject:te.selectedObject,selectedTitle:te.selectedTitle}),xu(),J.updateUI(),Ia(),Ot(),requestAnimationFrame(Ra)}function Kn(){return u.view?.scale||1}function vn(){u.view||(u.view={x:0,y:0,scale:1}),u.view.scale==null&&(u.view.scale=1)}function Lo(e){let t=he.getBoundingClientRect();return{x:(e.clientX-t.left)*he.width/t.width,y:(e.clientY-t.top)*he.height/t.height}}function Bt(e){let t=Lo(e),n=Kn();return{x:(t.x-(u.view?.x||0))/n,y:(t.y-(u.view?.y||0))/n}}function Au(e){let t=Kn();return{x:e.x*t+(u.view?.x||0),y:e.y*t+(u.view?.y||0)}}function Iu(){let e={left:f.x-90,top:f.y-130,right:f.x+f.cols*f.cell+260,bottom:f.y+f.rows*f.cell+220};if(u.phase==="build")for(let t of Ut(u)){let n=dt(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 ka(e,t){if(e<=t)return[e,t];let n=(e+t)/2;return[n,n]}function Ct(){vn();let e=Kn(),t=Iu(),[n,o]=ka(he.width-160-t.right*e,110-t.left*e),[r,i]=ka(he.height-150-t.bottom*e,150-t.top*e);u.view.x=An(u.view.x,n,o),u.view.y=An(u.view.y,r,i)}function Pa(){vn(),u.view.scale=1;let e=u.eggFarms?.[0],t=e?$(e.col,e.row):{x:f.x,y:f.y};u.view.x=280-t.x,u.view.y=190-t.y,Ct()}function $u(e){if(!e)return!1;vn();let t=Kn();return u.view.x=he.width/2-e.x*t,u.view.y=he.height/2-e.y*t,Ct(),!0}function Fu(e){let t=e?.ref;return t?e.type==="facility"?{x:t.x+t.w/2,y:t.y+t.h/2}:e.type==="eggFarm"?$(t.col,t.row):e.type==="scanner"?{x:$(t.col,t.row).x+f.cell/2,y:$(t.col,t.row).y}:e.type==="maintenanceRoom"?{x:$(t.col,t.row).x+f.cell/2,y:$(t.col,t.row).y+f.cell/2}:null:null}function Du(){let e=an(u),t=e.find(o=>o.type==="facility")||e[0]||Bu(),n=Fu(t);$u(n)&&t&&(u.selected={type:t.type,id:t.id})}function Bu(){let e=Object.values(u.facilities||{}).find(o=>o?.entry&&u.conveyorTiles.has(I(o.entry.col,o.entry.row)))||Object.values(u.facilities||{})[0];if(e)return{type:"facility",id:e.id,ref:e};let t=u.scanners?.find(o=>o);if(t)return{type:"scanner",id:t.id,ref:t};let n=u.eggFarms?.[0];return n?{type:"eggFarm",id:n.id,ref:n}:null}function Ou(e){let t=Lo(e);vn(),u.pan={start:t,viewX:u.view.x,viewY:u.view.y,moved:!1}}function Lu(e){if(!u.pan)return;let t=Lo(e);Math.hypot(t.x-u.pan.start.x,t.y-u.pan.start.y)>5&&(u.pan.moved=!0),vn(),u.view.x=u.pan.viewX+t.x-u.pan.start.x,u.view.y=u.pan.viewY+t.y-u.pan.start.y,Ct()}function Nu(e){e.preventDefault(),vn();let t=Lo(e),n=Bt(e),o=Math.exp(-e.deltaY*.0012),r=An(Kn()*o,.55,1.8);u.view.scale=r,u.view.x=t.x-n.x*r,u.view.y=t.y-n.y*r,Ct(),Mt()}function _u(e){return e?e.type==="conveyor"?{type:"conveyor",id:e.oldKey}:{type:e.type,id:e.ref.id}:null}function Gu(e){return e?e.type==="conveyor"?{type:"conveyor",id:e.oldKey}:e.ref||null:null}function Mt(){F.hoverTooltip&&(F.hoverTooltip.classList.remove("visible"),F.hoverTooltip.setAttribute("aria-hidden","true"),u.hover=null)}function Uu(e,t){if(!F.hoverTooltip||u.phase!=="build"||u.cardTargetPick?.pending||u.groupDrag||u.selectionBox||u.pan)return Mt();let n=Gu(t);if(!n)return Mt();let o=te.selectedTitle(n),r=te.flavorText?te.flavorText(n):"",i=te.disconnectedWarningFor?te.disconnectedWarningFor(n):"",a=te.selectedInfoLines(n).slice(0,7);F.hoverTooltip.innerHTML=`${o}${i?`${i}`:r?`${r}`:""}${a.map(g=>`${g}`).join("")}`;let c=Math.min(window.innerWidth-330,e.clientX+18),h=Math.min(window.innerHeight-220,e.clientY+18);F.hoverTooltip.style.left=`${Math.max(8,c)}px`,F.hoverTooltip.style.top=`${Math.max(8,h)}px`,F.hoverTooltip.classList.add("visible"),F.hoverTooltip.setAttribute("aria-hidden","false"),u.hover={type:t.type,id:t.oldKey||t.ref?.id||null}}function Wu(e){if(u.phase!=="build")return Mt();Uu(e,te.equipmentAtPoint(Bt(e)))}function Aa(e){Mt();let t=Bt(e),n=te.equipmentAtPoint(t),o=_u(n);if(!o){u.selected=null,u.multiSelected=[],J.updatePanels();return}u.selected=o,u.multiSelected=[o],J.updatePanels();let r=te.selectedObject();r?.type==="scanner"&&r.kind==="manual"&&te.showManualScannerMenu(r),r?.type==="scanner"&&r.kind==="auto"&&te.showAutoScannerMenu(r)}function mn(){u.buildTool=null,u.selected=null,u.multiSelected=[],u.selectionBox=null,u.cardTargetPick?.mode==="autoScannerMenu"&&(u.cardTargetPick=null),J.hideModal(),Mt(),J.updatePanels(),J.updateUI()}function ga(e){return String(e).replace(/[&<>"']/g,t=>({"&":"&","<":"<",">":">",'"':""","'":"'"})[t])}function Hu(e){let t=e?.queue?.[0];return t==null?null:u.chicks.find(n=>n.id===t)||null}function Ku(e){if(!e)return'WAIT';let t=e.sex==="poop"?"POOP":e.sex==="male"?"M MALE":"F FEMALE";return`${t}`}function qu(e,t){let n=new Set(u.eggFarms.map(C=>I(C.col,C.row))),o=new Map(u.scanners.map(C=>[I(C.col,C.row),C])),r=Fe(e,"inputA"),i=r?I(r.col,r.row):null,a=new Set(Object.values(u.facilities||{}).map(C=>C.entry?I(C.entry.col,C.entry.row):null).filter(Boolean)),c=t?.sex||"",h=c==="poop"?"P":c==="male"?"M":c==="female"?"F":"",g=[];for(let C=e.row-2;C<=e.row+2;C+=1)for(let P=e.col-2;P<=e.col+2;P+=1){let L=I(P,C),K=["mini-cell"],j="";P===e.col&&C===e.row?(K.push("scanner"),c&&K.push(c),j=h||"S"):L===i?(K.push("input"),j="IN"):n.has(L)?(K.push("egg"),j="E"):o.has(L)?(K.push("scanner-other"),j="S"):a.has(L)?(K.push("port"),j="P"):u.conveyorTiles.has(L)?(K.push("belt"),j="."):u.blockedCells?.has?.(L)&&(K.push("blocked"),j="X"),g.push(`${j}`)}return`
${g.join("")}
`}function Yu(e){let t=Au($(e.col,e.row)),n=76;return t.x<-n||t.y<-n||t.x>he.width+n||t.y>he.height+n}function Ia(){if(!F.manualScannerMonitor)return;let e=u.frameCache||(u.frameCache={}),t=u.view||{},n=[u.phase,Math.round((t.x||0)*10),Math.round((t.y||0)*10),Math.round((t.scale||1)*100),u.chicks.length,u.scanners.map(r=>[r.id,r.kind,r.col,r.row,r.queue?.join(",")||"",r.keyPressSide||"",Math.ceil((r.keyPressTime||0)*10)].join(":")).join("|")].join(";");if(e.manualMonitorSignature===n)return;if(e.manualMonitorSignature=n,u.phase!=="running"){F.manualScannerMonitor.classList.remove("visible"),F.manualScannerMonitor.innerHTML="";return}let o=u.scanners.filter(r=>r.kind==="manual"&&Yu(r));if(!o.length){F.manualScannerMonitor.classList.remove("visible"),F.manualScannerMonitor.innerHTML="";return}F.manualScannerMonitor.innerHTML=o.map(r=>{let i=Hu(r),a=r.keys?.left?.label||(r.slot===0?"A":"Left"),c=r.keys?.right?.label||(r.slot===0?"D":"Right"),h=r.role===0?"MIXER":"WASTE",g=r.role===0?"S2":"TRUCK",C=i?"":" disabled";return` +
Formula
Base = Cash + Total revenue + Factory value \xD70.5 + Event bonus \xD70.5 = ${R(oe.base)}
Daily earned = ceil(Base \xF7 Days) = ${R(oe.dailyEarned)}
Score = max(0, floor(Base + Daily earned + Correct bonus - Total outflow - Day penalty)) = ${oe.score}
+
Factory Value${R(oe.factoryValue)}
Total Revenue${R(e.totals.revenue)}
Daily Earned${R(oe.dailyEarned)} / day
Total Outflow${R(e.totals.penalty)}
Fairies Tribute${R(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${R(e.totals.explosionDamage)}
Event Bonus${R(e.totals.eventBonus)}
Day Penalty-${oe.dayPenalty}
Events${e.totals.eventSuccess} success / ${e.totals.eventFailed} failed
+ `,t.modalActions.innerHTML="",t.modalActions.appendChild(L(pe.actions.restart||"Restart",o,"primary-button restart-button")),t.modal.classList.remove("equipment-popover"),t.modal.classList.add("visible","gameover-modal")}function L(O,H,oe="primary-button"){let ue=document.createElement("button");return ue.type="button",ue.className=oe,ue.textContent=O,ue.addEventListener("click",H),ue}function V(){t.modal.classList.remove("visible","equipment-popover","gameover-modal"),t.modal.style.removeProperty("--popover-x"),t.modal.style.removeProperty("--popover-y")}function se(){}return{updatePanels:M,updateUI:G,showTitle:ie,showTurnResult:J,showEmergencyLoanDecision:ae,showGameOver:ye,hideModal:V,checkGameOver:se}}function ki(e){e.totals.processed+=1}function Fn(e){e.stats.correct+=1,e.totals.correct+=1}function vt(e){e.stats.mistakes+=1,e.totals.mistakes+=1}function ja(e){e.stats.autoSorted+=1,e.totals.autoSorted+=1}function Ja(e){e.stats.poopSpawned+=1}function Za(e){e.stats.mixerCount+=1,ki(e)}function el(e){e.stats.trashCount+=1,ki(e)}function tl(e,t){t==="female"?e.stats.truckFemale+=1:t==="male"&&(e.stats.truckMale+=1),ki(e)}function nr(e,t){let n=t==="truck"?"poopTruck":t==="trash"?"poopTrash":t==="mixer"?"poopMixer":null;n&&(e.stats[n]+=1,e.totals[n]+=1)}function wd(e){let t=2166136261;for(let n=0;n>>0)%1e5/1e5}function Mi(e={}){let t=_.imports.combinedLimitPerTruck,n=Math.max(0,Math.floor(Number(e.coalPerDay)||0)),o=Math.max(0,Math.floor(Number(e.zundaMochiPerDay)||0));if(n+o>t){let r=t/Math.max(1,n+o);n=Math.floor(n*r),o=Math.max(0,t-n)}return{coalPerDay:n,zundaMochiPerDay:o}}function md(e={}){let t=Mi(e);return t.coalPerDay*_.imports.prices.coal+t.zundaMochiPerDay*_.imports.prices.zundaMochi}function vd(e,t="imports"){let n=Mi(e),o=[...Array.from({length:n.coalPerDay},()=>({type:"coal"})),...Array.from({length:n.zundaMochiPerDay},()=>({type:"zundaMochi"}))],r=Math.max(1,o.length);return o.map((i,a)=>{let c=yt/r,b=(a+.5)/r*yt,M=(wd(`${t}:${i.type}:${a}`)*2-1)*_.imports.jitterRatio*c;return{type:i.type,due:Math.max(0,Math.min(yt,b+M)),released:!1}}).sort((i,a)=>i.due-a.due||i.type.localeCompare(a.type))}function nl(e){e.imports={schedules:new Map};for(let t of Object.values(e.facilities||{})){if((t.baseId||t.id)!=="inboundTruck")continue;t.inboundOrder=Mi(t.inboundOrder),t.inboundQueue=[],t.importClock=0;let n=md(t.inboundOrder);n>0&&(Mt(e,n),e.stats.importPurchaseCost=(e.stats.importPurchaseCost||0)+n,e.stats.coalOrdered=(e.stats.coalOrdered||0)+t.inboundOrder.coalPerDay,e.stats.zundaOrdered=(e.stats.zundaOrdered||0)+t.inboundOrder.zundaMochiPerDay,e.totals.importPurchaseCost=(e.totals.importPurchaseCost||0)+n,e.totals.coalOrdered=(e.totals.coalOrdered||0)+t.inboundOrder.coalPerDay,e.totals.zundaOrdered=(e.totals.zundaOrdered||0)+t.inboundOrder.zundaMochiPerDay),e.imports.schedules.set(t.id,vd(t.inboundOrder,`${e.runSeed}:${e.turn}:${t.id}`))}}function Sd(e,t){if(!t||!e.conveyorTiles.has(E(t.col,t.row)))return!1;let n=B(t.col,t.row);return!e.chicks.some(o=>o.stage!=="flying"&&Math.hypot(o.x-n.x,o.y-n.y)<25)}function kd(e,t,n){let o=B(n.col,n.row);return{id:e.nextId++,sex:t,x:o.x,y:o.y,route:[{x:o.x,y:o.y}],targetIndex:1,stage:"belt",scannerId:null,nextScannerId:null,pendingScannerId:null,radius:t==="coal"?10:11,bob:0,queueIndex:-1,queueRoute:null,stoppedTimer:0}}function ol(e,t,n=()=>{}){if(e.phase==="running")for(let o of Object.values(e.facilities||{})){if((o.baseId||o.id)!=="inboundTruck"||!nt(e,o))continue;o.importClock=(o.importClock||0)+t;let r=e.imports?.schedules?.get(o.id)||[];for(let b of r)!b.released&&b.due<=o.importClock&&(o.inboundQueue=o.inboundQueue||[],o.inboundQueue.push(b),b.released=!0);let i=He(e,o);if(!o.inboundQueue?.length||!kt(e,o)||!Sd(e,i))continue;let a=o.inboundQueue.shift(),c=a.type;a.delivered=!0,e.chicks.push(kd(e,c,i)),c==="coal"&&(e.stats.coalReleased=(e.stats.coalReleased||0)+1,e.totals.coalReleased=(e.totals.coalReleased||0)+1),c==="zundaMochi"&&(e.stats.zundaReleased=(e.stats.zundaReleased||0)+1,e.totals.zundaReleased=(e.totals.zundaReleased||0)+1),rn(e,o),n()}}function rl(e){for(let t of Object.values(e.facilities||{})){if((t.baseId||t.id)!=="inboundTruck")continue;let n=e.imports?.schedules?.get(t.id)||[];for(let o of n)o.delivered||(o.type==="coal"&&(e.stats.coalUndelivered=(e.stats.coalUndelivered||0)+1,e.totals.coalUndelivered=(e.totals.coalUndelivered||0)+1),o.type==="zundaMochi"&&(e.stats.zundaUndelivered=(e.stats.zundaUndelivered||0)+1,e.totals.zundaUndelivered=(e.totals.zundaUndelivered||0)+1));t.inboundQueue=[]}}function il({game:e,currentConveyorSpeed:t,onGameOverCheck:n}){function o(){return e.frameCache||(e.frameCache={frameId:-1,routingVersion:-1,chickRevision:0,spatialRevision:-1,spatial:new Map,congestionFrame:-1,componentChicks:new Map,manualMonitorSignature:"",draw:{}}),e.frameCache}function r(){let u=o();u.chickRevision=(u.chickRevision||0)+1,u.congestionFrame=-1}function i(){o().congestionFrame=-1}function a(u){let f=o();f.frameId!==u&&(f.frameId=u,f.spatialRevision=-1,f.congestionFrame=-1)}function c(u,f){return`${Math.floor(u/p.cell)},${Math.floor(f/p.cell)}`}function b(){let u=o();if(u.spatialRevision===u.chickRevision)return u.spatial;let f=new Map;for(let v of e.chicks){if(v.stage==="flying")continue;let k=c(v.x,v.y);f.has(k)||f.set(k,[]),f.get(k).push(v)}return u.spatial=f,u.spatialRevision=u.chickRevision,f}function M(u){let f=b(),v=Math.floor(u.x/p.cell),k=Math.floor(u.y/p.cell),S=[];for(let I=k-1;I<=k+1;I+=1)for(let A=v-1;A<=v+1;A+=1){let U=f.get(`${A},${I}`);U&&S.push(...U)}return S}function C(u){let f=Jt(u)*Yo(u)*Ra(e)/ks(e),v=io(e,u,f);return u.theoreticalRate=v,u.actualOutputRate=Math.min(v,Kt(e)),u.qualityMultiplier=so(v,Kt(e)),Math.max(.25,za(v,Kt(e)))}function $(u){let f=B(u.col,u.row),v=1+Ia(e,u),k=K(u,v).slice(0,1);if(!k.length)return W(u,f),!0;let S=0,I=0;for(let A of k){let U=B(A.col,A.row),he=G(U);if(he){he.stoppedTimer=.85,I+=1;continue}let de=ta(e,{stage:"belt",scannerId:null,route:[U]}),Xe=u.theoreticalRate||io(e,u,u.lastInterval||C(u));de.sourceQuality=so(Xe,Kt(e)),e.stats.farmTheoreticalRate=Math.max(e.stats.farmTheoreticalRate||0,Xe),e.stats.farmActualOutputRate=Math.max(e.stats.farmActualOutputRate||0,Math.min(Xe,Kt(e))),e.stats.qualityMultiplierSum=(e.stats.qualityMultiplierSum||0)+de.sourceQuality,e.stats.qualityMultiplierCount=(e.stats.qualityMultiplierCount||0)+1,e.stats.maxQualityMultiplier=Math.max(e.stats.maxQualityMultiplier||1,de.sourceQuality),de.sex==="poop"&&Ja(e),e.chicks.push(de),r(),da(e,u),S+=1}return S>0?(Ws(e,f.x,f.y),S>1&&Y(e,f.x,f.y-28,`x${S}`,w.green),!0):I===0}function W(u,f){(u.noRouteNoticeCooldown||0)<=0&&(Y(e,f.x,f.y-24,"NO BELT: PAUSED",w.warn),u.noRouteNoticeCooldown=2.5)}function K(u,f){let k=fe.map(S=>({col:u.col+S.dc,row:u.row+S.dr})).filter(S=>Re(S.col,S.row)&&e.conveyorTiles.has(E(S.col,S.row))).map(S=>({p:S,blocked:ae(null,B(S.col,S.row))?1:0,roll:Math.random()}));return k.sort((S,I)=>S.blocked-I.blocked||S.roll-I.roll),k.slice(0,Math.max(1,f||1)).map(S=>S.p)}function G(u){return M(u).find(f=>Math.hypot(f.x-u.x,f.y-u.y)0;if(e.timeLeft=Math.max(0,e.timeLeft-u),k&&e.timeLeft<=0&&f(),e.timeLeft<=0){e.shutdownTimeLeft>0&&(e.shutdownTimeLeft=Math.max(0,e.shutdownTimeLeft-u));for(let S of e.eggFarms)S.shutterProgress=Math.min(1,(S.shutterProgress||0)+u*2.8)}if(k&&e.timeLeft>0){for(let S of e.eggFarms)if(S.noRouteNoticeCooldown>0&&(S.noRouteNoticeCooldown=Math.max(0,S.noRouteNoticeCooldown-u)),S.zundaBoostTime>0&&(S.zundaBoostTime=Math.max(0,S.zundaBoostTime-u)),S.nextSpawn-=u,S.nextSpawn<=0){let I=$(S);S.nextSpawn=I?C(S):.15,S.lastInterval=S.nextSpawn}}for(let S=e.chicks.length-1;S>=0;S-=1){let I=e.chicks[S];I.bob+=u*7,I.stoppedTimer>0&&(I.stoppedTimer=Math.max(0,I.stoppedTimer-u)),I.stage!=="queued"&&(ce(I,S)||ie(I,S,u))}ot(u),fn(e.frameId),dr(),e.timeLeft<=0&&(e.chicks.length===0?v():e.shutdownTimeLeft<=0&&Q())}function Q(){if(e.cleanupBlown)return;e.cleanupBlown=!0,e.cleanupTimer=1;let u=[...e.chicks];for(let f of u)Jr(e,f.sex,f.x,f.y,-Math.PI/2,!0);e.chicks=[];for(let f of e.scanners)f.queue=[];if(r(),fn(e.frameId),u.length){let f=u.reduce((k,S)=>k+S.x,0)/u.length,v=u.reduce((k,S)=>k+S.y,0)/u.length;gt(e,f,v,w.warn,58),Y(e,f,v-24,"CLEANUP",w.warn)}}function ie(u,f,v){if(J(u)){u.stoppedTimer=.35;return}if(Ke(u,v),!(u.route&&u.targetIndex=u.route.length)return!1;let f=u.route[u.targetIndex];return ae(u,f)}function ae(u,f){if(!f)return!1;for(let v of M(f))if(!(u&&v.id===u.id)&&Math.hypot(v.x-f.x,v.y-f.y)1)&&u.push({x:f.x,y:f.y})}function L(u){let f=e.conveyorMeta?.get?.(u)||{},v=[];Array.isArray(f.outDirs)&&v.push(...f.outDirs),f.dir&&v.push(f.dir);let k=[...new Set(v)].filter(de=>fe.some(Xe=>Xe.name===de)),S=X(u),I=fe.filter(de=>xe(S,de.name,null)).map(de=>de.name),A=k.filter(de=>I.includes(de)),U=fe.filter(de=>e.conveyorTiles.has(E(S.col+de.dc,S.row+de.dr))).map(de=>de.name);if(U.length===1&&(!A.length||A.includes(U[0]))&&Ge(S,U[0]))return A.filter(de=>de!==U[0]);let he=I;return A.length?A:he}function V(u){return fe.find(f=>f.name===u)||null}function se(u,f){return e.scanners.find(v=>v.col<=u&&u<=v.col+1&&v.row-1<=f&&f<=v.row)||null}function O(u,f){let v=V(f);if(!v)return null;let k=se(u.col+v.dc,u.row+v.dr);if(!k)return null;let S=We(k,"inputA");return S&&S.col===u.col&&S.row===u.row?k:null}function H(u){return Object.entries(e.facilities||{}).find(([,f])=>f?.entry&&f.entry.col===u.col&&f.entry.row===u.row)||null}function oe(u,f){for(let v of Object.values(e.facilities||{})){let k=ue(v);if(!v.gridPlaced||!["biomassGenerator","coalGenerator"].includes(k))continue;if((k==="biomassGenerator"?[{col:v.col-1,row:v.row+1,dir:"right"},{col:v.col+1,row:v.row-1,dir:"down"},{col:v.col+1,row:v.row+(v.hCells||3),dir:"up"}]:[{col:v.col-1,row:v.row+1,dir:"right"}]).some(I=>I.col===u.col&&I.row===u.row&&I.dir===f))return v}return null}function ue(u){return u?.baseId||u?.id||""}function Ve(u,f){return u?.gridPlaced&&ue(u)==="sausageMaker"||u?.gridPlaced&&["biomassGenerator","coalGenerator"].includes(ue(u))?f==="right":u.side==="left"&&f==="left"||u.side==="right"&&f==="right"||u.side==="top"&&f==="up"||u.side==="bottom"&&f==="down"}function xe(u,f,v=null){let k=V(f);if(!k)return null;let S={col:u.col+k.dc,row:u.row+k.dr};if(Re(S.col,S.row)&&e.conveyorTiles.has(E(S.col,S.row)))return{type:"conveyor",dirName:f,target:B(S.col,S.row),nextCell:S};let I=O(u,f);if(I&&!["meat","sausage","coal","zundaMochi"].includes(v?.sex))return{type:"scanner",dirName:f,target:ge(I),scanner:I};let A=oe(u,f);if(A)return{type:"facility",dirName:f,facilityId:A.id,facility:A,target:B(u.col,u.row)};let U=H(u);return U&&Ve(U[1],f)?{type:"facility",dirName:f,facilityId:U[0],facility:U[1],target:B(u.col,u.row)}:null}function Ge(u,f){let v=V(f);if(!v)return!1;let k=e.conveyorMeta?.get?.(E(u.col+v.dc,u.row+v.dr));return k?[...new Set([...Array.isArray(k.outDirs)?k.outDirs:[],k.dir].filter(Boolean))].includes(v.opposite):!1}function x(u){return fe.filter(f=>Re(u.col+f.dc,u.row+f.dr)&&e.conveyorTiles.has(E(u.col+f.dc,u.row+f.dr))).map(f=>f.name)}function g(u,f,v="random"){let k=x(u);if(k.length<3)return f;let S=new Set(k.filter(U=>Ge(u,U))),I=k.filter(U=>!S.has(U)),A=S.size?I:[...new Set(f.filter(U=>k.includes(U)))];return!A.length&&!S.size&&(A=k),v&&v!=="random"&&v!=="pass"&&A.length>=2&&A.includes(v)?[v]:A}function D(u,f){return f&&(f.type==="facility"||!ae(u,f.target))}function Z(u,f,v,k="random"){let S=v.map(he=>xe(f,he,u)).filter(Boolean);if(!S.length)return null;let A=S.filter(he=>he.type!=="facility").filter(he=>!ae(u,he.target)),U=A.length?A:S.filter(he=>he.type==="facility");return U.length?U[Math.floor(Math.random()*U.length)]:null}function j(u,f){let v=[];ye(v,{x:u.x,y:u.y}),ye(v,f),u.route=v.length?v:[{x:u.x,y:u.y}],u.targetIndex=u.route.length>1?1:u.route.length}function le(u,f){let v=rt(u.x,u.y);if(!v)return Qt(f,"OFF GRID");let k=E(v.col,v.row);if(!e.conveyorTiles.has(k))return Qt(f,"OFF BELT");let S=e.conveyorMeta?.get(k)||{},I=g(v,L(k),S.branchMode||"random");if(!I.length){u.stoppedTimer=.25;return}let A=Z(u,v,I,S.branchMode||"random");if(!A){u.stoppedTimer=.25;return}if(A.type==="facility"){let U=ue(A.facility);if(U==="mixer")ir(f,A.facility);else if(U==="truck")uo(f,A.facility);else if(U==="trash")lr(f,A.facility);else if(U==="sausageMaker")ar(f,A.facility);else if(U==="biomassGenerator"||U==="coalGenerator")cr(f,A.facility);else if(U==="inboundTruck"){let he=e.chicks[f];he&&(he.stoppedTimer=.25)}else Qt(f,"DONE");return}A.type==="scanner"&&(u.pendingScannerId=A.scanner.id),u.prevConveyorCell=k,j(u,A.target)}function ce(u,f){if(u.sex!=="zundaMochi")return!1;let v=rt(u.x,u.y);if(!v)return!1;let k=e.eggFarms.find(I=>Math.abs(I.col-v.col)+Math.abs(I.row-v.row)===1);if(!k)return!1;Ya(e,k),pt(f);let S=B(k.col,k.row);return Y(e,S.x,S.y-24,"ZUNDA BOOST",w.green),!0}function ve(u){let f=rt(u.x,u.y);if(!f)return null;let v=E(f.col,f.row);return e.conveyorTiles.has(v)?v:null}function Me(u){let f=ve(u)||ve(u.route?.[u.targetIndex]||u);return t(f)}function Se(u,f){let v=ve(f);!v||u.lastWearKey===v||(u.lastWearKey=v,ua(e,v))}function Ke(u,f){let v=Me(u)*f;for(;v>0&&u.targetIndex=A?(u.x=k.x,u.y=k.y,Se(u,k),u.targetIndex+=1,v-=A,i()):(u.x+=S/A*v,u.y+=I/A*v,v=0,i())}}function T(u,f,v){u.stage="queued",u.route=null,u.queueRoute=v?v.map(k=>({x:k.x,y:k.y})):[ge(f)],u.scannerId=f.id,f.queue.includes(u.id)||f.queue.push(u.id),r(),ke(f)}function ke(u){u.queue=u.queue.filter(v=>e.chicks.some(k=>k.id===v&&k.stage==="queued"));let f=p.cell*.86*Aa(e);u.queue.forEach((v,k)=>{let S=e.chicks.find(I=>I.id===v);S&&(Ae(S,S.queueRoute||[ge(u)],k*f),S.queueIndex=k)})}function Ae(u,f,v){if(!f||!f.length)return;if(f.length===1||v<=0){let I=f[f.length-1];u.x=I.x,u.y=I.y,i();return}let k=v;for(let I=f.length-1;I>0;I-=1){let A=f[I-1],U=f[I],he=Math.hypot(U.x-A.x,U.y-A.y);if(k<=he){let de=1-k/he;u.x=A.x+(U.x-A.x)*de,u.y=A.y+(U.y-A.y)*de,i();return}k-=he}let S=f[0];u.x=S.x,u.y=S.y,i()}function ot(u){for(let f of e.scanners){let v=f.kind!=="auto"||nt(e,f);if(f.keyPressTime>0&&(f.keyPressTime=Math.max(0,f.keyPressTime-u)),ke(f),!v||(f.cooldown>0&&(f.cooldown=Math.max(0,f.cooldown-u)),f.kind!=="auto"||f.cooldown>0||!f.queue.length))continue;let k=f.queue[0],S=e.chicks.findIndex(I=>I.id===k);if(S<0){f.queue.shift();continue}Nn(S,Dr(f,e.chicks[S]),!0)&&(fa(e,f),f.cooldown=Bt(f,e),ja(e))}}function Ce(u){let f=$r(e,u);return!f||!f.queue.length?null:e.chicks.find(v=>v.id===f.queue[0])||null}function Ue(u,f){if(e.phase!=="running")return;let v=$r(e,u);St(v,f)}function St(u,f){if(e.phase!=="running"||!u||u.kind!=="manual"||(u.keyPressTime=.18,u.keyPressSide=f,!u.queue.length))return;let v=e.chicks.findIndex(k=>k.id===u.queue[0]);v>=0&&Nn(v,f,!1)}function et(u,f,v){return u.kind==="manual"&&Dr(u,f)===v}function Ln(u){let f=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,f>=10){let v=ge(u);nn(e,10,.25),Y(e,v.x,v.y-36,"COMBO RESET",w.danger)}}function Ht(u){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 f=e.manualCombo.count;if(e.manualCombo.lastBonus=0,f<10)return 0;let v=Math.max(1,Math.floor(f/10)),k=e.scanners.filter(U=>U.kind==="manual").length,S=Math.min(1,(20+k*10)/100),I=0;for(let U=0;U0){tt(e,A),e.stats.manualComboBonus=(e.stats.manualComboBonus||0)+A,e.totals.manualComboBonus=(e.totals.manualComboBonus||0)+A,e.manualCombo.lastBonus=A;let U=ge(u);Y(e,U.x,U.y-42,`COMBO +${R(A)}`,w.green)}return A}function lo(u,f){let v=We(u,f);if(!v)return null;let k=E(v.col,v.row);if(e.conveyorTiles.has(k))return{...v};let I=(at(e).scannerPorts.get(u.id)?.[f]?.keys||[]).filter(Boolean);if(!I.length)return null;let A=I.map(X).sort((he,de)=>{let Xe=B(he.col,he.row),_n=B(de.col,de.row),yo=ae(null,Xe)?1:0,pr=ae(null,_n)?1:0;return yo-pr});return A.find(he=>he.col===v.col&&he.row===v.row)||A[0]||null}function co(u,f,v,k){let S=[];ye(S,{x:u.x,y:u.y});let I=We(f,v);return I&&ye(S,B(I.col,I.row)),ye(S,B(k.col,k.row)),S}function Nn(u,f,v){let k=e.chicks[u];if(!k||k.stage!=="queued")return!1;let S=Zt(e,k.scannerId);if(!S||S.kind==="auto"&&!nt(e,S))return!1;if(["coal","zundaMochi"].includes(k.sex))return Y(e,k.x,k.y-16,"NOT SCANNABLE",w.warn),!1;let I=lo(S,f);if(!I)return!v&&S.kind==="manual"&&Ln(S),Y(e,k.x,k.y-16,f==="left"?"NO LEFT BELT":"NO RIGHT BELT",w.danger),!1;!v&&S.kind==="manual"&&(et(S,k,f)?Ht(S):Ln(S)),S.queue=S.queue.filter(U=>U!==k.id),k.stage="belt",k.route=co(k,S,f,I),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,r();let A=v?w.green:f==="left"?w.mixerBlue:w.truckPink;return Us(e,ge(S).x,ge(S).y,A),Y(e,k.x,k.y-20,v?"AUTO":f.toUpperCase(),A),!0}function pt(u){let f=e.chicks[u];return f?(e.chicks.splice(u,1),r(),f):null}function ir(u,f=e.facilities.mixer){if(!nt(e,f)){let A=e.chicks[u];A&&(A.stoppedTimer=.25);return}if(f?.processingCooldown>0){let A=e.chicks[u];A&&(A.stoppedTimer=.25);return}if(Xt(e.chicks[u]))return dn(u,"CHICKS ONLY");let v=pt(u);if(!v)return;let{x:k,y:S}=v;if(Za(e),rn(e,f),Xo(e,f),v.sex==="meat"||v.sex==="sausage"){vt(e),Y(e,k,S-18,"CHICKS ONLY",w.warn);return}if(v.sex==="poop"){let A=$s(e);nr(e,"mixer"),vt(e),Ks(e,k,S),Y(e,k,S-18,`-${R(A)}`,w.danger);return}v.sex==="male"?Fn(e):vt(e);let I=He(e,f);if(!I||!kt(e,f)||!fo(I)){let A=As(e);Y(e,k,S-18,`DIRECT +${R(A)}`,w.green);return}f.meatBuffer=Math.max(0,f.meatBuffer||0)+1,f.meatQualityQueue=f.meatQualityQueue||[],f.meatQualityQueue.push(v.sourceQuality||1),e.stats.meatProduced=(e.stats.meatProduced||0)+1,e.totals.meatProduced=(e.totals.meatProduced||0)+1,Vs(e,k,S),Y(e,k,S-18,"MEAT +1",w.meatRed),Yt()}function uo(u,f=e.facilities.truck){if(!nt(e,f)){let U=e.chicks[u];U&&(U.stoppedTimer=.25);return}if(Xt(e.chicks[u]))return dn(u,"NO IMPORTS");let v=pt(u);if(!v)return;let{x:k,y:S}=v;ur(v.sex,f,v.sourceQuality||1),zs(e,k,S,v.sex),tl(e,v.sex);let I=Sn(e),A=Cs(e,v.sex);if(v.sex==="sausage"){e.stats.sausageShipped=(e.stats.sausageShipped||0)+1,e.totals.sausageShipped=(e.totals.sausageShipped||0)+1,Y(e,k,S-18,`SAUSAGE +${R(lt(e))}`,w.sausageOrange);return}if(v.sex==="meat"){vt(e),Y(e,k,S-18,"RAW MEAT REJECT",w.warn);return}if(v.sex==="poop"){if(nr(e,"truck"),A)Fn(e),Y(e,k,S-18,`+${R(Je(e))}`,w.green);else{let U=Fs(e);vt(e),Y(e,k,S-18,`-${R(U)}`,w.danger)}return}if(v.sex==="female"){A?(Fn(e),Y(e,k,S-18,`+${R(Je(e))}`,w.green)):(vt(e),Y(e,k,S-18,I==="male"?"REJECT":"NO SALE",w.warn));return}if(A){Fn(e),Y(e,k,S-18,`+${R(Je(e))}`,w.green);return}if(gs(e)){let U=Os(e);vt(e),Y(e,k,S-18,`-${R(U)}`,w.danger)}else vt(e),Y(e,k,S-18,"REJECT",w.warn)}function sr(u,f){let v=B(f.col,f.row);return{id:e.nextId++,sex:u,x:v.x,y:v.y,route:[{x:v.x,y:v.y}],targetIndex:1,stage:"belt",scannerId:null,nextScannerId:null,pendingScannerId:null,radius:u==="sausage"?11:10,bob:Math.random()*Math.PI*2,queueIndex:-1,queueRoute:null,stoppedTimer:0}}function fo(u){if(!u||!e.conveyorTiles.has(E(u.col,u.row)))return!1;let f=B(u.col,u.row);return!ae(null,f)}function Gn(u,f,v){if(!nt(e,u))return!1;let k=He(e,u);if(!k||!kt(e,u)||!fo(k)||Math.max(0,u[v]||0)<=0)return!1;u[v]-=1;let S=sr(f,k);f==="meat"&&(S.sourceQuality=(u.meatQualityQueue||[]).shift()||1),f==="sausage"&&(S.sourceQuality=(u.sausageQualityQueue||[]).shift()||1),e.chicks.push(S),r();let I=B(k.col,k.row);return Y(e,I.x,I.y-16,f==="meat"?"MEAT OUT":"SAUSAGE OUT",f==="meat"?w.meatRed:w.sausageOrange),!0}function Yt(){for(let u of Object.values(e.facilities||{})){let f=ue(u);f==="mixer"&&Gn(u,"meat","meatBuffer"),f==="sausageMaker"&&Gn(u,"sausage","sausageBuffer")}}function ar(u,f){if(!nt(e,f)){let I=e.chicks[u];I&&(I.stoppedTimer=.25);return}if(f?.processingCooldown>0){let I=e.chicks[u];I&&(I.stoppedTimer=.25);return}if(Xt(e.chicks[u]))return dn(u,"MEAT ONLY");let v=pt(u);if(!v)return;let{x:k,y:S}=v;if(v.sex!=="meat"){vt(e),Y(e,k,S-18,"MEAT ONLY",w.warn);return}if(rn(e,f),Xo(e,f),f.meatBuffer=Math.max(0,f.meatBuffer||0)+1,f.meatQualityQueue=f.meatQualityQueue||[],f.meatQualityQueue.push(v.sourceQuality||1),f.meatBuffer>=2){f.meatBuffer-=2,f.sausageBuffer=Math.max(0,f.sausageBuffer||0)+1;let I=f.meatQualityQueue.shift()||1,A=f.meatQualityQueue.shift()||1;f.sausageQualityQueue=f.sausageQualityQueue||[],f.sausageQualityQueue.push((I+A)/2),e.stats.sausageProduced=(e.stats.sausageProduced||0)+1,e.totals.sausageProduced=(e.totals.sausageProduced||0)+1,Y(e,k,S-18,"SAUSAGE +1",w.sausageOrange)}else Y(e,k,S-18,"MEAT 1/2",w.meatRed);Yt()}function lr(u,f=e.facilities.trash){if(!nt(e,f)){let U=e.chicks[u];U&&(U.stoppedTimer=.25);return}if(f?.processingCooldown>0){let U=e.chicks[u];U&&(U.stoppedTimer=.25);return}if(Xt(e.chicks[u]))return dn(u,"NO IMPORTS");let v=pt(u);if(!v)return;let{x:k,y:S}=v;qs(e,k,S,v.sex),el(e),rn(e,f),Xo(e,f);let I={amount:0},A="";if(v.sex==="poop"){I=Rs(e),A=I.amount>0?` ${I.tier==="jackpot"?"JACKPOT ":"+"}${R(I.amount)}`:" MISS",nr(e,"trash"),Fn(e);let U=Math.ceil(Math.max(0,Number(e.cardEffects?.composter)||0)*3*Fo(e));if(U>0){let he=tt(e,U);e.stats.composterIncome=(e.stats.composterIncome||0)+he,e.totals.composterIncome=(e.totals.composterIncome||0)+he,Y(e,k,S-36,`COMPOST +${R(he)}`,w.green)}Y(e,k,S-18,`CLEAN${A}`,I.amount>0?w.green:w.green)}else vt(e),Y(e,k,S-18,I.amount>0?`WASTE${A}`:"WASTE",I.amount>0?w.green:w.ink)}function cr(u,f){let v=e.chicks[u];if(!v)return;if(!la(f,v.sex)){v.stoppedTimer=.25;return}let k=pt(u);k&&((f.baseId||f.id)==="biomassGenerator"&&(e.stats.biomassFuelConsumed=(e.stats.biomassFuelConsumed||0)+1,e.totals.biomassFuelConsumed=(e.totals.biomassFuelConsumed||0)+1),(f.baseId||f.id)==="coalGenerator"&&(e.stats.coalConsumed=(e.stats.coalConsumed||0)+1,e.totals.coalConsumed=(e.totals.coalConsumed||0)+1),rn(e,f),Ye(e),Y(e,k.x,k.y-18,"FUEL +V",w.green))}function Qt(u,f){let v=pt(u);v&&Y(e,v.x,v.y-12,f,w.muted)}function Xt(u){return u?.sex==="coal"||u?.sex==="zundaMochi"}function dn(u,f){let v=e.chicks[u];v&&(v.stoppedTimer=.25,Y(e,v.x,v.y-18,f,w.warn))}function ur(u,f=e.facilities.truck,v=1){let k=f||e.facilities.truck;k&&(e.shippedCargo=e.shippedCargo||[],e.shippedCargo.push({sex:u,sourceQuality:v}),e.truckCargo.push({sex:u,sourceQuality:v,x:ne(22,k.w-22),y:ne(66,k.h-24)}),e.truckCargo.length>45&&e.truckCargo.shift())}function po(u){let f=rt(u.x,u.y);if(f){let S=E(f.col,f.row);if(e.conveyorTiles.has(S))return S}if(!f)return null;let v=null,k=1/0;for(let S of[f,...fe.map(I=>({col:f.col+I.dc,row:f.row+I.dr}))]){if(!Re(S.col,S.row))continue;let I=E(S.col,S.row);if(!e.conveyorTiles.has(I))continue;let A=B(S.col,S.row),U=Math.hypot(u.x-A.x,u.y-A.y);Uv&&u.ratio=f){let k=pn(u.id);if(!k.length||k.some(A=>A.stage!=="queued"&&A.stoppedTimer<=.2))continue;let S=e.lastExplodedComponent.get(u.id)||0,I=performance.now();I-S>900&&(e.lastExplodedComponent.set(u.id,I),fr(u))}}}function pn(u){return fn(e.frameId||0),e.frameCache?.componentChicks?.get(u)||[]}function fr(u){let f=pn(u.id);if(!f.length)return;for(let S of f)Jr(e,S.sex,S.x,S.y);e.chicks=e.chicks.filter(S=>!f.some(I=>I.id===S.id));for(let S of e.scanners)S.queue=S.queue.filter(I=>e.chicks.some(A=>A.id===I));r();for(let S of u.cells){let I=X(S),A=B(I.col,I.row);gt(e,A.x,A.y,w.danger),$t(e,A.x,A.y,6),Do(e,A.x,A.y,4)}nn(e,18,.75);let v=X(u.cells[0]),k=B(v.col,v.row);e.stats.explosions=(e.stats.explosions||0)+1,e.totals.explosions=(e.totals.explosions||0)+1,Y(e,k.x,k.y-22,`${Math.round(Kr(e)*100)}% JAM EXPLOSION`,w.danger)}return{activeQueuedChick:Ce,beginFrame:a,currentSpawnDelay:C,sortSlot:Ue,updateCongestion:fn,sortScanner:St,updateRunning:z}}var me=document.getElementById("gameCanvas"),Md=me.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"),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"),modal:document.getElementById("modal"),modalTitle:document.getElementById("modalTitle"),modalBody:document.getElementById("modalBody"),modalActions:document.getElementById("modalActions"),hoverTooltip:document.getElementById("hoverTooltip"),manualScannerMonitor:document.getElementById("manualScannerMonitor"),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"),targetSelect:document.getElementById("debugTargetSelect"),eventSelect:document.getElementById("debugEventSelect"),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"),powerWire:document.getElementById("buildPowerWireButton"),insulatedPowerWire:document.getElementById("buildInsulatedPowerWireButton"),powerOverlay:document.getElementById("powerOverlayButton"),footbridge:document.getElementById("buildFootbridgeButton"),eggFarm:document.getElementById("buildEggFarmButton"),autoScanner:document.getElementById("buildAutoScannerButton"),manualScanner:document.getElementById("buildManualScannerButton"),mixer:document.getElementById("buildMixerButton"),trash:document.getElementById("buildTrashButton"),truck:document.getElementById("buildTruckButton"),inboundTruck:document.getElementById("buildInboundTruckButton"),sausageMaker:document.getElementById("buildSausageMakerButton"),biomassGenerator:document.getElementById("buildBiomassGeneratorButton"),coalGenerator:document.getElementById("buildCoalGeneratorButton"),maintenanceRoom:document.getElementById("buildMaintenanceRoomButton"),erase:document.getElementById("eraseButton"),undo:document.getElementById("undoButton"),redo:document.getElementById("redoButton")}};function Cd(){let e={conveyor:"conveyor",boostConveyor:"boostConveyor",powerWire:"powerWire",insulatedPowerWire:"insulatedPowerWire",footbridge:"footbridge",eggFarm:"eggFarm",autoScanner:"autoScanner",manualScanner:"manualScanner",mixer:"mixer",trash:"trash",truck:"truck",inboundTruck:"inboundTruck",sausageMaker:"sausageMaker",biomassGenerator:"biomassGenerator",coalGenerator:"coalGenerator",maintenanceRoom:"maintenanceRoom"};for(let[t,n]of Object.entries(e))N.buttons[t]&&(N.buttons[t].innerHTML=_i(n))}function gd(e){let t=new Set(e.tags||[]);return["upgradeEgg","crowdedFarming","hatchingFeed","extraEggOutlet"].includes(e.id)||t.has("EGG")?"Production / EGG":["upgradeTrash","poopRateBooster","composter","chemicalWeaponSubsidy"].includes(e.id)||t.has("POOP")?"Poop / Shredder":["upgradeAutoScanner","autoScannerTuning","scannerQueueSpacing"].includes(e.id)||t.has("SCANNER")?"Scanner":["bearing","dynamite"].includes(e.id)||t.has("CONVEYOR")?"Conveyor / Map":["safetyCover","preventiveMaintenance","durabilityCoating","sparePartsBin","laborExploitation"].includes(e.id)||t.has("MAINTENANCE")?"Maintenance / Risk":["dudFilter","dudRefund","freeReroll","extraCards"].includes(e.id)||t.has("CARD")?"Card Engine":t.has("ULTRA RARE")?"Ultra Rare":t.has("ECONOMY")||["upgradeMixer","upgradeTruck","upgradeSausageMaker","recyclingSubsidy","usedMachine","newMachine","flattery","legalWork","rescueLoan"].includes(e.id)?"Economy / Shipment":"Other"}function xd(){if(!N.debug?.panel||!N.debug.cardSelect)return;let e=["Production / EGG","Poop / Shredder","Scanner","Conveyor / Map","Maintenance / Risk","Card Engine","Economy / Shipment","Ultra Rare","Other"],t=new Map(e.map(n=>[n,[]]));for(let n of An){let o=gd(n);t.has(o)||t.set(o,[]),t.get(o).push(n)}N.debug.cardSelect.innerHTML="";for(let n of e){let o=t.get(n)||[];if(!o.length)continue;let r=document.createElement("optgroup");r.label=n;for(let i of o){let a=document.createElement("option");a.value=i.id;let c=i.rarity==="ultraRare"?"UR":i.rarity==="rare"?"R":"C";a.textContent=`[${c}] ${i.title}`,r.appendChild(a)}N.debug.cardSelect.appendChild(r)}N.debug.cardSelect.addEventListener("change",un),Td(),un()}function Td(){if(!N.debug?.eventSelect)return;N.debug.eventSelect.innerHTML="";let e=document.createElement("option");e.value="__roll__",e.textContent="Roll normally",N.debug.eventSelect.appendChild(e);let t=document.createElement("option");t.value="__none__",t.textContent="No event",N.debug.eventSelect.appendChild(t);for(let n of Hn){let o=document.createElement("option");o.value=n.key,o.textContent=n.title,N.debug.eventSelect.appendChild(o)}N.debug.eventSelect.value=d.debugNextEventKey||"__roll__",N.debug.eventSelect.addEventListener("change",Pd)}function cl(){let e=d.debugNextEventKey||"__roll__";return e==="__none__"?null:e==="__roll__"?ws(d):_r(d,e)}function un(){if(!N.debug?.targetSelect)return;let e=N.debug.cardSelect?.value,t=an(e),n=t?$a(d,t):[];if(N.debug.targetSelect.innerHTML="",!n.length){let r=document.createElement("option");r.value="__none__",r.textContent=t?"No target required":"No card selected",N.debug.targetSelect.appendChild(r),N.debug.targetSelect.disabled=!0;return}let o=document.createElement("option");o.value="__auto__",o.textContent=t?.id==="dynamite"?"Auto: first 10 blocked cells":"Auto: first valid target",N.debug.targetSelect.appendChild(o);for(let r of n){let i=document.createElement("option");i.value=r.key,i.textContent=r.label,N.debug.targetSelect.appendChild(i)}N.debug.targetSelect.disabled=!1}function Ed(){let e=Math.max(1,Math.floor(Number(N.debug.dayInput?.value)||1));d.turn=e,N.debug.dayInput&&(N.debug.dayInput.value=String(e)),Y(d,me.width/2-d.view.x,86-d.view.y,`DAY ${e}`,w.green),re.updatePanels(),re.updateUI(),un(),zt(`Day set to ${e}.`)}function Pd(){d.debugNextEventKey=N.debug.eventSelect?.value||"__roll__",d.phase==="build"&&(d.eventOffer=cl(),re.updatePanels(),re.updateUI());let e=N.debug.eventSelect?.selectedOptions?.[0]?.textContent||"Roll normally";zt(`Next event: ${e}.`)}function Rd(){let e=d.phase==="running"&&(d.timeLeft<=0||d.shutdownTimeLeft>0||d.cleanupTimer!==null);d.timeLeft=0,d.cleanupTimer!==null&&(d.cleanupTimer=0),e?d.shutdownTimeLeft=0:d.phase==="running"&&fl(),Y(d,me.width/2-d.view.x,86-d.view.y,"TIMER 0",w.warn),re.updatePanels(),re.updateUI(),zt("Timer set to 0.")}function Id(){let e=N.debug.cardSelect?.value,t=N.debug.targetSelect?.disabled?void 0:N.debug.targetSelect?.value,n=di(d,e,{targetKey:t});N.debug.grantCard&&(N.debug.grantCard.textContent=n.ok?"Granted":"Failed",window.setTimeout(()=>{N.debug.grantCard&&(N.debug.grantCard.textContent="Grant Selected")},900)),Ne?.updateCongestion?.(d.frameId||0),re.updatePanels(),re.updateUI(),un(),zt(n.reason)}function Ad(){let t=An.map(n=>di(d,n,{targetKey:"__auto__"})).filter(n=>n.ok).length;Ne?.updateCongestion?.(d.frameId||0),re.updatePanels(),re.updateUI(),un(),zt(`Granted ${t}/${An.length} cards.`)}function $d(){N.debug?.infiniteCash?.checked&&d.cash<999999&&(d.cash=999999,d.totals.maxCash=Math.max(d.totals.maxCash,d.cash))}function zt(e=""){N.debug?.readout&&(N.debug.readout.textContent=[e,`D${d.turn}`,R(d.cash),`Combo ${d.manualCombo?.count||0}`,`Event ${d.eventActive?.title||d.eventOffer?.title||"none"}`,`Next ${N.debug?.eventSelect?.selectedOptions?.[0]?.textContent||"Roll normally"}`,`Rescue ${d.cardEffects?.rescueLoanCharges||0}`,`Chem ${d.cardEffects?.chemicalWeaponSubsidy||0}`].filter(Boolean).join(" | "))}var d=Rr(),te,re,Ne,Bn;function Fd(e=null){let t=Vt(d,e);return e?t*eo(d,e):t}function Od(){Object.assign(d,Rr()),d.phase="running",d.view={x:0,y:0,scale:1},Ir(d),yl(),Be(d),re.hideModal(),Ne.updateCongestion(d.frameId||0),re.updatePanels(),re.updateUI(),zt()}function Bd(){if(d.phase!=="build")return;if(d.cardDraft?.pending||d.cardTargetPick?.pending){te.fail(d.cardTargetPick?.pending?"Choose upgrade target first.":"Choose an upgrade card first."),re.updatePanels();return}let e=$o(d);if(e.length){Wd(),te.fail(`Cannot start: ${e[0]}`),re.updatePanels();return}Be(d),ds(d);let t=_o(d);t.underVoltage?.size&&Y(d,me.width/2-d.view.x,136-d.view.y,`${t.underVoltage.size} UNDER-VOLTAGE`,w.warn),ba(d);let n=Math.max(0,Math.floor(d.lastResult?.profit??d.stats?.profit??0)),o=Ls(d,Math.max(1,d.turn||1),n);d.lastStartFees={fairiesTribute:o.amount||0,tribute:o},o.amount>0&&Y(d,me.width/2-d.view.x,116-d.view.y,`FAIRIES -${R(o.amount)}`,w.danger),ms(d),d.phase="running",d.turn+=1,d.timeLeft=Ss(d),d.shutdownTimeLeft=0,d.cleanupTimer=null,d.cleanupBlown=!1,d.chicks=[],d.effects=[],d.floatingTexts=[],d.shake={time:0,strength:0},d.truckCargo=[],d.shippedCargo=[],yl(),d.stats=Pr(),nl(d),d.stats.fairiesTribute=d.lastStartFees?.fairiesTribute||0,d.stats.fairiesTribute>0&&(d.stats.penalty+=d.stats.fairiesTribute,d.stats.profit-=d.stats.fairiesTribute);for(let r of d.scanners)r.queue=[],r.cooldown=0;for(let r of d.eggFarms)r.nextSpawn=Ne.currentSpawnDelay(r),r.lastInterval=r.nextSpawn,r.shutterProgress=0,r.shutterSparked=!1,r.zundaBoostTime=0;d.buildTool=null,d.selected=null,d.groupDrag=null,d.selectionBox=null,d.pan=null,Ne.updateCongestion(d.frameId||0),re.updatePanels(),re.updateUI()}function Dd(){if(d.phase!=="running")return;rl(d);for(let i of Object.values(d.facilities||{}))i.fuelPulses&&(i.fuelPulses=[]);for(let i of d.eggFarms||[])i.zundaBoostTime=0;let e=Ds(d),t=d.eventActive?{...d.eventActive}:null,n=Es(d),o=Ns(d);if(e.adjusted>0){let i=d.facilities.truck,a=i?i.x+i.w/2:me.width/2-d.view.x,c=i?i.y+26:96-d.view.y;Y(d,a,c,`${e.target==="poop"?"POOP SHIP":"SHIP"} +${R(e.adjusted)}`,w.green)}n?.bonus>0&&Y(d,me.width/2-d.view.x,96-d.view.y,`EVENT +${R(n.bonus)}`,w.green),o.amount>0&&Y(d,me.width/2-d.view.x,116-d.view.y,`CHEM SUBSIDY +${R(o.amount)}`,w.green),vs(d),d.totals.turnsCompleted+=1;let r=Ts(t);if(r!==1&&(d.pendingFairiesTributeMultiplier=r),d.lastResult={...d.stats,ship:e,event:n,chemical:o,rescue:{amount:0,used:0,remaining:d.cardEffects?.rescueLoanCharges||0},fairiesTribute:{amount:d.stats.fairiesTribute||0},tributeMultiplier:r,turn:d.turn,cash:d.cash},d.cash<0){if((d.cardEffects?.rescueLoanCharges||0)>0){d.phase="loanDecision",re.showEmergencyLoanDecision(dl,or);return}d.phase="gameover",re.showGameOver();return}ul()}function ul(){d.phase="build",d.truckCargo=[],d.shippedCargo=[],d.chicks=[],d.shutdownTimeLeft=0,d.cleanupTimer=null,d.cleanupBlown=!1,d.buildSession+=1,d.undoStack=[],d.redoStack=[],d.timeLeft=0;for(let e of d.scanners)e.queue=[];for(let e of d.eggFarms)e.shutterProgress=0,e.shutterSparked=!1;wa(d),d.eventOffer=cl(),Bn&&Bn.prepareDraft(),d.cardTargetPick=null,d.buildTool=null,d.groupDrag=null,d.selectionBox=null,d.pan=null,re.updatePanels(),re.showTurnResult(d.lastResult)}function dl(){if((d.cardEffects?.rescueLoanCharges||0)<=0)return or();d.cardEffects.rescueLoanCharges-=1,d.stats.rescueLoan=(d.stats.rescueLoan||0)+500,d.totals.rescueLoan=(d.totals.rescueLoan||0)+500;let e=tt(d,500);d.lastResult={...d.lastResult||{},...d.stats,rescue:{amount:(d.lastResult?.rescue?.amount||0)+e,used:(d.lastResult?.rescue?.used||0)+1,remaining:d.cardEffects.rescueLoanCharges},cash:d.cash},Y(d,me.width/2-d.view.x,176-d.view.y,`RESCUE +${R(e)}`,w.green),d.cash>=0?ul():(d.cardEffects?.rescueLoanCharges||0)>0?(d.phase="loanDecision",re.showEmergencyLoanDecision(dl,or)):or()}function or(){d.phase="gameover",re.showGameOver()}function fl(){d.shutdownTimeLeft<=0&&(d.shutdownTimeLeft=d.shutdownGraceSeconds||10);for(let e of d.eggFarms){if(e.shutterSparked)continue;e.shutterProgress=.02,e.shutterSparked=!0;let t=B(e.col,e.row);gt(d,t.x,t.y,w.ink),$t(d,t.x,t.y+10,10),Y(d,t.x,t.y-34,"SHUT +10s",w.ink)}}function pl(e){d.lastTimestamp||(d.lastTimestamp=e);let t=Math.min((e-d.lastTimestamp)/1e3,.05);d.lastTimestamp=e,d.frameId=(d.frameId||0)+1,Ne.beginFrame?.(d.frameId),sa(d,t),d.phase==="running"&&Ne.updateRunning(t,{closeFarmShutters:fl,completeTurn:Dd}),Ys(d,t,me,te.equipmentHitBoxes),Ne.updateCongestion(d.frameId),Ga(Md,me,d,{activeQueuedChick:Ne.activeQueuedChick,selectedObject:te.selectedObject,selectedTitle:te.selectedTitle}),$d(),re.updateUI(),bl(),zt(),requestAnimationFrame(pl)}function ao(){return d.view?.scale||1}function Dn(){d.view||(d.view={x:0,y:0,scale:1}),d.view.scale==null&&(d.view.scale=1)}function rr(e){let t=me.getBoundingClientRect();return{x:(e.clientX-t.left)*me.width/t.width,y:(e.clientY-t.top)*me.height/t.height}}function qt(e){let t=rr(e),n=ao();return{x:(t.x-(d.view?.x||0))/n,y:(t.y-(d.view?.y||0))/n}}function Ld(e){let t=ao();return{x:e.x*t+(d.view?.x||0),y:e.y*t+(d.view?.y||0)}}function Nd(){let e={left:p.x-90,top:p.y-130,right:p.x+p.cols*p.cell+260,bottom:p.y+p.rows*p.cell+220};if(d.phase==="build")for(let t of jt(d)){let n=Et(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 sl(e,t){if(e<=t)return[e,t];let n=(e+t)/2;return[n,n]}function Lt(){Dn();let e=ao(),t=Nd(),[n,o]=sl(me.width-160-t.right*e,110-t.left*e),[r,i]=sl(me.height-150-t.bottom*e,150-t.top*e);d.view.x=Kn(d.view.x,n,o),d.view.y=Kn(d.view.y,r,i)}function yl(){Dn(),d.view.scale=1;let e=d.eggFarms?.[0],t=e?B(e.col,e.row):{x:p.x,y:p.y};d.view.x=280-t.x,d.view.y=190-t.y,Lt()}function Gd(e){if(!e)return!1;Dn();let t=ao();return d.view.x=me.width/2-e.x*t,d.view.y=me.height/2-e.y*t,Lt(),!0}function _d(e){let t=e?.ref;return t?e.type==="facility"?{x:t.x+t.w/2,y:t.y+t.h/2}:e.type==="eggFarm"?B(t.col,t.row):e.type==="scanner"?{x:B(t.col,t.row).x+p.cell/2,y:B(t.col,t.row).y}:e.type==="maintenanceRoom"?{x:B(t.col,t.row).x+p.cell/2,y:B(t.col,t.row).y+p.cell/2}:null:null}function Wd(){let e=vn(d),t=e.find(o=>o.type==="facility")||e[0]||Ud(),n=_d(t);Gd(n)&&t&&(d.selected={type:t.type,id:t.id})}function Ud(){let e=Object.values(d.facilities||{}).find(o=>o?.entry&&d.conveyorTiles.has(E(o.entry.col,o.entry.row)))||Object.values(d.facilities||{})[0];if(e)return{type:"facility",id:e.id,ref:e};let t=d.scanners?.find(o=>o);if(t)return{type:"scanner",id:t.id,ref:t};let n=d.eggFarms?.[0];return n?{type:"eggFarm",id:n.id,ref:n}:null}function Vd(e){let t=rr(e);Dn(),d.pan={start:t,viewX:d.view.x,viewY:d.view.y,moved:!1}}function Kd(e){if(!d.pan)return;let t=rr(e);Math.hypot(t.x-d.pan.start.x,t.y-d.pan.start.y)>5&&(d.pan.moved=!0),Dn(),d.view.x=d.pan.viewX+t.x-d.pan.start.x,d.view.y=d.pan.viewY+t.y-d.pan.start.y,Lt()}function qd(e){e.preventDefault(),Dn();let t=rr(e),n=qt(e),o=Math.exp(-e.deltaY*.0012),r=Kn(ao()*o,.55,1.8);d.view.scale=r,d.view.x=t.x-n.x*r,d.view.y=t.y-n.y*r,Lt(),Nt()}function zd(e){return e?e.type==="conveyor"?{type:"conveyor",id:e.oldKey}:{type:e.type,id:e.ref.id}:null}function Hd(e){return e?e.type==="conveyor"?{type:"conveyor",id:e.oldKey}:e.ref||null:null}function Nt(){N.hoverTooltip&&(N.hoverTooltip.classList.remove("visible"),N.hoverTooltip.setAttribute("aria-hidden","true"),d.hover=null)}function Yd(e,t){if(!N.hoverTooltip||d.phase!=="build"||d.cardTargetPick?.pending||d.groupDrag||d.selectionBox||d.pan)return Nt();let n=Hd(t);if(!n)return Nt();let o=te.selectedTitle(n),r=te.flavorText?te.flavorText(n):"",i=te.disconnectedWarningFor?te.disconnectedWarningFor(n):"",a=te.selectedInfoLines(n).slice(0,7);N.hoverTooltip.innerHTML=`${o}${i?`${i}`:r?`${r}`:""}${a.map(M=>`${M}`).join("")}`;let c=Math.min(window.innerWidth-330,e.clientX+18),b=Math.min(window.innerHeight-220,e.clientY+18);N.hoverTooltip.style.left=`${Math.max(8,c)}px`,N.hoverTooltip.style.top=`${Math.max(8,b)}px`,N.hoverTooltip.classList.add("visible"),N.hoverTooltip.setAttribute("aria-hidden","false"),d.hover={type:t.type,id:t.oldKey||t.ref?.id||null}}function Qd(e){if(d.phase!=="build")return Nt();Yd(e,te.equipmentAtPoint(qt(e)))}function hl(e){Nt();let t=qt(e),n=te.equipmentAtPoint(t),o=zd(n);if(!o){d.selected=null,d.multiSelected=[],re.updatePanels();return}d.selected=o,d.multiSelected=[o],re.updatePanels();let r=te.selectedObject();r?.type==="scanner"&&r.kind==="manual"&&te.showManualScannerMenu(r),r?.type==="scanner"&&r.kind==="auto"&&te.showAutoScannerMenu(r),r?.type==="facility"&&(r.baseId||r.id)==="inboundTruck"&&te.showInboundTruckMenu(r)}function On(){d.buildTool=null,d.selected=null,d.multiSelected=[],d.selectionBox=null,d.cardTargetPick?.mode==="autoScannerMenu"&&(d.cardTargetPick=null),re.hideModal(),Nt(),re.updatePanels(),re.updateUI()}function al(e){return String(e).replace(/[&<>"']/g,t=>({"&":"&","<":"<",">":">",'"':""","'":"'"})[t])}function Xd(e){let t=e?.queue?.[0];return t==null?null:d.chicks.find(n=>n.id===t)||null}function jd(e){if(!e)return'WAIT';let t=e.sex==="poop"?"POOP":e.sex==="male"?"M MALE":"F FEMALE";return`${t}`}function Jd(e,t){let n=new Set(d.eggFarms.map(C=>E(C.col,C.row))),o=new Map(d.scanners.map(C=>[E(C.col,C.row),C])),r=We(e,"inputA"),i=r?E(r.col,r.row):null,a=new Set(Object.values(d.facilities||{}).map(C=>C.entry?E(C.entry.col,C.entry.row):null).filter(Boolean)),c=t?.sex||"",b=c==="poop"?"P":c==="male"?"M":c==="female"?"F":"",M=[];for(let C=e.row-2;C<=e.row+2;C+=1)for(let $=e.col-2;$<=e.col+2;$+=1){let W=E($,C),K=["mini-cell"],G="";$===e.col&&C===e.row?(K.push("scanner"),c&&K.push(c),G=b||"S"):W===i?(K.push("input"),G="IN"):n.has(W)?(K.push("egg"),G="E"):o.has(W)?(K.push("scanner-other"),G="S"):a.has(W)?(K.push("port"),G="P"):d.conveyorTiles.has(W)?(K.push("belt"),G="."):d.blockedCells?.has?.(W)&&(K.push("blocked"),G="X"),M.push(`${G}`)}return`
${M.join("")}
`}function Zd(e){let t=Ld(B(e.col,e.row)),n=76;return t.x<-n||t.y<-n||t.x>me.width+n||t.y>me.height+n}function bl(){if(!N.manualScannerMonitor)return;let e=d.frameCache||(d.frameCache={}),t=d.view||{},n=[d.phase,Math.round((t.x||0)*10),Math.round((t.y||0)*10),Math.round((t.scale||1)*100),d.chicks.length,d.scanners.map(r=>[r.id,r.kind,r.col,r.row,r.queue?.join(",")||"",r.keyPressSide||"",Math.ceil((r.keyPressTime||0)*10)].join(":")).join("|")].join(";");if(e.manualMonitorSignature===n)return;if(e.manualMonitorSignature=n,d.phase!=="running"){N.manualScannerMonitor.classList.remove("visible"),N.manualScannerMonitor.innerHTML="";return}let o=d.scanners.filter(r=>r.kind==="manual"&&Zd(r));if(!o.length){N.manualScannerMonitor.classList.remove("visible"),N.manualScannerMonitor.innerHTML="";return}N.manualScannerMonitor.innerHTML=o.map(r=>{let i=Xd(r),a=r.keys?.left?.label||(r.slot===0?"A":"Left"),c=r.keys?.right?.label||(r.slot===0?"D":"Right"),b=r.role===0?"MIXER":"WASTE",M=r.role===0?"S2":"TRUCK",C=i?"":" disabled";return`
S${(r.slot??0)+1}OFFSCREEN / Q:${r.queue.length}
- IN${Ku(i)}SCANNER + IN${jd(i)}SCANNER
- ${qu(r,i)} + ${Jd(r,i)}
- - + +
-
`}).join(""),F.manualScannerMonitor.classList.add("visible")}F.manualScannerMonitor?.addEventListener("click",e=>{let t=e.target.closest("button[data-scanner-id]");if(!t||t.disabled)return;let n=u.scanners.find(o=>o.id===Number(t.dataset.scannerId));n&&(Ae.sortScanner(n,t.dataset.side),Ia())});he.addEventListener("contextmenu",e=>e.preventDefault());he.addEventListener("wheel",Nu,{passive:!1});he.addEventListener("pointerdown",e=>{if(!["build","running"].includes(u.phase))return;if(e.button===2||e.button===1||u.phase==="running"&&e.button===0){he.setPointerCapture(e.pointerId),Ou(e);return}if(u.phase!=="build"||e.button!==0)return;he.setPointerCapture(e.pointerId);let t=Bt(e);if(u.cardTargetPick?.mode==="autoScannerMenu"){mn();return}if(u.cardTargetPick?.pending){bn.chooseTargetAtPoint(t);return}let n=te.expansionOfferAtPoint?.(t);if(n){te.buyGridExpansion(n),Ct(),J.updatePanels(),J.updateUI();return}if(te.cycleBranchModeAtPoint?.(t)){J.updatePanels(),J.updateUI();return}let o=tt(t.x,t.y),r=te.equipmentAtPoint(t);if(!u.buildTool&&r?.type==="conveyor"&&o&&te.cycleSingleConveyorDirection?.(o)){J.updatePanels(),J.updateUI();return}if(u.buildTool==="erase"){if(!te.equipmentAtPoint(t)){mn();return}te.eraseAtPoint(t);return}if(u.buildTool){let i=tt(t.x,t.y);if(!i&&!["mixer","trash","truck"].includes(u.buildTool)){mn();return}if(u.buildTool==="conveyor"||u.buildTool==="boostConveyor"){te.beginConveyorDrag(i);return}if(te.equipmentAtPoint(t)){u.buildTool=null,Aa(e);return}te.buildAtPoint(t);return}te.startGroupDrag(e)||te.startSelectionBox(e)});he.addEventListener("pointermove",e=>{if(u.pan&&e.buttons&7&&Lu(e),u.phase!=="build"){Mt();return}Wu(e),u.groupDrag&&e.buttons&1&&te.updateGroupDrag(e),u.selectionBox&&e.buttons&1&&te.updateSelectionBox(e),(u.buildTool==="conveyor"||u.buildTool==="boostConveyor")&&e.buttons&1&&te.continueConveyorDrag(tt(Bt(e).x,Bt(e).y)),u.buildTool==="erase"&&e.buttons&1&&te.eraseAtPoint(Bt(e))});he.addEventListener("pointerup",e=>{u.selectionBox?te.finishSelectionBox():u.groupDrag&&(u.groupDrag.committed||Aa(e),te.finishGroupDrag()),(e.button===2||e.button===1)&&u.pan&&!u.pan.moved&&u.buildTool&&u.phase==="build"&&mn(),(u.buildTool==="conveyor"||u.buildTool==="boostConveyor")&&te.endConveyorDrag?.(),u.groupDrag=null,u.pan=null;try{he.releasePointerCapture(e.pointerId)}catch{}});he.addEventListener("pointerleave",Mt);he.addEventListener("pointercancel",()=>{te?.endConveyorDrag?.(),u.groupDrag=null,u.selectionBox=null,u.pan=null,Mt()});document.addEventListener("pointerdown",e=>{!F.modal.classList.contains("visible")||!F.modal.classList.contains("equipment-popover")||e.target.closest("#modal .modal-card")||mn()},!0);F.buttons.s1Left.addEventListener("click",()=>Ae.sortSlot(0,"left"));F.buttons.s1Right.addEventListener("click",()=>Ae.sortSlot(0,"right"));F.buttons.s2Left.addEventListener("click",()=>Ae.sortSlot(1,"left"));F.buttons.s2Right.addEventListener("click",()=>Ae.sortSlot(1,"right"));F.buttons.nextTurn.addEventListener("click",Ru);F.buttons.conveyor.addEventListener("click",()=>te.setBuildTool("conveyor"));F.buttons.boostConveyor.addEventListener("click",()=>te.setBuildTool("boostConveyor"));F.buttons.eggFarm.addEventListener("click",()=>te.setBuildTool("eggFarm"));F.buttons.autoScanner.addEventListener("click",()=>te.setBuildTool("autoScanner"));F.buttons.manualScanner.addEventListener("click",()=>te.setBuildTool("manualScanner"));F.buttons.mixer.addEventListener("click",()=>te.setBuildTool("mixer"));F.buttons.trash.addEventListener("click",()=>te.setBuildTool("trash"));F.buttons.truck.addEventListener("click",()=>te.setBuildTool("truck"));F.buttons.sausageMaker.addEventListener("click",()=>te.setBuildTool("sausageMaker"));F.buttons.maintenanceRoom.addEventListener("click",()=>te.setBuildTool("maintenanceRoom"));F.buttons.erase.addEventListener("click",()=>te.setBuildTool("erase"));F.debug.setDay?.addEventListener("click",Su);F.debug.zeroTimer?.addEventListener("click",gu);F.debug.grantCard?.addEventListener("click",Cu);F.debug.grantAllCards?.addEventListener("click",Mu);F.buttons.undo.addEventListener("click",()=>{Mr(u)&&(Ct(),J.updatePanels(),J.updateUI(),Jt())});F.buttons.redo.addEventListener("click",()=>{xr(u)&&(Ct(),J.updatePanels(),J.updateUI(),Jt())});function Ca(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(u.phase==="running")for(let n of u.scanners.filter(o=>o.kind==="manual")){if(Ca(n,"left",e)){e.preventDefault(),Ae.sortScanner(n,"left");return}if(Ca(n,"right",e)){e.preventDefault(),Ae.sortScanner(n,"right");return}}u.phase==="build"&&(e.ctrlKey||e.metaKey)&&t==="z"&&(e.preventDefault(),Mr(u)&&(Ct(),J.updatePanels(),J.updateUI())),u.phase==="build"&&(e.ctrlKey||e.metaKey)&&(t==="y"||e.shiftKey&&t==="z")&&(e.preventDefault(),xr(u)&&(Ct(),J.updatePanels(),J.updateUI())),e.key==="Escape"&&u.cardTargetPick?.pending&&(e.preventDefault(),u.cardTargetPick.mode==="autoScannerMenu"?mn():bn.cancelTargetPick())});te=pa({game:u,ui:F,canvas:he,canvasPoint:Bt,onUpdatePanels:()=>J?.updatePanels()});Ae=Sa({game:u,currentConveyorSpeed:Tu,onGameOverCheck:()=>J?.checkGameOver()});bn=Xs({game:u,ui:F,onUpdatePanels:()=>J?.updatePanels()});J=ya({game:u,ui:F,build:te,startGame:Eu,beginCardDraft:()=>bn.showDraft(),activeQueuedChick:Ae.activeQueuedChick});mu();vu();ir(u);Re(u);Ae.updateCongestion(u.frameId||0);J.updatePanels();J.updateUI();J.showTitle();Ot();requestAnimationFrame(Ra); + `}).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=d.scanners.find(o=>o.id===Number(t.dataset.scannerId));n&&(Ne.sortScanner(n,t.dataset.side),bl())});me.addEventListener("contextmenu",e=>e.preventDefault());me.addEventListener("wheel",qd,{passive:!1});me.addEventListener("pointerdown",e=>{if(!["build","running"].includes(d.phase))return;if(e.button===2||e.button===1||d.phase==="running"&&e.button===0){me.setPointerCapture(e.pointerId),Vd(e);return}if(d.phase!=="build"||e.button!==0)return;me.setPointerCapture(e.pointerId);let t=qt(e);if(d.cardTargetPick?.mode==="autoScannerMenu"){On();return}if(d.cardTargetPick?.pending){Bn.chooseTargetAtPoint(t);return}let n=te.expansionOfferAtPoint?.(t);if(n){te.buyGridExpansion(n),Lt(),re.updatePanels(),re.updateUI();return}if(te.cycleBranchModeAtPoint?.(t)){re.updatePanels(),re.updateUI();return}let o=rt(t.x,t.y),r=te.equipmentAtPoint(t);if(!d.buildTool&&r?.type==="conveyor"&&o&&te.cycleSingleConveyorDirection?.(o)){re.updatePanels(),re.updateUI();return}if(d.buildTool==="erase"){if(!te.equipmentAtPoint(t)){On();return}te.eraseAtPoint(t);return}if(d.buildTool){let i=rt(t.x,t.y);if(!i&&!["mixer","trash","truck"].includes(d.buildTool)){On();return}if(d.buildTool==="conveyor"||d.buildTool==="boostConveyor"){te.beginConveyorDrag(i);return}if(te.equipmentAtPoint(t)){d.buildTool=null,hl(e);return}te.buildAtPoint(t);return}te.startGroupDrag(e)||te.startSelectionBox(e)});me.addEventListener("pointermove",e=>{if(d.pan&&e.buttons&7&&Kd(e),d.phase!=="build"){Nt();return}Qd(e),d.groupDrag&&e.buttons&1&&te.updateGroupDrag(e),d.selectionBox&&e.buttons&1&&te.updateSelectionBox(e),(d.buildTool==="conveyor"||d.buildTool==="boostConveyor")&&e.buttons&1&&te.continueConveyorDrag(rt(qt(e).x,qt(e).y)),d.buildTool==="erase"&&e.buttons&1&&te.eraseAtPoint(qt(e))});me.addEventListener("pointerup",e=>{d.selectionBox?te.finishSelectionBox():d.groupDrag&&(d.groupDrag.committed||hl(e),te.finishGroupDrag()),(e.button===2||e.button===1)&&d.pan&&!d.pan.moved&&d.buildTool&&d.phase==="build"&&On(),(d.buildTool==="conveyor"||d.buildTool==="boostConveyor")&&te.endConveyorDrag?.(),d.groupDrag=null,d.pan=null;try{me.releasePointerCapture(e.pointerId)}catch{}});me.addEventListener("pointerleave",Nt);me.addEventListener("pointercancel",()=>{te?.endConveyorDrag?.(),d.groupDrag=null,d.selectionBox=null,d.pan=null,Nt()});document.addEventListener("pointerdown",e=>{!N.modal.classList.contains("visible")||!N.modal.classList.contains("equipment-popover")||e.target.closest("#modal .modal-card")||On()},!0);N.buttons.s1Left.addEventListener("click",()=>Ne.sortSlot(0,"left"));N.buttons.s1Right.addEventListener("click",()=>Ne.sortSlot(0,"right"));N.buttons.s2Left.addEventListener("click",()=>Ne.sortSlot(1,"left"));N.buttons.s2Right.addEventListener("click",()=>Ne.sortSlot(1,"right"));N.buttons.nextTurn.addEventListener("click",Bd);N.buttons.conveyor.addEventListener("click",()=>te.setBuildTool("conveyor"));N.buttons.boostConveyor.addEventListener("click",()=>te.setBuildTool("boostConveyor"));N.buttons.powerWire?.addEventListener("click",()=>te.setBuildTool("powerWire"));N.buttons.insulatedPowerWire?.addEventListener("click",()=>te.setBuildTool("insulatedPowerWire"));N.buttons.powerOverlay?.addEventListener("click",()=>{d.powerOverlay=!d.powerOverlay,re.updatePanels()});N.buttons.footbridge?.addEventListener("click",()=>te.setBuildTool("footbridge"));N.buttons.eggFarm.addEventListener("click",()=>te.setBuildTool("eggFarm"));N.buttons.autoScanner.addEventListener("click",()=>te.setBuildTool("autoScanner"));N.buttons.manualScanner.addEventListener("click",()=>te.setBuildTool("manualScanner"));N.buttons.mixer.addEventListener("click",()=>te.setBuildTool("mixer"));N.buttons.trash.addEventListener("click",()=>te.setBuildTool("trash"));N.buttons.truck.addEventListener("click",()=>te.setBuildTool("truck"));N.buttons.inboundTruck?.addEventListener("click",()=>te.setBuildTool("inboundTruck"));N.buttons.sausageMaker.addEventListener("click",()=>te.setBuildTool("sausageMaker"));N.buttons.biomassGenerator?.addEventListener("click",()=>te.setBuildTool("biomassGenerator"));N.buttons.coalGenerator?.addEventListener("click",()=>te.setBuildTool("coalGenerator"));N.buttons.maintenanceRoom.addEventListener("click",()=>te.setBuildTool("maintenanceRoom"));N.buttons.erase.addEventListener("click",()=>te.setBuildTool("erase"));N.debug.setDay?.addEventListener("click",Ed);N.debug.zeroTimer?.addEventListener("click",Rd);N.debug.grantCard?.addEventListener("click",Id);N.debug.grantAllCards?.addEventListener("click",Ad);N.buttons.undo.addEventListener("click",()=>{Xr(d)&&(Lt(),re.updatePanels(),re.updateUI(),un())});N.buttons.redo.addEventListener("click",()=>{jr(d)&&(Lt(),re.updatePanels(),re.updateUI(),un())});function ll(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(d.phase==="running")for(let n of d.scanners.filter(o=>o.kind==="manual")){if(ll(n,"left",e)){e.preventDefault(),Ne.sortScanner(n,"left");return}if(ll(n,"right",e)){e.preventDefault(),Ne.sortScanner(n,"right");return}}if(d.phase==="build"&&(e.ctrlKey||e.metaKey)&&t==="z"&&(e.preventDefault(),Xr(d)&&(Lt(),re.updatePanels(),re.updateUI())),d.phase==="build"&&(e.ctrlKey||e.metaKey)&&(t==="y"||e.shiftKey&&t==="z")&&(e.preventDefault(),jr(d)&&(Lt(),re.updatePanels(),re.updateUI())),d.phase==="build"&&d.buildTool==="footbridge"&&t==="r"){e.preventDefault(),d.footbridgeOrientation=d.footbridgeOrientation==="horizontal"?"vertical":"horizontal",Y(d,me.width/2-d.view.x,86-d.view.y,`FOOTBRIDGE ${d.footbridgeOrientation.toUpperCase()}`,w.green),re.updatePanels();return}e.key==="Escape"&&d.cardTargetPick?.pending&&(e.preventDefault(),d.cardTargetPick.mode==="autoScannerMenu"?On():Bn.cancelTargetPick())});te=Qa({game:d,ui:N,canvas:me,canvasPoint:qt,onUpdatePanels:()=>re?.updatePanels()});Ne=il({game:d,currentConveyorSpeed:Fd,onGameOverCheck:()=>re?.checkGameOver()});Bn=Ba({game:d,ui:N,onUpdatePanels:()=>re?.updatePanels()});re=Xa({game:d,ui:N,build:te,startGame:Od,beginCardDraft:()=>Bn.showDraft(),activeQueuedChick:Ne.activeQueuedChick});Cd();xd();Ir(d);Be(d);Ne.updateCongestion(d.frameId||0);re.updatePanels();re.updateUI();re.showTitle();zt();requestAnimationFrame(pl); diff --git a/index.html b/index.html index 3197a04..02699e2 100644 --- a/index.html +++ b/index.html @@ -6,13 +6,13 @@ - Chick Sorter v28.21 — Boost Draw Hotfix + Chick Sorter v29.00
-
-
v28.21 VERIFIED
+
+
v29.00
@@ -39,7 +39,7 @@
-

CHICK SORTER v28.21

+

CHICK SORTER v29.00

@@ -50,13 +50,20 @@
+ + + + - + + + +
@@ -110,6 +117,6 @@
- + diff --git a/package-lock.json b/package-lock.json index 2c9dd91..cd9545f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "zunda-shiwake", - "version": "28.2.1", + "version": "29.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "zunda-shiwake", - "version": "28.2.1", + "version": "29.0.0", "devDependencies": { "esbuild": "^0.25.0" } diff --git a/package.json b/package.json index 8d03f46..49e6e46 100644 --- a/package.json +++ b/package.json @@ -1,12 +1,12 @@ { "name": "zunda-shiwake", - "version": "28.2.1", + "version": "29.0.0", "private": true, "type": "module", "scripts": { "clean": "node -e \"require('fs').rmSync('dist', { recursive: true, force: true })\"", - "build": "npm run clean && esbuild src/game.js --bundle --format=esm --target=es2020 --minify --outfile=dist/game.js", - "test": "node test_v28_21.mjs" + "build": "npm run clean && node scripts/build.mjs", + "test": "node -e \"const fs=require('fs'); const cp=require('child_process'); if (fs.existsSync('test_v28_21.mjs')) cp.execFileSync(process.execPath, ['test_v28_21.mjs'], { stdio: 'inherit' }); cp.execFileSync(process.execPath, ['test_v29_00.mjs'], { stdio: 'inherit' });\"" }, "devDependencies": { "esbuild": "^0.25.0" diff --git a/scripts/build.mjs b/scripts/build.mjs new file mode 100644 index 0000000..a345a7a --- /dev/null +++ b/scripts/build.mjs @@ -0,0 +1,15 @@ +import { build } from 'esbuild'; +import { dirname, resolve } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const root = resolve(dirname(fileURLToPath(import.meta.url)), '..'); + +await build({ + absWorkingDir: root, + entryPoints: [resolve(root, 'src/game.js')], + bundle: true, + format: 'esm', + target: 'es2020', + minify: true, + outfile: resolve(root, 'dist/game.js') +}); diff --git a/src/core/balance.js b/src/core/balance.js index 2de476e..b096bca 100644 --- a/src/core/balance.js +++ b/src/core/balance.js @@ -1,7 +1,7 @@ // Central balance sheet for tuning gameplay. // Edit this file first when adjusting prices, income, penalties, timing, card rates, or caps. export const BALANCE = { - version: 'v28.21 BOOST DRAW HOTFIX', + version: 'v29.00', time: { daySeconds: 60, farmShutdownGraceSeconds: 10 @@ -84,7 +84,46 @@ export const BALANCE = { conveyor: { baseSpeed: 52, maxSpeed: 300, - bearingSpeedMultiplier: 1.075 + bearingSpeedMultiplier: 1.075, + minimumItemSpacingPx: 25 + }, + power: { + recalcHz: 10, + maxAssignmentPasses: 8, + wires: { + powerWire: { id: 'powerWire', name: 'Power Wire', shortName: 'WIRE', price: 10, lossV: 0.5 }, + insulatedPowerWire: { id: 'insulatedPowerWire', name: 'Insulated Wire', shortName: 'INS WIRE', price: 22, lossV: 0.15 } + }, + exempt: ['manualScanner', 'eggFarm', 'conveyor', 'boostConveyor', 'powerWire', 'insulatedPowerWire', 'footbridge', 'starterGenerator', 'biomassGenerator', 'coalGenerator'], + machines: { + mixer: { minVoltage: 10, drawV: 4, relayDropV: 4, powerPriority: 100 }, + trash: { minVoltage: 8, drawV: 3, relayDropV: 3, powerPriority: 100 }, + truck: { minVoltage: 6, drawV: 2, relayDropV: 2, powerPriority: 100 }, + autoScanner: { minVoltage: 8, drawV: 2, relayDropV: 2, powerPriority: 50 }, + sausageMaker: { minVoltage: 12, drawV: 5, relayDropV: 5, powerPriority: 50 }, + maintenanceRoom: { minVoltage: 6, drawV: 1, relayDropV: 1, powerPriority: 50 }, + inboundTruck: { minVoltage: 5, drawV: 1, relayDropV: 1, powerPriority: 50 } + }, + generators: { + starterGenerator: { outputV: 25 }, + biomassGenerator: { baseOutputV: 0, pulseVoltage: 5, pulseSeconds: 2 }, + coalGenerator: { baseOutputV: 0, pulseVoltage: 15, pulseSeconds: 3 } + } + }, + imports: { + combinedLimitPerTruck: 80, + prices: { coal: 2, zundaMochi: 4 }, + jitterRatio: 0.15 + }, + farmQuality: { + overflowCoefficient: 0.45, + overflowExponent: 0.85, + minMultiplier: 1, + maxMultiplier: 2.5, + zundaSeconds: 8, + zundaMaxSeconds: 30, + zundaIntervalMultiplier: 0.55, + safeMinIntervalSeconds: 0.25 }, effects: { explosionHorizontalVelocityMultiplier: 0.82 @@ -406,7 +445,11 @@ export const BALANCE = { autoScanner: 850, mixer: 775, trash: 950, - sausageMaker: 825 + sausageMaker: 825, + starterGenerator: 1000, + biomassGenerator: 900, + coalGenerator: 900, + inboundTruck: 850 }, processingDelayMaxSeconds: { mixer: 1.20, @@ -427,6 +470,18 @@ export const BALANCE = { id: 'boostConveyor', type: 'conveyor', name: 'Boost Conveyor', shortName: 'BOOST BELT', price: 75, buildable: true, upgradeable: false, speedMultiplier: 2 }, + powerWire: { + id: 'powerWire', type: 'powerWire', name: 'Power Wire', shortName: 'WIRE', price: 10, + buildable: true, upgradeable: false + }, + insulatedPowerWire: { + id: 'insulatedPowerWire', type: 'powerWire', name: 'Insulated Power Wire', shortName: 'INS WIRE', price: 22, + buildable: true, upgradeable: false + }, + footbridge: { + id: 'footbridge', type: 'footbridge', name: 'Footbridge', shortName: 'BRIDGE', price: 90, + buildable: true, upgradeable: false, footprint: { cols: 1, rows: 3 } + }, eggFarm: { id: 'eggFarm', type: 'eggFarm', name: 'Egg Farm', shortName: 'EGG', price: 250, buildable: true, upgradeable: true, maxLevel: 4 @@ -448,13 +503,29 @@ export const BALANCE = { buildable: true, upgradeable: true, body: { w: 180, h: 100 } }, truck: { - id: 'truck', type: 'facility', name: 'Truck', shortName: 'TRUCK', price: 450, + id: 'truck', type: 'facility', name: 'Shipping Truck', shortName: 'SHIP TRUCK', price: 450, buildable: true, upgradeable: true, incomeKey: 'truck', body: { w: 176, h: 138 } }, sausageMaker: { id: 'sausageMaker', type: 'facility', name: 'Sausage Machine', shortName: 'SAUSAGE', price: 380, buildable: true, upgradeable: true, incomeKey: 'sausage', footprint: { cols: 3, rows: 3 }, body: { w: 138, h: 138 }, outputProduct: 'sausage' }, + starterGenerator: { + id: 'starterGenerator', type: 'facility', name: 'Starter Generator', shortName: 'STARTER', price: 0, + buildable: false, upgradeable: false, footprint: { cols: 2, rows: 2 }, body: { w: 92, h: 92 } + }, + biomassGenerator: { + id: 'biomassGenerator', type: 'facility', name: 'Biomass Generator', shortName: 'BIOMASS', price: 520, + buildable: true, upgradeable: false, footprint: { cols: 3, rows: 3 }, body: { w: 138, h: 138 } + }, + coalGenerator: { + id: 'coalGenerator', type: 'facility', name: 'Coal Generator', shortName: 'COAL GEN', price: 680, + buildable: true, upgradeable: false, footprint: { cols: 2, rows: 3 }, body: { w: 92, h: 138 } + }, + inboundTruck: { + id: 'inboundTruck', type: 'facility', name: 'Inbound Truck', shortName: 'INBOUND', price: 350, + buildable: true, upgradeable: false, body: { w: 176, h: 120 } + }, maintenanceRoom: { id: 'maintenanceRoom', type: 'maintenanceRoom', name: 'Maintenance Room', shortName: 'REPAIR ROOM', price: 220, buildable: true, upgradeable: false, footprint: { cols: 2, rows: 2 } diff --git a/src/core/config.js b/src/core/config.js index 60aa59a..5857359 100644 --- a/src/core/config.js +++ b/src/core/config.js @@ -26,9 +26,9 @@ 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', 'boostConveyor', 'eggFarm', 'autoScanner', 'manualScanner', 'mixer', 'trash', 'truck', 'sausageMaker', 'maintenanceRoom']; -export const EDGE_FACILITY_IDS = ['mixer', 'trash', 'truck']; -export const GRID_FACILITY_IDS = ['sausageMaker']; +export const BUILD_TOOL_IDS = ['conveyor', 'boostConveyor', 'powerWire', 'insulatedPowerWire', 'footbridge', 'eggFarm', 'autoScanner', 'manualScanner', 'mixer', 'trash', 'truck', 'inboundTruck', 'sausageMaker', 'biomassGenerator', 'coalGenerator', 'maintenanceRoom']; +export const EDGE_FACILITY_IDS = ['mixer', 'trash', 'truck', 'inboundTruck']; +export const GRID_FACILITY_IDS = ['sausageMaker', 'starterGenerator', 'biomassGenerator', 'coalGenerator']; export const MACHINE_FACILITY_IDS = [...EDGE_FACILITY_IDS, ...GRID_FACILITY_IDS]; export const REQUIRED_RECEIVER_FACILITY_IDS = ['mixer', 'trash', 'truck']; export const INCOME_FACILITY_IDS = ['mixer', 'truck', 'sausageMaker']; diff --git a/src/core/entities.js b/src/core/entities.js index 9faf1dc..a39ac49 100644 --- a/src/core/entities.js +++ b/src/core/entities.js @@ -108,8 +108,9 @@ function facilityKind(facility) { return facility?.baseId || facility?.id || ''; export function syncGridFacilityGeometry(facility) { if (!facility?.gridPlaced) return facility; - facility.wCells = Math.max(1, facility.wCells || 3); - facility.hCells = Math.max(1, facility.hCells || 3); + const def = FACILITY_DEFS[facilityKind(facility)] || {}; + facility.wCells = Math.max(1, facility.wCells || def.footprint?.cols || 3); + facility.hCells = Math.max(1, facility.hCells || def.footprint?.rows || 3); facility.x = GRID.x + facility.col * GRID.cell; facility.y = GRID.y + facility.row * GRID.cell; facility.w = facility.wCells * GRID.cell; @@ -118,6 +119,10 @@ export function syncGridFacilityGeometry(facility) { facility.entry = { col: facility.col - 1, row: facility.row + Math.floor(facility.hCells / 2) }; facility.side = 'grid'; } + if (facilityKind(facility) === 'biomassGenerator' || facilityKind(facility) === 'coalGenerator') { + facility.entry = { col: facility.col - 1, row: facility.row + Math.floor(facility.hCells / 2) }; + facility.side = 'grid'; + } return facility; } @@ -193,6 +198,7 @@ export function createFacility(game, id, p, price) { price: price || spec.price, builtSession: game.buildSession }; + if (id === 'inboundTruck') f.inboundOrder = { coalPerDay: 0, zundaMochiPerDay: 0 }; return layoutFacilityOnEdge(f, entry, side); } @@ -204,13 +210,14 @@ export function createGridFacility(game, id, col, row, price) { name: def?.name || id, baseId: id, col, row, - wCells: id === 'sausageMaker' ? 3 : 1, - hCells: id === 'sausageMaker' ? 3 : 1, + wCells: def?.footprint?.cols || (id === 'sausageMaker' ? 3 : 1), + hCells: def?.footprint?.rows || (id === 'sausageMaker' ? 3 : 1), gridPlaced: true, level: 1, price: price || def?.price || 0, builtSession: game.buildSession }; + if (id === 'biomassGenerator' || id === 'coalGenerator') f.fuelPulses = []; return syncGridFacilityGeometry(f); } diff --git a/src/core/state.js b/src/core/state.js index 0e87cde..33ccc29 100644 --- a/src/core/state.js +++ b/src/core/state.js @@ -48,7 +48,30 @@ export function newTurnStats() { explosions: 0, eventResult: null, conveyorPasses: 0, - eggProduced: 0 + eggProduced: 0, + coalOrdered: 0, + coalReleased: 0, + coalConsumed: 0, + coalUndelivered: 0, + zundaOrdered: 0, + zundaReleased: 0, + zundaConsumed: 0, + zundaUndelivered: 0, + zundaBoostSeconds: 0, + zundaBoostSecondsByFarm: {}, + importPurchaseCost: 0, + biomassFuelConsumed: 0, + maxGeneratorOutput: {}, + qualityRevenue: 0, + farmTheoreticalRate: 0, + farmActualOutputRate: 0, + qualityMultiplierSum: 0, + qualityMultiplierCount: 0, + maxQualityMultiplier: 1, + generatorAssignmentChanges: 0, + underVoltageSeconds: 0, + underVoltageSecondsByMachine: {}, + unreachableRepairTargets: 0 }; } @@ -89,7 +112,22 @@ export function newTotalStats() { eventSuccess: 0, eventFailed: 0, conveyorPasses: 0, - eggProduced: 0 + eggProduced: 0, + coalOrdered: 0, + coalReleased: 0, + coalConsumed: 0, + coalUndelivered: 0, + zundaOrdered: 0, + zundaReleased: 0, + zundaConsumed: 0, + zundaUndelivered: 0, + zundaBoostSeconds: 0, + importPurchaseCost: 0, + biomassFuelConsumed: 0, + qualityRevenue: 0, + generatorAssignmentChanges: 0, + underVoltageSeconds: 0, + unreachableRepairTargets: 0 }; } @@ -115,6 +153,7 @@ export function createGame() { ownedCells: new Set(), blockedCells: new Set(), buildTool: null, + footbridgeOrientation: 'vertical', buildSession: 0, undoStack: [], redoStack: [], @@ -130,8 +169,15 @@ export function createGame() { floatingTexts: [], shake: { time: 0, strength: 0 }, truckCargo: [], + shippedCargo: [], conveyorTiles: new Set(), conveyorMeta: new Map(), + powerWires: new Map(), + footbridges: new Map(), + powerOverlay: false, + power: { dirty: true, elapsed: 0, routes: new Map(), assignments: new Map(), sources: new Map(), underVoltage: new Set() }, + imports: { schedules: new Map() }, + walkabilityVersion: 0, eggFarms: [], scanners: [], facilities: {}, @@ -316,9 +362,73 @@ function protectedInitialCells(game) { for (const p of farmOutputCells(farm)) add(p); } for (const f of Object.values(game.facilities)) add(f.entry); + for (const f of Object.values(game.facilities)) { + if (f.gridPlaced) { + for (let dr = 0; dr < (f.hCells || 1); dr += 1) for (let dc = 0; dc < (f.wCells || 1); dc += 1) add({ col: f.col + dc, row: f.row + dr }); + } + } return reserved; } +function addPowerWire(game, col, row, kind = 'powerWire', free = false) { + if (!inGrid(col, row)) return; + game.powerWires.set(key(col, row), { + kind, + price: free ? 0 : (BALANCE.power.wires[kind]?.price || 0), + builtSession: free ? null : game.buildSession + }); +} + +function installStarterGenerator(game) { + const def = FACILITY_DEFS.starterGenerator; + const starter = { + type: 'facility', + id: 'starterGenerator', + baseId: 'starterGenerator', + name: def.name, + col: 6, + row: 6, + wCells: def.footprint.cols, + hCells: def.footprint.rows, + gridPlaced: true, + level: 1, + price: 0, + builtSession: null, + unsellable: true + }; + starter.x = GRID.x + starter.col * GRID.cell; + starter.y = GRID.y + starter.row * GRID.cell; + starter.w = starter.wCells * GRID.cell; + starter.h = starter.hCells * GRID.cell; + game.facilities.starterGenerator = starter; +} + +function createStarterPowerLayout(game) { + game.powerWires = new Map(); + const routeCells = [ + [5, 6], [4, 6], [3, 6], [2, 6], [1, 6], [0, 6], [0, 5], [0, 4], + [8, 7], [8, 8], [9, 8], [10, 8], [11, 8], [12, 8], [12, 9], + [13, 8], [14, 8], [15, 8], [16, 8], [17, 8], [18, 8], [18, 9] + ]; + for (const [col, row] of routeCells) addPowerWire(game, col, row, 'powerWire', true); +} + +function validateStarterPowerLayout(game) { + const bodyCells = new Set(); + for (const f of Object.values(game.facilities || {})) { + if (!f.gridPlaced) continue; + for (let dr = 0; dr < (f.hCells || 1); dr += 1) { + for (let dc = 0; dc < (f.wCells || 1); dc += 1) bodyCells.add(key(f.col + dc, f.row + dr)); + } + } + for (const k of game.powerWires.keys()) { + if (game.blockedCells.has(k) || bodyCells.has(k)) throw new Error(`Invalid starter power wire at ${k}`); + } + for (const id of ['starterGenerator', 'mixer', 'trash', 'truck']) { + if (!game.facilities[id]) throw new Error(`Missing starter power facility: ${id}`); + } +} + function installInitialConveyorBackbone(game) { const cells = INITIAL_CONVEYOR_BACKBONE.map(([col, row]) => ({ col, row })); for (const { col, row } of cells) addConveyorTile(game, col, row); @@ -347,11 +457,14 @@ export function resetLayout(game) { } game.conveyorTiles.clear(); game.conveyorMeta.clear(); + game.powerWires = new Map(); + game.footbridges = new Map(); game.maintenanceRooms = []; game.repairmen = []; game.blockedCells = new Set(); installInitialConveyorBackbone(game); game.facilities = defaultFacilities(); + installStarterGenerator(game); game.scanners = INITIAL_SCANNERS.map(scanner => ({ ...scanner, keys: scanner.keys ? { left: { ...scanner.keys.left }, right: { ...scanner.keys.right } } : null, queue: [] })); game.eggFarms = INITIAL_EGG_FARMS.map(farm => { const nextSpawn = nextSpawnDelay(farm); @@ -363,10 +476,16 @@ export function resetLayout(game) { // scanner body/ports, farm output, and machine receiver. This guarantees that // the starter line is always continuous from Egg Farm -> S1 -> S2 -> exits even // when roughly 3% of the initial map is blocked. + createStarterPowerLayout(game); const reserved = protectedInitialCells(game); + for (const k of game.powerWires.keys()) reserved.add(k); game.blockedCells = generateBlockedCells({ seed: game.runSeed, targetRatio: BALANCE.map.blockedRatio, reserved: [...reserved] }); scrubBlockedCellsFromInitialBackbone(game); installInitialConveyorBackbone(game); + createStarterPowerLayout(game); + validateStarterPowerLayout(game); + game.power = { dirty: true, elapsed: 0, routes: new Map(), assignments: new Map(), sources: new Map(), underVoltage: new Set() }; + game.walkabilityVersion = (game.walkabilityVersion || 0) + 1; game.routingVersion = (game.routingVersion || 0) + 1; game.factoryGraph = null; game.factoryGraphCommittedVersion = -1; diff --git a/src/core/text.js b/src/core/text.js index 1835764..11de26e 100644 --- a/src/core/text.js +++ b/src/core/text.js @@ -32,8 +32,11 @@ export const TEXT = { routeLabels: { mixer: 'MIXER', truck: 'TRUCK', + inboundTruck: 'INBOUND', trash: 'WASTE', sausageMaker: 'SAUSAGE', + biomassGenerator: 'BIOMASS', + coalGenerator: 'COAL GEN', scanner: 'NEXT', 'scanner-role-1': 'NEXT', input: 'IN' @@ -71,7 +74,13 @@ export function buildToolFlavorText(id) { manualScanner: 'A manual checkpoint. The operator is the algorithm.', mixer: 'Makes meat when OUT is clear; otherwise falls back to direct sale.', trash: 'A polite shredder for poop and other regrets.', - truck: 'Ships target chicks and all sausages. Wrong live cargo still leaves a paper trail.', + truck: 'Shipping Truck. Ships target chicks and all sausages.', + powerWire: 'Overlay wire. Loses 0.5V per tile.', + insulatedPowerWire: 'Overlay wire. Loses 0.15V per tile.', + footbridge: 'Lets repair workers cross conveyor cells.', + inboundTruck: 'Imports coal and zunda mochi during the day.', + biomassGenerator: 'Fuel generator. Chicks and poop add timed voltage.', + coalGenerator: 'Fuel generator. Coal adds timed voltage.', sausageMaker: 'A 3×3 grid machine. Left inlet takes 2 meat; right outlet emits 1 sausage.', maintenanceRoom: '2×2 repair bay. Each room dispatches one repairman during the day.' }; diff --git a/src/game.js b/src/game.js index 2b73711..1852aed 100644 --- a/src/game.js +++ b/src/game.js @@ -14,6 +14,8 @@ import { CARD_DEFS, cardById, conveyorSpeedForGame, createCardSystem, debugCardT import { floating, shockwave, sparkBurst, updateEffects } from './systems/effects.js'; import { EVENT_TARGETS, createEventOffer, rollEventOffer, activateEventOffer, clearActiveEvent, resolveEvent, eventDaySeconds, fairiesTributeMultiplierFromEvent } from './systems/events.js'; import { ensureMaintenanceState, activateRepairmanForDay, deactivateRepairman, conveyorSpeedFactorForKey } from './systems/maintenance.js'; +import { updatePowerSystem, recalculatePowerNetwork } from './systems/power.js'; +import { startInboundOrders, settleUndeliveredImports } from './systems/imports.js'; const canvas = document.getElementById('gameCanvas'); const ctx = canvas.getContext('2d'); @@ -42,7 +44,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'), 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'), sausageMaker: document.getElementById('buildSausageMakerButton'), maintenanceRoom: document.getElementById('buildMaintenanceRoomButton'), erase: document.getElementById('eraseButton'), undo: document.getElementById('undoButton'), redo: document.getElementById('redoButton') + nextTurn: document.getElementById('nextTurnButton'), conveyor: document.getElementById('buildConveyorButton'), boostConveyor: document.getElementById('buildBoostConveyorButton'), powerWire: document.getElementById('buildPowerWireButton'), insulatedPowerWire: document.getElementById('buildInsulatedPowerWireButton'), powerOverlay: document.getElementById('powerOverlayButton'), footbridge: document.getElementById('buildFootbridgeButton'), eggFarm: document.getElementById('buildEggFarmButton'), autoScanner: document.getElementById('buildAutoScannerButton'), manualScanner: document.getElementById('buildManualScannerButton'), mixer: document.getElementById('buildMixerButton'), trash: document.getElementById('buildTrashButton'), truck: document.getElementById('buildTruckButton'), inboundTruck: document.getElementById('buildInboundTruckButton'), sausageMaker: document.getElementById('buildSausageMakerButton'), biomassGenerator: document.getElementById('buildBiomassGeneratorButton'), coalGenerator: document.getElementById('buildCoalGeneratorButton'), maintenanceRoom: document.getElementById('buildMaintenanceRoomButton'), erase: document.getElementById('eraseButton'), undo: document.getElementById('undoButton'), redo: document.getElementById('redoButton') } }; @@ -51,13 +53,19 @@ function initializeStaticText() { const buttonToolMap = { conveyor: 'conveyor', boostConveyor: 'boostConveyor', + powerWire: 'powerWire', + insulatedPowerWire: 'insulatedPowerWire', + footbridge: 'footbridge', eggFarm: 'eggFarm', autoScanner: 'autoScanner', manualScanner: 'manualScanner', mixer: 'mixer', trash: 'trash', truck: 'truck', + inboundTruck: 'inboundTruck', sausageMaker: 'sausageMaker', + biomassGenerator: 'biomassGenerator', + coalGenerator: 'coalGenerator', maintenanceRoom: 'maintenanceRoom' }; for (const [buttonKey, toolId] of Object.entries(buttonToolMap)) { @@ -285,6 +293,10 @@ function startNextTurn() { } ensureMaintenanceState(game); commitFactoryGraphForDay(game); + const powerState = recalculatePowerNetwork(game); + if (powerState.underVoltage?.size) { + floating(game, canvas.width / 2 - game.view.x, 136 - game.view.y, `${powerState.underVoltage.size} UNDER-VOLTAGE`, THEME.warn); + } activateRepairmanForDay(game); const priorProfit = Math.max(0, Math.floor(game.lastResult?.profit ?? game.stats?.profit ?? 0)); const tribute = collectFairiesTribute(game, Math.max(1, game.turn || 1), priorProfit); @@ -303,15 +315,17 @@ function startNextTurn() { game.floatingTexts = []; game.shake = { time: 0, strength: 0 }; game.truckCargo = []; + game.shippedCargo = []; resetCameraToFactoryStart(); game.stats = newTurnStats(); + startInboundOrders(game); game.stats.fairiesTribute = game.lastStartFees?.fairiesTribute || 0; if (game.stats.fairiesTribute > 0) { game.stats.penalty += game.stats.fairiesTribute; game.stats.profit -= game.stats.fairiesTribute; } for (const scanner of game.scanners) { scanner.queue = []; scanner.cooldown = 0; } - for (const farm of game.eggFarms) { farm.nextSpawn = chicks.currentSpawnDelay(farm); farm.lastInterval = farm.nextSpawn; farm.shutterProgress = 0; farm.shutterSparked = false; } + for (const farm of game.eggFarms) { farm.nextSpawn = chicks.currentSpawnDelay(farm); farm.lastInterval = farm.nextSpawn; farm.shutterProgress = 0; farm.shutterSparked = false; farm.zundaBoostTime = 0; } game.buildTool = null; game.selected = null; game.groupDrag = null; @@ -325,6 +339,11 @@ function startNextTurn() { function completeTurn() { if (game.phase !== 'running') return; + settleUndeliveredImports(game); + for (const facility of Object.values(game.facilities || {})) { + if (facility.fuelPulses) facility.fuelPulses = []; + } + for (const farm of game.eggFarms || []) farm.zundaBoostTime = 0; const ship = settleTruckRevenue(game); const activeEvent = game.eventActive ? { ...game.eventActive } : null; const eventResult = resolveEvent(game); @@ -356,6 +375,7 @@ function completeTurn() { function enterBuildPhase() { game.phase = 'build'; game.truckCargo = []; + game.shippedCargo = []; game.chicks = []; game.shutdownTimeLeft = 0; game.cleanupTimer = null; @@ -415,6 +435,7 @@ function update(timestamp) { game.lastTimestamp = timestamp; game.frameId = (game.frameId || 0) + 1; chicks.beginFrame?.(game.frameId); + updatePowerSystem(game, dt); if (game.phase === 'running') chicks.updateRunning(dt, { closeFarmShutters, completeTurn }); updateEffects(game, dt, canvas, build.equipmentHitBoxes); chicks.updateCongestion(game.frameId); @@ -591,6 +612,7 @@ function clickSelect(event) { const obj = build.selectedObject(); if (obj?.type === 'scanner' && obj.kind === 'manual') build.showManualScannerMenu(obj); if (obj?.type === 'scanner' && obj.kind === 'auto') build.showAutoScannerMenu(obj); + if (obj?.type === 'facility' && (obj.baseId || obj.id) === 'inboundTruck') build.showInboundTruckMenu(obj); } function cancelBuildAction() { @@ -807,7 +829,7 @@ document.addEventListener('pointerdown', event => { }, 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.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.sausageMaker.addEventListener('click', () => build.setBuildTool('sausageMaker')); ui.buttons.maintenanceRoom.addEventListener('click', () => build.setBuildTool('maintenanceRoom')); 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.powerWire?.addEventListener('click', () => build.setBuildTool('powerWire')); ui.buttons.insulatedPowerWire?.addEventListener('click', () => build.setBuildTool('insulatedPowerWire')); ui.buttons.powerOverlay?.addEventListener('click', () => { game.powerOverlay = !game.powerOverlay; uiSystem.updatePanels(); }); ui.buttons.footbridge?.addEventListener('click', () => build.setBuildTool('footbridge')); 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.inboundTruck?.addEventListener('click', () => build.setBuildTool('inboundTruck')); ui.buttons.sausageMaker.addEventListener('click', () => build.setBuildTool('sausageMaker')); ui.buttons.biomassGenerator?.addEventListener('click', () => build.setBuildTool('biomassGenerator')); ui.buttons.coalGenerator?.addEventListener('click', () => build.setBuildTool('coalGenerator')); ui.buttons.maintenanceRoom.addEventListener('click', () => build.setBuildTool('maintenanceRoom')); ui.buttons.erase.addEventListener('click', () => build.setBuildTool('erase')); ui.debug.setDay?.addEventListener('click', debugSetDay); ui.debug.zeroTimer?.addEventListener('click', debugZeroTimer); ui.debug.grantCard?.addEventListener('click', debugGrantSelectedCard); @@ -830,6 +852,13 @@ 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 (game.phase === 'build' && game.buildTool === 'footbridge' && name === 'r') { + event.preventDefault(); + game.footbridgeOrientation = game.footbridgeOrientation === 'horizontal' ? 'vertical' : 'horizontal'; + floating(game, canvas.width / 2 - game.view.x, 86 - game.view.y, `FOOTBRIDGE ${game.footbridgeOrientation.toUpperCase()}`, THEME.green); + uiSystem.updatePanels(); + return; + } if (event.key === 'Escape' && game.cardTargetPick?.pending) { event.preventDefault(); if (game.cardTargetPick.mode === 'autoScannerMenu') cancelBuildAction(); diff --git a/src/render/draw.js b/src/render/draw.js index e5111b2..7042e77 100644 --- a/src/render/draw.js +++ b/src/render/draw.js @@ -6,6 +6,7 @@ import { upgradedTruckPrice, upgradedSausagePrice } from '../systems/economy.js' import { cardTargetBounds, conveyorSpeedForGame } from '../systems/cards.js'; import { wearRatio, conveyorSpeedFactorForKey } from '../systems/maintenance.js'; import { facilityBodyPortPoint } from '../core/entities.js'; +import { powerDetailsFor, currentGeneratorOutputV } from '../systems/power.js'; // Optional art overlay hook. // Keep external art probing disabled by default so a clean checkout does not emit 404s for missing PNGs. @@ -67,6 +68,7 @@ function makeCanvas(width, height) { } export function drawAll(ctx, canvas, game, helpers) { + ctx.__gameRef = game; ctx.imageSmoothingEnabled = false; ctx.clearRect(0, 0, canvas.width, canvas.height); drawBackground(ctx, canvas, game); @@ -79,6 +81,8 @@ export function drawAll(ctx, canvas, game, helpers) { drawGrid(ctx, game); drawExpansionOffers(ctx, game); drawConveyors(ctx, game); + drawPowerWires(ctx, game); + drawFootbridges(ctx, game); drawScannerConnectors(ctx, game); drawMaintenanceRooms(ctx, game); drawFacilities(ctx, game); @@ -565,6 +569,54 @@ function drawConveyors(ctx, game) { ctx.restore(); } +function drawPowerWires(ctx, game) { + if (!game.powerWires?.size) return; + ctx.save(); + for (const [k, wire] of game.powerWires) { + const p = parseKey(k); + const c = cellCenter(p.col, p.row); + const insulated = wire.kind === 'insulatedPowerWire'; + ctx.globalAlpha = game.powerOverlay || game.phase === 'build' ? 0.95 : 0.32; + ctx.strokeStyle = insulated ? '#d7eef7' : '#1e252b'; + ctx.lineWidth = insulated ? 8 : 5; + ctx.lineCap = 'round'; + ctx.beginPath(); + ctx.moveTo(c.x - 15, c.y); + ctx.lineTo(c.x + 15, c.y); + ctx.moveTo(c.x, c.y - 15); + ctx.lineTo(c.x, c.y + 15); + ctx.stroke(); + ctx.strokeStyle = insulated ? '#2c8ec4' : '#f3c33d'; + ctx.lineWidth = insulated ? 3 : 2; + ctx.stroke(); + if (game.powerOverlay) label(ctx, c.x, c.y - 14, insulated ? 'I' : 'V', insulated ? '#2c8ec4' : THEME.warn); + } + ctx.restore(); +} + +function drawFootbridges(ctx, game) { + if (!game.footbridges?.size) return; + ctx.save(); + for (const k of game.footbridges.keys()) { + const p = parseKey(k); + const c = cellCenter(p.col, p.row); + ctx.fillStyle = 'rgba(210,222,228,.52)'; + ctx.strokeStyle = '#e6edf2'; + ctx.lineWidth = 3; + rect(ctx, c.x - 20, c.y - 14, 40, 28, true, true); + ctx.strokeStyle = '#34404a'; + ctx.lineWidth = 2; + ctx.beginPath(); + ctx.moveTo(c.x - 18, c.y - 9); + ctx.lineTo(c.x + 18, c.y - 9); + ctx.moveTo(c.x - 18, c.y + 9); + ctx.lineTo(c.x + 18, c.y + 9); + ctx.stroke(); + drawSelection(ctx, game, 'footbridge', k, c.x, c.y, 44, 34); + } + ctx.restore(); +} + export function drawAnimatedConveyorStripes(ctx, game, center, tileKey, meta = {}, ratio = 0) { const boost = meta.kind === 'boostConveyor'; @@ -1065,11 +1117,22 @@ function drawFacilityDetails(ctx, f, accent = THEME.green) { rect(ctx, f.x + f.w - 44, f.y + 12, 30, 22, true, true); ctx.fillStyle = accent; ctx.fillRect(f.x + f.w - 38, f.y + 18, 6, 6); ctx.fillStyle = THEME.danger; ctx.fillRect(f.x + f.w - 27, f.y + 18, 6, 6); + const power = powerDetailsFor(nullSafeGame(ctx), f); + if (power && power.receivedV < power.minVoltage) { + ctx.fillStyle = THEME.danger; + ctx.font = '900 18px ui-monospace, monospace'; + ctx.textAlign = 'center'; + ctx.fillText('X⚡', f.x + f.w - 28, f.y + 52); + } ctx.strokeStyle = '#47515f'; ctx.lineWidth = 3; ctx.beginPath(); ctx.moveTo(f.x + 12, f.y + f.h - 35); ctx.bezierCurveTo(f.x + 42, f.y + f.h - 52, f.x + 60, f.y + f.h - 20, f.x + 88, f.y + f.h - 38); ctx.stroke(); ctx.restore(); } +function nullSafeGame(ctx) { + return ctx.__gameRef || {}; +} + function drawFacilityTexture(ctx, image, f, maxCells = 1.45) { if (!image?.loaded) return false; // Keep external-machine sprites at roughly the same pixel density as a grid object. @@ -1122,12 +1185,53 @@ function drawFacilities(ctx, game) { else if (kind === 'trash') drawTrash(ctx, game, f); else if (kind === 'truck') drawTruck(ctx, game, f); else if (kind === 'sausageMaker') drawSausageMaker(ctx, game, f); + else if (kind === 'starterGenerator' || kind === 'biomassGenerator' || kind === 'coalGenerator') drawGenerator(ctx, game, f, kind); + else if (kind === 'inboundTruck') drawInboundTruck(ctx, game, f); } for (const f of Object.values(game.facilities || {})) { drawFacilityReceiver(ctx, game, f); drawFacilityOutput(ctx, game, f); } } + +function drawGenerator(ctx, game, f, kind) { + ctx.save(); + const accent = kind === 'starterGenerator' ? THEME.warn : (kind === 'biomassGenerator' ? THEME.green : '#d0d5dc'); + drawMetalPanel(ctx, f.x, f.y, f.w, f.h, '#252b31', THEME.ink, accent); + drawFacilityDetails(ctx, f, accent); + ctx.fillStyle = THEME.white; + ctx.font = '900 13px ui-monospace, monospace'; + ctx.textAlign = 'left'; + const title = kind === 'starterGenerator' ? 'STARTER' : (kind === 'biomassGenerator' ? 'BIOMASS' : 'COAL GEN'); + ctx.fillText(title, f.x + 12, f.y + 18); + ctx.font = '900 18px ui-monospace, monospace'; + ctx.textAlign = 'center'; + ctx.fillStyle = accent; + ctx.fillText(kind === 'biomassGenerator' ? 'LEAF' : '⚡', f.x + f.w / 2, f.y + f.h / 2 - 4); + ctx.font = '900 12px ui-monospace, monospace'; + ctx.fillText(`${currentGeneratorOutputV(f).toFixed(0)}V`, f.x + f.w / 2, f.y + f.h / 2 + 20); + drawSelection(ctx, game, 'facility', f.id, f.x + f.w / 2, f.y + f.h / 2, f.w + 12, f.h + 12); + ctx.restore(); +} + +function drawInboundTruck(ctx, game, t) { + ctx.save(); + drawExternalDuct(ctx, t); + drawMetalPanel(ctx, t.x, t.y, t.w, t.h, '#29313a', THEME.ink, '#8bd0ff'); + drawFacilityDetails(ctx, t, '#8bd0ff'); + ctx.fillStyle = THEME.white; + ctx.font = '900 14px ui-monospace, monospace'; + ctx.textAlign = 'left'; + ctx.fillText('INBOUND', t.x + 14, t.y + 18); + ctx.fillStyle = '#8bd0ff'; + ctx.font = '900 11px ui-monospace, monospace'; + ctx.fillText(`COAL ${t.inboundOrder?.coalPerDay || 0} / ZUNDA ${t.inboundOrder?.zundaMochiPerDay || 0}`, t.x + 14, t.y + 36); + ctx.fillStyle = 'rgba(10,18,24,.9)'; + rect(ctx, t.x + 18, t.y + 54, t.w - 36, t.h - 70, true, true); + label(ctx, t.x + t.w / 2, t.y + t.h - 18, `Q ${t.inboundQueue?.length || 0}`, '#8bd0ff'); + 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 drawFacilityOutput(ctx, game, f) { const p = facilityOutputCell(game, f); if (!p) return; @@ -1263,7 +1367,7 @@ function drawTruck(ctx, game, t = game.facilities.truck) { drawMetalPanel(ctx, t.x, t.y, t.w, t.h, '#343128', THEME.ink, THEME.truckPink); drawFacilityDetails(ctx, t, THEME.truckPink); ctx.fillStyle = THEME.white; ctx.font = '900 14px ui-monospace, monospace'; ctx.textAlign = 'left'; - ctx.fillText(`TRUCK L${t.level}`, t.x + 14, t.y + 18); + ctx.fillText(`SHIP TRUCK L${t.level}`, t.x + 14, t.y + 18); ctx.fillStyle = THEME.muted; ctx.font = '900 10px ui-monospace, monospace'; ctx.fillText(`UNIT ${yen(upgradedTruckPrice(game))}`, t.x + 14, t.y + 34); drawFacilityTexture(ctx, assets.truck, t, 2.0); @@ -1296,6 +1400,8 @@ function drawChick(ctx, chick, active, game) { if (chick.sex === 'poop') drawPoop(ctx, chick.x, y, chick.radius); else if (chick.sex === 'meat') drawMeatItem(ctx, chick.x, y, chick.radius); else if (chick.sex === 'sausage') drawSausageItem(ctx, chick.x, y, chick.radius); + else if (chick.sex === 'coal') drawCoalItem(ctx, chick.x, y, chick.radius); + else if (chick.sex === 'zundaMochi') drawZundaItem(ctx, chick.x, y, chick.radius); else { const img = chick.sex === 'male' ? assets.chickMale : assets.chickFemale; const spriteDrawn = drawImageIfLoaded(ctx, img, chick.x - chick.radius, y - chick.radius, chick.radius * 2, chick.radius * 2); @@ -1367,6 +1473,31 @@ function drawSausageItem(ctx, x, y, radius = 10) { } ctx.restore(); } +function drawCoalItem(ctx, x, y, radius = 10) { + ctx.save(); + ctx.fillStyle = '#111318'; + ctx.strokeStyle = '#050609'; + ctx.lineWidth = 3; + rect(ctx, x - radius, y - radius * 0.85, radius * 2, radius * 1.7, true, true); + ctx.fillStyle = '#3b424b'; + ctx.fillRect(x - radius * 0.5, y - radius * 0.5, radius * 0.8, radius * 0.35); + ctx.restore(); +} +function drawZundaItem(ctx, x, y, radius = 10) { + ctx.save(); + ctx.fillStyle = '#6fce57'; + ctx.strokeStyle = THEME.ink; + ctx.lineWidth = 3; + ctx.beginPath(); + ctx.moveTo(x, y - radius); + ctx.lineTo(x + radius, y); + ctx.lineTo(x, y + radius); + ctx.lineTo(x - radius, y); + ctx.closePath(); + ctx.fill(); + ctx.stroke(); + ctx.restore(); +} function drawTinyChick(ctx, x, y, sex, warning = false) { ctx.save(); const bodyColor = sex === 'male' ? THEME.male : THEME.female; diff --git a/src/systems/buildSystem.js b/src/systems/buildSystem.js index 425d4bc..07224cd 100644 --- a/src/systems/buildSystem.js +++ b/src/systems/buildSystem.js @@ -13,6 +13,8 @@ import { record } from './history.js'; import { floating, eraseEffect } from './effects.js'; import { createSelectionSystem } from './selectionSystem.js'; import { ensureMaintenanceState, remainingPercent, performanceFactor, autoScannerDelayMultiplier, eggSpawnDelayMultiplier, facilityProcessingDelay, durabilityCapFor } from './maintenance.js'; +import { markPowerDirty, powerDetailsFor } from './power.js'; +import { boostOutputCapacityPerSecond, farmTheoreticalRatePerSecond, qualityForRate, qualityGrade } from './farmQuality.js'; export function createBuildSystem({ game, ui, canvas, canvasPoint, onUpdatePanels }) { function updatePanels() { if (onUpdatePanels) onUpdatePanels(); } @@ -42,6 +44,8 @@ export function createBuildSystem({ game, ui, canvas, canvasPoint, onUpdatePanel game.blockedCells = shiftRowIndexedSet(game.blockedCells, rowDelta); game.conveyorTiles = shiftRowIndexedSet(game.conveyorTiles, rowDelta); game.conveyorMeta = shiftRowIndexedMap(game.conveyorMeta, rowDelta); + game.powerWires = shiftRowIndexedMap(game.powerWires || new Map(), rowDelta); + game.footbridges = shiftRowIndexedMap(game.footbridges || new Map(), rowDelta); for (const farm of game.eggFarms || []) farm.row += rowDelta; for (const scanner of game.scanners || []) scanner.row += rowDelta; for (const facility of Object.values(game.facilities || {})) { @@ -59,6 +63,8 @@ export function createBuildSystem({ game, ui, canvas, canvasPoint, onUpdatePanel game.blockedCells = shiftColIndexedSet(game.blockedCells, colDelta); game.conveyorTiles = shiftColIndexedSet(game.conveyorTiles, colDelta); game.conveyorMeta = shiftColIndexedMap(game.conveyorMeta, colDelta); + game.powerWires = shiftColIndexedMap(game.powerWires || new Map(), colDelta); + game.footbridges = shiftColIndexedMap(game.footbridges || new Map(), colDelta); for (const farm of game.eggFarms || []) farm.col += colDelta; for (const scanner of game.scanners || []) scanner.col += colDelta; for (const room of game.maintenanceRooms || []) room.col += colDelta; @@ -245,16 +251,22 @@ export function createBuildSystem({ game, ui, canvas, canvasPoint, onUpdatePanel } function gridFacilityPlacementBlocked(id, col, row, moving = null) { - const draft = { baseId: id, id, gridPlaced: true, col, row, wCells: id === 'sausageMaker' ? 3 : 1, hCells: id === 'sausageMaker' ? 3 : 1 }; + const def = BALANCE.facilities[id] || {}; + const draft = { baseId: id, id, gridPlaced: true, col, row, wCells: def.footprint?.cols || (id === 'sausageMaker' ? 3 : 1), hCells: def.footprint?.rows || (id === 'sausageMaker' ? 3 : 1) }; for (const cell of gridFacilityFootprintCells(draft)) { if (!pointInGrid(cell.col, cell.row) || isBlockedCell(cell.col, cell.row) || isEquipmentCell(cell.col, cell.row, moving) || facilityPortAt(cell.col, cell.row, moving)) return true; } - if (id === 'sausageMaker') { + if (id === 'sausageMaker' || id === 'biomassGenerator' || id === 'coalGenerator') { const input = { col: col - 1, row: row + 1 }; - const output = { col: col + 3, row: row + 1 }; - if (!pointInGrid(input.col, input.row) || !pointInGrid(output.col, output.row)) return true; - if (isBlockedCell(input.col, input.row) || isBlockedCell(output.col, output.row)) return true; - for (const port of [input, output]) { + const output = { col: col + (draft.wCells || 1), row: row + 1 }; + const biomassPorts = [ + input, + { col: col + 1, row: row - 1 }, + { col: col + 1, row: row + (draft.hCells || 3) } + ]; + const ports = id === 'sausageMaker' ? [input, output] : (id === 'biomassGenerator' ? biomassPorts : [input]); + for (const port of ports) { + if (!pointInGrid(port.col, port.row) || isBlockedCell(port.col, port.row)) return true; const portGridFacility = gridFacilityAt(game, port.col, port.row); if (portGridFacility && portGridFacility.id !== moving?.ref?.id) return true; if (farmAt(game, port.col, port.row) || scannerAt(game, port.col, port.row) || maintenanceRoomAt(game, port.col, port.row)) return true; @@ -269,6 +281,8 @@ export function createBuildSystem({ game, ui, canvas, canvasPoint, onUpdatePanel if (game.selected.type === 'eggFarm') return game.eggFarms.find(f => f.id === game.selected.id) || null; if (game.selected.type === 'scanner') return game.scanners.find(s => s.id === game.selected.id) || null; if (game.selected.type === 'conveyor') return game.conveyorTiles.has(game.selected.id) ? { type: 'conveyor', id: game.selected.id } : null; + if (game.selected.type === 'powerWire') return game.powerWires?.has(game.selected.id) ? { type: 'powerWire', id: game.selected.id } : null; + if (game.selected.type === 'footbridge') return game.footbridges?.has(game.selected.id) ? { type: 'footbridge', id: game.selected.id } : null; if (game.selected.type === 'facility') return game.facilities[game.selected.id] || null; if (game.selected.type === 'maintenanceRoom') return (game.maintenanceRooms || []).find(r => r.id === game.selected.id) || null; return null; @@ -279,6 +293,8 @@ export function createBuildSystem({ game, ui, canvas, canvasPoint, onUpdatePanel 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 game.conveyorMeta.get(obj.id)?.kind === 'boostConveyor' ? equipmentName('boostConveyor') : equipmentName('conveyor'); + if (obj.type === 'powerWire') return game.powerWires?.get(obj.id)?.kind === 'insulatedPowerWire' ? equipmentName('insulatedPowerWire') : equipmentName('powerWire'); + if (obj.type === 'footbridge') return equipmentName('footbridge'); if (obj.type === 'facility') return obj.name; if (obj.type === 'maintenanceRoom') return `${equipmentName('maintenanceRoom')} #${obj.id}`; return 'Equipment'; @@ -286,6 +302,8 @@ export function createBuildSystem({ game, ui, canvas, canvasPoint, onUpdatePanel function equipmentPrice(hit) { if (hit?.type === 'conveyor') return game.conveyorMeta?.get(hit.oldKey || hit.ref?.id)?.price || equipmentBasePrice(hit); + if (hit?.type === 'powerWire') return game.powerWires?.get(hit.oldKey || hit.ref?.id)?.price || 0; + if (hit?.type === 'footbridge') return game.footbridges?.get(hit.oldKey || hit.ref?.id)?.price || BALANCE.facilities.footbridge.price; return equipmentBasePrice(hit); } @@ -307,6 +325,7 @@ export function createBuildSystem({ game, ui, canvas, canvasPoint, onUpdatePanel function lastProtectedSaleReason(hit) { const obj = hit?.ref || hit || {}; + if (obj.unsellable || (obj.baseId || obj.id) === 'starterGenerator') return 'Starter Generator cannot be sold.'; if (hit?.type === 'eggFarm' || obj.type === 'eggFarm') { return game.eggFarms.length <= 1 ? 'Cannot sell the last EGG FARM.' : ''; } @@ -643,6 +662,50 @@ export function createBuildSystem({ game, ui, canvas, canvasPoint, onUpdatePanel conveyorDragRecordedDirectionEdit = false; } + function overlayPlacementBlocked(col, row) { + return !pointInGrid(col, row) || isBlockedCell(col, row) || farmAt(game, col, row) || scannerAt(game, col, row) || maintenanceRoomAt(game, col, row) || gridFacilityAt(game, col, row); + } + + function buildPowerWireCell(cell, kind) { + if (!cell) return { ok: false, reason: TEXT.fail.outOfGrid }; + if (overlayPlacementBlocked(cell.col, cell.row)) return { ok: false, reason: 'Power wire cannot cross equipment or blocked ground.' }; + const k = key(cell.col, cell.row); + const price = BALANCE.power.wires[kind]?.price || buildPrice(kind, game); + const existing = game.powerWires?.get(k); + const existingPrice = existing ? (BALANCE.power.wires[existing.kind]?.price || 0) : 0; + const delta = price - existingPrice; + if (delta > 0 && game.cash < delta) return { ok: false, reason: TEXT.fail.notEnoughCash }; + record(game); + if (delta > 0) spendCash(game, delta); + if (delta < 0) refundCash(game, -delta); + game.powerWires = game.powerWires || new Map(); + game.powerWires.set(k, { kind, price, builtSession: game.buildSession }); + markPowerDirty(game); + game.selected = { type: 'powerWire', id: k }; + updatePanels(); + return { ok: true }; + } + + function buildFootbridgeCell(cell) { + if (!cell) return fail(TEXT.fail.outOfGrid); + const vertical = (game.footbridgeOrientation || 'vertical') !== 'horizontal'; + const cells = vertical + ? [cell.row - 1, cell.row, cell.row + 1].map(row => ({ col: cell.col, row })) + : [cell.col - 1, cell.col, cell.col + 1].map(col => ({ col, row: cell.row })); + if (cells.some(part => overlayPlacementBlocked(part.col, part.row))) return fail('Footbridge cannot cross equipment or blocked ground.'); + if (cells.some(part => game.footbridges?.has(key(part.col, part.row)))) return fail('Footbridge already covers that cell.'); + const cost = buildPrice('footbridge', game); + if (game.cash < cost) return fail(TEXT.fail.notEnoughCash); + const bridgeId = `footbridge:${game.nextId++}`; + record(game); + spendCash(game, cost); + game.footbridges = game.footbridges || new Map(); + for (const part of cells) game.footbridges.set(key(part.col, part.row), { kind: 'footbridge', bridgeId, orientation: vertical ? 'vertical' : 'horizontal', price: cost, builtSession: game.buildSession }); + game.walkabilityVersion = (game.walkabilityVersion || 0) + 1; + game.selected = { type: 'footbridge', id: key(cell.col, cell.row) }; + updatePanels(); + } + function buildAtCell(cell) { if (!cell) return fail(TEXT.fail.outOfGrid); const { col, row } = cell; @@ -652,6 +715,12 @@ export function createBuildSystem({ game, ui, canvas, canvasPoint, onUpdatePanel refreshRoutingAfterEdit(game); return; } + if (game.buildTool === 'powerWire' || game.buildTool === 'insulatedPowerWire') { + const result = buildPowerWireCell(cell, game.buildTool); + if (!result.ok) return fail(result.reason || TEXT.fail.cellOccupied); + return; + } + if (game.buildTool === 'footbridge') return buildFootbridgeCell(cell); if (isBlockedCell(col, row)) return fail('Cannot build on blocked ground.'); if (facilityPortAt(col, row) && !['conveyor', 'boostConveyor'].includes(game.buildTool)) return fail('Machine port must remain open for a conveyor.'); if (game.buildTool === 'maintenanceRoom') { @@ -670,7 +739,7 @@ export function createBuildSystem({ game, ui, canvas, canvasPoint, onUpdatePanel function buildAtPoint(p) { const cell = pointToCell(p.x, p.y); - if (['conveyor', 'boostConveyor', 'eggFarm', 'manualScanner', 'autoScanner', 'maintenanceRoom', ...GRID_FACILITY_IDS].includes(game.buildTool)) return buildAtCell(cell); + if (['conveyor', 'boostConveyor', 'powerWire', 'insulatedPowerWire', 'footbridge', 'eggFarm', 'manualScanner', 'autoScanner', 'maintenanceRoom', ...GRID_FACILITY_IDS.filter(id => id !== 'starterGenerator')].includes(game.buildTool)) return buildAtCell(cell); if (EDGE_FACILITY_IDS.includes(game.buildTool)) return buildFacility(p, game.buildTool); } @@ -712,6 +781,8 @@ export function createBuildSystem({ game, ui, canvas, canvasPoint, onUpdatePanel room.price = cost; game.maintenanceRooms = game.maintenanceRooms || []; game.maintenanceRooms.push(room); + markPowerDirty(game); + game.walkabilityVersion = (game.walkabilityVersion || 0) + 1; game.selected = { type: 'maintenanceRoom', id: room.id }; const center = roomCenter(room); floating(game, center.x, center.y, `-${yen(cost)}`, THEME.ink); @@ -734,6 +805,8 @@ export function createBuildSystem({ game, ui, canvas, canvasPoint, onUpdatePanel spendCash(game, cost); game.facilities[storageId] = f; refreshRoutingAfterEdit(game); + markPowerDirty(game); + game.walkabilityVersion = (game.walkabilityVersion || 0) + 1; game.selected = { type: 'facility', id: storageId }; floating(game, f.x + f.w / 2, f.y + f.h / 2, `-${yen(cost)}`, THEME.ink); } @@ -754,6 +827,8 @@ export function createBuildSystem({ game, ui, canvas, canvasPoint, onUpdatePanel spendCash(game, cost); game.facilities[storageId] = f; refreshRoutingAfterEdit(game); + markPowerDirty(game); + game.walkabilityVersion = (game.walkabilityVersion || 0) + 1; game.selected = { type: 'facility', id: storageId }; floating(game, p.x, p.y - 14, `-${yen(cost)}`, THEME.ink); } @@ -772,7 +847,24 @@ export function createBuildSystem({ game, ui, canvas, canvasPoint, onUpdatePanel const saleReason = lastProtectedSaleReason(hit); if (saleReason) return fail(saleReason); record(game); - if (hit.type === 'conveyor') { + if (hit.type === 'footbridge') { + const c = parseKey(hit.oldKey); + const center = cellCenter(c.col, c.row); + refundOrSell(hit, center.x, center.y); + const bridgeId = game.footbridges.get(hit.oldKey)?.bridgeId; + for (const [k, bridge] of game.footbridges || new Map()) { + if (!bridgeId || bridge.bridgeId === bridgeId) game.footbridges.delete(k); + } + game.walkabilityVersion = (game.walkabilityVersion || 0) + 1; + eraseEffect(game, center.x, center.y); + } else if (hit.type === 'powerWire') { + const c = parseKey(hit.oldKey); + const center = cellCenter(c.col, c.row); + refundOrSell(hit, center.x, center.y); + game.powerWires.delete(hit.oldKey); + markPowerDirty(game); + eraseEffect(game, center.x, center.y); + } else if (hit.type === 'conveyor') { const c = parseKey(hit.oldKey); const center = cellCenter(c.col, c.row); refundOrSell(hit, center.x, center.y); @@ -780,34 +872,38 @@ export function createBuildSystem({ game, ui, canvas, canvasPoint, onUpdatePanel game.conveyorMeta.delete(hit.oldKey); eraseEffect(game, center.x, center.y); refreshRoutingAfterEdit(game); - } - if (hit.type === 'eggFarm') { + game.walkabilityVersion = (game.walkabilityVersion || 0) + 1; + } else if (hit.type === 'eggFarm') { const c = cellCenter(hit.ref.col, hit.ref.row); refundOrSell(hit, c.x, c.y); game.eggFarms = game.eggFarms.filter(f => f.id !== hit.ref.id); eraseEffect(game, c.x, c.y); refreshRoutingAfterEdit(game); - } - if (hit.type === 'scanner') { + game.walkabilityVersion = (game.walkabilityVersion || 0) + 1; + } else if (hit.type === 'scanner') { const c = scannerCenter(hit.ref); refundOrSell(hit, c.x, c.y); game.scanners = game.scanners.filter(s => s.id !== hit.ref.id); eraseEffect(game, c.x, c.y); refreshRoutingAfterEdit(game); - } - if (hit.type === 'facility') { + markPowerDirty(game); + game.walkabilityVersion = (game.walkabilityVersion || 0) + 1; + } else if (hit.type === 'facility') { const f = hit.ref; refundOrSell(hit, f.x + f.w / 2, f.y + f.h / 2); delete game.facilities[f.id]; eraseEffect(game, f.x + f.w / 2, f.y + f.h / 2); refreshRoutingAfterEdit(game); - } - if (hit.type === 'maintenanceRoom') { + markPowerDirty(game); + game.walkabilityVersion = (game.walkabilityVersion || 0) + 1; + } else if (hit.type === 'maintenanceRoom') { const room = hit.ref; const center = roomCenter(room); refundOrSell(hit, center.x, center.y); game.maintenanceRooms = (game.maintenanceRooms || []).filter(r => r.id !== room.id); eraseEffect(game, center.x, center.y); + markPowerDirty(game); + game.walkabilityVersion = (game.walkabilityVersion || 0) + 1; } game.selected = null; game.multiSelected = []; @@ -832,6 +928,8 @@ export function createBuildSystem({ game, ui, canvas, canvasPoint, onUpdatePanel const room = maintenanceRoomAt(game, cell.col, cell.row); if (room) return { type: 'maintenanceRoom', ref: room }; const k = key(cell.col, cell.row); + if (game.footbridges?.has(k)) return { type: 'footbridge', oldKey: k, ref: { type: 'footbridge', id: k } }; + if (game.powerOverlay && game.powerWires?.has(k)) return { type: 'powerWire', oldKey: k, ref: { type: 'powerWire', id: k } }; if (game.conveyorTiles.has(k)) return { type: 'conveyor', oldKey: k, ref: { type: 'conveyor', id: k } }; return null; } @@ -846,6 +944,24 @@ export function createBuildSystem({ game, ui, canvas, canvasPoint, onUpdatePanel const obj = selectedObject(); if (!obj) return; if (obj.type === 'conveyor') eraseAtPoint(cellCenter(...Object.values(parseKey(obj.id)))); + else if (obj.type === 'powerWire') { + const cell = parseKey(obj.id); + const meta = game.powerWires?.get(obj.id); + if (!meta) return; + record(game); + const center = cellCenter(cell.col, cell.row); + refundOrSell({ type: 'powerWire', oldKey: obj.id, ref: { id: obj.id, price: meta.price || 0, builtSession: meta.builtSession } }, center.x, center.y); + game.powerWires.delete(obj.id); + markPowerDirty(game); + eraseEffect(game, center.x, center.y); + game.selected = null; + game.multiSelected = []; + updatePanels(); + } + else if (obj.type === 'footbridge') { + const cell = parseKey(obj.id); + eraseAtPoint(cellCenter(cell.col, cell.row)); + } else if (obj.type === 'eggFarm') eraseAtPoint(cellCenter(obj.col, obj.row)); else if (obj.type === 'scanner') eraseAtPoint(scannerCenter(obj)); else if (obj.type === 'facility') eraseAtPoint({ x: obj.x + obj.w / 2, y: obj.y + obj.h / 2 }); @@ -933,6 +1049,47 @@ export function createBuildSystem({ game, ui, canvas, canvasPoint, onUpdatePanel ui.modal.classList.add('visible', 'equipment-popover'); } + function showInboundTruckMenu(truck) { + if (!truck || (truck.baseId || truck.id) !== 'inboundTruck') return; + truck.inboundOrder = truck.inboundOrder || { coalPerDay: 0, zundaMochiPerDay: 0 }; + const limit = BALANCE.imports.combinedLimitPerTruck; + const coal = Math.max(0, Math.floor(truck.inboundOrder.coalPerDay || 0)); + const zunda = Math.max(0, Math.floor(truck.inboundOrder.zundaMochiPerDay || 0)); + const cost = coal * BALANCE.imports.prices.coal + zunda * BALANCE.imports.prices.zundaMochi; + const total = coal + zunda; + const avgInterval = total > 0 ? (BALANCE.time.daySeconds / total).toFixed(1) : 'none'; + ui.modalTitle.textContent = `Inbound Truck #${truck.id}`; + ui.modalBody.innerHTML = ` +
+ + +

Combined total: ${total}/${limit}

+

Daily purchase cost: ${yen(cost)}

+

Average release interval: ${avgInterval}${total > 0 ? 's' : ''}

+

Output queue: ${truck.inboundQueue?.length || 0}

+
`; + ui.modalActions.innerHTML = ''; + const applyButton = modalButton('Apply', () => { + if (game.phase !== 'build') return; + const nextCoal = Math.max(0, Math.floor(Number(document.getElementById('inboundCoalInput')?.value) || 0)); + const nextZunda = Math.max(0, Math.floor(Number(document.getElementById('inboundZundaInput')?.value) || 0)); + const total = nextCoal + nextZunda; + const scale = total > limit ? limit / total : 1; + record(game); + truck.inboundOrder = { + coalPerDay: Math.floor(nextCoal * scale), + zundaMochiPerDay: Math.floor(nextZunda * scale) + }; + showInboundTruckMenu(truck); + updatePanels(); + }, 'primary-button'); + applyButton.disabled = game.phase !== 'build'; + ui.modalActions.appendChild(applyButton); + ui.modalActions.appendChild(modalButton('Close', hideModal, 'facility-action')); + positionEquipmentPopover(truck); + ui.modal.classList.add('visible', 'equipment-popover'); + } + function manualKeyConflicts(scanner) { const own = [ { side: 'left', binding: scanner.keys?.left }, @@ -1055,6 +1212,18 @@ export function createBuildSystem({ game, ui, canvas, canvasPoint, onUpdatePanel 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 }); } + if (game.powerOverlay) { + for (const [k, wire] of game.powerWires || new Map()) { + const p = parseKey(k); + const c = cellCenter(p.col, p.row); + boxes.push({ id: `powerWire:${k}`, price: wire.price || 0, x: c.x - 18, y: c.y - 18, w: 36, h: 36 }); + } + } + for (const [k, bridge] of game.footbridges || new Map()) { + const p = parseKey(k); + const c = cellCenter(p.col, p.row); + boxes.push({ id: `footbridge:${k}`, price: bridge.price || buildPrice('footbridge', game), x: c.x - 22, y: c.y - 22, w: 44, h: 44 }); + } return boxes; } @@ -1117,6 +1286,12 @@ export function createBuildSystem({ game, ui, canvas, canvasPoint, onUpdatePanel lines.push(`Level: ${obj.level}/4`); lines.push(`Random interval: ${min.toFixed(2)}-${max.toFixed(2)}s`); lines.push(`Durability: ${remainingPercent(obj)}% | Spawn delay x${eggSpawnDelayMultiplier(obj).toFixed(2)}`); + const cap = boostOutputCapacityPerSecond(game); + const theoretical = obj.theoreticalRate || farmTheoreticalRatePerSecond(game, obj, obj.lastInterval || 1); + const quality = qualityForRate(theoretical, cap); + lines.push(`Rate: ${theoretical.toFixed(2)}/s theoretical | ${Math.min(theoretical, cap).toFixed(2)}/s actual`); + lines.push(`Boost cap: ${cap.toFixed(2)}/s | Quality: x${quality.toFixed(2)} ${qualityGrade(quality)}`); + if (obj.zundaBoostTime > 0) lines.push(`Zunda boost: ${obj.zundaBoostTime.toFixed(1)}s`); } else if (obj.type === 'scanner') { lines.push(`Price: ${yen(equipmentPrice(obj))}`); lines.push(`Sale value: ${yen(resaleValueFor({ type: obj.type, ref: obj }, game).amount)}`); @@ -1126,7 +1301,8 @@ export function createBuildSystem({ game, ui, canvas, canvasPoint, onUpdatePanel 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)}`); if (obj.kind === 'manual') lines.push('Durability: none | Always normal'); - + const power = powerDetailsFor(game, obj); + if (power) appendPowerLines(lines, power); } else if (obj.type === 'conveyor') { const meta = game.conveyorMeta.get(obj.id); const comp = game.componentLookup?.get(obj.id); @@ -1148,11 +1324,25 @@ export function createBuildSystem({ game, ui, canvas, canvasPoint, onUpdatePanel lines.push('Branch mode: needs 3+ conveyor connections.'); } lines.push(`Sale value: ${yen(resaleValueFor({ type: 'conveyor', oldKey: obj.id, ref: obj }, game).amount)}`); + } else if (obj.type === 'powerWire') { + const meta = game.powerWires?.get(obj.id); + const def = BALANCE.power.wires[meta?.kind || 'powerWire']; + lines.push(`Price: ${yen(meta?.price || def.price)}`); + lines.push(`Cell: ${obj.id}`); + lines.push(`Type: ${def.name} | Loss ${def.lossV}V/tile`); + } else if (obj.type === 'footbridge') { + const meta = game.footbridges?.get(obj.id); + lines.push(`Price: ${yen(meta?.price || buildPrice('footbridge', game))}`); + lines.push(`Cell: ${obj.id}`); + lines.push(`Orientation: ${meta?.orientation || 'vertical'}`); + lines.push('Repair workers may cross this conveyor cell.'); } else if (obj.type === 'maintenanceRoom') { lines.push(`Price: ${yen(equipmentPrice({ type: 'maintenanceRoom', ref: obj }))}`); lines.push(`Sale value: ${yen(resaleValueFor({ type: 'maintenanceRoom', ref: obj }, game).amount)}`); lines.push('Size: 2×2 grid cells'); lines.push('Dispatch: 1 repairman during each production day'); + const power = powerDetailsFor(game, obj); + if (power) appendPowerLines(lines, power); } 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)}`); } @@ -1189,10 +1379,19 @@ export function createBuildSystem({ game, ui, canvas, canvasPoint, onUpdatePanel lines.push(`IN: ${input ? `${input.col},${input.row}` : 'none'} | OUT: ${out ? `${out.col},${out.row}` : 'none'} | conveyor ${facilityOutputHasConveyor(game, obj) ? 'connected' : 'missing'}`); } if (obj.entry) lines.push(`Receiver: edge cell ${obj.entry.col},${obj.entry.row} (${obj.side})`); + const power = powerDetailsFor(game, obj); + if (power) appendPowerLines(lines, power); } return lines; } + function appendPowerLines(lines, power) { + lines.push(`Power: ${power.receivedV.toFixed(1)}V / ${power.minVoltage}V`); + lines.push(`Source: ${power.sourceName} | Source bus: ${power.sourceBusV.toFixed(1)}V`); + lines.push(`Route loss: ${power.routeLossV.toFixed(1)}V | Voltage Load: ${power.drawV}V`); + if (power.receivedV < power.minVoltage) lines.push('Status: UNDER VOLTAGE'); + } + function disconnectedWarningFor(obj) { if (!obj || game.phase !== 'build') return ''; const match = disconnectedBuildWarnings(game).find(item => item.type === obj.type && item.id === obj.id); @@ -1230,7 +1429,7 @@ export function createBuildSystem({ game, ui, canvas, canvasPoint, onUpdatePanel beginConveyorDrag, continueConveyorDrag, endConveyorDrag, cycleSingleConveyorDirection, branchSwitcherAtPoint, cycleBranchModeAtPoint, cycleBranchModeForKey, branchSelectableExitDirsForKey, - removeSelected, switchScannerRole, showManualScannerMenu, + removeSelected, switchScannerRole, showManualScannerMenu, showInboundTruckMenu, showAutoScannerMenu, showSelectedMenu, setBuildTool, fail, isEquipmentCell, equipmentHitBoxes, routeFromFarmToScanner diff --git a/src/systems/chickSystem.js b/src/systems/chickSystem.js index c39320b..601f528 100644 --- a/src/systems/chickSystem.js +++ b/src/systems/chickSystem.js @@ -9,6 +9,9 @@ import { productionMultiplier, truckTarget, isTargetTruckCargo, shouldFineMaleTr import { floating, shake, spawnPulse, scannerPulse, meatEffect, sludgeEffect, shredEffect, truckLoadEffect, shockwave, sparkBurst, smokeBurst, flyingDebris, rageEffect } from './effects.js'; import { countAutoSorted, countCorrect, countMistake, countMixer, countPoopDestination, countPoopSpawned, countTrash, countTruckCargo } from './stats.js'; import { eggSpawnDelayMultiplier, recordFarmProduction, recordConveyorPass, recordAutoScan, recordFacilityProcess, updateProcessingCooldowns, setFacilityCooldownAfterProcess, updateRepairman } from './maintenance.js'; +import { isEquipmentPowered, addFuelPulse, markPowerDirty } from './power.js'; +import { updateInboundTrucks } from './imports.js'; +import { addZundaBoost, actualSpawnIntervalForRate, boostOutputCapacityPerSecond, farmTheoreticalRatePerSecond, qualityForRate } from './farmQuality.js'; export function createChickSystem({ game, currentConveyorSpeed, onGameOverCheck }) { function frameCache() { @@ -80,13 +83,18 @@ export function createChickSystem({ game, currentConveyorSpeed, onGameOverCheck } function currentSpawnDelay(farm) { - return nextSpawnDelay(farm) * eggSpawnDelayMultiplier(farm) * eggProductionDelayMultiplier(game) / productionMultiplier(game); + const base = nextSpawnDelay(farm) * eggSpawnDelayMultiplier(farm) * eggProductionDelayMultiplier(game) / productionMultiplier(game); + const rate = farmTheoreticalRatePerSecond(game, farm, base); + farm.theoreticalRate = rate; + farm.actualOutputRate = Math.min(rate, boostOutputCapacityPerSecond(game)); + farm.qualityMultiplier = qualityForRate(rate, boostOutputCapacityPerSecond(game)); + return Math.max(0.25, actualSpawnIntervalForRate(rate, boostOutputCapacityPerSecond(game))); } function spawnChick(farm) { const farmCenter = cellCenter(farm.col, farm.row); const maxOutputs = 1 + extraEggOutletCount(game, farm); - const starts = farmOutputStarts(farm, maxOutputs); + const starts = farmOutputStarts(farm, maxOutputs).slice(0, 1); if (!starts.length) { suppressUnconnectedFarmSpawn(farm, farmCenter); return true; @@ -102,6 +110,13 @@ export function createChickSystem({ game, currentConveyorSpeed, onGameOverCheck continue; } const chick = createChick(game, { stage: 'belt', scannerId: null, route: [start] }); + const theoretical = farm.theoreticalRate || farmTheoreticalRatePerSecond(game, farm, farm.lastInterval || currentSpawnDelay(farm)); + chick.sourceQuality = qualityForRate(theoretical, boostOutputCapacityPerSecond(game)); + game.stats.farmTheoreticalRate = Math.max(game.stats.farmTheoreticalRate || 0, theoretical); + game.stats.farmActualOutputRate = Math.max(game.stats.farmActualOutputRate || 0, Math.min(theoretical, boostOutputCapacityPerSecond(game))); + game.stats.qualityMultiplierSum = (game.stats.qualityMultiplierSum || 0) + chick.sourceQuality; + game.stats.qualityMultiplierCount = (game.stats.qualityMultiplierCount || 0) + 1; + game.stats.maxQualityMultiplier = Math.max(game.stats.maxQualityMultiplier || 1, chick.sourceQuality); if (chick.sex === 'poop') countPoopSpawned(game); game.chicks.push(chick); markChicksChanged(); @@ -140,6 +155,7 @@ export function createChickSystem({ game, currentConveyorSpeed, onGameOverCheck function updateRunning(dt, { closeFarmShutters, completeTurn }) { updateRepairman(game, dt); updateProcessingCooldowns(game, dt); + updateInboundTrucks(game, dt, markChicksChanged); flushFacilityOutputs(); if (game.cleanupTimer !== null) { game.cleanupTimer = Math.max(0, game.cleanupTimer - dt); @@ -157,6 +173,7 @@ export function createChickSystem({ game, currentConveyorSpeed, onGameOverCheck if (producing && game.timeLeft > 0) { for (const farm of game.eggFarms) { if (farm.noRouteNoticeCooldown > 0) farm.noRouteNoticeCooldown = Math.max(0, farm.noRouteNoticeCooldown - dt); + if (farm.zundaBoostTime > 0) farm.zundaBoostTime = Math.max(0, farm.zundaBoostTime - dt); farm.nextSpawn -= dt; if (farm.nextSpawn <= 0) { const spawned = spawnChick(farm); @@ -170,6 +187,7 @@ export function createChickSystem({ game, currentConveyorSpeed, onGameOverCheck chick.bob += dt * 7; if (chick.stoppedTimer > 0) chick.stoppedTimer = Math.max(0, chick.stoppedTimer - dt); if (chick.stage === 'queued') continue; + if (consumeAdjacentZunda(chick, i)) continue; updateRoutedChick(chick, i, dt); } updateScannerQueues(dt); @@ -279,12 +297,29 @@ 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 gridGeneratorReceivingFrom(cell, dirName) { + for (const facility of Object.values(game.facilities || {})) { + const kind = facilityKind(facility); + if (!facility.gridPlaced || !['biomassGenerator', 'coalGenerator'].includes(kind)) continue; + const ports = kind === 'biomassGenerator' + ? [ + { col: facility.col - 1, row: facility.row + 1, dir: 'right' }, + { col: facility.col + 1, row: facility.row - 1, dir: 'down' }, + { col: facility.col + 1, row: facility.row + (facility.hCells || 3), dir: 'up' } + ] + : [{ col: facility.col - 1, row: facility.row + 1, dir: 'right' }]; + if (ports.some(port => port.col === cell.col && port.row === cell.row && port.dir === dirName)) return facility; + } + return null; + } + function facilityKind(f) { return f?.baseId || f?.id || ''; } function dirExitsToFacility(facility, dirName) { if (facility?.gridPlaced && facilityKind(facility) === 'sausageMaker') return dirName === 'right'; + if (facility?.gridPlaced && ['biomassGenerator', 'coalGenerator'].includes(facilityKind(facility))) return dirName === 'right'; return (facility.side === 'left' && dirName === 'left') || (facility.side === 'right' && dirName === 'right') || (facility.side === 'top' && dirName === 'up') @@ -299,7 +334,9 @@ export function createChickSystem({ game, currentConveyorSpeed, onGameOverCheck return { type: 'conveyor', dirName, target: cellCenter(next.col, next.row), nextCell: next }; } const scanner = scannerReceivingFrom(cell, dirName); - if (scanner && !['meat', 'sausage'].includes(item?.sex)) return { type: 'scanner', dirName, target: scannerCenter(scanner), scanner }; + if (scanner && !['meat', 'sausage', 'coal', 'zundaMochi'].includes(item?.sex)) return { type: 'scanner', dirName, target: scannerCenter(scanner), scanner }; + const generator = gridGeneratorReceivingFrom(cell, dirName); + if (generator) return { type: 'facility', dirName, facilityId: generator.id, facility: generator, target: cellCenter(cell.col, cell.row) }; const facilityPair = facilityAtEntryCell(cell); if (facilityPair && dirExitsToFacility(facilityPair[1], dirName)) { return { type: 'facility', dirName, facilityId: facilityPair[0], facility: facilityPair[1], target: cellCenter(cell.col, cell.row) }; @@ -379,6 +416,8 @@ export function createChickSystem({ game, currentConveyorSpeed, onGameOverCheck else if (kind === 'truck') resolveTruck(index, option.facility); else if (kind === 'trash') resolveTrash(index, option.facility); else if (kind === 'sausageMaker') resolveSausageMaker(index, option.facility); + else if (kind === 'biomassGenerator' || kind === 'coalGenerator') resolveGenerator(index, option.facility); + else if (kind === 'inboundTruck') { const chick = game.chicks[index]; if (chick) chick.stoppedTimer = 0.25; } else removeChick(index, 'DONE'); return; } @@ -387,6 +426,19 @@ export function createChickSystem({ game, currentConveyorSpeed, onGameOverCheck setSingleSegmentRoute(chick, option.target); } + function consumeAdjacentZunda(chick, index) { + if (chick.sex !== 'zundaMochi') return false; + const cell = pointToCell(chick.x, chick.y); + if (!cell) return false; + const farm = game.eggFarms.find(candidate => Math.abs(candidate.col - cell.col) + Math.abs(candidate.row - cell.row) === 1); + if (!farm) return false; + addZundaBoost(game, farm); + takeChick(index); + const c = cellCenter(farm.col, farm.row); + floating(game, c.x, c.y - 24, 'ZUNDA BOOST', THEME.green); + return true; + } + function conveyorKeyFromPoint(p) { const cell = pointToCell(p.x, p.y); if (!cell) return null; @@ -484,9 +536,11 @@ export function createChickSystem({ game, currentConveyorSpeed, onGameOverCheck function updateScannerQueues(dt) { for (const scanner of game.scanners) { - if (scanner.cooldown > 0) scanner.cooldown = Math.max(0, scanner.cooldown - dt); + const powered = scanner.kind !== 'auto' || isEquipmentPowered(game, scanner); if (scanner.keyPressTime > 0) scanner.keyPressTime = Math.max(0, scanner.keyPressTime - dt); positionScannerQueue(scanner); + if (!powered) continue; + if (scanner.cooldown > 0) scanner.cooldown = Math.max(0, scanner.cooldown - dt); if (scanner.kind !== 'auto' || scanner.cooldown > 0 || !scanner.queue.length) continue; const id = scanner.queue[0]; const index = game.chicks.findIndex(c => c.id === id); @@ -601,6 +655,11 @@ export function createChickSystem({ game, currentConveyorSpeed, onGameOverCheck if (!chick || chick.stage !== 'queued') return false; const scanner = scannerById(game, chick.scannerId); if (!scanner) return false; + if (scanner.kind === 'auto' && !isEquipmentPowered(game, scanner)) return false; + if (['coal', 'zundaMochi'].includes(chick.sex)) { + floating(game, chick.x, chick.y - 16, 'NOT SCANNABLE', THEME.warn); + return false; + } const startCell = scannerOutputStartCell(scanner, side); if (!startCell) { if (!auto && scanner.kind === 'manual') resetManualCombo(scanner); @@ -636,11 +695,17 @@ export function createChickSystem({ game, currentConveyorSpeed, onGameOverCheck } function resolveMixer(index, mixer = game.facilities.mixer) { + if (!isEquipmentPowered(game, mixer)) { + const chick = game.chicks[index]; + if (chick) chick.stoppedTimer = 0.25; + return; + } if (mixer?.processingCooldown > 0) { const chick = game.chicks[index]; if (chick) chick.stoppedTimer = 0.25; return; } + if (isImportItem(game.chicks[index])) return rejectImportItem(index, 'CHICKS ONLY'); const chick = takeChick(index); if (!chick) return; const { x, y } = chick; @@ -669,6 +734,8 @@ export function createChickSystem({ game, currentConveyorSpeed, onGameOverCheck return; } mixer.meatBuffer = Math.max(0, mixer.meatBuffer || 0) + 1; + mixer.meatQualityQueue = mixer.meatQualityQueue || []; + mixer.meatQualityQueue.push(chick.sourceQuality || 1); game.stats.meatProduced = (game.stats.meatProduced || 0) + 1; game.totals.meatProduced = (game.totals.meatProduced || 0) + 1; meatEffect(game, x, y); @@ -677,10 +744,16 @@ export function createChickSystem({ game, currentConveyorSpeed, onGameOverCheck } function resolveTruck(index, truck = game.facilities.truck) { + if (!isEquipmentPowered(game, truck)) { + const chick = game.chicks[index]; + if (chick) chick.stoppedTimer = 0.25; + return; + } + if (isImportItem(game.chicks[index])) return rejectImportItem(index, 'NO IMPORTS'); const chick = takeChick(index); if (!chick) return; const { x, y } = chick; - addTruckCargo(chick.sex, truck); + addTruckCargo(chick.sex, truck, chick.sourceQuality || 1); truckLoadEffect(game, x, y, chick.sex); countTruckCargo(game, chick.sex); const target = truckTarget(game); @@ -751,11 +824,15 @@ export function createChickSystem({ game, currentConveyorSpeed, onGameOverCheck } function emitFromFacility(facility, product, bufferKey) { + if (!isEquipmentPowered(game, facility)) return false; const out = facilityOutputCell(game, facility); if (!out || !facilityOutputHasConveyor(game, facility) || !outputCellFree(out)) return false; if (Math.max(0, facility[bufferKey] || 0) <= 0) return false; facility[bufferKey] -= 1; - game.chicks.push(createProduct(product, out)); + const created = createProduct(product, out); + if (product === 'meat') created.sourceQuality = (facility.meatQualityQueue || []).shift() || 1; + if (product === 'sausage') created.sourceQuality = (facility.sausageQualityQueue || []).shift() || 1; + game.chicks.push(created); markChicksChanged(); const c = cellCenter(out.col, out.row); floating(game, c.x, c.y - 16, product === 'meat' ? 'MEAT OUT' : 'SAUSAGE OUT', product === 'meat' ? THEME.meatRed : THEME.sausageOrange); @@ -771,11 +848,17 @@ export function createChickSystem({ game, currentConveyorSpeed, onGameOverCheck } function resolveSausageMaker(index, machine) { + if (!isEquipmentPowered(game, machine)) { + const item = game.chicks[index]; + if (item) item.stoppedTimer = 0.25; + return; + } if (machine?.processingCooldown > 0) { const item = game.chicks[index]; if (item) item.stoppedTimer = 0.25; return; } + if (isImportItem(game.chicks[index])) return rejectImportItem(index, 'MEAT ONLY'); const item = takeChick(index); if (!item) return; const { x, y } = item; @@ -787,9 +870,15 @@ export function createChickSystem({ game, currentConveyorSpeed, onGameOverCheck recordFacilityProcess(game, machine); setFacilityCooldownAfterProcess(game, machine); machine.meatBuffer = Math.max(0, machine.meatBuffer || 0) + 1; + machine.meatQualityQueue = machine.meatQualityQueue || []; + machine.meatQualityQueue.push(item.sourceQuality || 1); if (machine.meatBuffer >= 2) { machine.meatBuffer -= 2; machine.sausageBuffer = Math.max(0, machine.sausageBuffer || 0) + 1; + const q1 = machine.meatQualityQueue.shift() || 1; + const q2 = machine.meatQualityQueue.shift() || 1; + machine.sausageQualityQueue = machine.sausageQualityQueue || []; + machine.sausageQualityQueue.push((q1 + q2) / 2); game.stats.sausageProduced = (game.stats.sausageProduced || 0) + 1; game.totals.sausageProduced = (game.totals.sausageProduced || 0) + 1; floating(game, x, y - 18, 'SAUSAGE +1', THEME.sausageOrange); @@ -800,11 +889,17 @@ export function createChickSystem({ game, currentConveyorSpeed, onGameOverCheck } function resolveTrash(index, trash = game.facilities.trash) { + if (!isEquipmentPowered(game, trash)) { + const chick = game.chicks[index]; + if (chick) chick.stoppedTimer = 0.25; + return; + } if (trash?.processingCooldown > 0) { const chick = game.chicks[index]; if (chick) chick.stoppedTimer = 0.25; return; } + if (isImportItem(game.chicks[index])) return rejectImportItem(index, 'NO IMPORTS'); const chick = takeChick(index); if (!chick) return; const { x, y } = chick; @@ -833,16 +928,51 @@ export function createChickSystem({ game, currentConveyorSpeed, onGameOverCheck } } + function resolveGenerator(index, generator) { + const item = game.chicks[index]; + if (!item) return; + if (!addFuelPulse(generator, item.sex)) { + item.stoppedTimer = 0.25; + return; + } + const used = takeChick(index); + if (!used) return; + if ((generator.baseId || generator.id) === 'biomassGenerator') { + game.stats.biomassFuelConsumed = (game.stats.biomassFuelConsumed || 0) + 1; + game.totals.biomassFuelConsumed = (game.totals.biomassFuelConsumed || 0) + 1; + } + if ((generator.baseId || generator.id) === 'coalGenerator') { + game.stats.coalConsumed = (game.stats.coalConsumed || 0) + 1; + game.totals.coalConsumed = (game.totals.coalConsumed || 0) + 1; + } + recordFacilityProcess(game, generator); + markPowerDirty(game); + floating(game, used.x, used.y - 18, 'FUEL +V', THEME.green); + } + function removeChick(index, label) { const chick = takeChick(index); if (!chick) return; floating(game, chick.x, chick.y - 12, label, THEME.muted); } - function addTruckCargo(sex, truck = game.facilities.truck) { + function isImportItem(item) { + return item?.sex === 'coal' || item?.sex === 'zundaMochi'; + } + + function rejectImportItem(index, label) { + const item = game.chicks[index]; + if (!item) return; + item.stoppedTimer = 0.25; + floating(game, item.x, item.y - 18, label, THEME.warn); + } + + function addTruckCargo(sex, truck = game.facilities.truck, sourceQuality = 1) { 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) }); + game.shippedCargo = game.shippedCargo || []; + game.shippedCargo.push({ sex, sourceQuality }); + game.truckCargo.push({ sex, sourceQuality, 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 0733147..3ff303e 100644 --- a/src/systems/economy.js +++ b/src/systems/economy.js @@ -257,12 +257,23 @@ export function applyExplosionDamage(game, price) { export function settleTruckRevenue(game) { const target = truckTarget(game); - const targetCount = targetTruckCount(game.stats, target); const unitPrice = target === 'poop' ? Math.ceil(upgradedTruckPrice(game) * poopValueMultiplier(game)) : upgradedTruckPrice(game); - const base = targetCount * unitPrice; - const sausageCount = Math.max(0, game?.stats?.sausageShipped || 0); + const cargo = Array.isArray(game.shippedCargo) ? game.shippedCargo : (Array.isArray(game.truckCargo) ? game.truckCargo : []); + const targetCargo = cargo.filter(item => item.sex === target); + const targetCount = targetCargo.length || targetTruckCount(game.stats, target); + const base = targetCargo.length + ? targetCargo.reduce((sum, item) => sum + Math.ceil(unitPrice * (target === 'female' ? Math.max(1, item.sourceQuality || 1) : 1)), 0) + : targetCount * unitPrice; + const sausageCargo = cargo.filter(item => item.sex === 'sausage'); + const sausageCount = sausageCargo.length || Math.max(0, game?.stats?.sausageShipped || 0); const sausageUnitPrice = upgradedSausagePrice(game); - const sausageRevenue = sausageCount * sausageUnitPrice; + const sausageRevenue = sausageCargo.length + ? sausageCargo.reduce((sum, item) => sum + Math.ceil(sausageUnitPrice * Math.max(1, item.sourceQuality || 1)), 0) + : sausageCount * sausageUnitPrice; + const qualityBaseline = targetCount * unitPrice + sausageCount * sausageUnitPrice; + const qualityRevenue = Math.max(0, base + sausageRevenue - qualityBaseline); + game.stats.qualityRevenue = (game.stats.qualityRevenue || 0) + qualityRevenue; + game.totals.qualityRevenue = (game.totals.qualityRevenue || 0) + qualityRevenue; const adjusted = positivePayout(game, base + sausageRevenue); game.stats.pendingTruckRevenue = adjusted; game.stats.truckRevenue = adjusted; diff --git a/src/systems/farmQuality.js b/src/systems/farmQuality.js new file mode 100644 index 0000000..4e43383 --- /dev/null +++ b/src/systems/farmQuality.js @@ -0,0 +1,42 @@ +import { BALANCE } from '../core/balance.js'; +import { conveyorSpeedForGame } from './cards.js'; + +export function boostOutputCapacityPerSecond(game) { + return conveyorSpeedForGame(game) * (BALANCE.facilities.boostConveyor.speedMultiplier || 2) / Math.max(1, BALANCE.conveyor.minimumItemSpacingPx || 25); +} + +export function farmTheoreticalRatePerSecond(game, farm, currentSpawnDelay) { + const delay = Math.max(BALANCE.farmQuality.safeMinIntervalSeconds, Number(currentSpawnDelay) || 1); + const zunda = (farm.zundaBoostTime || 0) > 0 ? BALANCE.farmQuality.zundaIntervalMultiplier : 1; + return 1 / Math.max(BALANCE.farmQuality.safeMinIntervalSeconds, delay * zunda); +} + +export function qualityForRate(rate, capacity) { + const rules = BALANCE.farmQuality; + const c = Math.max(0.0001, capacity); + const overflowRatio = Math.max(0, rate / c - 1); + return Math.max(rules.minMultiplier, Math.min(rules.maxMultiplier, 1 + rules.overflowCoefficient * Math.pow(overflowRatio, rules.overflowExponent))); +} + +export function actualSpawnIntervalForRate(rate, capacity) { + return 1 / Math.max(0.0001, Math.min(rate, capacity)); +} + +export function qualityGrade(multiplier) { + if (multiplier >= 1.75) return 'Luxury'; + if (multiplier >= 1.35) return 'Premium'; + if (multiplier >= 1.15) return 'Select'; + return 'Standard'; +} + +export function addZundaBoost(game, farm) { + const before = farm.zundaBoostTime || 0; + farm.zundaBoostTime = Math.min(BALANCE.farmQuality.zundaMaxSeconds, before + BALANCE.farmQuality.zundaSeconds); + const added = Math.max(0, farm.zundaBoostTime - before); + game.stats.zundaConsumed = (game.stats.zundaConsumed || 0) + 1; + game.stats.zundaBoostSeconds = (game.stats.zundaBoostSeconds || 0) + added; + game.totals.zundaConsumed = (game.totals.zundaConsumed || 0) + 1; + game.totals.zundaBoostSeconds = (game.totals.zundaBoostSeconds || 0) + added; + game.stats.zundaBoostSecondsByFarm = game.stats.zundaBoostSecondsByFarm || {}; + game.stats.zundaBoostSecondsByFarm[farm.id] = (game.stats.zundaBoostSecondsByFarm[farm.id] || 0) + added; +} diff --git a/src/systems/history.js b/src/systems/history.js index c86e9c0..bd42646 100644 --- a/src/systems/history.js +++ b/src/systems/history.js @@ -29,6 +29,13 @@ export function snapshot(game) { scanners: game.scanners.map(cleanScanner), conveyorTiles: [...game.conveyorTiles], conveyorMeta: [...game.conveyorMeta.entries()], + powerWires: [...(game.powerWires || new Map()).entries()], + footbridges: [...(game.footbridges || new Map()).entries()], + footbridgeOrientation: game.footbridgeOrientation || 'vertical', + powerOverlay: !!game.powerOverlay, + imports: { + schedules: [...(game.imports?.schedules || new Map()).entries()] + }, branchCounters: [...game.branchCounters.entries()], cardEffects: game.cardEffects, cardDraft: game.cardDraft, @@ -67,6 +74,11 @@ export function restore(game, text) { game.scanners = (data.scanners || []).map(s => ({ ...s, queue: [], cooldown: s.cooldown || 0 })); game.conveyorTiles = new Set(data.conveyorTiles || []); game.conveyorMeta = new Map(data.conveyorMeta || []); + game.powerWires = new Map(data.powerWires || []); + game.footbridges = new Map(data.footbridges || []); + game.footbridgeOrientation = data.footbridgeOrientation || 'vertical'; + game.powerOverlay = !!data.powerOverlay; + game.imports = { schedules: new Map(data.imports?.schedules || []) }; game.branchCounters = new Map(data.branchCounters || []); game.cardEffects = data.cardEffects || { bearing: 0, legalWork: 0, fairiesFlatteryNext: 0, scannerQueueSpacing: 0 }; if (game.cardEffects.drawBonus != null) delete game.cardEffects.drawBonus; @@ -78,6 +90,8 @@ export function restore(game, text) { game.selectionBox = null; game.pan = null; game.routingVersion = (game.routingVersion || 0) + 1; + game.walkabilityVersion = (game.walkabilityVersion || 0) + 1; + game.power = { ...(game.power || {}), dirty: true }; game.factoryGraph = null; game.factoryGraphCommittedVersion = -1; } diff --git a/src/systems/imports.js b/src/systems/imports.js new file mode 100644 index 0000000..0b33dca --- /dev/null +++ b/src/systems/imports.js @@ -0,0 +1,149 @@ +import { BALANCE } from '../core/balance.js'; +import { TURN_SECONDS } from '../core/config.js'; +import { cellCenter, key } from '../core/utils.js'; +import { applyPenalty } from './economy.js'; +import { facilityOutputCell, facilityOutputHasConveyor } from './routing.js'; +import { isEquipmentPowered } from './power.js'; +import { recordFacilityProcess } from './maintenance.js'; + +function hash01(seed) { + let h = 2166136261; + for (let i = 0; i < String(seed).length; i += 1) { + h ^= String(seed).charCodeAt(i); + h = Math.imul(h, 16777619); + } + return ((h >>> 0) % 100000) / 100000; +} + +export function clampInboundOrder(order = {}) { + const limit = BALANCE.imports.combinedLimitPerTruck; + let coal = Math.max(0, Math.floor(Number(order.coalPerDay) || 0)); + let zunda = Math.max(0, Math.floor(Number(order.zundaMochiPerDay) || 0)); + if (coal + zunda > limit) { + const scale = limit / Math.max(1, coal + zunda); + coal = Math.floor(coal * scale); + zunda = Math.max(0, limit - coal); + } + return { coalPerDay: coal, zundaMochiPerDay: zunda }; +} + +export function inboundOrderCost(order = {}) { + const clean = clampInboundOrder(order); + return clean.coalPerDay * BALANCE.imports.prices.coal + clean.zundaMochiPerDay * BALANCE.imports.prices.zundaMochi; +} + +export function createImportSchedule(order, seed = 'imports') { + const clean = clampInboundOrder(order); + const items = [ + ...Array.from({ length: clean.coalPerDay }, () => ({ type: 'coal' })), + ...Array.from({ length: clean.zundaMochiPerDay }, () => ({ type: 'zundaMochi' })) + ]; + const total = Math.max(1, items.length); + return items.map((item, i) => { + const interval = TURN_SECONDS / total; + const baseTime = ((i + 0.5) / total) * TURN_SECONDS; + const jitter = (hash01(`${seed}:${item.type}:${i}`) * 2 - 1) * BALANCE.imports.jitterRatio * interval; + return { type: item.type, due: Math.max(0, Math.min(TURN_SECONDS, baseTime + jitter)), released: false }; + }).sort((a, b) => a.due - b.due || a.type.localeCompare(b.type)); +} + +export function startInboundOrders(game) { + game.imports = { schedules: new Map() }; + for (const facility of Object.values(game.facilities || {})) { + if ((facility.baseId || facility.id) !== 'inboundTruck') continue; + facility.inboundOrder = clampInboundOrder(facility.inboundOrder); + facility.inboundQueue = []; + facility.importClock = 0; + const cost = inboundOrderCost(facility.inboundOrder); + if (cost > 0) { + applyPenalty(game, cost); + game.stats.importPurchaseCost = (game.stats.importPurchaseCost || 0) + cost; + game.stats.coalOrdered = (game.stats.coalOrdered || 0) + facility.inboundOrder.coalPerDay; + game.stats.zundaOrdered = (game.stats.zundaOrdered || 0) + facility.inboundOrder.zundaMochiPerDay; + game.totals.importPurchaseCost = (game.totals.importPurchaseCost || 0) + cost; + game.totals.coalOrdered = (game.totals.coalOrdered || 0) + facility.inboundOrder.coalPerDay; + game.totals.zundaOrdered = (game.totals.zundaOrdered || 0) + facility.inboundOrder.zundaMochiPerDay; + } + game.imports.schedules.set(facility.id, createImportSchedule(facility.inboundOrder, `${game.runSeed}:${game.turn}:${facility.id}`)); + } +} + +function outputCellFree(game, cell) { + if (!cell || !game.conveyorTiles.has(key(cell.col, cell.row))) return false; + const c = cellCenter(cell.col, cell.row); + return !game.chicks.some(item => item.stage !== 'flying' && Math.hypot(item.x - c.x, item.y - c.y) < 25); +} + +function createImportItem(game, type, cell) { + const c = cellCenter(cell.col, cell.row); + return { + id: game.nextId++, + sex: type, + x: c.x, + y: c.y, + route: [{ x: c.x, y: c.y }], + targetIndex: 1, + stage: 'belt', + scannerId: null, + nextScannerId: null, + pendingScannerId: null, + radius: type === 'coal' ? 10 : 11, + bob: 0, + queueIndex: -1, + queueRoute: null, + stoppedTimer: 0 + }; +} + +export function updateInboundTrucks(game, dt, markChicksChanged = () => {}) { + if (game.phase !== 'running') return; + for (const facility of Object.values(game.facilities || {})) { + if ((facility.baseId || facility.id) !== 'inboundTruck') continue; + if (!isEquipmentPowered(game, facility)) continue; + facility.importClock = (facility.importClock || 0) + dt; + const schedule = game.imports?.schedules?.get(facility.id) || []; + for (const item of schedule) { + if (!item.released && item.due <= facility.importClock) { + facility.inboundQueue = facility.inboundQueue || []; + facility.inboundQueue.push(item); + item.released = true; + } + } + const out = facilityOutputCell(game, facility); + if (!facility.inboundQueue?.length || !facilityOutputHasConveyor(game, facility) || !outputCellFree(game, out)) continue; + const queued = facility.inboundQueue.shift(); + const type = queued.type; + queued.delivered = true; + game.chicks.push(createImportItem(game, type, out)); + if (type === 'coal') { + game.stats.coalReleased = (game.stats.coalReleased || 0) + 1; + game.totals.coalReleased = (game.totals.coalReleased || 0) + 1; + } + if (type === 'zundaMochi') { + game.stats.zundaReleased = (game.stats.zundaReleased || 0) + 1; + game.totals.zundaReleased = (game.totals.zundaReleased || 0) + 1; + } + recordFacilityProcess(game, facility); + markChicksChanged(); + } +} + +export function settleUndeliveredImports(game) { + for (const facility of Object.values(game.facilities || {})) { + if ((facility.baseId || facility.id) !== 'inboundTruck') continue; + const schedule = game.imports?.schedules?.get(facility.id) || []; + for (const item of schedule) { + const undelivered = !item.delivered; + if (!undelivered) continue; + if (item.type === 'coal') { + game.stats.coalUndelivered = (game.stats.coalUndelivered || 0) + 1; + game.totals.coalUndelivered = (game.totals.coalUndelivered || 0) + 1; + } + if (item.type === 'zundaMochi') { + game.stats.zundaUndelivered = (game.stats.zundaUndelivered || 0) + 1; + game.totals.zundaUndelivered = (game.totals.zundaUndelivered || 0) + 1; + } + } + facility.inboundQueue = []; + } +} diff --git a/src/systems/maintenance.js b/src/systems/maintenance.js index f343832..3023d11 100644 --- a/src/systems/maintenance.js +++ b/src/systems/maintenance.js @@ -1,7 +1,8 @@ import { BALANCE } from '../core/balance.js'; -import { GRID, THEME } from '../core/config.js'; -import { cellCenter, parseKey, key } from '../core/utils.js'; +import { DIRS, GRID, THEME } from '../core/config.js'; +import { cellCenter, parseKey, key, inGrid } from '../core/utils.js'; import { floating } from './effects.js'; +import { isEquipmentPowered } from './power.js'; const RULES = BALANCE.maintenance; @@ -69,6 +70,8 @@ export function ensureMaintenanceState(game) { const kind = facilityKind(f); if (kind === 'mixer') attachMaintenance(f, 'mixer', game); if (kind === 'trash') attachMaintenance(f, 'trash', game); + if (kind === 'sausageMaker') attachMaintenance(f, 'sausageMaker', game); + if (['starterGenerator', 'biomassGenerator', 'coalGenerator', 'inboundTruck'].includes(kind)) attachMaintenance(f, kind, game); } } @@ -195,7 +198,7 @@ export function recordFacilityProcess(game, target) { ensureMaintenanceState(game); const f = facilityFromTarget(game, target); const id = facilityKind(f || target); - if (!f || !['mixer', 'trash', 'sausageMaker'].includes(id)) return; + if (!f || !['mixer', 'trash', 'sausageMaker', 'starterGenerator', 'biomassGenerator', 'coalGenerator', 'inboundTruck'].includes(id)) return; attachMaintenance(f, id); f.maintenance.uses = Math.min(f.maintenance.durability, (f.maintenance.uses || 0) + degradationUseMultiplier(game)); } @@ -211,6 +214,7 @@ export function setFacilityCooldownAfterProcess(game, target) { export function updateProcessingCooldowns(game, dt) { for (const f of Object.values(game.facilities || {})) { if (!['mixer', 'trash', 'sausageMaker'].includes(f.baseId || f.id)) continue; + if (!isEquipmentPowered(game, f)) continue; if (f?.processingCooldown > 0) f.processingCooldown = Math.max(0, f.processingCooldown - dt); } } @@ -223,7 +227,7 @@ export function equipmentMaintenanceTargets(game) { 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 f of Object.values(game.facilities || {})) { const kind = facilityKind(f); - if (!['mixer', 'trash', 'sausageMaker'].includes(kind)) continue; + if (!['mixer', 'trash', 'sausageMaker', 'starterGenerator', 'biomassGenerator', 'coalGenerator', 'inboundTruck'].includes(kind)) continue; targets.push({ type: kind, ref: f, label: `${f.shortName || f.name || kind} #${f.id}`, center: { x: f.x + f.w / 2, y: f.y + f.h / 2 } }); } return targets; @@ -251,7 +255,8 @@ export function activateRepairmanForDay(game) { ensureMaintenanceState(game); const rooms = game.maintenanceRooms || []; game.repairmen = rooms.map((room, index) => { - const home = repairmanHome(game, index); + const exitCell = nearestWalkableFromRoom(game, room); + const home = exitCell ? cellCenter(exitCell.col, exitCell.row) : repairmanHome(game, index); return { id: `room-${room.id}`, roomId: room.id, @@ -286,6 +291,7 @@ function resolveStoredTarget(game, stored) { function chooseRepairTarget(game) { let best = null; for (const t of equipmentMaintenanceTargets(game)) { + if (!repairWorkCell(game, t)) continue; const w = targetWear(t); if (w <= 0.001) continue; if (!best || w > targetWear(best)) best = t; @@ -311,8 +317,10 @@ export function updateRepairman(game, dt) { function updateSingleRepairman(game, r, dt, assigned) { if (!r.active) return; + const room = (game.maintenanceRooms || []).find(item => item.id === r.roomId); + if (room && !isEquipmentPowered(game, room)) return; let target = resolveStoredTarget(game, r.target); - if (!target || targetWear(target) <= 0.001) { + if (!target || targetWear(target) <= 0.001 || !repairWorkCell(game, target)) { if (r.target?.key) assigned.delete(r.target.key); target = chooseRepairTargetAvoiding(game, assigned); } @@ -322,10 +330,34 @@ function updateSingleRepairman(game, r, dt, assigned) { } const tKey = targetKey(target); assigned.add(tKey); - r.target = { key: tKey, label: target.label }; + const workCell = repairWorkCell(game, target, r); + if (!workCell) { + game.stats.unreachableRepairTargets = (game.stats.unreachableRepairTargets || 0) + 1; + game.totals.unreachableRepairTargets = (game.totals.unreachableRepairTargets || 0) + 1; + assigned.delete(tKey); + r.target = null; + return; + } + r.target = { key: tKey, label: target.label, workKey: key(workCell.col, workCell.row) }; + ensureRepairPath(game, r, workCell); + if (r.path?.length && r.pathIndex < r.path.length) { + const next = cellCenter(r.path[r.pathIndex].col, r.path[r.pathIndex].row); + const dx = next.x - r.x; + const dy = next.y - r.y; + const dist = Math.hypot(dx, dy); + if (dist > 4) { + const step = Math.min(dist, RULES.repairman.walkSpeed * dt); + r.x += dx / dist * step; + r.y += dy / dist * step; + return; + } + r.pathIndex += 1; + return; + } const speed = RULES.repairman.walkSpeed; - const dx = target.center.x - r.x; - const dy = target.center.y - r.y; + const workCenter = cellCenter(workCell.col, workCell.row); + const dx = workCenter.x - r.x; + const dy = workCenter.y - r.y; const dist = Math.hypot(dx, dy); if (dist > 8) { const step = Math.min(dist, speed * dt); @@ -351,6 +383,7 @@ function chooseRepairTargetAvoiding(game, assigned = new Set()) { let best = null; for (const t of equipmentMaintenanceTargets(game)) { if (assigned.has(targetKey(t))) continue; + if (!repairWorkCell(game, t)) continue; const w = targetWear(t); if (w <= 0.001) continue; if (!best || w > targetWear(best)) best = t; @@ -358,6 +391,135 @@ function chooseRepairTargetAvoiding(game, assigned = new Set()) { return best || chooseRepairTarget(game); } +function cellsForTarget(target) { + if (target.type === 'conveyor') return [parseKey(target.key)]; + if (target.type === 'eggFarm') return [{ col: target.ref.col, row: target.ref.row }]; + if (target.type === 'autoScanner') { + const s = target.ref; + return [{ col: s.col, row: s.row - 1 }, { col: s.col + 1, row: s.row - 1 }, { col: s.col, row: s.row }, { col: s.col + 1, row: s.row }]; + } + const f = target.ref; + if (f?.gridPlaced) { + const cells = []; + for (let dr = 0; dr < (f.hCells || 1); dr += 1) for (let dc = 0; dc < (f.wCells || 1); dc += 1) cells.push({ col: f.col + dc, row: f.row + dr }); + return cells; + } + return f?.entry ? [f.entry] : []; +} + +function equipmentCellKeys(game) { + const blocked = new Set(); + for (const farm of game.eggFarms || []) blocked.add(key(farm.col, farm.row)); + for (const s of game.scanners || []) for (const cell of [{ col: s.col, row: s.row - 1 }, { col: s.col + 1, row: s.row - 1 }, { col: s.col, row: s.row }, { col: s.col + 1, row: s.row }]) blocked.add(key(cell.col, cell.row)); + for (const room of game.maintenanceRooms || []) for (let dr = 0; dr < 2; dr += 1) for (let dc = 0; dc < 2; dc += 1) blocked.add(key(room.col + dc, room.row + dr)); + for (const f of Object.values(game.facilities || {})) { + if (!f.gridPlaced) continue; + for (let dr = 0; dr < (f.hCells || 1); dr += 1) for (let dc = 0; dc < (f.wCells || 1); dc += 1) blocked.add(key(f.col + dc, f.row + dr)); + } + return blocked; +} + +function walkable(game, col, row) { + if (!inGrid(col, row) || !game.ownedCells?.has?.(key(col, row)) || game.blockedCells?.has?.(key(col, row))) return false; + const k = key(col, row); + if (equipmentCellKeys(game).has(k)) return false; + if (game.conveyorTiles?.has(k) && !game.footbridges?.has(k)) return false; + return true; +} + +function nearestWalkableFromRoom(game, room) { + if (!room) return { col: 0, row: Math.max(0, GRID.rows - 1) }; + const candidates = []; + for (let dc = 0; dc < 2; dc += 1) { + candidates.push({ col: room.col + dc, row: room.row - 1 }, { col: room.col + dc, row: room.row + 2 }); + } + for (let dr = 0; dr < 2; dr += 1) { + candidates.push({ col: room.col - 1, row: room.row + dr }, { col: room.col + 2, row: room.row + dr }); + } + return candidates.find(cell => walkable(game, cell.col, cell.row)) || null; +} + +function bfsPath(game, start, goals) { + if (!start) return null; + const goalKeys = new Set(goals.map(cell => key(cell.col, cell.row))); + const startKey = key(start.col, start.row); + const queue = [start]; + const cameFrom = new Map([[startKey, null]]); + while (queue.length) { + const current = queue.shift(); + const currentKey = key(current.col, current.row); + if (goalKeys.has(currentKey)) { + const path = []; + let cursor = currentKey; + while (cursor) { + const cell = parseKey(cursor); + path.push(cell); + cursor = cameFrom.get(cursor); + } + return path.reverse(); + } + for (const d of DIRS) { + const next = { col: current.col + d.dc, row: current.row + d.dr }; + const nk = key(next.col, next.row); + if (cameFrom.has(nk) || !walkable(game, next.col, next.row)) continue; + cameFrom.set(nk, currentKey); + queue.push(next); + } + } + return null; +} + +function repairCandidateCells(game, target) { + const candidates = []; + for (const cell of cellsForTarget(target)) { + if (target.type === 'conveyor' && game.footbridges?.has(key(cell.col, cell.row)) && walkable(game, cell.col, cell.row)) candidates.push(cell); + for (const d of DIRS) { + const candidate = { col: cell.col + d.dc, row: cell.row + d.dr }; + if (walkable(game, candidate.col, candidate.row)) candidates.push(candidate); + } + } + const seen = new Set(); + return candidates.filter(cell => { + const k = key(cell.col, cell.row); + if (seen.has(k)) return false; + seen.add(k); + return true; + }); +} + +function repairWorkCell(game, target, repairman = null) { + const goals = repairCandidateCells(game, target); + if (!goals.length) return null; + const room = (game.maintenanceRooms || []).find(item => item.id === repairman?.roomId) || (game.maintenanceRooms || [])[0]; + const start = repairman ? repairmanPathStart(game, repairman, room) : nearestWalkableFromRoom(game, room); + const path = bfsPath(game, start, goals); + return path?.[path.length - 1] || null; +} + +function pointCell(x, y) { + const col = Math.floor((x - GRID.x) / GRID.cell); + const row = Math.floor((y - GRID.y) / GRID.cell); + return inGrid(col, row) ? { col, row } : null; +} + +function repairmanPathStart(game, repairman, room) { + const current = pointCell(repairman.x, repairman.y); + if (current && walkable(game, current.col, current.row)) return current; + return nearestWalkableFromRoom(game, room); +} + +function ensureRepairPath(game, r, workCell) { + const workKey = key(workCell.col, workCell.row); + if (r.path && r.pathVersion === game.walkabilityVersion && r.target?.workKey === workKey && r.pathIndex < r.path.length) return; + const room = (game.maintenanceRooms || []).find(item => item.id === r.roomId); + const start = repairmanPathStart(game, r, room); + r.path = bfsPath(game, start, [workCell]) || []; + const current = pointCell(r.x, r.y); + const startsOnWalkablePath = current && start && current.col === start.col && current.row === start.row && walkable(game, current.col, current.row); + r.pathIndex = startsOnWalkablePath && r.path.length > 1 ? 1 : 0; + r.pathVersion = game.walkabilityVersion || 0; +} + function worstDegradation(game) { let worst = 0; diff --git a/src/systems/power.js b/src/systems/power.js new file mode 100644 index 0000000..2e4b1ff --- /dev/null +++ b/src/systems/power.js @@ -0,0 +1,302 @@ +import { BALANCE } from '../core/balance.js'; +import { DIRS } from '../core/config.js'; +import { key, parseKey } from '../core/utils.js'; +import { gridFacilityFootprintCells, maintenanceRoomFootprint } from '../core/entities.js'; + +function facilityKind(item) { + return item?.baseId || item?.id || ''; +} + +export function powerSpecFor(item) { + if (!item) return null; + const kind = item.type === 'scanner' ? `${item.kind}Scanner` : facilityKind(item) || item.type; + if (BALANCE.power.exempt.includes(kind)) return null; + return BALANCE.power.machines[kind] || null; +} + +export function generatorSpecFor(item) { + return BALANCE.power.generators[facilityKind(item)] || null; +} + +export function currentGeneratorOutputV(generator) { + const spec = generatorSpecFor(generator); + if (!spec) return 0; + const base = spec.outputV || spec.baseOutputV || 0; + const pulses = (generator.fuelPulses || []).reduce((sum, pulse) => sum + Math.max(0, pulse.voltage || 0), 0); + return base + pulses; +} + +function sourceCells(generator) { + if (!generator?.gridPlaced) return []; + return gridFacilityFootprintCells(generator); +} + +function contactCellsFor(item) { + if (!item) return []; + if (item.type === 'scanner') { + return [ + { col: item.col, row: item.row - 1 }, + { col: item.col + 1, row: item.row - 1 }, + { col: item.col, row: item.row }, + { col: item.col + 1, row: item.row } + ]; + } + if (item.type === 'maintenanceRoom') return maintenanceRoomFootprint(item); + if (item.gridPlaced) { + const kind = facilityKind(item); + const midRow = item.row + Math.floor((item.hCells || 1) / 2); + if (kind === 'sausageMaker') return [ + { col: item.col - 1, row: midRow }, + { col: item.col + (item.wCells || 3), row: midRow } + ]; + return [ + { col: item.col - 1, row: midRow }, + { col: item.col + (item.wCells || 1), row: midRow }, + { col: item.col + Math.floor((item.wCells || 1) / 2), row: item.row - 1 }, + { col: item.col + Math.floor((item.wCells || 1) / 2), row: item.row + (item.hCells || 1) } + ]; + } + if (item.entry) return [item.entry]; + return []; +} + +function adjacentWireLosses(game, cells, distances) { + let best = Infinity; + for (const cell of cells) { + for (const d of DIRS) { + const nk = key(cell.col + d.dc, cell.row + d.dr); + const distance = distances.get(nk); + if (distance != null) best = Math.min(best, distance); + } + const same = distances.get(key(cell.col, cell.row)); + if (same != null) best = Math.min(best, same); + } + return Number.isFinite(best) ? best : null; +} + +function propagateFromGenerator(game, generator) { + const distances = new Map(); + const queue = []; + const push = (k, loss) => { + if (loss < 0 || loss >= currentGeneratorOutputV(generator)) return; + if (distances.has(k) && distances.get(k) <= loss) return; + distances.set(k, loss); + queue.push({ k, loss }); + }; + for (const cell of sourceCells(generator)) { + for (const d of DIRS) { + const nk = key(cell.col + d.dc, cell.row + d.dr); + if (game.powerWires?.has(nk)) push(nk, BALANCE.power.wires[game.powerWires.get(nk).kind]?.lossV || 0); + } + } + while (queue.length) { + queue.sort((a, b) => a.loss - b.loss); + const current = queue.shift(); + if (distances.get(current.k) !== current.loss) continue; + const cell = parseKey(current.k); + for (const d of DIRS) { + const nk = key(cell.col + d.dc, cell.row + d.dr); + const wire = game.powerWires?.get(nk); + if (!wire) continue; + const step = BALANCE.power.wires[wire.kind]?.lossV ?? 0.5; + push(nk, current.loss + step); + } + } + return distances; +} + +function poweredItems(game) { + const items = []; + for (const facility of Object.values(game.facilities || {})) if (powerSpecFor(facility)) items.push(facility); + for (const scanner of game.scanners || []) if (powerSpecFor(scanner)) items.push(scanner); + for (const room of game.maintenanceRooms || []) if (powerSpecFor(room)) items.push(room); + return items; +} + +function generatorItems(game) { + return Object.values(game.facilities || {}).filter(generatorSpecFor); +} + +function stableId(item) { + return String(item.id ?? `${item.type}:${item.col},${item.row}`); +} + +function buildOrder(item) { + return item.builtSession == null ? -1 : item.builtSession; +} + +export function recalculatePowerNetwork(game) { + if (!game) return { dirty: false, routes: new Map(), assignments: new Map(), sources: new Map(), underVoltage: new Set() }; + const routesBySource = new Map(); + const sources = generatorItems(game).map(generator => { + const distances = propagateFromGenerator(game, generator); + routesBySource.set(generator.id, distances); + return { generator, outputV: currentGeneratorOutputV(generator), admitted: [], busVoltage: currentGeneratorOutputV(generator) }; + }); + const candidates = []; + for (const item of poweredItems(game)) { + const spec = powerSpecFor(item); + const routesForItem = []; + for (const source of sources) { + const loss = adjacentWireLosses(game, contactCellsFor(item), routesBySource.get(source.generator.id)); + if (loss == null) continue; + routesForItem.push({ source, loss, openCircuit: source.outputV - loss }); + } + routesForItem.sort((a, b) => b.openCircuit - a.openCircuit || a.loss - b.loss || stableId(a.source.generator).localeCompare(stableId(b.source.generator))); + if (routesForItem[0]) candidates.push({ item, spec, routes: routesForItem }); + } + candidates.sort((a, b) => + (b.spec.powerPriority || 50) - (a.spec.powerPriority || 50) + || a.routes[0].loss - b.routes[0].loss + || buildOrder(a.item) - buildOrder(b.item) + || stableId(a.item).localeCompare(stableId(b.item)) + ); + let assignments = new Map(); + let previousSignature = ''; + let stablePasses = 0; + const maxPasses = Math.max(1, BALANCE.power.maxAssignmentPasses || 8); + for (let pass = 0; pass < maxPasses; pass += 1) { + stablePasses = pass + 1; + for (const source of sources) { + source.admitted = []; + source.busVoltage = source.outputV; + } + const nextAssignments = new Map(); + for (const candidate of candidates) { + for (const route of candidate.routes) { + const source = route.source; + const nextLoad = source.admitted.reduce((sum, entry) => sum + entry.spec.drawV, 0) + candidate.spec.drawV; + const nextBus = source.outputV - nextLoad; + const admittedRemainPowered = source.admitted.every(entry => nextBus - entry.loss >= entry.spec.minVoltage); + const candidatePowered = nextBus - route.loss >= candidate.spec.minVoltage; + if (!admittedRemainPowered || !candidatePowered) continue; + source.admitted.push({ item: candidate.item, spec: candidate.spec, loss: route.loss }); + source.busVoltage = nextBus; + nextAssignments.set(stableId(candidate.item), { + sourceId: source.generator.id, + sourceName: source.generator.name || source.generator.shortName || source.generator.id, + sourceBusV: nextBus, + routeLossV: route.loss, + receivedV: nextBus - route.loss, + minVoltage: candidate.spec.minVoltage, + drawV: candidate.spec.drawV + }); + break; + } + } + const signature = [...nextAssignments.entries()].map(([id, assignment]) => `${id}:${assignment.sourceId}:${assignment.receivedV.toFixed(3)}`).join('|'); + assignments = nextAssignments; + if (signature === previousSignature) break; + previousSignature = signature; + } + const routes = new Map(); + const sourceSummary = new Map(); + for (const source of sources) { + sourceSummary.set(source.generator.id, { + outputV: source.outputV, + busVoltage: source.busVoltage, + admitted: source.admitted.length, + stablePasses + }); + } + for (const [id, data] of assignments) routes.set(id, data); + const previousUnder = game.power?.underVoltage || new Set(); + const underVoltage = new Set(); + for (const item of poweredItems(game)) if (!assignments.has(stableId(item))) underVoltage.add(stableId(item)); + const previousAssignments = game.power?.assignments || new Map(); + let assignmentChanges = 0; + for (const [id, assignment] of assignments) { + if (previousAssignments.get(id)?.sourceId !== assignment.sourceId) assignmentChanges += 1; + } + if (game.phase === 'running' && game.stats) { + game.stats.generatorAssignmentChanges = (game.stats.generatorAssignmentChanges || 0) + assignmentChanges; + if (game.totals) game.totals.generatorAssignmentChanges = (game.totals.generatorAssignmentChanges || 0) + assignmentChanges; + } + game.power = { ...(game.power || {}), dirty: false, routes, assignments, sources: sourceSummary, underVoltage, previousUnder }; + return game.power; +} + +export function ensurePowerState(game) { + if (!game.power || game.power.dirty) recalculatePowerNetwork(game); + return game.power; +} + +export function markPowerDirty(game) { + if (!game.power) game.power = {}; + game.power.dirty = true; +} + +export function updatePowerSystem(game, dt) { + let pulseChanged = false; + for (const generator of generatorItems(game)) { + if (game.stats) { + game.stats.maxGeneratorOutput = game.stats.maxGeneratorOutput || {}; + const id = generator.id || facilityKind(generator); + game.stats.maxGeneratorOutput[id] = Math.max(game.stats.maxGeneratorOutput[id] || 0, currentGeneratorOutputV(generator)); + } + if (!generator.fuelPulses?.length) continue; + for (const pulse of generator.fuelPulses) pulse.timeLeft -= dt; + const before = generator.fuelPulses.length; + generator.fuelPulses = generator.fuelPulses.filter(pulse => pulse.timeLeft > 0); + if (generator.fuelPulses.length !== before) pulseChanged = true; + } + if (pulseChanged) markPowerDirty(game); + if (!game.power) game.power = { dirty: true, elapsed: 0 }; + game.power.elapsed = (game.power.elapsed || 0) + dt; + const interval = 1 / Math.max(1, BALANCE.power.recalcHz || 10); + if (game.power.dirty || game.power.elapsed >= interval) { + game.power.elapsed = 0; + recalculatePowerNetwork(game); + } + const powered = ensurePowerState(game); + if (game.phase === 'running') { + const count = powered.underVoltage?.size || 0; + if (count) { + game.stats.underVoltageSeconds = (game.stats.underVoltageSeconds || 0) + count * dt; + game.stats.underVoltageSecondsByMachine = game.stats.underVoltageSecondsByMachine || {}; + for (const id of powered.underVoltage) { + game.stats.underVoltageSecondsByMachine[id] = (game.stats.underVoltageSecondsByMachine[id] || 0) + dt; + } + game.totals.underVoltageSeconds = (game.totals.underVoltageSeconds || 0) + count * dt; + } + } +} + +export function receivedVoltageFor(game, equipment) { + return ensurePowerState(game).assignments.get(stableId(equipment))?.receivedV || 0; +} + +export function isEquipmentPowered(game, equipment) { + if (!powerSpecFor(equipment)) return true; + return ensurePowerState(game).assignments.has(stableId(equipment)); +} + +export function powerDetailsFor(game, equipment) { + const spec = powerSpecFor(equipment); + if (!spec) return null; + return ensurePowerState(game).assignments.get(stableId(equipment)) || { + sourceName: 'None', + sourceBusV: 0, + routeLossV: 0, + receivedV: 0, + minVoltage: spec.minVoltage, + drawV: spec.drawV + }; +} + +export function powerSummary(game) { + const state = ensurePowerState(game); + const sources = [...(state.sources || new Map()).entries()].map(([id, data]) => `${id} ${data.outputV.toFixed(0)}V / ${data.admitted} powered`); + return `POWER: ${sources.join(' | ') || 'none'} / ${state.underVoltage?.size || 0} under-voltage`; +} + +export function addFuelPulse(generator, itemType) { + const kind = facilityKind(generator); + const spec = BALANCE.power.generators[kind]; + if (!spec) return false; + if (kind === 'biomassGenerator' && !['male', 'female', 'poop'].includes(itemType)) return false; + if (kind === 'coalGenerator' && itemType !== 'coal') return false; + generator.fuelPulses = generator.fuelPulses || []; + generator.fuelPulses.push({ voltage: spec.pulseVoltage, timeLeft: spec.pulseSeconds }); + return true; +} diff --git a/src/systems/routing.js b/src/systems/routing.js index c9074e5..34c8289 100644 --- a/src/systems/routing.js +++ b/src/systems/routing.js @@ -51,7 +51,7 @@ export function facilityInputCell(game, facility) { export function facilityOutputCell(game, facility) { const f = typeof facility === 'string' ? game?.facilities?.[facility] : facility; const kind = facilityKind(f); - if (!f || !['mixer', 'sausageMaker'].includes(kind)) return null; + if (!f || !['mixer', 'sausageMaker', 'inboundTruck'].includes(kind)) return null; if (kind === 'sausageMaker' && f.gridPlaced) { const p = { col: f.col + (f.wCells || 3), row: f.row + Math.floor((f.hCells || 3) / 2) }; return usableOwnedCell(game, p) ? p : null; @@ -559,7 +559,7 @@ export function autoSideFor(scanner, chick) { export function destinationLabel(dest) { return routeLabel(dest); } export function destinationColor(dest) { - return { mixer: '#2477ff', truck: '#ff6aa8', trash: '#22b94f', sausageMaker: '#d78b3f', 'scanner-role-1': '#526456', scanner: '#526456', input: '#526456' }[dest] || '#102015'; + return { mixer: '#2477ff', truck: '#ff6aa8', inboundTruck: '#8bd0ff', trash: '#22b94f', sausageMaker: '#d78b3f', biomassGenerator: '#7bd84f', coalGenerator: '#d0d5dc', 'scanner-role-1': '#526456', scanner: '#526456', input: '#526456' }[dest] || '#102015'; } export function routeFromFarmToScanner(game, farm, advance = false) { diff --git a/src/systems/selectionSystem.js b/src/systems/selectionSystem.js index 5f501b6..eb6bbb1 100644 --- a/src/systems/selectionSystem.js +++ b/src/systems/selectionSystem.js @@ -4,6 +4,7 @@ import { nearestGridEdge, layoutFacilityOnEdge, maintenanceRoomFootprint, gridFa import { farmAt, scannerAt, scannerCenter, scannerConnector, scannerFootprintCells, gridFacilityAt, facilityInputCell, facilityOutputCell, refreshRoutingAfterEdit } from './routing.js'; import { snapshot } from './history.js'; import { buildPrice } from './economy.js'; +import { markPowerDirty } from './power.js'; export function createSelectionSystem({ game, canvasPoint, updatePanels, equipmentAtPoint, fail, pointInGrid, rectOfFacility, rectsOverlap }) { function selectionToken(item) { @@ -32,6 +33,8 @@ export function createSelectionSystem({ game, canvasPoint, updatePanels, equipme if (sel.type === 'facility') return game.facilities[sel.id] || null; if (sel.type === 'maintenanceRoom') return (game.maintenanceRooms || []).find(r => r.id === sel.id) || null; if (sel.type === 'conveyor') return game.conveyorTiles.has(sel.id) ? { type: 'conveyor', id: sel.id } : null; + if (sel.type === 'powerWire') return game.powerWires?.has(sel.id) ? { type: 'powerWire', id: sel.id } : null; + if (sel.type === 'footbridge') return game.footbridges?.has(sel.id) ? { type: 'footbridge', id: sel.id } : null; return null; } @@ -42,6 +45,16 @@ export function createSelectionSystem({ game, canvasPoint, updatePanels, equipme const c = cellCenter(p.col, p.row); items.push({ type: 'conveyor', id: k, center: c, cell: p }); } + if (game.powerOverlay) { + for (const [k] of game.powerWires || new Map()) { + const p = parseKey(k); + items.push({ type: 'powerWire', id: k, center: cellCenter(p.col, p.row), cell: p }); + } + } + for (const [k] of game.footbridges || new Map()) { + const p = parseKey(k); + items.push({ type: 'footbridge', id: k, center: cellCenter(p.col, p.row), cell: p }); + } for (const farm of game.eggFarms) items.push({ type: 'eggFarm', id: farm.id, ref: farm, center: cellCenter(farm.col, farm.row), cell: { col: farm.col, row: farm.row } }); for (const scanner of game.scanners) items.push({ type: 'scanner', id: scanner.id, ref: scanner, center: scannerCenter(scanner), cell: { col: scanner.col, row: scanner.row } }); for (const room of game.maintenanceRooms || []) { @@ -123,6 +136,22 @@ export function createSelectionSystem({ game, canvasPoint, updatePanels, equipme const cell = parseKey(sel.id); return { ...sel, obj, oldKey: sel.id, currentKey: sel.id, meta: game.conveyorMeta.get(sel.id) || { price: buildPrice('conveyor'), builtSession: null }, col: cell.col, row: cell.row }; } + if (sel.type === 'powerWire') { + const cell = parseKey(sel.id); + return { ...sel, obj, oldKey: sel.id, currentKey: sel.id, meta: { ...(game.powerWires?.get(sel.id) || {}) }, col: cell.col, row: cell.row }; + } + if (sel.type === 'footbridge') { + const cell = parseKey(sel.id); + const meta = game.footbridges?.get(sel.id) || {}; + const cells = []; + for (const [k, bridge] of game.footbridges || new Map()) { + if ((meta.bridgeId && bridge.bridgeId === meta.bridgeId) || k === sel.id) { + const part = parseKey(k); + cells.push({ key: k, dc: part.col - cell.col, dr: part.row - cell.row, meta: { ...bridge } }); + } + } + return { ...sel, obj, oldKey: sel.id, currentKey: sel.id, meta: { ...meta }, cells, dragToken: `footbridge:${meta.bridgeId || sel.id}`, col: cell.col, row: cell.row }; + } if (sel.type === 'eggFarm' || sel.type === 'scanner' || sel.type === 'maintenanceRoom') return { ...sel, obj, col: obj.col, row: obj.row }; if (sel.type === 'facility') return obj.gridPlaced ? { ...sel, obj, gridPlaced: true, col: obj.col, row: obj.row, x: obj.x, y: obj.y, w: obj.w, h: obj.h, entry: obj.entry ? { ...obj.entry } : null, side: obj.side, center: { x: obj.x + obj.w / 2, y: obj.y + obj.h / 2 } } @@ -135,7 +164,14 @@ export function createSelectionSystem({ game, canvasPoint, updatePanels, equipme const hit = equipmentAtPoint(p); if (!hit) return false; const selections = selectedSetForDrag(hit); - const origins = selections.map(selectionOrigin).filter(Boolean); + const origins = []; + const seenOrigins = new Set(); + for (const origin of selections.map(selectionOrigin).filter(Boolean)) { + const token = origin.dragToken || selectionToken(origin); + if (seenOrigins.has(token)) continue; + seenOrigins.add(token); + origins.push(origin); + } if (!origins.length) return false; game.groupDrag = { start: p, selections, origins, historySnapshot: snapshot(game), committed: false, lastDCol: 0, lastDRow: 0 }; game.multiSelected = selections; @@ -194,6 +230,20 @@ export function createSelectionSystem({ game, canvasPoint, updatePanels, equipme return false; } + function overlayTargetBlocked(origin, col, row, selectedTokens) { + const k = key(col, row); + if (!pointInGrid(col, row) || game.blockedCells?.has?.(k)) return 'Selection hits blocked ground'; + if (farmAt(game, col, row) || scannerAt(game, col, row)) return 'Cell occupied'; + const room = (game.maintenanceRooms || []).find(item => maintenanceRoomFootprint(item).some(cell => cell.col === col && cell.row === row)); + if (room && !selectedTokens.has(`maintenanceRoom:${room.id}`)) return 'Cell occupied'; + const gridFacility = gridFacilityAt(game, col, row); + if (gridFacility && !selectedTokens.has(`facility:${gridFacility.id}`)) return 'Cell occupied'; + const originOwnsCell = origin.cells?.some(part => part.key === k); + if (origin.type === 'powerWire' && game.powerWires?.has(k) && !selectedTokens.has(`powerWire:${k}`)) return 'Power wire already covers that cell'; + if (origin.type === 'footbridge' && game.footbridges?.has(k) && !selectedTokens.has(`footbridge:${k}`) && !originOwnsCell) return 'Footbridge already covers that cell'; + return ''; + } + function facilityDragWouldOverlap(dx, dy, selectedTokens) { const candidateRects = []; for (const origin of game.groupDrag.origins) { @@ -254,6 +304,19 @@ export function createSelectionSystem({ game, canvasPoint, updatePanels, equipme for (const origin of game.groupDrag.origins) { if (origin.type === 'facility' && !origin.gridPlaced) continue; const col = origin.col + dcol, row = origin.row + drow; + if (origin.type === 'powerWire' || origin.type === 'footbridge') { + const parts = origin.type === 'footbridge' && origin.cells?.length + ? origin.cells.map(part => ({ col: col + part.dc, row: row + part.dr })) + : [{ col, row }]; + for (const part of parts) { + const reason = overlayTargetBlocked(origin, part.col, part.row, selectedTokens); + if (reason) return fail(reason); + const tk = `${origin.type}:${key(part.col, part.row)}`; + if (targetCells.has(tk)) return fail('Selection overlap'); + targetCells.add(tk); + } + continue; + } const cells = origin.type === 'scanner' ? scannerFootprintCells({ col, row }) : (origin.type === 'maintenanceRoom' @@ -293,6 +356,25 @@ export function createSelectionSystem({ game, canvasPoint, updatePanels, equipme game.conveyorMeta.set(newK, o.meta || { price: buildPrice('conveyor'), builtSession: null, uses: 0 }); o.currentKey = newK; } + const wireOrigins = game.groupDrag.origins.filter(o => o.type === 'powerWire'); + for (const o of wireOrigins) game.powerWires.delete(o.currentKey || o.oldKey); + for (const o of wireOrigins) { + const newK = key(o.col + dcol, o.row + drow); + game.powerWires.set(newK, o.meta || {}); + o.currentKey = newK; + } + const footbridgeOrigins = game.groupDrag.origins.filter(o => o.type === 'footbridge'); + for (const o of footbridgeOrigins) { + for (const part of o.cells || [{ key: o.currentKey || o.oldKey }]) game.footbridges.delete(part.key); + } + for (const o of footbridgeOrigins) { + const newK = key(o.col + dcol, o.row + drow); + for (const part of o.cells || [{ dc: 0, dr: 0, meta: o.meta || {} }]) { + const partKey = key(o.col + dcol + (part.dc || 0), o.row + drow + (part.dr || 0)); + game.footbridges.set(partKey, { ...(part.meta || o.meta || {}) }); + } + o.currentKey = newK; + } for (const origin of game.groupDrag.origins) { if (origin.type === 'eggFarm') { origin.obj.col = origin.col + dcol; @@ -314,10 +396,12 @@ export function createSelectionSystem({ game, canvasPoint, updatePanels, equipme layoutFacilityOnEdge(origin.obj, entry, side); } } - game.groupDrag.selections = game.groupDrag.origins.map(o => o.type === 'conveyor' ? { type: 'conveyor', id: o.currentKey || o.oldKey } : { type: o.type, id: o.id }); + game.groupDrag.selections = game.groupDrag.origins.map(o => ['conveyor', 'powerWire', 'footbridge'].includes(o.type) ? { type: o.type, id: o.currentKey || o.oldKey } : { type: o.type, id: o.id }); game.multiSelected = [...game.groupDrag.selections]; game.selected = game.multiSelected[0] || null; refreshRoutingAfterEdit(game); + if (game.groupDrag.origins.some(origin => origin.type !== 'powerWire')) game.walkabilityVersion = (game.walkabilityVersion || 0) + 1; + if (game.groupDrag.origins.some(origin => origin.type === 'powerWire' || (origin.type !== 'conveyor' && origin.type !== 'footbridge'))) markPowerDirty(game); updatePanels(); } diff --git a/src/systems/uiSystem.js b/src/systems/uiSystem.js index 180ef58..fd71ce4 100644 --- a/src/systems/uiSystem.js +++ b/src/systems/uiSystem.js @@ -5,6 +5,7 @@ import { routeFromFarmToScanner, facilityConnectionIssues } from './routing.js'; import { buildPrice, fairiesTributeInfo, finalScore, maleTruckPenalty, mixerPoopPenalty, truckPoopPenalty } from './economy.js'; import { conveyorSpeedForGame } from './cards.js'; import { maintenanceSummary } from './maintenance.js'; +import { powerSummary } from './power.js'; export function createUISystem({ game, ui, build, startGame, beginCardDraft, activeQueuedChick }) { function escapeHtml(value) { @@ -35,6 +36,8 @@ export function createUISystem({ game, ui, build, startGame, beginCardDraft, act if (!btn?.classList?.contains('tool-button')) continue; btn.classList.toggle('active', game.buildTool === name); } + ui.buttons.powerOverlay?.classList.toggle('active', !!game.powerOverlay); + if (ui.buttons.powerOverlay) ui.buttons.powerOverlay.disabled = game.phase !== 'build' || !!game.cardDraft?.pending || !!game.cardTargetPick?.pending; for (const id of BUILD_TOOL_IDS) { const btn = ui.buttons[id]; if (!btn) continue; @@ -81,6 +84,7 @@ export function createUISystem({ game, ui, build, startGame, beginCardDraft, act ui.turnSummary.innerHTML = [ `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`, `Penalties: wrong shipment -${yen(maleTruckPenalty(game))} | Mixer poop -${yen(mixerPoopPenalty(game))} | Shipment poop -${yen(truckPoopPenalty(game))}`, + powerSummary(game), `Maintenance: worst ${maintenanceSummary(game).worst.percent}% dirty (${maintenanceSummary(game).worst.label}) | Rooms: ${(game.maintenanceRooms || []).length} | Repairmen: ${(game.repairmen || []).length}`, `Card draft: ${game.cardDraft?.pending ? Math.max(1, game.cardDraft.picksRemaining || 1) + ' pick(s) left' : 'ready after each day'} | Farms with scanner route: ${connected}/${game.eggFarms.length}`, game.cardEffects?.usedMachineActive ? 'Procurement: USED MACHINE MODE / no refunds / 60% durability' : '', @@ -184,6 +188,10 @@ export function createUISystem({ game, ui, build, startGame, beginCardDraft, act const negative = value => `${value > 0 ? '-' : ''}${yen(value)}`; const netClass = r.profit >= 0 ? 'cash-positive' : 'cash-negative'; const penalties = (r.mixerPoopFine || 0) + (r.truckPoopFine || 0) + (r.maleTruckFine || 0) + (r.explosionDamage || 0) + (r.cardRerollCost || 0); + const avgQuality = r.qualityMultiplierCount ? (r.qualityMultiplierSum || 0) / r.qualityMultiplierCount : 1; + const qualityRevenue = r.qualityRevenue || 0; + const qualityClass = qualityRevenue > 0 ? 'cash-positive' : ''; + const maxGeneratorOutput = Object.values(r.maxGeneratorOutput || {}).reduce((max, value) => Math.max(max, Number(value) || 0), 0); ui.modalTitle.textContent = `Day ${r.turn} Settlement`; ui.modalBody.innerHTML = `
Net result${yen(r.profit)}
@@ -192,6 +200,14 @@ export function createUISystem({ game, ui, build, startGame, beginCardDraft, act
Poop shipment income${positive(r.poopShipmentIncome || 0)}
Sausage shipment income${positive(r.sausageRevenue || 0)}
Meat / sausages${r.meatProduced || 0} meat -> ${r.sausageProduced || 0} made / ${r.sausageShipped || 0} shipped
+
Importscoal ${r.coalReleased || 0}/${r.coalOrdered || 0}, zunda ${r.zundaReleased || 0}/${r.zundaOrdered || 0}
+
Import usecoal fuel ${r.coalConsumed || 0}, zunda used ${r.zundaConsumed || 0}, undelivered ${r.coalUndelivered || 0}/${r.zundaUndelivered || 0}
+
Import cost${negative(r.importPurchaseCost || 0)}
+
Generator fuelbiomass ${r.biomassFuelConsumed || 0}, max output ${maxGeneratorOutput.toFixed(0)}V
+
Quality revenue${qualityRevenue > 0 ? '+' : ''}${yen(qualityRevenue)} avg x${avgQuality.toFixed(2)} / max x${(r.maxQualityMultiplier || 1).toFixed(2)}
+
Zunda boost${(r.zundaBoostSeconds || 0).toFixed(1)} farm-seconds
+
Power trouble${(r.underVoltageSeconds || 0).toFixed(1)} machine-seconds / ${r.generatorAssignmentChanges || 0} source changes
+
Repair access${r.unreachableRepairTargets || 0} unreachable target checks
Composter income${positive(r.composterIncome || 0)}
DUD refund income${positive(r.dudRefundIncome || 0)}
Manual combo bonus${positive(r.manualComboBonus || 0)}
diff --git a/test_v29_00.mjs b/test_v29_00.mjs new file mode 100644 index 0000000..da5c8fb --- /dev/null +++ b/test_v29_00.mjs @@ -0,0 +1,177 @@ +import assert from 'node:assert/strict'; +import { BALANCE } from './src/core/balance.js'; +import { createGame, resetLayout } from './src/core/state.js'; +import { key } from './src/core/utils.js'; +import { recalculatePowerNetwork, isEquipmentPowered, currentGeneratorOutputV, addFuelPulse, receivedVoltageFor, updatePowerSystem } from './src/systems/power.js'; +import { clampInboundOrder, createImportSchedule, inboundOrderCost } from './src/systems/imports.js'; +import { boostOutputCapacityPerSecond, qualityForRate } from './src/systems/farmQuality.js'; + +function approx(actual, expected, epsilon = 1e-6) { + assert.ok(Math.abs(actual - expected) <= epsilon, `expected ${actual} to be within ${epsilon} of ${expected}`); +} + +function emptyPowerGame() { + const game = createGame(); + resetLayout(game); + game.facilities = {}; + game.scanners = []; + game.maintenanceRooms = []; + game.powerWires = new Map(); + game.power = { dirty: true, elapsed: 0, routes: new Map(), assignments: new Map(), sources: new Map(), underVoltage: new Set() }; + return game; +} + +function starter(id, col, row, outputV = 25) { + const generator = { + type: 'facility', + id, + baseId: 'starterGenerator', + name: id, + col, + row, + wCells: 2, + hCells: 2, + gridPlaced: true, + fuelPulses: [] + }; + if (outputV !== 25) generator.fuelPulses = [{ voltage: outputV - 25, timeLeft: 10 }]; + return generator; +} + +function biomass(id, col, row) { + return { type: 'facility', id, baseId: 'biomassGenerator', name: id, col, row, wCells: 3, hCells: 3, gridPlaced: true, fuelPulses: [] }; +} + +function edgeMachine(id, baseId, col, row, builtSession = 1) { + return { type: 'facility', id, baseId, name: id, entry: { col, row }, side: 'left', builtSession }; +} + +function wireLine(game, row, fromCol, toCol, kind = 'powerWire') { + const step = fromCol <= toCol ? 1 : -1; + for (let col = fromCol; col !== toCol + step; col += step) game.powerWires.set(key(col, row), { kind, price: 0, builtSession: null }); +} + +{ + assert.equal(BALANCE.version, 'v29.00'); + assert.equal(BALANCE.power.wires.powerWire.lossV, 0.5); + assert.equal(BALANCE.power.wires.insulatedPowerWire.lossV, 0.15); + assert.equal(BALANCE.imports.prices.coal, 2); + assert.equal(BALANCE.imports.prices.zundaMochi, 4); +} + +{ + const game = createGame(); + resetLayout(game); + recalculatePowerNetwork(game); + assert.ok(game.facilities.starterGenerator, 'new games include starter generator'); + assert.ok(game.powerWires.size > 0, 'new games include starter wires'); + assert.ok(isEquipmentPowered(game, game.facilities.mixer), 'initial mixer is powered'); + assert.ok(isEquipmentPowered(game, game.facilities.trash), 'initial shredder is powered'); + assert.ok(isEquipmentPowered(game, game.facilities.truck), 'initial shipping truck is powered'); + const loads = ['mixer', 'trash', 'truck'].reduce((sum, id) => sum + BALANCE.power.machines[id].drawV, 0); + assert.equal(loads, 9); + for (const [wireKey, wire] of game.powerWires) { + assert.equal(wire.price, 0, `starter wire ${wireKey} is free`); + assert.equal(game.blockedCells.has(wireKey), false, `starter wire ${wireKey} avoids obstacles`); + } + assert.equal(game.facilities.starterGenerator.price, 0); + assert.equal(game.facilities.starterGenerator.unsellable, true); +} + +{ + const game = emptyPowerGame(); + game.facilities.gen = starter('gen', 5, 5); + game.facilities.mixer = edgeMachine('mixer', 'mixer', 3, 5); + wireLine(game, 5, 4, 4, 'powerWire'); + game.phase = 'build'; + recalculatePowerNetwork(game); + approx(receivedVoltageFor(game, game.facilities.mixer), 20.5); + assert.equal(game.stats.generatorAssignmentChanges, 0, 'build-mode power summaries do not affect daily assignment stats'); + game.phase = 'running'; + game.powerWires.set(key(4, 4), { kind: 'powerWire', price: 0, builtSession: null }); + game.power.dirty = true; + recalculatePowerNetwork(game); + assert.equal(game.stats.generatorAssignmentChanges, 0, 'unchanged running assignment does not increment source changes'); +} + +{ + const game = emptyPowerGame(); + game.facilities.gen = starter('gen', 12, 5); + game.facilities.mixer = edgeMachine('mixer', 'mixer', 1, 5); + wireLine(game, 5, 11, 2, 'powerWire'); + recalculatePowerNetwork(game); + approx(receivedVoltageFor(game, game.facilities.mixer), 16); +} + +{ + const game = emptyPowerGame(); + game.facilities.gen = starter('gen', 12, 5); + game.facilities.mixer = edgeMachine('mixer', 'mixer', 1, 5); + wireLine(game, 5, 11, 2, 'insulatedPowerWire'); + recalculatePowerNetwork(game); + approx(receivedVoltageFor(game, game.facilities.mixer), 19.5); +} + +{ + const game = emptyPowerGame(); + game.facilities.gen = starter('gen', 12, 5); + game.facilities.mixer = edgeMachine('mixer', 'mixer', 1, 5); + for (let col = 11; col >= 7; col -= 1) game.powerWires.set(key(col, 5), { kind: 'powerWire', price: 0, builtSession: null }); + for (let col = 6; col >= 2; col -= 1) game.powerWires.set(key(col, 5), { kind: 'insulatedPowerWire', price: 0, builtSession: null }); + recalculatePowerNetwork(game); + approx(receivedVoltageFor(game, game.facilities.mixer), 17.75); +} + +{ + const game = emptyPowerGame(); + game.facilities.genA = biomass('genA', 2, 2); + game.facilities.genB = biomass('genB', 7, 3); + addFuelPulse(game.facilities.genA, 'male'); + addFuelPulse(game.facilities.genB, 'female'); + game.facilities.truck = edgeMachine('truck', 'truck', 5, 5); + game.powerWires.set(key(5, 4), { kind: 'powerWire', price: 0, builtSession: null }); + game.powerWires.set(key(6, 5), { kind: 'powerWire', price: 0, builtSession: null }); + recalculatePowerNetwork(game); + assert.equal(isEquipmentPowered(game, game.facilities.truck), false, 'separate generators do not sum voltage'); + addFuelPulse(game.facilities.genA, 'poop'); + recalculatePowerNetwork(game); + assert.equal(isEquipmentPowered(game, game.facilities.truck), true, 'one source with enough stacked pulse can power the truck'); +} + +{ + const generator = { baseId: 'biomassGenerator', fuelPulses: [] }; + addFuelPulse(generator, 'male'); + addFuelPulse(generator, 'female'); + addFuelPulse(generator, 'poop'); + assert.equal(currentGeneratorOutputV(generator), 15); + const coal = { baseId: 'coalGenerator', fuelPulses: [] }; + addFuelPulse(coal, 'coal'); + assert.equal(currentGeneratorOutputV(coal), 15); + updatePowerSystem({ facilities: { coal }, stats: {}, totals: {}, power: { dirty: false, elapsed: 0 } }, 3.1); + assert.equal(currentGeneratorOutputV(coal), 0); + assert.equal(addFuelPulse(coal, 'poop'), false); +} + +{ + assert.deepEqual(clampInboundOrder({ coalPerDay: 100, zundaMochiPerDay: 100 }), { coalPerDay: 40, zundaMochiPerDay: 40 }); + assert.equal(inboundOrderCost({ coalPerDay: 10, zundaMochiPerDay: 5 }), 40); + const sixty = createImportSchedule({ coalPerDay: 60 }, 'test'); + assert.equal(sixty.length, 60); + approx(sixty[1].due - sixty[0].due, 1, 0.35); + const six = createImportSchedule({ coalPerDay: 6 }, 'test'); + approx(six[1].due - six[0].due, 10, 3.1); + const mixed = createImportSchedule({ coalPerDay: 40, zundaMochiPerDay: 40 }, 'test'); + assert.equal(mixed.length, BALANCE.imports.combinedLimitPerTruck); + approx(mixed[1].due - mixed[0].due, 0.75, 0.3); +} + +{ + const game = createGame(); + const cap = boostOutputCapacityPerSecond(game); + assert.ok(cap > 0); + assert.equal(qualityForRate(cap, cap), 1); + assert.ok(qualityForRate(cap * 2, cap) > qualityForRate(cap * 1.5, cap)); + assert.equal(qualityForRate(cap * 100, cap), BALANCE.farmQuality.maxMultiplier); +} + +console.log('v29.00 tests passed');