From e815391980ddc595154823565103d73acac0039a Mon Sep 17 00:00:00 2001 From: ddidderr Date: Sat, 22 Aug 2026 17:03:46 +0200 Subject: [PATCH] fix(ui): preserve the original canvas and display styling High-DPI sizing and a resizable client allowed the 640x460 canvas to be placed inside a much taller window, producing the large internal black band captured in the gameplay recording. Open an aspect-correct fixed 960x690 client with high-DPI framebuffer scaling disabled, giving every platform a predictable 1.5x canvas. Use exact active-table patches for the cyan score fields and derive transparent numeral glyphs from the recovered Win16 bitmap font. Fill the complete display slots, move the KBytes readout clear of its border, and place shortcuts on a readable footer so no dynamic text is clipped or left over an inactive panel. Test Plan: - `cargo fmt -- --check` -- passed - `cargo test --all-targets` -- passed - `cargo clippy --all-targets -- -D warnings` -- passed - launched the Linux window and confirmed a 960x690 client with no letterbox, full-width cyan fields, recovered score digits, and readable footer -- passed - `git diff --check` -- passed --- tdkpin-rs/README.md | 4 ++ tdkpin-rs/RECONSTRUCTION.md | 2 +- tdkpin-rs/src/app.rs | 84 +++++++++++++++++++++++++++---------- tdkpin-rs/src/assets.rs | 18 ++++++++ tdkpin-rs/src/main.rs | 12 +++--- 5 files changed, 92 insertions(+), 28 deletions(-) diff --git a/tdkpin-rs/README.md b/tdkpin-rs/README.md index 240d249..8215b70 100644 --- a/tdkpin-rs/README.md +++ b/tdkpin-rs/README.md @@ -5,6 +5,10 @@ Machine**. It uses the extracted original artwork and sound, recreates the playfield as a fixed-step simulation, and runs from the same source on Linux, macOS, and Windows. +The game opens at 960x690, an exact 1.5x enlargement of its original 640x460 +canvas, so modern desktop scaling cannot distort or vertically offset the +pixel-art presentation. + The original program is not required at runtime. All required game assets are embedded in the executable at build time. diff --git a/tdkpin-rs/RECONSTRUCTION.md b/tdkpin-rs/RECONSTRUCTION.md index ed76a3b..573b60f 100644 --- a/tdkpin-rs/RECONSTRUCTION.md +++ b/tdkpin-rs/RECONSTRUCTION.md @@ -28,7 +28,7 @@ implementation. | Numeric scoring | Partly inferred | Visible 2000-6000 target values and recovered registration values are preserved. Some bumper, bank-completion, robot, wheel, lock, and media thresholds are best-evidence reconstructions because the decompiler did not recover meaningful names or a clean rule table. | | High scores | Compatible import | The original 276-byte table is decoded as ten `IWIK`-XOR-obfuscated little-endian scores plus ten 22-byte names, sorted, then migrated to portable JSON. | | Configuration | Behaviorally compatible | Sound, language, and five detail levels are retained. Storage moves from a local Win16 INI file to the platform user-data directory. | -| Windows UI shell | Deliberately modernized | Win16 menus, modal dialogs, GDI blitting, and multimedia timers are replaced by a resizable, letterboxed native window with keyboard overlays. The visible game and original help remain at 640x460 logical pixels. | +| Windows UI shell | Deliberately modernized | Win16 menus, modal dialogs, GDI blitting, and multimedia timers are replaced by an aspect-correct 960x690 native window with keyboard overlays. The visible game and original help remain at 640x460 logical pixels. | ## Extracted asset inventory diff --git a/tdkpin-rs/src/app.rs b/tdkpin-rs/src/app.rs index b3a6c24..84c4929 100644 --- a/tdkpin-rs/src/app.rs +++ b/tdkpin-rs/src/app.rs @@ -559,24 +559,33 @@ impl App { }, ); } - draw_text("F1 HELP F2 SETUP", 345.0, 448.0, 11.0, BLACK); + draw_rectangle( + 340.0, + 444.0, + 292.0, + 14.0, + Color::from_rgba(214, 214, 214, 235), + ); + draw_text("F1 HELP F2 SETUP", 347.0, 455.0, 10.0, BLACK); } #[allow(clippy::cast_precision_loss)] fn draw_displays(&self, game: &Game) { - draw_rectangle( + draw_texture_ex( + &self.assets.active_table, 371.0, - 235.0, - 222.0, - 31.0, - Color::from_rgba(0, 191, 209, 255), + 233.0, + WHITE, + DrawTextureParams { + dest_size: Some(vec2(244.0, 34.0)), + source: Some(Rect::new(371.0, 233.0, 244.0, 34.0)), + ..Default::default() + }, ); - draw_text( - format!("BONUS {:07}", game.bonus), - 374.0, - 258.0, - 24.0, - BLACK, + self.draw_digits( + &format!("{:07}", game.bonus), + vec2(458.0, 240.0), + vec2(10.0, 18.0), ); let media_level = game.player().media_level; @@ -587,20 +596,31 @@ impl App { draw_text( format!("{:07} KBytes", game.player().score / 1_000), 395.0, - 211.0, - 16.0, + 205.0, + 14.0, WHITE, ); for (index, player) in game.players.iter().enumerate() { let y = 274.0 + index as f32 * 44.0; - let panel = if index == game.current_player { - Color::from_rgba(0, 191, 209, 255) - } else { - Color::from_rgba(36, 82, 137, 255) - }; - draw_rectangle(472.0, y, 121.0, 30.0, panel); - draw_text(format!("{:09}", player.score), 478.0, y + 21.0, 18.0, BLACK); + if index == game.current_player { + draw_texture_ex( + &self.assets.active_table, + 472.0, + y, + WHITE, + DrawTextureParams { + dest_size: Some(vec2(143.0, 30.0)), + source: Some(Rect::new(472.0, y, 143.0, 30.0)), + ..Default::default() + }, + ); + } + self.draw_digits( + &format!("{:09}", player.score), + vec2(480.0, y + 6.0), + vec2(10.0, 18.0), + ); for ball in 0..6 { let lit = ball < usize::from(player.balls + player.extra_balls); draw_circle( @@ -613,6 +633,28 @@ impl App { } } + fn draw_digits(&self, digits: &str, position: Vec2, digit_size: Vec2) { + let mut x = position.x; + for digit in digits.bytes() { + if !digit.is_ascii_digit() { + continue; + } + let source_x = f32::from(digit - b'0') * 16.0; + draw_texture_ex( + &self.assets.digits, + x, + position.y, + BLACK, + DrawTextureParams { + dest_size: Some(digit_size), + source: Some(Rect::new(source_x, 0.0, 16.0, 28.0)), + ..Default::default() + }, + ); + x += digit_size.x; + } + } + #[allow(clippy::cast_precision_loss)] fn draw_settings(&self) { draw_texture(&self.assets.inactive_table, 0.0, 0.0, WHITE); diff --git a/tdkpin-rs/src/assets.rs b/tdkpin-rs/src/assets.rs index 41616e8..2725c6d 100644 --- a/tdkpin-rs/src/assets.rs +++ b/tdkpin-rs/src/assets.rs @@ -14,6 +14,7 @@ pub struct Assets { pub robot: Texture2D, pub magnet: Texture2D, pub ball: Texture2D, + pub digits: Texture2D, sounds: Vec<(u16, Sound)>, } @@ -54,6 +55,8 @@ impl Assets { include_bytes!("../assets/original/images/bitmap_00101.png"), include_bytes!("../assets/original/images/bitmap_00102.png"), ); + let digits = + monochrome_texture(include_bytes!("../assets/original/images/bitmap_00500.png")); let sound_bytes: [(u16, &[u8]); 16] = [ ( @@ -139,6 +142,7 @@ impl Assets { robot, magnet, ball, + digits, sounds, } } @@ -182,3 +186,17 @@ fn masked_texture(color_bytes: &[u8], mask_bytes: &[u8]) -> Texture2D { texture.set_filter(FilterMode::Nearest); texture } + +fn monochrome_texture(bytes: &[u8]) -> Texture2D { + let mut image = + Image::from_file_with_format(bytes, None).expect("embedded monochrome bitmap must decode"); + let (pixels, remainder) = image.bytes.as_chunks_mut::<4>(); + assert!(remainder.is_empty()); + for pixel in pixels { + pixel[3] = u8::MAX - pixel[0]; + pixel[..3].fill(u8::MAX); + } + let texture = Texture2D::from_image(&image); + texture.set_filter(FilterMode::Nearest); + texture +} diff --git a/tdkpin-rs/src/main.rs b/tdkpin-rs/src/main.rs index 3ede069..391bb3f 100644 --- a/tdkpin-rs/src/main.rs +++ b/tdkpin-rs/src/main.rs @@ -7,16 +7,16 @@ mod persistence; use app::App; use macroquad::prelude::*; -const LOGICAL_WIDTH: i32 = 640; -const LOGICAL_HEIGHT: i32 = 460; +const WINDOW_WIDTH: i32 = 960; +const WINDOW_HEIGHT: i32 = 690; fn window_conf() -> Conf { Conf { window_title: "TDK Pinball Machine".to_owned(), - window_width: LOGICAL_WIDTH, - window_height: LOGICAL_HEIGHT, - window_resizable: true, - high_dpi: true, + window_width: WINDOW_WIDTH, + window_height: WINDOW_HEIGHT, + window_resizable: false, + high_dpi: false, ..Default::default() } }