From 8719f77ec200d3a2dab4e4910c7e919e5adc203b Mon Sep 17 00:00:00 2001 From: funman300 Date: Fri, 8 May 2026 09:52:33 -0700 Subject: [PATCH] fix(engine): regenerate table backgrounds to flat Terminal palette MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The post-Option-D screenshot showed Terminal cards correctly but a green felt play surface — the chrome migration only retuned in-engine constants, leaving the on-disk PNGs at assets/backgrounds/bg_*.png as the legacy felt textures. Adds solitaire_engine/examples/background_generator.rs following the same regeneratable pattern as card_face_generator. Five solid near-black variants from the base16-eighties palette: - bg_0: #151515 (Terminal canonical, BG_PRIMARY) - bg_1: #0a0a0a (BG_DEEPEST) - bg_2: #1a1a1a (BG_ELEVATED — same as card face) - bg_3: #121820 (slight cool tint) - bg_4: #201812 (slight warm tint) Per design-system.md the Terminal play surface is *flat* — no felt, no gradient — so all 5 slots are pure solid colours. Each PNG is 120 × 168 (matches the legacy tile size; spawn_background stretches to window_size * 2.0 at runtime so source resolution is immaterial). On-disk weight drops from ~16KB average to ~100 bytes per tile. Run with: cargo run --example background_generator --release Co-Authored-By: Claude Opus 4.7 --- assets/backgrounds/bg_0.png | Bin 32378 -> 469 bytes assets/backgrounds/bg_1.png | Bin 2000 -> 469 bytes assets/backgrounds/bg_2.png | Bin 4404 -> 469 bytes assets/backgrounds/bg_3.png | Bin 22620 -> 472 bytes assets/backgrounds/bg_4.png | Bin 25054 -> 472 bytes .../examples/background_generator.rs | 72 ++++++++++++++++++ 6 files changed, 72 insertions(+) create mode 100644 solitaire_engine/examples/background_generator.rs diff --git a/assets/backgrounds/bg_0.png b/assets/backgrounds/bg_0.png index c75dc16f39489debf5620d69584832e292ebe83d..4b25f19fb569956246412586193c0faf098d8153 100644 GIT binary patch literal 469 zcmeAS@N?(olHy`uVBq!ia0vp^6+pa#gAGV(M``_KU|^i%>EaktG3V{YMotDrfdd8; z;~##L4_#i-GRY_4g}HJ1#I5n{AM~3a^Yj^8oO@s~%Ai{y#N~?nRWVHziasj{j4lRG LS3j3^P6>E&_KEM1&L+IAYL&Qr2M|ETe)ft%!&mW0K9L(xfv&y9;AP zLkTPjw{GTPW$OSDB{>LN=xlXS8fzbg;;6A|&@Pnu$;Q~;=Yc$SE(V+Rm(lL4;8{aiHr0aQq zvbyK!;kBe1iwJyR z;IixdC1VxgCsbOrAFLO>>0LNTc0Xr)=p25Jxwi8StDXOUUpW_4#R>vml4nzC=O^DU ztQS4n5$L7Byz{5-s|}02lWLcR*tgQuCsh2RWWDI}Zg(9Mb(6+1 zI8_#S^31cEyxK|KSJoA0Bbp;&GI}ia2^BRZ>qQ^ARXxf}GyNDqMGNR}v0n7>$BA`O{+-X{F=&8_&VOf@cj*EE z005931OWh`gU?v^q5%N%jsFs*3jhEOO3YeCGo~&805tp&yj3(`>H+{j!x_X|Mf3G8 z0RRy3#G!N%U_^BI0RS>U{kc3UtZg)Wy-NT9M10V!Z8VI%O8@`>0000)6D`y24QV_i zRJ0Hk%{X=e0001xWnhSP=>h-%0FY&1h;``#0001xWnc&i05TzY)@F3ZDw?l%2><{9 zAj{Z2Ywr>O002Oiv3uU$B>(^b004kAdh29F{%m;z00000#j9FUI~_;>khv$0wTeap zfXKawtb5UnZ8S&#(6}CybuSvXm*)Edute+v34mPMno}jGV!H{Yi;&U<000000D%4F z=ePn$+}9WY0GZJNm;oSQtO4)vGG+kK7?%O>l9<~|kpMsf011GMqgF(5FaUsnsZXpI z9SML8nv^*V%kWmwxB`gAm^`RxF)A7f02>JjM7Kdvx`-%UTAJr-D>SMvc2jX;ro3d>Qm%KE9lQ7sjo7Nz diff --git a/assets/backgrounds/bg_1.png b/assets/backgrounds/bg_1.png index ef9525e973f4b75a33dec7827539b0def8672393..84212aca13bd017cd55d597eb19315a9b194e71e 100644 GIT binary patch literal 469 zcmeAS@N?(olHy`uVBq!ia0vp^6+pa#gAGV(M``_KU|^i%>EaktG3V{YgPaVC0tXx> zR_FfznCvL50{xSVvA=`0Dne>7=qYSzQ!uoC8v)3{e{Nh_G28=ES MPgg&ebxsLQ0L|gH>i_@% literal 2000 zcmeAS@N?(olHy`uVBq!ia0vp^6+pa#gAGV(M``_KU|^r_>Eakt!T9z7^8%H~X~#bP zFMrp2;>_|Q@8os4x3@u2X3xB7UhC%>87sZ}S++f^YFWE=_ErD>eTj!-zSfIYec_dU zw(9@t3)%aR@WL5vH}1_SzWQJGx4SmOms|RU%{yQH*Ft45ZZm%dG~oEZi(CwQzj@zD zSodo`7b>ISZLekc-}@Wuw=g-Z|0`rZWA(rFPACkCgzDsTq5sR@$ggEUbpc(SG8!02 y@iH11qk(}v$Bw3q(UdWoGJsVb-iqy!JmVa`33=z1PZR{!)C``kelF{r5}E+Lm3M>y diff --git a/assets/backgrounds/bg_2.png b/assets/backgrounds/bg_2.png index 2023a48002c74508c16a7a1abce98b0f4d8a5560..53b26a294ff6359259ce827e9c7889fbf414ac83 100644 GIT binary patch literal 469 zcmeAS@N?(olHy`uVBq!ia0vp^6+pa#gAGV(M``_KU|^i%>EaktG3V{YMotDrfdd8; z=Rf*(KX`dX%OszG7v{$46Su~*f6#A!%+qIVaqfY|D1&Z+5SJ@{&WUM(fvxaVV01Bf My85}Sb4q9e06<94i2wiq literal 4404 zcmcgwZAep57`{`~FoylfK|+HP1SJLq`6E9{6w|;}C?kt4h@!F~7}TcjgQ6lVghcxh zOfyPxg?S& z@4D)aoavQX^r6>+&WNW7FxO_pHD5Swtp$`jr`~dXvdd+ zi}s6rk{f!V>Dhoiko{cYX?9;Gdc+4{WMuKP7R~0BP5vs2O1mhS6J~n6U-U7qjHY21hcCdvF45C9bqCDXgQuf|>m37M@va_-d&$!ShfbQCM(=04Knq z#g#hO&Dv3)|CdO0k9;Jd1N>64x|T3HZHd$0^4*j5t+|aS;S$wTk^EJ^4Q%b6^yr<0 z7;Z)hqpKq%DTxz$tzTEfEQOy-6Z05|6Jt;)rVkX76;~^iv3i z0CuiYlc&X712xK@+v$93`h?4_zlJEvBXqbO zOM%vJok1L%Wt$*X5J#0^&YM^bfgpXFrXe$Pb!gvte2S3)8LU3Zge= zEb?Z+Sgx&Mt`6;6UJzD3Y%4{IZSPCRU;~lR2s%Nwt1RmHF}BsQv-cmGB7Z9ztwA(y z^Y~;Dyp9-od<}P)P4J)!R%e;mL8qUBYTpsY9>kED%}(i1*^RI}VRJ0n*!wUj@+28D zBt;l|!dRd^J4K#EH-o*+Z;HG*l=+p?O)wsdHXeBk$Re1G_TlS}Jz>^7N^^D+kSbvQ f4?=vrd3R4Fzf3qcKH>=e8-kUUR226Xoxb)DUdQ?Q diff --git a/assets/backgrounds/bg_3.png b/assets/backgrounds/bg_3.png index 7302d3c613b6627e10d3240110d3cb58a85c6d12..1b0cf428e826c8c8ef0b41a3078c2239b1ad6c84 100644 GIT binary patch literal 472 zcmeAS@N?(olHy`uVBq!ia0vp^6+pa#gAGV(M``_KU|^i@>EaktG3V{YgPaEx7!EjS z`aM{E{@wm1eu}LH=8g)*d+*Do*w(WbOg~u2c3e^>ygTe~DWM4fv>3OL literal 22620 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 diff --git a/assets/backgrounds/bg_4.png b/assets/backgrounds/bg_4.png index 382637c48d1111388136e0f2ab8c7455db5b7858..dff83ad09d2571c599e2f69666daeba082d3e48b 100644 GIT binary patch literal 472 zcmeAS@N?(olHy`uVBq!ia0vp^6+pa#gAGV(M``_KU|^i@>EaktG3V{YgPaEx7!EjS z`aM{E{@wm1eu}LH=8g)VtFOzY*w(WbOg~u2c3e^>ygTe~DWM4f4Rp8n literal 25054 zcmeI5O-K}B7{}jbpbbHWx)>ccu)5S?F%J>##wr`N2^P{N53S)L`;f##jVrN!9KtlZ zDM83w`Z$>fS-Z$Clon($mmu6ISTQ`f#^ACU>yE9fFd#Mj55vp`f7p-9dl=rC=fA(_ z|2*%rLzkM*RaYIX5<*l*&WBq{-%9EIa_^qf=keBKMIo*|i-a34E1qmhQjc62ozpk( ztv-4`|DwC8eWEj+*m>O;86UmyYWzff?dtEl(>G(!ADq7R`J&&*_^2r85tZ#r?6W7P;sj__gT7qXnsDQ3{^!+&q)1$w{php`{u77yq{o=lan_DJgdQJS)ny3H? zH@A$Thy9FE%Rw9p5u-3DBSwK7%g{qZj~E4|XT&Hlmn24^c{MSL7-eg&L5wnU6iaW5 z7)6W%`3PJ_El0JSZWLt66QhVx_EwIV5~Es9H;QhQt>sdN9vXUdqv%FKd_*@26ftUh zFdXMbfy47SHwqji$hlGAu*J#@Jr?y?)MHVPMZNMUS&rZQueF@LQIKiH7IW0JVo{Gp zJzFzpp42ld=9YzZwwSZU9CUcWWz=#|%Z+_44+oXt;n#`M|2Bxo+2-&>W81(V@|bU5 diff --git a/solitaire_engine/examples/background_generator.rs b/solitaire_engine/examples/background_generator.rs new file mode 100644 index 0000000..8fb465b --- /dev/null +++ b/solitaire_engine/examples/background_generator.rs @@ -0,0 +1,72 @@ +//! Table-background generator — writes 5 Terminal-aesthetic solid-colour +//! PNGs into `assets/backgrounds/`. +//! +//! Run with: +//! +//! ```sh +//! cargo run --example background_generator --release +//! ``` +//! +//! The play-surface fill is `Settings::selected_background`-indexed +//! into `assets/backgrounds/bg_{0..4}.png` by +//! `table_plugin::load_background_images`. Per `design-system.md` the +//! Terminal play surface is *flat* — no felt texture, no gradient — +//! so all five slots are simple solid colours from the base16-eighties +//! palette, each visually distinct enough for the picker but all on +//! brand. +//! +//! Output is small (a 120 × 168 PNG with one solid colour compresses +//! to ≈100 bytes) and stretched to `window_size * 2.0` at runtime by +//! `table_plugin::spawn_background`, so the source resolution is +//! immaterial — keeping it 120 × 168 preserves the legacy tile size. + +use std::path::PathBuf; +use tiny_skia::{Color, IntSize, Pixmap}; + +const TILE_W: u32 = 120; +const TILE_H: u32 = 168; + +/// Five Terminal-palette play-surface variants. Slot 0 is the canonical +/// design-system `#151515`; the others stay in the same near-black +/// family so the picker offers choice without ever leaving the brand. +const BACKGROUNDS: [(u8, u8, u8); 5] = [ + (0x15, 0x15, 0x15), // 0 — Terminal canonical (#151515 BG_PRIMARY) + (0x0a, 0x0a, 0x0a), // 1 — deeper near-black (BG_DEEPEST) + (0x1a, 0x1a, 0x1a), // 2 — BG_ELEVATED (matches card face) + (0x12, 0x18, 0x20), // 3 — slight cool tint + (0x20, 0x18, 0x12), // 4 — slight warm tint +]; + +fn main() { + let dir = workspace_assets_dir().join("backgrounds"); + std::fs::create_dir_all(&dir).expect("create backgrounds dir"); + + let size = IntSize::from_wh(TILE_W, TILE_H).expect("non-zero tile size"); + + for (idx, (r, g, b)) in BACKGROUNDS.iter().enumerate() { + let mut pixmap = Pixmap::new(size.width(), size.height()).expect("alloc pixmap"); + pixmap.fill(Color::from_rgba8(*r, *g, *b, 0xff)); + + let path = dir.join(format!("bg_{idx}.png")); + pixmap + .save_png(&path) + .unwrap_or_else(|e| panic!("write {}: {e}", path.display())); + } + + println!( + "Wrote 5 PNGs ({}×{} solid Terminal colours) to {}", + TILE_W, + TILE_H, + dir.display(), + ); +} + +/// Resolves the workspace-root `assets/` directory relative to the +/// running example crate. `CARGO_MANIFEST_DIR` is the engine crate; +/// its parent is the workspace root. +fn workspace_assets_dir() -> PathBuf { + PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .parent() + .expect("solitaire_engine crate has a workspace-root parent") + .join("assets") +}