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
This commit is contained in:
@@ -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,
|
playfield as a fixed-step simulation, and runs from the same source on Linux,
|
||||||
macOS, and Windows.
|
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
|
The original program is not required at runtime. All required game assets are
|
||||||
embedded in the executable at build time.
|
embedded in the executable at build time.
|
||||||
|
|
||||||
|
|||||||
@@ -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. |
|
| 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. |
|
| 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. |
|
| 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
|
## Extracted asset inventory
|
||||||
|
|
||||||
|
|||||||
+63
-21
@@ -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)]
|
#[allow(clippy::cast_precision_loss)]
|
||||||
fn draw_displays(&self, game: &Game) {
|
fn draw_displays(&self, game: &Game) {
|
||||||
draw_rectangle(
|
draw_texture_ex(
|
||||||
|
&self.assets.active_table,
|
||||||
371.0,
|
371.0,
|
||||||
235.0,
|
233.0,
|
||||||
222.0,
|
WHITE,
|
||||||
31.0,
|
DrawTextureParams {
|
||||||
Color::from_rgba(0, 191, 209, 255),
|
dest_size: Some(vec2(244.0, 34.0)),
|
||||||
|
source: Some(Rect::new(371.0, 233.0, 244.0, 34.0)),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
);
|
);
|
||||||
draw_text(
|
self.draw_digits(
|
||||||
format!("BONUS {:07}", game.bonus),
|
&format!("{:07}", game.bonus),
|
||||||
374.0,
|
vec2(458.0, 240.0),
|
||||||
258.0,
|
vec2(10.0, 18.0),
|
||||||
24.0,
|
|
||||||
BLACK,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let media_level = game.player().media_level;
|
let media_level = game.player().media_level;
|
||||||
@@ -587,20 +596,31 @@ impl App {
|
|||||||
draw_text(
|
draw_text(
|
||||||
format!("{:07} KBytes", game.player().score / 1_000),
|
format!("{:07} KBytes", game.player().score / 1_000),
|
||||||
395.0,
|
395.0,
|
||||||
211.0,
|
205.0,
|
||||||
16.0,
|
14.0,
|
||||||
WHITE,
|
WHITE,
|
||||||
);
|
);
|
||||||
|
|
||||||
for (index, player) in game.players.iter().enumerate() {
|
for (index, player) in game.players.iter().enumerate() {
|
||||||
let y = 274.0 + index as f32 * 44.0;
|
let y = 274.0 + index as f32 * 44.0;
|
||||||
let panel = if index == game.current_player {
|
if index == game.current_player {
|
||||||
Color::from_rgba(0, 191, 209, 255)
|
draw_texture_ex(
|
||||||
} else {
|
&self.assets.active_table,
|
||||||
Color::from_rgba(36, 82, 137, 255)
|
472.0,
|
||||||
};
|
y,
|
||||||
draw_rectangle(472.0, y, 121.0, 30.0, panel);
|
WHITE,
|
||||||
draw_text(format!("{:09}", player.score), 478.0, y + 21.0, 18.0, BLACK);
|
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 {
|
for ball in 0..6 {
|
||||||
let lit = ball < usize::from(player.balls + player.extra_balls);
|
let lit = ball < usize::from(player.balls + player.extra_balls);
|
||||||
draw_circle(
|
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)]
|
#[allow(clippy::cast_precision_loss)]
|
||||||
fn draw_settings(&self) {
|
fn draw_settings(&self) {
|
||||||
draw_texture(&self.assets.inactive_table, 0.0, 0.0, WHITE);
|
draw_texture(&self.assets.inactive_table, 0.0, 0.0, WHITE);
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ pub struct Assets {
|
|||||||
pub robot: Texture2D,
|
pub robot: Texture2D,
|
||||||
pub magnet: Texture2D,
|
pub magnet: Texture2D,
|
||||||
pub ball: Texture2D,
|
pub ball: Texture2D,
|
||||||
|
pub digits: Texture2D,
|
||||||
sounds: Vec<(u16, Sound)>,
|
sounds: Vec<(u16, Sound)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,6 +55,8 @@ impl Assets {
|
|||||||
include_bytes!("../assets/original/images/bitmap_00101.png"),
|
include_bytes!("../assets/original/images/bitmap_00101.png"),
|
||||||
include_bytes!("../assets/original/images/bitmap_00102.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] = [
|
let sound_bytes: [(u16, &[u8]); 16] = [
|
||||||
(
|
(
|
||||||
@@ -139,6 +142,7 @@ impl Assets {
|
|||||||
robot,
|
robot,
|
||||||
magnet,
|
magnet,
|
||||||
ball,
|
ball,
|
||||||
|
digits,
|
||||||
sounds,
|
sounds,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -182,3 +186,17 @@ fn masked_texture(color_bytes: &[u8], mask_bytes: &[u8]) -> Texture2D {
|
|||||||
texture.set_filter(FilterMode::Nearest);
|
texture.set_filter(FilterMode::Nearest);
|
||||||
texture
|
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
|
||||||
|
}
|
||||||
|
|||||||
@@ -7,16 +7,16 @@ mod persistence;
|
|||||||
use app::App;
|
use app::App;
|
||||||
use macroquad::prelude::*;
|
use macroquad::prelude::*;
|
||||||
|
|
||||||
const LOGICAL_WIDTH: i32 = 640;
|
const WINDOW_WIDTH: i32 = 960;
|
||||||
const LOGICAL_HEIGHT: i32 = 460;
|
const WINDOW_HEIGHT: i32 = 690;
|
||||||
|
|
||||||
fn window_conf() -> Conf {
|
fn window_conf() -> Conf {
|
||||||
Conf {
|
Conf {
|
||||||
window_title: "TDK Pinball Machine".to_owned(),
|
window_title: "TDK Pinball Machine".to_owned(),
|
||||||
window_width: LOGICAL_WIDTH,
|
window_width: WINDOW_WIDTH,
|
||||||
window_height: LOGICAL_HEIGHT,
|
window_height: WINDOW_HEIGHT,
|
||||||
window_resizable: true,
|
window_resizable: false,
|
||||||
high_dpi: true,
|
high_dpi: false,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user