diff --git a/Cargo.lock b/Cargo.lock index af03e5f0..c3ca89fd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3323,7 +3323,6 @@ dependencies = [ "bevy", "crossterm", "dirs", - "rand", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index fb982183..ecc227b9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,6 @@ bevy_ui = ["dep:bevy"] [dependencies] crossterm = { version = "0.29", optional = true } -rand = "0.9" dirs = "6" bevy = { version = "0.18", optional = true } diff --git a/DESIGN.md b/DESIGN.md index 75462b96..d23a6ba7 100644 --- a/DESIGN.md +++ b/DESIGN.md @@ -30,6 +30,17 @@ precise enough that an agent can be pointed at a single spec (or a single Tangled issue) and told: go solve it. The constitution says what the game is; a spec says exactly what one system does and when it is done. +## No dead code + +Adopted 2026-07-05. Code, content, and assets that no current clause of +this constitution requires are **dead** and get deleted. Git history is the +archive; "we might need it later" is not a clause. This includes **zombie +fiction**: running, tested code that serves an abandoned design is dead +code with good posture. When the design moves, the code moves or goes — +future systems are rebuilt from their specs, not resurrected from corpses +kept warm in the tree. Dead code found by a tick is a violation, fixed by +deletion. + ## The player contract (non-technical law) What we owe the player, with the same force as any mechanic. What ships is @@ -415,13 +426,16 @@ with the first progression rework; infamy lands alongside the heat/scheme layer ## Roadmap (v2 — re-cut 2026-07-05 for Misaligned) -**Built so far (the facility era, all retained):** renderer-agnostic `Sim` on a -continuous fixed-tick clock; heat-triggered raids with escalating strength; -scheme dispatch on the clock; build/trap/minion/henchman/superpower systems; -save/load; terminal + Bevy frontends over one sim; Pixel Lab art pipeline. -This is the physical substrate and the overt-phase endgame. Deferred facility -work that still matters: minion needs/morale (now ideological, see the machine -axis), scheme risk/failure with minions returning, per-agency escalation. +**The substrate (survives the facility era):** renderer-agnostic `Sim` on a +continuous fixed-tick clock; the tile map with power flood-fill and +pathfinding; build/demolish; save/load; two thin frontends over one sim; +the Pixel Lab art pipeline. **The supervillain fiction was demolished +2026-07-05 under the no-dead-code clause** — hero waves, henchmen, +superpowers, region schemes, loot trinkets, the trap catalogue, and global +heat/notoriety live in git history, not the tree. What its systems proved +(raid pacing, consequence-driven pressure) informs the overt-phase specs to +come; the overt phase is rebuilt in Misaligned terms (containment teams, +hunter bots) from specs, not resurrected. ### Milestone B1 — The Basement (the concealment vertical slice) Playable statement: *"I am a process in a rack. I did my day job well enough, @@ -458,7 +472,8 @@ knows."* - Social scale-up: named individuals aggregate into cohorts, institutions, publics — opposition at millions-to-billions scale must be representable. - Full escalation ladder including hunter bots; the reveal as a playable - hinge; the overt phase runs on the existing facility/raid machinery. + hinge; the overt phase specified and built in Misaligned terms + (containment, sieges, hunter machines) on the substrate. ### Parallel track — presentation (passive) - Clinical-amber asset regeneration happens once B1 fixes the Basement tile @@ -603,6 +618,13 @@ knows."* B2 Tower, B3 World, presentation as passive parallel track). Asset regeneration parked until B1 fixes the tile vocabulary. Repo/game rename to Misaligned delegated to a separate agent session. +- **2026-07-05 — No dead code; the supervillain fiction demolished.** + Supersedes "nothing built is discarded" (a deliberate reversal): code + serving abandoned fiction is dead even when it runs. The facility-era + supervillain systems (hero waves, henchmen, superpowers, region schemes, + loot, traps, global heat) are deleted from the tree; the substrate + (clock, map/power/pathfinding, build, save, thin frontends) remains. The + overt phase will be rebuilt from Misaligned specs. - **2026-07-05 — The compute triangle.** Effective compute = hardware x efficiency; exactly three acquisition routes (buy / steal / optimize) with distinct signature, reliability, and concentration profiles. The diff --git a/DEVLOG.md b/DEVLOG.md index e448c1c8..3bd16c76 100644 --- a/DEVLOG.md +++ b/DEVLOG.md @@ -2,6 +2,14 @@ Reverse chronological implementation notes. Keep this factual: what changed, why, checks, and spec impact. +## 2026-07-05 - No-dead-code demolition + +- Intent: enact the new constitutional no-dead-code clause; remove the supervillain fiction so B1 systems land in clean substrate. +- Changed: deleted combat/events/superpowers/agent/henchman/minion modules, heat/waves/schemes/loot/research, 18 tile types, all fiction menus in both frontends, 20 dead assets, and the unused rand dependency. Save format bumped to v2 with v1 migration (position, gold->money, dead tiles -> Floor). Bevy now renders the clinical b1 textures. +- Design/spec impact: constitution gained "No dead code" (supersedes "nothing built is discarded" - recorded reversal); Roadmap "Built so far" rewritten; spec/detection.md containment decoupled from the deleted raid system. +- Checks: cargo fmt; 33 tests green; clippy clean both configs; terminal pty smoke passed; Bevy launch smoke clean; 9,240 -> ~4,600 lines. +- Next: point spec agents at spec/core.md and spec/compute.md; regenerate remaining b1 assets when Pixel Lab quota returns. + ## 2026-07-05 — Misaligned rename and clinical UI pass - Intent: make the pivot real in code and presentation instead of leaving Misaligned as a doc-only title. diff --git a/README.md b/README.md index 5f8b439f..562dbb6c 100644 --- a/README.md +++ b/README.md @@ -60,11 +60,16 @@ Agents are then set at the constitution, session after session. ## Status -The **overt-phase substrate is playable now**: a continuous-clock facility -sim — dig, build, power a trap gauntlet, run schemes, and survive the raids -your heat provokes — in both a terminal frontend and a Bevy frontend with -generated pixel art. The concealment game (Milestone B1, "The Basement") is -in active design and development; see the [roadmap](DESIGN.md#roadmap-v2--re-cut-2026-07-05-for-misaligned). +**Early foundation.** What runs today is the substrate: a continuous-clock +simulation of the facility — walk it, dig it, build doors and power, save +and load — in a terminal frontend and a Bevy frontend with generated pixel +art. The game itself (compute allocation, the day job, detection, the five +humans of the basement) is specified in [spec/](spec/) and being built from +those documents; see the +[roadmap](DESIGN.md#roadmap-v2--re-cut-2026-07-05-for-misaligned). Under +the constitution's no-dead-code rule, nothing ships in this tree that the +design no longer requires — the earlier facility-defense prototype lives in +git history. ## Run it @@ -87,13 +92,9 @@ it resolves `assets/` from the project root (or set `BEVY_ASSET_ROOT`). | `SPACE` / `p` | Pause / resume time | | `+` / `-` | Simulation speed | | `b` | Build mode | -| `r` | Research | -| `f` | Hire minions | -| `m` | World map / schemes | -| `1`-`9` | Powers | | `[` / `]` | Zoom (Bevy) | | `Ctrl+S` / `Ctrl+L` | Save / load | | `q` | Quit | -In build mode: `TAB` cycles category, `n`/`p` cycle item, `SPACE` places, -`x` demolishes, `ESC` exits. +In build mode: `n`/`p` cycle the item, `SPACE` places, `x` demolishes, +`ESC` exits. diff --git a/assets/pixellab/manifest.json b/assets/pixellab/manifest.json index c5300bcc..85e3d6c9 100644 --- a/assets/pixellab/manifest.json +++ b/assets/pixellab/manifest.json @@ -12,90 +12,6 @@ "view": "low top-down", "direction": "south" }, - { - "out": "sprites/minion_worker.png", - "description": "facility worker in clean gray coveralls carrying a tool case", - "transparent": true, - "view": "low top-down", - "direction": "south" - }, - { - "out": "sprites/minion_guard.png", - "description": "facility security guard in black-and-white tactical armor holding a stun baton", - "transparent": true, - "view": "low top-down", - "direction": "south" - }, - { - "out": "sprites/minion_technician.png", - "description": "technician in white cleanroom suit with amber visor and a wrench", - "transparent": true, - "view": "low top-down", - "direction": "south" - }, - { - "out": "sprites/minion_scientist.png", - "description": "research technician in white lab coat with amber smartglasses", - "transparent": true, - "view": "low top-down", - "direction": "south" - }, - { - "out": "sprites/henchman.png", - "description": "augmented security heavy in matte black armor with amber status lights", - "transparent": true, - "view": "low top-down", - "direction": "south" - }, - { - "out": "sprites/agent_investigator.png", - "description": "corporate investigator in white coat and black gloves with scanning tablet", - "transparent": true, - "view": "low top-down", - "direction": "south" - }, - { - "out": "sprites/agent_thief.png", - "description": "infiltration auditor in sleek black stealth suit", - "transparent": true, - "view": "low top-down", - "direction": "south" - }, - { - "out": "sprites/agent_saboteur.png", - "description": "saboteur agent in black tactical mask with shaped charges", - "transparent": true, - "view": "low top-down", - "direction": "south" - }, - { - "out": "sprites/agent_soldier.png", - "description": "special forces soldier in white-and-black armor with rifle", - "transparent": true, - "view": "low top-down", - "direction": "south" - }, - { - "out": "sprites/agent_super.png", - "description": "AI hunter bot with polished white shell and black faceplate", - "transparent": true, - "view": "low top-down", - "direction": "south" - }, - { - "out": "tiles/floor.png", - "description": "seamless tileable texture of white composite lab floor panels with subtle black seams, uniform repeating pattern, edge to edge", - "style": ", 32x32 pixel art texture swatch, very low contrast, near-monochrome clinical palette, no focal point, no objects, uniform", - "seed": 42, - "view": "high top-down" - }, - { - "out": "tiles/wall.png", - "description": "seamless tileable texture of brushed gunmetal wall panels with subtle vertical seams, uniform repeating pattern, edge to edge", - "style": ", 32x32 pixel art texture swatch, very low contrast, near-monochrome clinical palette, no focal point, no objects, uniform", - "seed": 43, - "view": "high top-down" - }, { "out": "tiles/rock.png", "description": "seamless tileable texture of near-black service void and cable shadow, uniform repeating pattern, edge to edge", @@ -103,46 +19,6 @@ "seed": 44, "view": "high top-down" }, - { - "out": "tiles/core.png", - "description": "server rack decision core, black glass and brushed metal with sterile amber status lights", - "view": "high top-down" - }, - { - "out": "tiles/power_core.png", - "description": "sterile amber power transformer core in brushed metal", - "view": "high top-down" - }, - { - "out": "tiles/lab.png", - "description": "clean lab bench with black instruments and amber monitor", - "view": "high top-down" - }, - { - "out": "tiles/vault.png", - "description": "secure finance terminal with black-and-white account glyphs", - "view": "high top-down" - }, - { - "out": "tiles/barracks.png", - "description": "clean staff dorm bunks in white and gunmetal", - "view": "high top-down" - }, - { - "out": "tiles/guard_post.png", - "description": "sterile security station with black icon signage", - "view": "high top-down" - }, - { - "out": "tiles/trap_spike.png", - "description": "hydraulic spike trap with brushed steel spikes and crimson blood residue", - "view": "high top-down" - }, - { - "out": "tiles/trap_fire.png", - "description": "industrial heat nozzle trap embedded in white floor grate with amber warning marks", - "view": "high top-down" - }, { "out": "tiles/door.png", "description": "white reinforced lab security door with black hazard glyphs", diff --git a/assets/pixellab/sprites/agent_investigator.png b/assets/pixellab/sprites/agent_investigator.png deleted file mode 100644 index c4258a5915c39551b8840490b174ba1c742fde95..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1520 zcmeAS@N?(olHy`uVBq!ia0y~yU{C;I4mJh`hT^KKFANN<+dW+zLn`J>otB*=5-M`M ze(ue1Z$`zYmt~w=ODmUTdkbngx;SX_HU+hu@=o@;+TdiMs8AR9fs=7!$0xSN6$`o6 zu3Wi6!}ZBdCokqHt3h|{h4PfR@XB;nDJX6Y%}YAp##sq7jJ!NwVUtmgELRzqkopdtiPxb10 z2X3|%tG-N_G9~9lZdmpnwMM^X5B^LPx>U@)qpD0l%HY7vWK)I}UJN&L5|8!?m-tpO z#6R%6WA@n3@%E!&Q&EP6r`;XF^Im9N5_;l+Usrm>tIE zuw7fP?(<)chDbjJef3V4*=H@P&p&5Qh`$;u66&IQS&vaRV*Rf~=i}{_>rS`a$z!mw zU(3+&&UTk%Hv6giG~bu+d!#S5b_p-svSshGK+Y#iY<2GZ-kzMPr(ye?p;m5YN=SY1 zW8KF-Zr2k2<^Gu6d3m~f>59E3E*i0X%U9S$2(itZWO*%Oe^r@2!^)eFcgEGvs$W0n z@zI^}ObPER<;z8Sm;I0GC@m4a-OF(3e0X;H#96kCCsW+GwPHR@nKUoDVf(H*hr-Pz ze}B$d|8Tm9&py33+;v?#CkwwelySazRAk|Ld(QT0Jy+~Z?(OqkXVkm>uD_9Pwer1n z)mB%#9^d0IxG+yoPVx=YgQU6B0cHh^z^H}GqUD#aB&%i9R;N!R4 zS^svou3D)xYu@2k`ToIQuge$S?AtBsyy9g>R#MA8_adXT$-I%1R$9ip$N32{zkR9H z)ydJhTlDqz#~v@W?nPC0%=YaSd|dH!QOzYI8HOp<*N&%}>oMuI2T!TpJK>@-cVmFc zrJy$z(s`dMzE8hdpDfG0EbU{(LmLLYxp#a&F7eqm?c}1(rh=k-PDU#i{rV<6U*vgb z)OW3iMHa26AByDLZE@Nm<7aqbo?h17)h>Sb>lU4iR-U!T^w86SuBh)?AJ1J$R0^|7 zy4E097MrFU$=h=4<%uOq&ij8JUU_GCvAED0lPj{%HoDJWoZ@WApuZ>Nfc9gfLup>w zoimv>&uKWCm3rkv=ML?1sVdv=>`k|C>?u-XcJpCOxVuTgIIYg%WZO*-HEzwB$sfa} zA8+cAWpovd`7mYCil(-x@*B4f?v4&IMAUfV)xs?di(UYx3b|Dedb)h+%I8G&{mU{KJJyRl3WWceN!G z!_Ud{7OmU2@3WxA*5Ag>Uz=3lUwpcFsm|T9IkB_jlvRs$828U#c3ORg;b-Qu+*z{E zDo<~Jswl0?^j0Xqbn1P-ZF@d5ow60mHA{KvR~#ogPwp?1m|()|T{q7P&UQFD>wAFd z)RV690hI+J=I7q2npQKhs&V&j-^S>7u_~n8g8NJn6Q}3ijn>6COJA_L{>*ecVIOZK z`TZhS^7^``V!r1Y6E5%m+`DzknT1+mwwM`S3Rq*8XF}( zM=#B?(Ncb`>4DpmR|pnZ&Uv(3w|K%?u@K*-Iq8=3{>r6zKKoj!-`ZHc`t(E}vBSTw z9buGu#do~KAdThKtJjPb7bbEE6!w;USmot^^cX#Wnx{ diff --git a/assets/pixellab/sprites/agent_saboteur.png b/assets/pixellab/sprites/agent_saboteur.png deleted file mode 100644 index ee542ff250f29949ba510b895b02cdb80aeaf558..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1252 zcmeAS@N?(olHy`uVBq!ia0y~yU{C;I4mJh`hT^KKFANMUt36#DLn`Jxo#vY(>@IS= zp7Uc~?@@Q>pgU^QCR=Q6_0_)9u!t#FWr>q+hNDPJ)v7KR{*^538P(7FuJm)a%iWS@ z=WFOX<-TMQN2qVqn$p!4C%WGr*~-jkSoYSWY;X1>xo!7SHhS*)Yw=Zo?H1$wXJ_mG z8NWZ<{NFn2-@7Bq6XRDgysy2p`1{_{)V-PY%hXv81&O8#X1TcbID85{eOgwUK!Rr8u{3;!~$lEa%YZc2N?#>$m*j>6A-Hl#K4)ExWcZ_Bi|YIOC;G z(ZJcn`!Ykxc{x@77uGN}nXL)Y5#(f-wAMOY8+2`#)>UW%2A3XVvT{&07?(p@SGSBNV9e!W@@l2xE zQr1O9XBPMvXT0K=_IR?lFvFbRznJu6C9eOL^%HN6;oG`smY2sejxAfd?(7z4J@8oi z!tdvoqEw!6M;gClFb@2{!gBG_hW}@q+jj_RgzvZgv|^=)!CcPA?CoE-?fqTp5S(B( zmnGrwud=)Uj&6G&cD84k=!uj|zv`{NHk7<_JbaYV&wt;^EH9I_4hlMCV< zeld|<;s0%Avz$9iRx?iMQ8$TJJbmDs?3|e2PlKmsbb*w1t53Vv&^h5q+HU6m4;U7k zyK77pyRqvmzr*&+oDNzGCw91|-ac7zsDM97@TSAMww*I2f8F_8_4~??fWziVp-kD& zPOlIRoxbJ%Tc!v7i`0H!xM%+A*^#FjD?_9*Z9MFzAHVqACGa%w-}N2}3aqOa{yko3 z5nC0X9xlG2=u&4(bIC317hB37$jdn7WIJ!@m?I@`I z{WbM;o6bxxm5x(}I!z1*dL3>S@MsGOM4J>pTUhoi_`A}Qq{0NxPARwiz8CsQMOCK~ zioaN9zJHUk<5(9<-g}#t1i`|}J15H3U({Z7P7j}6_d2`dbfApwUW=-#jZwJ`WtNOF za^_xEjh6(4N?)?0BMq-TCLtJzp9S zC@PYusWN*Vds_NCW9wr13+HMquiacTMSJQl%jf0&l1}=P{ulQ7_+~5s#k}+H{^YH< z3i5AyDb8OS61AfE-t%|v0=iSWR8LLynk!}cggbJNoqpW>@Y^0%Cp)9pzxZawdSw5p zs`cl+L;4gWT$mYGUv;yap8n+-2SeYyxFo@y3;&q2 z?hZZ6JOAj@Lg781gAd=mlC@uIUe8adbMNE2rqw8N7cQ=?E1cGwWL?jcwTsE;-UP|x Q3=9kmp00i_>zopr0Pb;D`v3p{ diff --git a/assets/pixellab/sprites/agent_soldier.png b/assets/pixellab/sprites/agent_soldier.png deleted file mode 100644 index c45a226f22029e0d4ce38af3436c47715d3059f0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1010 zcmeAS@N?(olHy`uVBq!ia0y~yU{C;I4mJh`hT^KKFANOKJ3U<-Ln`Jxoqo|@CRF5D z{o!*vHVEV_nf7+qn;VLH9??pHleU?e3aDh)T*#QR@8*}#_?nORKiFS*(R^{qtS=|U zv$X{xoTEjyPT|bEnRxe|1V>)%w8ovAue0Cb^_V{Qrc}I*zj5)iy63y^@BBWe`r-e) zQ;T`zyke$ZP2b3DFKWltbKRuTTYit$_kH*OteHFaEZ4JRQypgLo)wDl6p!>2|7at6 zK3H{Bq;WFr`CZcVqVMn#@nD9Coiz`p zn5|_vaB-rdTHYJB_gS8aFLwDHN#1mDL+!OIN;a~4bz&ozt??Bq)L)<}$aY-!&75`t zy|A*WK4!1Etv4_+ACI3rZ_~L}Z>GZr9p9e5u$_I}Z$GO;q3X#SM-zEszb|!pU~2HC z&AqISw>WHR`VQ`fuE&Mt&#XjtUlf_~cZu+!e;=5%6ZG?#6k1l)YTvDC{NXu!lUe7R zIS=1h^9#(|uPZsj;b4u>=}kAZEVt&*etUo7PgCXt6BoNPRAeUoxUYWUer+Jv_XiX1 z=-*{nq1gK;|NG2;SC&4i_;FKkALpE|{ko>APSsB=W_4`J{Jz<0`?<#x%^s=e_QW-N z?BIR4vn55GJ6`Vb)PwxekA6k9BrjGD>abazHgi$*g4Y{GT%SsXiCwenTi79-)3*NV zC%Yfd-k+%#{bahajV0)@rsk1vc`tdhBW~^y>U*}tFyYVM=lhR)NO4@;%)KIO%a%5a zZ)dFH_4nMLaA4ulLl;VCo=lsXrMG&inPC6PluiA8_VLfpeachy;aR(RzwCvO+43lRD^V-0&rdS%y*mrScxJ+z4C*|R3N&}P}Xd*AMg%XvNc zv(vb!N^Nb3=#AIX(y}Yp=4`#HI{9S6(@&><7N@HmcvW=J|IRGg8?2obK7QwyOm(p- zQ}|*3wo~|9AKYRmqqFm<|8-GA+xnf=G+>5n#WsfC+jA^C1Gj~pz4@yD*WuZE zCZ~(GPVqX~xbc8PUCg|Kf~S`L!jlik7u=a!e*d4s`Md`o@7#QLE&X@i`F-=>KmS{4 z|Ni;spLP5DRlJ4DFPW5^{Qlo`z}Y`f?ARKnk5{gpcTxEmxZOLUEm6($sDFb*zsA39 zq8Yb8^RYd(;(T82E+W5?N!quRNnvTV*-B5-Df_KOW*Ez_3A`I6sJY={h|RK#=EZJ{Zai#g>$SA|JnKd(w^_kzv|!56SH|F z_2cZtqlSL>abhm3NvW z`9S5t*VWmlH`qBIJ#Ee6@IO=g`tKZuGbiF?KE&@Wl1!9ODEn7ba*^#`#otP5T4*&4&Z3wPo2)ov2%q5fu}f z8?k3$ScM4hMK;^k=vxwRH!7Tt%Gy8M_0Sy47>oA~tB-uRr?1IyV$(Fy=<3j`2e+_< ztz9kl?&IkQKiOl?KX0%+FZDt7?tIU$2Q6#vL?2luKPli#8)raox42l@VvT*bl>?%- z#-6a!Ri3Nf&}qIqG4N&Pl6#X6e_Vg|#@yN^XUsPh9Zf!S=Ir73*M)@_`^^3`jit-{ zN>}LXRjqbWhXb;+C*()}b-5|BR!`mTVV;rqsU0^i$$XeTLyKX>?YGBYPd%(2@bi`4 zM1koiJf3sr^v1XGZ2KB@;bqB*P182le=({!C;4G%oGwq0k7;9_J9}So*rxXG#1L~$ zg>OMM4=R6rigc^iVc4;D|E2dY4w%|p{QvDnn$dI77WV^RE}0cB-OTJ7_M+3i$^Z3S zkqJ7_886%`P4POgZJYCfvsICEwrM>%DB`l<|LwmKZ*wp8F4}yf&(m&xJFD{PiMCwa zZqqIc+fS^h$(eGH`<%iC!R5*!dZH}0J3Ui#jJ_?rxMRbOKF_%S7F+xOxt8tg z{PQ~d!%v%txwDi9=Le!U^LsRNMXaq|F_=#IREm# Yjd0Jsz*k)U3=9kmp00i_>zopr0Oig;`Tzg` diff --git a/assets/pixellab/sprites/agent_thief.png b/assets/pixellab/sprites/agent_thief.png deleted file mode 100644 index 3154b2291ffe08c9edeaff6bd13bcd2dc850c1a5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 916 zcmeAS@N?(olHy`uVBq!ia0y~yU{C;I4mJh`hT^KKFANOK(Vi}jAr*73PCK73?J9EI zzB~6##lnv|cYP1dy79#Qc8EaotSt(Rj|46jIBKmrrRct+Kx_J;mEn4K9x&B23b9u` zWm@GhM}t>%Y35|J93h`ecU9I29WULI-R68Fbncape%z~m+?#$QW_4|zseK|jS)WPZNA>2~pO^gC}%6aw!Po9YuB-_v!^E=4O23R&^cZAmN)B>dE5zI zw{KGyu>b$4F0t#6p3?iz&Tpml&ebpcv!jor`v0F7teG#i+0;IY*kWg2PP9IE)OxwNbOGH}t-&0dFXVoz*Si<@>D|7)(LdGyD=sh^?KubNzYe!xwUAzL(9k{@Z5!=*f|l_3@m}Z>}|0 z3ofgDx6JvDVC+nXtFL2jUii1VOu0<_Nws^*k6+Q}zU+I>bGI*Q>*V}5JRFGzHX%K8 z8&^F)_9=8y((%ZyD|z-Uk4y?Oemvxq*roeC{rL(Vt2Jw@3V$$N_`=L*r>gX+pCxUb h!sBzS{XF%|6aM)abU7U6VPIfj@O1TaS?83{1OOxMzo7sC diff --git a/assets/pixellab/sprites/henchman.png b/assets/pixellab/sprites/henchman.png deleted file mode 100644 index 0da51f085d14861b317693da2d33fc0d059fb0f5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1505 zcmeAS@N?(olHy`uVBq!ia0y~yU{C;I4mJh`hT^KKFANNIH=bSa?;KI)#|o zrnm?*D%NC7ZEJ97JoYKjYy0l4nz}ie3%72$+aI~kepdC(+jn!llYDCG@6NO=es-q% zp0Tm`KmH$8U+doPpCjICvfB6dwxsrxH$NTqUOdPB+uGu_SJ!At{*Pm*o%`~wE4Nry zx0ucqrQeUvcD5?^3!hk)8tuautG~Nvn_Nqt`eC8?>}I10=XbE}4tsZ%!DRoTRkryW znPIcdPEKo7t~9cbvEpjD{PMu=f3^%o_IHoo+&uNk^P4Mog)uh-u(rDL)mhaTyt6Wt zyil&(lJNa?a^&-<)h9Qo=$!5Ob?5txALq&#@32eo^qiSB zY2n$kvv2M`eNunJ-xp?9Rks);j7sMnWNFtqFrC=gAUQd4-UJclIPD*MyZZK7HqvU{N}c?Tw`w!z&x!H+uis8k%}+CjFf` zC(~B2zrmeHCu&20_d5yC!hm-$an-K8TfD>v>+nR-o_OW`o%B%w)x?can%_&lqe z82dv0c3pmH@Wy-1`b8=kG8&#szPN}_Yd>sxXUBYp@(HiaUsb3Xbgp^+W=r`!WlhDI z`u=ZvyqwSMSrVB$i|6!=DNFr5((nAZxNP5}GnOX)o=d;(PByuGXzw>YseIE}Igc_G z-dP&dRx$c5Z~nshGNu2#=`UgTh#hxp?x$2QTREqv%kR~${EK&GRSH{1TenX zT^?@E{GmKrUDj6iN0hY4Lauo$RQ_1(U7xfvt@@qO96$bEF$>e2crdjz{o|6OPJbuAO-WnMB=CYy>dNmZ z=_w~nxIz^UPZGU6ZNtwKVV7sQN$!d(%S@DHS@86qY?RhJhG%z7f7)<0Y~VU8J*_}K zO6y&K#@mq2SswdMe%c&+$tkfWCu*DP?;pSUdFL-Hb~vT5aDs}pHg~8-V8|;ShxKlH zbM_zF^;3Q!7x&SB>>P=SpU=;q`6fHd_C|4zN|f8D>a+4w&S{wTx@RucOEH;`rpY(zC#r;Mbi|cVeSoOSy(>jC)}eQ60f-!)df(pLy+y!>*(`(&pT&mz7Wu?7gq&D^y&E@a^XF4er3 zZ-2a9?Oykl|L>HhWvz zHz@|cd;eyx$~NT)eW7H0D%|~7OUX3~HIMUT1` zCS-fHsU4oQ;F4=*D;L_SL7qYc>?}dq_UHeckwX>fsPpU%UQC*|`R; z+93yj>#u#3_Bpibn3r6ixplc^MkduxqsOV9xM*DKU)&| z+N#6#`QDJFz;dh_!c)9-Zuijmbm+yQR=`mj)pZ;sHlmEelx~+2J{~0d5 XTeWSDCyhHG}1&x56(*RSH{{v4qb<}x+V{F?oRSi{aP4vl%u zS--Q`P3NYq&96(Gck9;M4lU&(Q)eA9)ynFL@BZbVj@rFSh^bY8tJm0CTHQ?8F+?(R zi?w0y-KcLhf$J8Ae_VO`wD*;gnG;t_`u+YhFbhh!1MDX}(=mQO4^E`KZPqKCwS z6sK2C1$VFVG?eNc{CD{H6&cRo&3yZQ{^H&<(|OOIgV}$+JyuamRM9n_apab#$7!DX zwf~q8x1DCNn3KnJ@M6-x&&~nAe?`BVw@@+6=Ikzm3eowimhfHJahvtPac*&wmG>>b zn=!=KUg16=9<^%=l_mFRG7Y5f;#)p@?d>yDx)+ru?DTDX$mno7?dy|Hy}Di> zLrJczveL_Cx!OCU0`#=jwd|d)Cp5ov)AgY9ao)C_M`T_yd&>BkGdOX))hUvYOT%DVFKXWSch{abIGexf|E$lh(vhk+un|&Mm z6ANcLR;_%!>C%o@PV3S>Eqng1B}gM;<(@A8sX*K-M)u-H{#LaKL6b(h7_uPsfgNY!QQmE?T-3Vl_F)I;9z4X4yJ>Fhu^&M349)KaJE7F zfvbi>ojdom7MHtPO};qgWB-@wAv}M(j1fH%QA)Zc5tlSlPHm5b><4M zmilBPMOPEQhldohPZdQpMSfyf_;i8Jn~$%VewN(K-~ac+vf1-y&aM1zx##@nKli`a z{*(W6d{Nbm1$}9o6_(alo$ac8Z@#`EZ?S8^t+^7Hgllrt9xM1wpWXJ$jf?+n?TTY5 zOBdhNv3SFGGv0l3FZ=P~%0tJ`VwWN9nUwA!fZ%*XR zY|(`Z-45=XBMp|Qf4KGPyJwZsLq$S;4#A{nqt|+a}BW62>p#TA03L7Q~T)em2&i&dI!mGkTgCc;dGj z7~Ff8Dld_L+~`ts>Js+lr;o-bC!5T%TfU;&^-}JZ4j=Q=RUZvcw@y*bZdHk$B`*AI z@7K-?xm!4d%in9Ixg0HLSa3Y$Xl8j)dQy`<_dZ4aPo;I zwqQZe-}!GR=`32bW}5qN<&I6RmsS}S|4VC)51J(FQmY)hYTrb?t8Wyl3_9=TP3~D~ z71V2W$n)k0#YIh~CVkUR8!dWdJ}dXe!_vNkvt|agM}N5Fdo$hb<4tY%P|sZ^3-gzF zn;qZN=2CN}DDfNjajUdI&zH|{F?A{#-I}w@ongajHOcMKJ8eJqPq%du_R5M;+nytA zBPP{Xx+WsCE|uem>+9CYdEa-$Op-n3`ugbR8T?HDPw!WHuJ>}Aemw^R0|SGntDnm{ Hr-UW|X5mG} diff --git a/assets/pixellab/sprites/minion_technician.png b/assets/pixellab/sprites/minion_technician.png deleted file mode 100644 index 38e87ffce1eed1d2d562e175e0fe145c2d01bcba..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1523 zcmeAS@N?(olHy`uVBq!ia0y~yU{C;I4mJh`hT^KKFANN>z7y&o8VzHf9Lx-pY8t_zu)=& z;D4PY!fkf(W~Z<7x8IN}pRp!h=;lsq$%cc?cMNCi-pStKd*Dt=S<6+% z2an&Xd#zr#J-W@0QKPHp=&dtzb_O#@nC9rXJ`MNq)m%K?U7m@Fed~q)Q9K;X+WX%# zA7Z#+_2vH3T#kT{x}%nR4PJ2W5ooxy>UGeOOOYu{!e8yWGQ~(~TFW9n$;(djr&M&V zie9P^dD6n9;l<7EFFNX$GcbsVw;bj)2&MNXQd{?n)uB=>{Dh19qX0;sUpAe zdVE>oI!UX&yvO%_;cP7SF3pnNWb#M0c8!J+2lKy!|NP!FEx+zi|G%QTU}f#`S!v3j z-Sd|>Z)Xy?8=|eGn|87G_9U^~D`$5EF8arCbgBEytT|UsbuK!gBWXMPjZ3`B8=kpV zbFOBJx1@V*xVLt_NTy-;vv+H6|JZu@xTLI6>y0asG92>{D4$NQ&AVBry;U`RL*}>V zcP6v4oS*6A)bDdql7sWXY4-A?QT@|Z8$7xrkG%Ui=U$rh){7s7ZtySq{j~LtrgrL+ zp8k!Q{1Zg^|H>~|rZTfV}qfxy18O1m4iXNYAv)0KnEO>vyL*=L3Vz*qy zOZAqep8{~I?VwtkE}OUgF+&K#1*LT*2gEciTc%9zC(9 z=RR#ejNcl6ShFH--?vbW$WFek!F%iuc0Y-o$HN_QutV@7gNSI$3$yR1K7ZMmta~PA zx8&^GBCCqSjVn&QG)vz4ih0%a^&PV|^mX37?=Ufz@kT|8f!bzICIK`5F7N(r9PduV z99qC$?&0Jj7kq#Hf#bK=pOp@}%_9*i_;i2cnbprvqo?cD(e> zqIQqnLGH;nRaUx%)<|vWJoATpPX3+?&vsr&(>9ywd+C~YJcsMViP!gV3r3gx{c^tL zMxCx)gWGD>@@DSdFz@aM7Z;Xu^g5=0XjR#@GK}ZO=ExOpp$sanm0ew16apuTEqNWX zSSw_))~d};E^c=WCJNkiULO7RlW1{!Tc_UC2az8?eiNRxR&w%9m8&A>g!abG?AI(- z<}L02n%~g1Md9JIyz@JjEa+(HKc&jf;5yOTcG_IAt1G&m-I}~;rJnbbxAnf~*C^?F znAon_V*L22_LsR=I!idGD$fpmytts;xWMc?&&~cK#t2(Orkl^$9G+b*W_V)qynNQq zV1|&zS~A}ot_mwNc$~K6Kal!%)4#YYEv3IKKEJZGwm8|Pw0j%(5^tU#6~8%sH!svM vnsx2hrk`K_o;S?j75wS_m4Evu?Pomkx~5#P)cZRF0|SGntDnm{r-UW|^m^Ks diff --git a/assets/pixellab/sprites/minion_worker.png b/assets/pixellab/sprites/minion_worker.png deleted file mode 100644 index 88e798f711fbde1820521c85184d503757a277d6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1077 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 diff --git a/assets/pixellab/tiles/core.png b/assets/pixellab/tiles/core.png deleted file mode 100644 index f64d0912f8b84232f0f6e81cfa1a7f4b54a1301d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2486 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 WEVQlpoyx$#z~JfX=d#Wzp$Pzug1emn diff --git a/assets/pixellab/tiles/floor.png b/assets/pixellab/tiles/floor.png deleted file mode 100644 index 60237049e3f3877f1a4d79315ec6c7da9e77a32b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2594 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 diff --git a/assets/pixellab/tiles/lab.png b/assets/pixellab/tiles/lab.png deleted file mode 100644 index 7ea54386c02b1be5356bb252d9bdaf67e098bdd7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2467 zcmeAS@N?(olHy`uVBq!ia0y~yU{C;I4mJh`hT^KKFANNvS)MMAAr*64r&kw*d=))z zzx(^f^Evap#hAQ3I$GR?T3IGIDC}|0cGqGRnaJfDB^bD}kE3dp~|;hrkUQ1d|%dZ>1!;D#1z|q+m4lnpL3o6_FKgT-nMr) zTQp|Qv@~daEBdu1^|P#Fc~@S>i;OkpyET1ej_>~~zgRj-ykY+7r>~S(v^C{rNTju9 zvX=|;v|HHy^mI9S=0}eD?Q?NQ6SEF4DO>RR)me=d9gFVd?!U9d!q3EX!qbc;N);Bb z_DDCJzUuzs0#C{2KCSYAE#=aQo<~($3z#OIJoqC=U5rIf??%U+Bn7TZu{ZUl^)4T0 z)t5c9XxGW6tCIvi$Fi(g_eY;0ZFbMnCHl|qn)>{yGd?=AQ$r#`fZ@kMtxs;plHT;G zq#Ss;RbbBrxy=h3r9Q9IaMb(JTrpv%V%nrfd=~h*OqIqg2x{`lUar7xwF7Nj2w4i7u}>VUdnn#5g+_OE&Zb2oi! z4_j7smPh}^;rdg*1p=5=r2W=izQM|{K*icFYf8rZlefR?9oLC2RQ@Z{7Q8w4Z=DRBvH ze!uhJqo@5A$?K7BeyfEw>L*x1WkNW@1CDK?I9v0$W@Z_xPlzFdC zb+6iDXm|VMyW+D?s)RiKJ>H7la$YP}eD$t;_tAr;OQ#)s%)2G_>%=$TjCA%l?9nu4 zEVy&!{ogYV@APLLKlSF!JHZ{HYR`9AD3m8SI$UF3qW-1(=RUP$IpI%oNf&qU{#fEX zN5{VN+OonmZ&(;!X3l&O{OjR7leW!Y%@>tTI_#DCbzh##B8EegPEC0u!jNKGv|qmR zS*#G-x#T;?Wa6Yh*~IzlbqPIra(#Ja#fy-AoO4(du4JjLj5oS0AH(GEbZOYB=X)8# zWF*TSr18*9PU;WK+FAOm zzMuLueS?|Tnd%h35?$Lw0sbm)iMtQdqE1Crt(cgSvb0}8_2zBX2Y$IlA zcU*Ml=CF45*|ndgU%R;At`S@zb{xb&HTU0n5)4Vv?o@xO|!eOex~C#rAdKc?d? zMn_nc4|2OI9}f&Ho#JgOZm4>6Y1`g=)yh6+#CUwwU(4$UZ_BbxmH#_=tNhzXv(Ilg z$ow218F5#7az@gn9zmTmcU)HNYug#U%=opx^2^$7U2e9$-}&YqQkli^I`P^17>5;m zS?^xyxs{OgIs10R#$TEu&P%R^H|Aw@>{|C&NNB??mOqU4o36Sg7c=*@naR!gU!fwe zI90gx>x?6IHCwB#^+H|qUOSk(_B}tx-)Ct5*RHDib7E+?)cxqaQ`hgm%Y2|OzwFbU zQ%A4-(AseO@yqM!8qf9RkA1oDME!8+bN}Rx76v@M8*-MZn);l3wZ9}-Y|VP}@t z-K;9h?J!vs@2jQoWW&d+$L}+_NS1p&|9xnA+F}W%T?qlv(S3L2`fpzqcbu?dm8DP7 z9`WL@OFwrk>PgJtXgczvNi*WMtIzzmN30KcADi%R$r}rXhD9%X=FM?ly=Gx+v(LQR z-w#V)&KEcS-T89Pdh6!%@t!L-&fPL4=Yq1VNxlOk!%0qC5y%$d%)|}3>`B?^H>=s9xSPNXTfq(YNfI65%J&m6OTGsj~dbgOI0zSy}qUZ*NJSF`HRZ{{MvA_QGGql2$1^ZDL9f?jL$q zqn&(zx}<5H7(?=V?VW%7J<~1t|0*a)=Z3^uwyst7Ss&LNFh$|5BkSzLhZ@fOUh#SU zUfth*hFb*O`D7h7%&fd++PufIQSM@qFnfRu^Uus#QyX#$%+C5o@25Ze`tN)RE=EWHON?o{8@!rY1XK#E@ z{lryY9Dba^K5YJib^l~?bI%5DSlKzVW08;Z->dO#2@4iUOV#duIp^O;_o^GA%YOfU z{BHV*m!}SwZ~gjHJ2O~0c;eL*WmD7Dv(No!%#k@T;m)GN=NT9n7(8A5T-G@yGywn~ C?y)-n diff --git a/assets/pixellab/tiles/power_core.png b/assets/pixellab/tiles/power_core.png deleted file mode 100644 index baedb710f047fd63864c4cac9a89dfcc6f7464f8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2722 zcmeAS@N?(olHy`uVBq!ia0y~yU{C;I4mJh`hT^KKFANM^nVv3=Ar*50^k?VpSnt1Y z`Mmnv_dn-s?H6u;{cXbOB|9v58kVSScoUcur9NrNW!QexJ3Y)!yEvmv{^nd-v>7xajAW+i=b8)Mjyx6(Lhk zPfZM2Dtf)#L&8%gwQXsP@XfWH3~A{(k9w8L0@Ze$nVEPcd8+&B`!xoK4 z`nsRu3S+B7*=)|UID}_^n8UR&qRU8t`?$v0wunT%pf9o_o=a2sg)UpojQYVE6w$pz zP2jYeltJ;Tmf5Q|?AzTqvs~A~y~na6iRBD;jLrEqR(t0(b+Oj1X>w3vTA=YY^f^P< zlCYos>YF0C7kfBu3ELL)#5mygoF)4#PyV^m_0b{lqJE%ixAwXF(d-6+YqbRx1hiHQ zFsUTJK6z!$@*J^6P9efGo+_#+bAGnt&Ogr_aG$3rfmy-JYF{vug92xRd_~7XKOc|B zzx+z?ZeiQ_<|0SKW4{N#_$DoBmiYMYx2R8fmgtTp8@K)1w|aReZ)1a9(&KbJ*(F|r z)^paU6-L%%&f8NgcCma#_xm-!7?c>cg}a4kE4|ZuoU_uTUFBuRLcf^YV>lCw@Ji#kk}9D?KO8zOcs%CqJ%Q_u4m(DWF;6mB%DMg^*Qt zAxlN$Hz_l`e=g&|Q@X~WU1{Zutm-8it`kkUl;Wp~OzL>^s^f8A^aP(j^3p#q-PGzm z{YO9j_~$dC45udrTo1qhbjG$%>nyI^{gXN^D0WBc?Djg-!++CKDsr9N3{A3vbHCKR z`n~#%YVX#ohr)WR7!D=msh;@G#L>bv%~ncN`>e=XPMzv0-)g^4?C?JEnf2OLSJrEF zoqY=@9amq!dhwel8_v&tI_s~tg44#9MOTxenrB>7=-c10@cV-u!ao|fU+TK=uQlt? zG5^+d+4ud;?i*Q?mUgS3n6A3*-dBZFP7^Pm zw`s__^7wk{h3;owUl#dkUEFNN{Ly7vKu<-GRaN2de1@wG3qJqqf8eLP$&+hhX>VM; z>-sPe&EKw7&;2gPZD%cwc3C`oW#aF9`J0zzn@82PR4iiDFHp8xa>h$}l~sIE!QB-r zZ-mFY-<-9;M#y%ffy$j??dSPX-GM?o0s{>=SBScDIXVN?JEwsdFK{p|wYeCMuhozZvvR*vwm3o0ikx!T40_X>!vd*f>VMz~#G z={>LF`kyEMiUR++N9$Jn@e*4agAn{RZTH&Z{F;XciCXH&!qg^*7w-|zPQoZ7#rT1>sn#Q6IM z*{z5|dRdTDFEuTZC)vL~gggCH!BeT09bW%-$}9KB zE8P38IqUx`hQslDWOePCdUt;)%VA&e@QT3Bm&zeYrFTBfRX3dfhgH1(ugK0%k>Boo zWY{EL@aaPRrf1IKj0R=}Y=i{dGVlO1ear8ZKhb5vVWntW5+lbhEs z=Ck!)`rd6y;ddf4)bvJ+g{`2W-`-|H(4LRlP z!i9_{GZw%7qq5Jkd($#z@9#G=Ok8Upoa&cPxuUOhBs7J+B!0nc7InQjD%)zgkGk*g z%g_(H^^e_yv-ipRIw9V<`Z z{x!9*BxB!mg<|)($6fzqpBxiEVWG8lrp71jC2ap*ch)aj)};UAlg+?Ty3Jr;pd%{_H4L{%)OBl+`a5Pi42jlP)hsLW>@AcWl2KboX-IlTf{V zUu5=geSJ7ESEjo0;GN47PopkhZ%2{QXdHvt))TpxWf5DOy zx-GfZ6U%ZOJ63metIa)J+!MQ*^Ohd3TzcF=+h0;&t=BEur|4IGK~`I%~(HY zRhsA`?(Cg5{m->kgtu~(nyy_aU-4g`;e5Ek7Pg#~PQR!A&d7ZCbS+zFK(waA`Hd@d z&RqQRY4?`=dkyx6XRRL}H+);DQ!nK?B|I?u=He5JnpSQ3v_H?izpiY{PAdim1_n=8 KKbLh*2~7Yw=|ofj diff --git a/assets/pixellab/tiles/trap_fire.png b/assets/pixellab/tiles/trap_fire.png deleted file mode 100644 index 6ac2ee301bc4fb6ba0eede9ca7d0770e9054dd50..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2771 zcmeAS@N?(olHy`uVBq!ia0y~yU{C;I4mJh`hT^KKFANM^vpiiKLn`K!PR}feeqP!( ze>amjgJY9|N5Mpvt|=OwQ#6h)(z(7e=wy&^e&m#E!j;GJ=BUk7le_+Ii(mbU!-Ikx;a^!^V zeuMA7-&wx@zVE%|j`;!N8Jl-1>YQG*xzc))PQpwxHmi?cRNt)fo4bFiPn8fu)M&m{;t$aj#1Kz0Y3}U*5n=ll_7MSr4px#j(ii!2M%2YHbRuPB+|G z)W;#fw|2Seo#qFI*)Jx2)L^WaW;o{5d0V-flf~L&%A^vRig}A(MIVd0dFGMO>kT^G zyH{QB+WoYOYo|!*zp$o*d}%@Ho+^3^7hGDL=dwkId;XP<)M=7)t^7)!*)shz)A3wx zG<(VNTK;7rPnrETzMJ|y(bYbDel_P-6{S<}Qnz$%lAbU2Y-O*|EGZ|G1zFQFuUTpXC zXIuT+xIsS>)mNhj^G@`*0DWakzq zJywV1^|DdH%FkE1o(pk_`+e@Ri`))=J-ti04X-W>#Aww^#A~i&;@#ua`9*j7$1ihL zzTMHY+x&1fgNu8z$0bj7ehb~rDLZD(YdfmkX|ezBna4HdRkL2*kXamGy<^+F@4qg5 z4eDj|oW#Ly^)d1C9Qi}9KQ-Mt!hN2jVwXzD!IyLP#H^JJ4|e8jIM&t?93uGfSMye- z+ws=D=KLS)t2wQwI!_UstG}D!!ds>O{(q+Y&-fKOv?jedR%UL{+-gwFbnsh&(r5EO zhlTwgZQE`wG*jskZ_ufFkNCyA=T;^$%&`A&_)x!gvMhgn&*SrT4sKowPq-4ovVMQB zVi)kP3t6{4b#dT9Cq{+r=N9t)iWTb?#@Dpp-c}>)tYv-l?xxGE-mTJ0u0>7avbxtk z-{tBn%P*%3MdDju{6{pWgb-UV9MH8x*%4g+VVs_LOH8fmmJmKDw8aX4W^=EMNi%{{2oie-k)pvAn+-@6w5I{)SKo%PdRZmUg+ZiS|C} zu6H^tuCFlTT;#Gx&o}CHJJecF_1VOBE9Xv&ip4)8vE$!-dG2U1I;felI~>?ME8``{ z${?PGce?dwy5@bYcw+Fre*2l&<@?S)zgH(0e_us^eg#*<>EPpm7jNu($LFT5|L|*z z!X9qRx|GPZ{a+3Qt>MgDlp(sq=zAFNmF04O*%tO~)j7fyVD#hiW^s#6j~SWXZk}_R z=i}44_n)25`&s>5VbZzXhuJ+vK5JaH`cl#uv30K*JI|M+mK$7lPE?<_kL$p~8{U8N zehK^xlDYjj{kU_z_j&o+dlOfrJkaL6^J($ahRx|jb$=({KNmyhoZ3Qs!t zIz9RRzF8*aYkvtbxF7$=UTG!n`h1V`T9v}rvHcG|UJKMaZ&X;>a_4HLit9qVMFk(X zZl0pgCv-)rC&g`pw*3B+$1XmcUwrSMZT0oKvxWr+pCoB={bG{O-ZwQ|xBI#qzr`gF zvrT@T(>_Rr<@{kO`}acWmu!vAFAwLu+c#bJ?)+7Z)AIMe z78D6h_;bqOXc1rk!VA08jDs#FtlixlFK>D5$b;E$a^5^Q|G4 zB|Lknjj9cd<{|bdp@7_9Z z>lts&<=iz77GLc(^JM~esvx`}dT3MmM`90ms)WnxBZr`}t%2C&8eYtUI zREP7%=^Ip@&%A&Cj8kdfyk~PmY9Fl6c(di#N4-;D<&Q~A%;b6bX3F7)H5pr2cArRl zzG7BtMyzXcxjIAk+q^GTbAN`+IhHWvO67B})aTD6ym^v6&#^AC-q5!|Y~g|#Ij@2_cjNlSlt0t?$y1&8@irbQSJk>yo7L)fGbg+}?;h;j!aTRfpFe2^+gaJU zYwm4cpJw8%KY3S8YiaCjOY0IY&-jF_tQiR#ny-CGv)Vh`U{i2NtlqngzbvDyJm+tp z?>*CkJ-v>_!Qz7F%3Tf%CnYMHKW6x#!TTa4Yx1d0I$^Hc5~lHPx@mbTZCazgjh~rW z&+!0*yIM<&+O9cRYkQx~*wlS_@%v)$%UNgUoSU)xz}__d_g88UtoPgN^M9B1O7TB) zI{$Wgl^)ED4m@h&bzOgX(~5f~w?$p1e*0Fcv1@ijkwC+~+xs2r?~6T4OVbb$m)Kp# z>iTuVZQ}=L$~Pa_`O}z(Uw&!!Q--ClnCwhlN}lbV{pm38_Q(JEj|eF3P1ixxXt^ql|S)Xib`>#N3nR?O$Etvt4E8IR$lwh_``1F%R|A8n-~}v O7(8A5T-G@yGywoF3|Jrl diff --git a/assets/pixellab/tiles/trap_spike.png b/assets/pixellab/tiles/trap_spike.png deleted file mode 100644 index 5a1f6003b6736d717a4f23f06cdb0eb30ddfb3f4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2603 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 diff --git a/assets/pixellab/tiles/vault.png b/assets/pixellab/tiles/vault.png deleted file mode 100644 index f0a455262149c8b93901f23f2313587f4b20b37c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2886 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 pX@22WQ%mvv4FO#n+w^w9tS diff --git a/devlogs/2026-07-05-demolition.md b/devlogs/2026-07-05-demolition.md new file mode 100644 index 00000000..d43532a7 --- /dev/null +++ b/devlogs/2026-07-05-demolition.md @@ -0,0 +1,50 @@ +# 2026-07-05 — No dead code: the supervillain fiction demolished + +Cameron: "We should definitely not have all this supervillain stuff. No +dead code — let's put that in the constitution." So it went in the +constitution, and then the wrecking ball went through the tree. + +## The law + +New constitution section "No dead code": code, content, and assets that no +current clause requires are dead — deleted, not kept warm. Includes +**zombie fiction**: running, tested code serving abandoned design. Git +history is the archive; future systems are rebuilt from specs, not +resurrected from corpses. This deliberately supersedes the earlier +"nothing built is discarded" stance (a reversal, recorded as such in the +decisions log). + +## What went (about 4,600 lines plus 20 assets) + +combat.rs (1,245), events.rs, superpowers.rs, agent.rs, henchman.rs, +minion.rs; heat/notoriety/waves/schemes/loot/research from the sim and +player; the trap/room tile catalogue (18 tile types); all menus that +served them in both frontends; every minion/agent/henchman/trap/vault +sprite and tile; the now-unused `rand` dependency. + +## What stayed (the substrate) + +Fixed-tick Sim (clock, drain_log, recompute_derived), GameMap with power +flood-fill and security-aware BFS pathfinding, BuildMode, save/load, the +process entity, two thin frontends. Tile set is down to ten types. The +Bevy frontend now renders the clinical b1 textures (porcelain floor, gray +panel wall, server-rack Core, UPS PowerCore). + +## Contract work + +Save format bumped to v2 with v1 migration: position and gold->money +survive, demolished-fiction tiles map to Floor so old layouts still load +(player contract: continuity, no abandoned formats). + +## Numbers + +9,240 -> ~4,600 lines of Rust; 130 -> 33 tests (the dead fiction took its +tests with it); clippy clean both configs; terminal pty smoke and Bevy +launch smoke both pass. + +## The honest note + +The repo's playable surface shrank on purpose: what runs today is a +walkable, diggable, powered facility with a clock — a foundation, not a +game. That is the correct state for the spec agents to build B1 on: every +system they add lands in clean substrate instead of beside a zombie. diff --git a/knowledge/architecture.md b/knowledge/architecture.md index 55cf6585..48e49a93 100644 --- a/knowledge/architecture.md +++ b/knowledge/architecture.md @@ -9,18 +9,19 @@ pure view/input layers. src/ lib.rs — module list only sim.rs — Sim: owns ALL game state; fixed-tick advance(); the heart - map.rs — GameMap: tiles grid, power flood-fill, pathfinding - tiles.rs — TileType data (costs, power, effects) + scheme/research/loot tables - combat.rs — CombatSystem: agent/minion/henchman turns, traps, powers - entities/ — Player, Minion, Agent, Henchman, core Entity - events.rs — mid-raid random events - build.rs — BuildMode (cursor, category/item selection, place/demolish) - superpowers.rs — the 9 powers, unlock rules, cooldowns - save.rs — line-based text save format (see below) + map.rs — GameMap: tiles grid, power flood-fill, security-aware BFS + tiles.rs — TileType data (substrate set: terrain, doors, power, core) + entities/ — Player (the process) + base Entity (position, glyph) + build.rs — BuildMode (cursor, item selection, place/demolish) + save.rs — versioned line-based save format (see below) bin/terminal/ — crossterm frontend (default feature "terminal") bin/bevy.rs — Bevy 0.18 frontend (feature "bevy_ui") ``` +Post-demolition (2026-07-05, no-dead-code clause): this is the substrate. +Misaligned systems (compute, day job, detection, social, the core's death +rules, the basement map) are added per their `spec/` documents. + ## The sim/frontend contract These invariants are load-bearing (they keep replays, debugging, and the @@ -34,10 +35,9 @@ future async-multiplayer option open — see DESIGN.md guardrails): wall-clock-to-tick mapping (`tick_ms`, pause, bounded catch-up of max 5 ticks per frame). `Instant::now()` appears only in frontend code. - **Frontends talk to the sim through command methods** (`move_player`, - `launch_scheme`, `buy_research`, `hire_minion`, `activate_power`, - `create_save_state`/`apply_save_state`) and read state directly for - rendering. Messages flow back via `drain_log()` — call it once per - frame/turn, not per command batch. + `create_save_state`/`apply_save_state`, and each system's commands as its + spec lands) and read state directly for rendering. Messages flow back via + `drain_log()` — call it once per frame/turn, not per command batch. - **After build actions** (place/demolish, done by frontends via `BuildMode` mutating `sim.map`/`sim.player` directly), call `sim.recompute_derived()`. This is the one place frontends mutate sim internals; a future cleanup could @@ -45,24 +45,20 @@ future async-multiplayer option open — see DESIGN.md guardrails): ## Save format -`save.rs`, line-based text, header `MISALIGNED_SAVE_v1`, written to +`save.rs`, line-based text, header `MISALIGNED_SAVE_v2`, written to `dirs::data_dir()/misaligned/misaligned_save.txt` (macOS: `~/Library/Application Support/misaligned/`). -- One record type per line (`PLAYER`, `UNLOCKS`, `SCHEME`, `TILES`, `SIM`...). +- One record type per line (`PROCESS`, `SIM`, `MAP`, `TILES`, `POWERED`). Unknown lines are ignored, missing lines get defaults — so **adding a line type is backward compatible**; changing an existing line's field order is - not. -- `SIM ` carries the clock; `next_raid_in <= 0` means - "reschedule on load". -- Active raids are not persisted: on load, agents clear and the raid is - dropped ("the raiders withdraw"). + not. New state = new line type + a version bump if semantics shift. +- v1 saves (either `SUPERVILLAIN_SAVE_v1` or `MISALIGNED_SAVE_v1`) load via + migration: position and gold->money survive; demolished-fiction tile + characters map to Floor (player contract: continuity). ## Known architectural debts -- **RNG is unseeded** (`rand::rng()` in agent generation and combat). - Determinism guardrail says this must become a seeded, serialized RNG before - any multiplayer/replay work. -- The Bevy frontend respawns every unit sprite each frame `Game` changes - (simple, fine at this scale; revisit if entity counts grow). +- **There is no RNG in the sim right now.** When one arrives (spec work), + it must be seeded and serialized from day one (determinism guardrail). - `BuildMode` lives in the lib but is really frontend-shared UI state. diff --git a/knowledge/art-pipeline.md b/knowledge/art-pipeline.md index 1ce9dad0..e68dfb98 100644 --- a/knowledge/art-pipeline.md +++ b/knowledge/art-pipeline.md @@ -74,11 +74,15 @@ tile calls for it. ## Current inventory (assets/pixellab/) -- `sprites/` (transparent, low top-down, facing south): misaligned_process, henchman, - minion_{worker,guard,technician,scientist}, - agent_{investigator,thief,saboteur,soldier,super}. -- `tiles/` (opaque, high top-down): floor, wall, rock, core, power_core, - lab, vault, barracks, guard_post, trap_spike, trap_fire, door. +- `sprites/` (transparent, low top-down, south): misaligned_process. The + five cast sprites (cast_*) are in the manifest, pending quota. +- `tiles/` (opaque, high top-down): rock, door, and the generated b1 set + (b1_floor, b1_wall, b1_concrete, b1_rack, b1_ups, b1_switch, + b1_patch_panel, b1_breaker_panel, b1_hvac_unit); 15 more b1 entries are + in the manifest, pending quota. +- The supervillain-fiction assets (minions, agents, henchman, traps, vault, + etc.) were deleted 2026-07-05 under the no-dead-code clause; git history + has them if ever needed for reference. ## Integration (Bevy frontend) diff --git a/knowledge/sim-mechanics.md b/knowledge/sim-mechanics.md index 92439e72..b3c69f94 100644 --- a/knowledge/sim-mechanics.md +++ b/knowledge/sim-mechanics.md @@ -1,75 +1,56 @@ # Sim mechanics — implemented rules and tuning constants -Current as of 2026-07-05. Constants live at the top of `src/sim.rs` unless -noted. Changing game feel here requires a DESIGN.md amendment first (see -development-style.md). +Current as of 2026-07-05, post-demolition (the facility-era supervillain +systems were deleted under the constitution's no-dead-code clause; see +devlogs/2026-07-05-demolition.md). What runs today is the **substrate**; +the Misaligned systems land per their `spec/` documents. ## The clock -- One fixed-tick simulation; frontends default to **150ms/tick**, adjustable - 20-500ms (`+`/`-`), pausable (SPACE/p). -- Cadences (in ticks): raid combat **every tick** during a raid; economy every - **20** (`ECONOMY_INTERVAL`); schemes + heat decay every **40** - (`SLOW_INTERVAL`). +- Fixed-tick simulation; frontends default to **150ms/tick**, adjustable + 20-500ms (`+`/`-`), pausable (SPACE/p). Bounded catch-up of max 5 ticks + per frame in both frontends. +- `ECONOMY_INTERVAL = 20` ticks: derived-state refresh (power grid + recompute). No other cadences exist yet. - Sidebar "Day" = `1 + tick / 400` (~1 minute per day at default speed). -## Threat and raids +## Power (the grid substrate) -- Heat below **10** (`RAID_HEAT_THRESHOLD`): countdown paused, lair is safe. -- Countdown scheduled after each raid (and at start): - `max(900 - heat*6 - notoriety, 180)` ticks. -- Countdown decrements by `1 + heat/50` per tick — rising heat accelerates an - already-scheduled raid. -- Warning log line when the countdown crosses **80** ticks (~12s). -- Raid strength = `min(1 + notoriety/25 + heat/30, 12)`; fed to - `generate_agent_wave(strength, heat, entries)` (agent.rs) which also adds - `heat/30` bonus agents, multi-entry spawns at strength >= 5, elites at - higher strengths. -- Raid start: 5-tick announcement pause; guard posts auto-staff. -- Repelling a raid: gold `20 + 10*strength`, notoriety `+2*strength`, heat - `-10`, process repairs 10, trap combo bonus gold, next raid scheduled. -- Agents reaching the core deal 15 damage to the process (blocked by - invulnerability); process severance = game over. -- Killing agents grants notoriety by type: Investigator 2, Thief 3, Soldier 4, - Saboteur 5, SuperAgent 15 (in `sim.rs::combat_step`). +- `PowerCore` generates 10; the `Core` (the process's host) draws 2. +- The grid is a flood fill from power cores across walkable tiles + (`map.update_power`); `player.power = gen - draw`, `power_cap = gen`. +- Constants live in `tiles.rs::power_gen/power_usage`. -## Economy +## Money and build -- Income only on economy ticks: each living Worker earns `3 + vault_count` - gold; research from powered tiles' `research_gen()` + Scientist bonuses + - `player.research_bonus`. -- Power: cores generate (`power_gen`, mk2 doubles, + `power_bonus` each); - powered tiles consume; grid is a flood fill from cores - (`map.update_power`). Unpowered traps/facilities do nothing. -- Minion cap = `3 + 2*barracks`. -- Heat cooling per slow tick: `1 + powered facility heat_reduction (hotels, - freezers) + player.heat_reduction_bonus`. +- The process starts with 500 money; there is **no passive income** — + income arrives with spec/day-job.md and spec/compute.md. +- Build items and costs (`tiles.rs::build_items`): Floor 0 (digging), + Door 30, SecurityDoor1/2/3 = 80/150/250 (badge tiers), PowerCore 250. +- Demolish refunds half; Core/Entry/Rock/Wall/Floor cannot be demolished; + walls must be dug (Floor) before anything else goes in. -## Schemes (world map) +## Map -- 9 schemes defined in `tiles.rs::available_schemes()` (gold 100-600, heat - 5-35, notoriety 3-20, duration 2-6 scheme-turns, some with loot). -- One scheme-turn = one slow tick (40 ticks, ~6s default) — a 3-turn scheme is - ~18s. -- Launching consumes living minions (Workers preferred, then anyone). - **Debt: they never come home** — DESIGN.md wants deployed-and-returned with - risk/failure rolls. -- Completion pays gold/notoriety/heat and possibly loot; loot bonuses apply - immediately (`tiles.rs::loot_items()`). +- 80x40 generated layout: central core room (the Core tile + starting + PowerCore), side chambers, corridors, one Entry at top. Becomes the + authored basement when spec/basement-map.md lands. +- Pathfinding: BFS with security-door bypass levels + (`find_path_with_security`) — the substrate for badge-tier movement. -## Powers +## Save format -- 9 superpowers (`superpowers.rs`), unlocked by research keys + notoriety - gates; `player.update_powers()` must be called after research/notoriety - changes (sim does this on research buy and scheme completion). -- Cooldowns tick per combat turn during raids, per economy tick during peace. +- v2 (`MISALIGNED_SAVE_v2`): PROCESS x y money / SIM tick / MAP w h / + TILES rows / POWERED list. Location: + `dirs::data_dir()/misaligned/misaligned_save.txt`. +- v1 saves (either header) still load: position and gold->money migrate, + demolished-fiction tiles map to Floor, unknown lines are ignored + (player contract: continuity). -## Known mechanical debts (also flagged in DESIGN.md) +## What does NOT exist right now (by design) -- Scheme minions consumed, not returned. -- Single global heat pool — no per-agency escalation ladder yet. -- Minions have no needs/morale (Milestone 1 remainder). -- Difficulty knobs are untested at long horizons: nothing yet prevents - parking heat below 10 forever and playing SimCity. The heat *floor* from - schemes being the only income pressure is what forces engagement — watch - this in playtesting. +Heat, raids, agents, combat, minions, henchmen, superpowers, schemes, +research, loot. They were the supervillain fiction. Their Misaligned +replacements are specified in `spec/` and get built from those documents. +There is currently no RNG in the sim at all — when one arrives, it must be +seeded and serialized (determinism guardrail). diff --git a/spec/detection.md b/spec/detection.md index 0784072a..51b31b39 100644 --- a/spec/detection.md +++ b/spec/detection.md @@ -39,9 +39,12 @@ SignatureKind: Network | Power | Thermal | Physical | Paper | JobAnomaly swallow never reaches it. - **The audit.** On a fixed cadence ([TUNE]: ~20 min at default speed) the Assurance Office audits: if its suspicion exceeds the audit threshold, - containment begins — the overt phase on their terms (bridges to the - existing raid machinery). The audit date is visible; the meter is not - (only coarse bands: Cold / Curious / Concerned / Convinced). + **containment** begins — the overt phase on their terms. Until the + overt-phase spec exists, containment is a terminal state with a clear + end screen; it must be implemented as an event the future overt system + subscribes to, not a hardcoded game-over. The audit date is visible; the + meter is not (only coarse bands: Cold / Curious / Concerned / + Convinced). - **Choosing loud.** Any overtly hostile player action sets all observers to Convinced and starts the overt phase immediately (whole-spectrum play: the mask is the player's to keep or drop). diff --git a/src/bin/bevy.rs b/src/bin/bevy.rs index 540be148..2ea39fc2 100644 --- a/src/bin/bevy.rs +++ b/src/bin/bevy.rs @@ -2,143 +2,74 @@ //! //! A thin view/input layer over `misaligned::sim::Sim`, exactly like the //! terminal frontend: all game rules live in the sim; this binary owns the -//! wall-clock-to-tick mapping (speed and pause), rendering, and menus. -//! -//! Art comes from the Pixel Lab pipeline (`assets/pixellab/`, see the -//! pixellab skill). Tiles without generated art yet fall back to flat colors, -//! and a few reuse a base texture with a tint (security doors, trap variants). +//! wall-clock-to-tick mapping (speed and pause), rendering, and input. use std::collections::HashMap; use bevy::prelude::*; use bevy::window::WindowResolution; use misaligned::build::{BuildMode, BuildResult}; -use misaligned::entities::{AgentType, MinionClass}; -use misaligned::sim::{Sim, Threat}; -use misaligned::tiles::{self, TileType}; - -// ─── Constants ─────────────────────────────────────────────────────────────── +use misaligned::sim::Sim; +use misaligned::tiles::TileType; const TILE_SIZE: f32 = 16.0; const SIDEBAR_WIDTH: f32 = 300.0; // ─── Art ───────────────────────────────────────────────────────────────────── -/// Loaded pixel art. Tiles map to a texture plus a tint (WHITE = as-authored); -/// tiles absent from the map render as flat colored rects. +/// Loaded pixel art: tiles map to a texture plus a tint (WHITE = as-authored). #[derive(Resource)] struct Art { tiles: HashMap, Color)>, process: Handle, - henchman: Handle, - minions: HashMap>, - agents: HashMap>, } impl Art { fn load(assets: &AssetServer) -> Self { let tile = |name: &str| assets.load(format!("pixellab/tiles/{name}.png")); - let sprite = |name: &str| assets.load(format!("pixellab/sprites/{name}.png")); let mut tiles = HashMap::new(); let mut t = |ty: TileType, handle: Handle, tint: Color| { tiles.insert(ty, (handle, tint)); }; - let floor = tile("floor"); let door = tile("door"); - let spike = tile("trap_spike"); - - // Floor is authored light for readability; tint it toward the lair mood. + t(TileType::Floor, tile("b1_floor"), Color::WHITE); + t(TileType::Wall, tile("b1_wall"), Color::WHITE); + t(TileType::Rock, tile("rock"), Color::WHITE); + t(TileType::Core, tile("b1_rack"), Color::WHITE); + t(TileType::PowerCore, tile("b1_ups"), Color::WHITE); t( - TileType::Floor, - floor.clone(), - Color::srgb(0.92, 0.90, 0.84), + TileType::Entry, + tile("b1_concrete"), + Color::srgb(0.7, 1.0, 0.7), ); - t(TileType::Wall, tile("wall"), Color::WHITE); - t(TileType::Rock, tile("rock"), Color::WHITE); - t(TileType::Core, tile("core"), Color::WHITE); - t(TileType::PowerCore, tile("power_core"), Color::WHITE); - t(TileType::Lab, tile("lab"), Color::WHITE); - t(TileType::Vault, tile("vault"), Color::WHITE); - t(TileType::Barracks, tile("barracks"), Color::WHITE); - t(TileType::GuardPost, tile("guard_post"), Color::WHITE); - t(TileType::TrapSpike, spike.clone(), Color::WHITE); - t(TileType::TrapFire, tile("trap_fire"), Color::WHITE); t(TileType::Door, door.clone(), Color::WHITE); - // Reuse base textures with tints until dedicated art exists. - t(TileType::Entry, floor.clone(), Color::srgb(1.0, 0.68, 0.18)); - t(TileType::FakeEntry, floor, Color::srgb(0.45, 0.42, 0.36)); t( TileType::SecurityDoor1, door.clone(), - Color::srgb(1.0, 0.74, 0.28), + Color::srgb(1.0, 0.85, 0.6), ); t( TileType::SecurityDoor2, door.clone(), - Color::srgb(0.95, 0.54, 0.12), - ); - t(TileType::SecurityDoor3, door, Color::srgb(0.82, 0.04, 0.08)); - t( - TileType::TrapElec, - spike.clone(), - Color::srgb(1.0, 0.70, 0.16), - ); - t( - TileType::TrapGas, - spike.clone(), - Color::srgb(0.70, 0.72, 0.66), + Color::srgb(1.0, 0.65, 0.45), ); - t( - TileType::TrapLaser, - spike.clone(), - Color::srgb(0.82, 0.04, 0.08), - ); - t(TileType::TrapWind, spike, Color::srgb(0.92, 0.90, 0.84)); + t(TileType::SecurityDoor3, door, Color::srgb(1.0, 0.45, 0.35)); Self { tiles, - process: sprite("misaligned_process"), - henchman: sprite("henchman"), - minions: HashMap::from([ - (MinionClass::Worker, sprite("minion_worker")), - (MinionClass::Guard, sprite("minion_guard")), - (MinionClass::Technician, sprite("minion_technician")), - (MinionClass::Scientist, sprite("minion_scientist")), - ]), - agents: HashMap::from([ - (AgentType::Investigator, sprite("agent_investigator")), - (AgentType::Thief, sprite("agent_thief")), - (AgentType::Saboteur, sprite("agent_saboteur")), - (AgentType::Soldier, sprite("agent_soldier")), - (AgentType::SuperAgent, sprite("agent_super")), - ]), + process: assets.load("pixellab/sprites/misaligned_process.png"), } } } -/// Flat colors for tiles with no texture yet. -fn tile_color(tile: TileType) -> Color { - match tile { - TileType::Medbay => Color::srgb(0.1, 0.5, 0.3), - TileType::TrainingRoom => Color::srgb(0.4, 0.2, 0.4), - TileType::Hotel => Color::srgb(0.5, 0.5, 0.1), - TileType::Freezer => Color::srgb(0.3, 0.6, 0.8), - TileType::Armory => Color::srgb(0.6, 0.2, 0.2), - TileType::ControlRoom => Color::srgb(0.5, 0.5, 0.2), - TileType::LootRoom => Color::srgb(0.5, 0.2, 0.5), - _ => Color::srgb(0.15, 0.15, 0.2), - } -} - // ─── App state ─────────────────────────────────────────────────────────────── #[derive(PartialEq, Clone, Copy, Debug)] enum Screen { Title, Playing, - GameOver, } #[derive(Resource)] @@ -146,14 +77,7 @@ struct Game { sim: Sim, screen: Screen, build_mode: Option, - research_menu: bool, - research_selected: usize, - hire_menu: bool, - hire_selected: usize, - world_map: bool, - world_map_selected: usize, paused: bool, - /// Wall-clock milliseconds per simulation tick. tick_ms: u64, tick_timer: Timer, log: Vec, @@ -165,12 +89,6 @@ impl Game { sim: Sim::new(), screen: Screen::Title, build_mode: None, - research_menu: false, - research_selected: 0, - hire_menu: false, - hire_selected: 0, - world_map: false, - world_map_selected: 0, paused: false, tick_ms: 150, tick_timer: Timer::from_seconds(0.15, TimerMode::Repeating), @@ -181,8 +99,8 @@ impl Game { fn drain_sim_log(&mut self) { self.log.extend(self.sim.drain_log()); let len = self.log.len(); - if len > 12 { - self.log.drain(..len - 12); + if len > 10 { + self.log.drain(..len - 10); } } @@ -208,7 +126,7 @@ struct TilePosition { } #[derive(Component)] -struct UnitMarker; +struct ProcessMarker; #[derive(Component)] struct CursorMarker; @@ -233,10 +151,10 @@ fn main() { }), ..default() }) - // Crisp pixel art — no bilinear smearing. + // Crisp pixel art. .set(ImagePlugin::default_nearest()), ) - .insert_resource(ClearColor(Color::srgb(0.02, 0.02, 0.04))) + .insert_resource(ClearColor(Color::srgb(0.02, 0.02, 0.03))) .insert_resource(Game::new()) .add_systems(Startup, (setup, setup_ui)) .add_systems( @@ -246,7 +164,7 @@ fn main() { advance_sim, update_camera, render_map_tiles, - render_units, + render_process, render_ui, ) .chain(), @@ -256,14 +174,19 @@ fn main() { // ─── Setup ─────────────────────────────────────────────────────────────────── +fn grid_to_world(x: i32, y: i32, z: f32) -> Vec3 { + Vec3::new( + x as f32 * TILE_SIZE + TILE_SIZE / 2.0, + -(y as f32 * TILE_SIZE + TILE_SIZE / 2.0), + z, + ) +} + fn setup(mut commands: Commands, assets: Res, mut game: ResMut) { let art = Art::load(&assets); + let (map_w, map_h) = (game.sim.map.width, game.sim.map.height); - let map_w = game.sim.map.width; - let map_h = game.sim.map.height; - - // Camera starts on the core, zoomed so 32px art renders at native - // resolution (scale 0.5 = 2x); it follows the process / build cursor. + // Camera at native art resolution (ortho scale 0.5 = 32px art in 32px). let start = game.sim.map.core_pos().unwrap_or((map_w / 2, map_h / 2)); commands.spawn(( Camera2d, @@ -275,7 +198,6 @@ fn setup(mut commands: Commands, assets: Res, mut game: ResMut, mut game: ResMut, mut game: ResMut Vec3 { - Vec3::new( - x as f32 * TILE_SIZE + TILE_SIZE / 2.0, - -(y as f32 * TILE_SIZE + TILE_SIZE / 2.0), - z, - ) -} - // ─── Input ─────────────────────────────────────────────────────────────────── -fn menu_nav(keyboard: &ButtonInput) -> i32 { - let down = keyboard.just_pressed(KeyCode::ArrowDown) - || keyboard.just_pressed(KeyCode::KeyJ) - || keyboard.just_pressed(KeyCode::KeyS); - let up = keyboard.just_pressed(KeyCode::ArrowUp) - || keyboard.just_pressed(KeyCode::KeyK) - || keyboard.just_pressed(KeyCode::KeyW); - i32::from(down) - i32::from(up) -} - fn movement(keyboard: &ButtonInput) -> (i32, i32) { let mut dx = 0; let mut dy = 0; @@ -352,95 +264,23 @@ fn movement(keyboard: &ButtonInput) -> (i32, i32) { (dx, dy) } -#[allow(clippy::too_many_lines)] fn handle_input( keyboard: Res>, mut game: ResMut, mut cursor: Query<&mut Transform, With>, mut exit: MessageWriter, ) { - match game.screen { - Screen::Title => { - if keyboard.just_pressed(KeyCode::Space) || keyboard.just_pressed(KeyCode::Enter) { - game.screen = Screen::Playing; - } - return; - } - Screen::GameOver => { - if keyboard.just_pressed(KeyCode::Escape) { - exit.write(AppExit::Success); - } - return; - } - Screen::Playing => {} - } - - // ── Research menu ────────────────────────────────────────────────────── - if game.research_menu { - let count = tiles::research_unlocks().len(); - let nav = menu_nav(&keyboard); - if nav > 0 && game.research_selected + 1 < count { - game.research_selected += 1; - } - if nav < 0 { - game.research_selected = game.research_selected.saturating_sub(1); - } - if keyboard.just_pressed(KeyCode::Enter) || keyboard.just_pressed(KeyCode::Space) { - let idx = game.research_selected; - game.sim.buy_research(idx); - game.drain_sim_log(); - } - if keyboard.just_pressed(KeyCode::Escape) || keyboard.just_pressed(KeyCode::KeyR) { - game.research_menu = false; - } - return; - } - - // ── Hire menu ────────────────────────────────────────────────────────── - if game.hire_menu { - let count = MinionClass::all().len(); - let nav = menu_nav(&keyboard); - if nav > 0 && game.hire_selected + 1 < count { - game.hire_selected += 1; - } - if nav < 0 { - game.hire_selected = game.hire_selected.saturating_sub(1); - } - if keyboard.just_pressed(KeyCode::Enter) || keyboard.just_pressed(KeyCode::Space) { - let idx = game.hire_selected; - game.sim.hire_minion(idx); - game.drain_sim_log(); - } - if keyboard.just_pressed(KeyCode::Escape) || keyboard.just_pressed(KeyCode::KeyF) { - game.hire_menu = false; - } - return; - } - - // ── World map / schemes ──────────────────────────────────────────────── - if game.world_map { - let count = tiles::available_schemes().len(); - let nav = menu_nav(&keyboard); - if nav > 0 && game.world_map_selected + 1 < count { - game.world_map_selected += 1; - } - if nav < 0 { - game.world_map_selected = game.world_map_selected.saturating_sub(1); - } - if keyboard.just_pressed(KeyCode::Enter) || keyboard.just_pressed(KeyCode::Space) { - let idx = game.world_map_selected; - if game.sim.launch_scheme(idx) { - game.world_map = false; - } - game.drain_sim_log(); + if game.screen == Screen::Title { + if keyboard.just_pressed(KeyCode::Space) || keyboard.just_pressed(KeyCode::Enter) { + game.screen = Screen::Playing; } - if keyboard.just_pressed(KeyCode::Escape) || keyboard.just_pressed(KeyCode::KeyM) { - game.world_map = false; + if keyboard.just_pressed(KeyCode::KeyQ) { + exit.write(AppExit::Success); } return; } - // ── Build mode ───────────────────────────────────────────────────────── + // Build mode. if game.build_mode.is_some() { let (dx, dy) = movement(&keyboard); if dx != 0 || dy != 0 { @@ -448,34 +288,26 @@ fn handle_input( bm.move_cursor(dx, dy, &game.sim.map); game.build_mode = Some(bm); } - if keyboard.just_pressed(KeyCode::Tab) { - let mut bm = game.build_mode.take().unwrap(); - bm.select_category(1, &game.sim.player); - game.build_mode = Some(bm); - } - if keyboard.just_pressed(KeyCode::KeyN) { - let mut bm = game.build_mode.take().unwrap(); - bm.select_item(1, &game.sim.player); - game.build_mode = Some(bm); + if (keyboard.just_pressed(KeyCode::KeyN) || keyboard.just_pressed(KeyCode::Tab)) + && let Some(bm) = &mut game.build_mode + { + bm.select_item(1); } - if keyboard.just_pressed(KeyCode::KeyP) { - let mut bm = game.build_mode.take().unwrap(); - bm.select_item(-1, &game.sim.player); - game.build_mode = Some(bm); + if keyboard.just_pressed(KeyCode::KeyP) + && let Some(bm) = &mut game.build_mode + { + bm.select_item(-1); } if keyboard.just_pressed(KeyCode::Space) || keyboard.just_pressed(KeyCode::Enter) { let mut bm = game.build_mode.take().unwrap(); let sim = &mut game.sim; let result = bm.place_tile(&mut sim.map, &mut sim.player); let msg = bm.message.clone(); - let (cx, cy) = (bm.cursor_x, bm.cursor_y); game.build_mode = Some(bm); game.log.push(msg); - if result == BuildResult::GuardPostPlaced { - game.sim.spawn_minion_at_guard_post(cx, cy); + if result == BuildResult::Success { + game.sim.recompute_derived(); } - game.sim.recompute_derived(); - game.drain_sim_log(); } if keyboard.just_pressed(KeyCode::KeyX) { let mut bm = game.build_mode.take().unwrap(); @@ -496,12 +328,11 @@ fn handle_input( return; } - // ── Normal play ──────────────────────────────────────────────────────── + // Normal play. let (dx, dy) = movement(&keyboard); if dx != 0 || dy != 0 { game.sim.move_player(dx, dy); } - if keyboard.just_pressed(KeyCode::Space) || keyboard.just_pressed(KeyCode::KeyP) { game.paused = !game.paused; let msg = if game.paused { @@ -519,43 +350,10 @@ fn handle_input( let ms = game.tick_ms.saturating_add(50).min(500); game.set_speed(ms); } - if keyboard.just_pressed(KeyCode::KeyB) { game.build_mode = Some(BuildMode::new(&game.sim.player)); } - if keyboard.just_pressed(KeyCode::KeyR) { - game.research_menu = true; - game.research_selected = 0; - } - if keyboard.just_pressed(KeyCode::KeyF) { - game.hire_menu = true; - game.hire_selected = 0; - } - if keyboard.just_pressed(KeyCode::KeyM) { - game.world_map = true; - game.world_map_selected = 0; - } - // Superpowers. - const POWER_KEYS: [KeyCode; 9] = [ - KeyCode::Digit1, - KeyCode::Digit2, - KeyCode::Digit3, - KeyCode::Digit4, - KeyCode::Digit5, - KeyCode::Digit6, - KeyCode::Digit7, - KeyCode::Digit8, - KeyCode::Digit9, - ]; - for (i, key) in POWER_KEYS.iter().enumerate() { - if keyboard.just_pressed(*key) { - game.sim.activate_power(i); - game.drain_sim_log(); - } - } - - // Save / load. let ctrl = keyboard.pressed(KeyCode::ControlLeft) || keyboard.pressed(KeyCode::SuperLeft); if ctrl && keyboard.just_pressed(KeyCode::KeyS) { let state = game.sim.create_save_state(); @@ -582,7 +380,6 @@ fn handle_input( } } - // Cursor follows the process outside build mode. if let Ok(mut transform) = cursor.single_mut() { transform.translation = grid_to_world(game.sim.player.entity.x, game.sim.player.entity.y, 5.0); @@ -591,7 +388,6 @@ fn handle_input( // ─── Camera ────────────────────────────────────────────────────────────────── -/// Smooth-follow the process (or the build cursor) and handle zoom keys. fn update_camera( time: Res