Pixel look: the world always renders 360 rows high (270/480 selectable), sharp scaling to any screen

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Richard ZinkandClaude Opus 5.5 committed 2026-11-06 07:14:17 +01:00
1 parent 42281b67a5
commit ed1b87e5f3
22 files changed
+90 -49

No files matched your search

+2 -1
View File
@@ -371,7 +371,8 @@ dir.w = západ
dir.nw = severozápad
val.auto = Auto ({0}x)
set.render_scale = Rozlišení 3D
set.render_scale = Pixelový vzhled
set.pixel_rows_val = {0} řádků
set.debug_fps = Zobrazit FPS
set.debug_frame_time = Zobrazit čas snímku
set.debug_ping = Zobrazit ping
+2 -1
View File
@@ -370,7 +370,8 @@ dir.w = vest
dir.nw = nordvest
val.auto = Auto ({0}x)
set.render_scale = 3D-opløsning
set.render_scale = Pixel-look
set.pixel_rows_val = {0} linjer
set.debug_fps = Vis FPS
set.debug_frame_time = Vis billedtid
set.debug_ping = Vis ping
+2 -1
View File
@@ -370,7 +370,8 @@ dir.w = Westen
dir.nw = Nordwesten
val.auto = Auto ({0}x)
set.render_scale = 3D-Auflösung
set.render_scale = Pixel-Look
set.pixel_rows_val = {0} Zeilen
set.debug_fps = FPS anzeigen
set.debug_frame_time = Framezeit anzeigen
set.debug_ping = Ping anzeigen
+2 -1
View File
@@ -371,7 +371,8 @@ dir.w = δύση
dir.nw = βορειοδυτικά
val.auto = Αυτόμ. ({0}x)
set.render_scale = Ανάλυση 3D
set.render_scale = Εμφάνιση pixel
set.pixel_rows_val = {0} γραμμές
set.debug_fps = Εμφάνιση FPS
set.debug_frame_time = Εμφάνιση χρόνου καρέ
set.debug_ping = Εμφάνιση ping
+2 -1
View File
@@ -371,7 +371,8 @@ dir.w = west
dir.nw = north-west
val.auto = Auto ({0}x)
set.render_scale = 3D resolution
set.render_scale = Pixel look
set.pixel_rows_val = {0} rows
set.debug_fps = Show FPS
set.debug_frame_time = Show frame time
set.debug_ping = Show ping
+2 -1
View File
@@ -371,7 +371,8 @@ dir.w = oeste
dir.nw = noroeste
val.auto = Auto ({0}x)
set.render_scale = Resolución 3D
set.render_scale = Aspecto pixelado
set.pixel_rows_val = {0} líneas
set.debug_fps = Mostrar FPS
set.debug_frame_time = Mostrar ms por fotograma
set.debug_ping = Mostrar ping
+2 -1
View File
@@ -371,7 +371,8 @@ dir.w = länsi
dir.nw = luode
val.auto = Auto ({0}x)
set.render_scale = 3D-tarkkuus
set.render_scale = Pikseliulkoasu
set.pixel_rows_val = {0} riviä
set.debug_fps = Näytä FPS
set.debug_frame_time = Näytä ruutuaika
set.debug_ping = Näytä ping
+2 -1
View File
@@ -371,7 +371,8 @@ dir.w = zapad
dir.nw = sjeverozapad
val.auto = Auto ({0}x)
set.render_scale = 3D razlučivost
set.render_scale = Pikselni izgled
set.pixel_rows_val = {0} redaka
set.debug_fps = Prikaži FPS
set.debug_frame_time = Prikaži vrijeme sličice
set.debug_ping = Prikaži ping
+2 -1
View File
@@ -371,7 +371,8 @@ dir.w = nyugat
dir.nw = északnyugat
val.auto = Auto ({0}x)
set.render_scale = 3D felbontás
set.render_scale = Pixeles kinézet
set.pixel_rows_val = {0} sor
set.debug_fps = FPS mutatása
set.debug_frame_time = Képidő mutatása
set.debug_ping = Ping mutatása
+2 -1
View File
@@ -371,7 +371,8 @@ dir.w = ovest
dir.nw = nord-ovest
val.auto = Auto ({0}x)
set.render_scale = Risoluzione 3D
set.render_scale = Aspetto pixel
set.pixel_rows_val = {0} righe
set.debug_fps = Mostra FPS
set.debug_frame_time = Mostra tempo frame
set.debug_ping = Mostra ping
+2 -1
View File
@@ -371,7 +371,8 @@ dir.w = westen
dir.nw = noordwesten
val.auto = Auto ({0}x)
set.render_scale = 3D-resolutie
set.render_scale = Pixellook
set.pixel_rows_val = {0} regels
set.debug_fps = FPS tonen
set.debug_frame_time = Frametijd tonen
set.debug_ping = Ping tonen
+2 -1
View File
@@ -370,7 +370,8 @@ dir.w = vest
dir.nw = nordvest
val.auto = Auto ({0}x)
set.render_scale = 3D-oppløsning
set.render_scale = Pikselutseende
set.pixel_rows_val = {0} linjer
set.debug_fps = Vis FPS
set.debug_frame_time = Vis bildetid
set.debug_ping = Vis ping
+2 -1
View File
@@ -371,7 +371,8 @@ dir.w = oeste
dir.nw = noroeste
val.auto = Auto ({0}x)
set.render_scale = Resolução 3D
set.render_scale = Aspeto pixelizado
set.pixel_rows_val = {0} linhas
set.debug_fps = Mostrar FPS
set.debug_frame_time = Mostrar tempo de frame
set.debug_ping = Mostrar ping
+2 -1
View File
@@ -371,7 +371,8 @@ dir.w = vest
dir.nw = nord-vest
val.auto = Auto ({0}x)
set.render_scale = Rezoluție 3D
set.render_scale = Aspect pixelat
set.pixel_rows_val = {0} rânduri
set.debug_fps = Arată FPS
set.debug_frame_time = Arată timpul cadrului
set.debug_ping = Arată ping
+2 -1
View File
@@ -371,7 +371,8 @@ dir.w = запад
dir.nw = северо-запад
val.auto = Авто ({0}x)
set.render_scale = Разрешение 3D
set.render_scale = Пиксельный вид
set.pixel_rows_val = {0} строк
set.debug_fps = Показывать FPS
set.debug_frame_time = Время кадра
set.debug_ping = Показывать пинг
+2 -1
View File
@@ -371,7 +371,8 @@ dir.w = západ
dir.nw = severozápad
val.auto = Auto ({0}x)
set.render_scale = Rozlíšenie 3D
set.render_scale = Pixelový vzhľad
set.pixel_rows_val = {0} riadkov
set.debug_fps = Zobraziť FPS
set.debug_frame_time = Zobraziť čas snímky
set.debug_ping = Zobraziť ping
+2 -1
View File
@@ -371,7 +371,8 @@ dir.w = запад
dir.nw = северозапад
val.auto = Ауто ({0}x)
set.render_scale = 3D резолуција
set.render_scale = Пикселни изглед
set.pixel_rows_val = {0} редова
set.debug_fps = Прикажи FPS
set.debug_frame_time = Прикажи време кадра
set.debug_ping = Прикажи пинг
+2 -1
View File
@@ -371,7 +371,8 @@ dir.w = väster
dir.nw = nordväst
val.auto = Auto ({0}x)
set.render_scale = 3D-upplösning
set.render_scale = Pixelutseende
set.pixel_rows_val = {0} rader
set.debug_fps = Visa FPS
set.debug_frame_time = Visa bildtid
set.debug_ping = Visa ping
+1 -1
View File
@@ -456,7 +456,7 @@ fn apply_settings(s: &Settings, r3d: &mut R3d) {
r3d.outlines = s.outlines;
r3d.set_wall_detail(s.wall_subdivisions());
r3d.max_lights = s.max_lights as usize;
r3d.render_div = s.render_scale.clamp(1, 16) as u32;
r3d.pixel_rows = if [270, 360, 480].contains(&s.pixel_rows) { s.pixel_rows as u32 } else { 360 };
audio::set_volumes(s.music_volume, s.volume, s.muted);
}
+2 -10
View File
@@ -151,15 +151,7 @@ fn item_value(it: Item, s: &Settings) -> String {
Item::DebugTriggerZones => onoff(s.debug_trigger_zones),
Item::DebugCommandMarkers => onoff(s.debug_command_markers),
Item::DebugPathLines => onoff(s.debug_path_lines),
Item::RenderScale => {
// the setting is for a 1080 pixel high window; show what applies to this one
let now = crate::render3d::effective_div(s.render_scale.max(1) as u32, crate::ui::sh());
if now as u8 == s.render_scale.max(1) {
format!("1/{}", s.render_scale.max(1))
} else {
format!("1/{} (1/{})", s.render_scale.max(1), now)
}
}
Item::RenderScale => crate::i18n::trf("set.pixel_rows_val", &[s.pixel_rows.to_string()]),
Item::GameSpeed => format!("{}x", s.game_speed as i32),
Item::Hints => onoff(s.hints),
Item::Events => onoff(s.events),
@@ -201,7 +193,7 @@ fn item_change(it: Item, s: &mut Settings, d: i32) {
Item::WallDetail => s.wall_detail = cycle(&[1u8, 2, 3], s.wall_detail, d),
Item::Lights => s.max_lights = cycle(&[8u8, 16, 24], s.max_lights, d),
Item::UiScale => s.ui_factor = cycle(&[0u8, 1, 2, 3, 4], s.ui_factor, d),
Item::RenderScale => s.render_scale = cycle(&(1u8..=16).collect::<Vec<u8>>(), s.render_scale.clamp(1, 16), d),
Item::RenderScale => s.pixel_rows = cycle(&[270u16, 360, 480], if [270, 360, 480].contains(&s.pixel_rows) { s.pixel_rows } else { 360 }, d),
Item::DebugFps => s.debug_fps = !s.debug_fps,
Item::DebugFrameTime => s.debug_frame_time = !s.debug_frame_time,
Item::DebugPing => s.debug_ping = !s.debug_ping,
+48 -19
View File
@@ -1122,7 +1122,8 @@ pub struct R3d {
pub outlines: bool, // tile outlines on/off
wall_sub: usize, // wall face subdivisions (detail)
pub max_lights: usize,
pub render_div: u32, // 3D resolution divisor (pixel look)
pub pixel_rows: u32, // pixel look: the world is rendered this many rows high (0: off)
up: Option<(RenderTarget, u32, u32)>, // the world scaled up by a whole factor (sharp scaling)
target: Option<(RenderTarget, u32, u32)>, // low resolution render target
lava: Vec<f32>, // lava light per tile (see lava_field)
}
@@ -1130,13 +1131,16 @@ pub struct R3d {
// The 3D resolution setting refers to a window 1080 device pixels high: smaller windows use a
// proportionally smaller divisor, so the 3D pixels keep their size relative to the window.
// 1 (full resolution) stays 1.
pub const RENDER_REF_HEIGHT: f32 = 1080.0;
pub fn effective_div(setting: u32, height: f32) -> u32 {
if setting <= 1 {
return 1;
// Pixel look: the world is rendered `rows` rows high on every screen, whatever its resolution
// and pixel density; the width follows the aspect ratio. Returns the target size and the scale
// to the screen (None: the screen has hardly more rows, render at full resolution).
pub fn pixel_target(rows: u32, pw: f32, ph: f32) -> Option<(u32, u32, f32)> {
if rows == 0 || ph < rows as f32 * 1.2 {
return None;
}
((setting as f32 * height / RENDER_REF_HEIGHT).round() as u32).max(1)
let scale = ph / rows as f32;
Some((((pw / scale).round() as u32).max(1), rows, scale))
}
fn rgba(u: u32) -> Color {
@@ -1182,7 +1186,7 @@ fn chunk_sig(map: &Map, cx: i32, cz: i32) -> u64 {
impl R3d {
pub fn new() -> R3d {
R3d { lit: lit_material(), glow: glow_material(), chunks: Vec::new(), ncx: 0, ncz: 0, outlines: true, wall_sub: 3, max_lights: MAX_LIGHTS, render_div: 1, target: None, lava: Vec::new() }
R3d { lit: lit_material(), glow: glow_material(), chunks: Vec::new(), ncx: 0, ncz: 0, outlines: true, wall_sub: 3, max_lights: MAX_LIGHTS, pixel_rows: 360, up: None, target: None, lava: Vec::new() }
}
// Changing the wall detail rebuilds all chunks
@@ -1422,13 +1426,13 @@ impl R3d {
let m = cam.matrix();
self.update_chunks(g, gfx, &m);
EYE.with(|e| e.set(cam.eye_dir()));
// pixel look: render the world at 1/div resolution and scale it up by exactly div
// pixel look: the world is rendered at a fixed number of rows and scaled up to the screen
let d = crate::ui::dpr();
let (pw, ph) = ((screen_width() * d).round(), (screen_height() * d).round());
let div = effective_div(self.render_div, ph);
let pix = pixel_target(self.pixel_rows, pw, ph);
let div = if pix.is_some() { 2 } else { 1 }; // > 1: a target is used
let mut cam3 = cam.camera();
if div > 1 {
let (tw, th) = ((pw / div as f32).ceil() as u32, (ph / div as f32).ceil() as u32);
if let Some((tw, th, _)) = pix {
if self.target.as_ref().map_or(true, |(_, w, h)| *w != tw || *h != th) {
let rt = render_target_ex(tw, th, RenderTargetParams { sample_count: 1, depth: true });
rt.texture.set_filter(FilterMode::Nearest);
@@ -1439,6 +1443,7 @@ impl R3d {
cam3.aspect = Some(*tw as f32 / *th as f32);
} else {
self.target = None;
self.up = None;
}
set_camera(&cam3);
if div > 1 {
@@ -1523,9 +1528,31 @@ impl R3d {
draw_parts_glow(g);
gl_use_default_material();
crate::ui::begin_ui();
if let (true, Some((rt, tw, th))) = (div > 1, self.target.as_ref()) {
let size = vec2((*tw * div) as f32, (*th * div) as f32);
if let (Some((_, _, scale)), Some((rt, tw, th))) = (pix, self.target.as_ref()) {
let whole = (scale - scale.round()).abs() < 0.01;
if whole {
// a whole factor: every world pixel becomes the same square of screen pixels
let size = vec2(*tw as f32 * scale.round(), *th as f32 * scale.round());
draw_texture_ex(&rt.texture, 0.0, 0.0, WHITE, DrawTextureParams { dest_size: Some(size), flip_y: true, ..Default::default() });
} else {
// sharp scaling: up by the next whole factor (nearest), then smoothly down to the
// screen - all pixels the same size, the edges crisp
let n = scale.ceil() as u32;
let (uw, uh) = (*tw * n, *th * n);
if self.up.as_ref().map_or(true, |(_, w, h)| *w != uw || *h != uh) {
let up = render_target_ex(uw, uh, RenderTargetParams { sample_count: 1, depth: false });
up.texture.set_filter(FilterMode::Linear);
self.up = Some((up, uw, uh));
}
let (up, _, _) = self.up.as_ref().unwrap();
let mut c = Camera2D::from_display_rect(Rect::new(0.0, 0.0, uw as f32, uh as f32));
c.render_target = Some(up.clone());
set_camera(&c);
clear_background(Color::from_rgba(0, 0, 0, 255));
draw_texture_ex(&rt.texture, 0.0, 0.0, WHITE, DrawTextureParams { dest_size: Some(vec2(uw as f32, uh as f32)), flip_y: true, ..Default::default() });
crate::ui::begin_ui();
draw_texture_ex(&up.texture, 0.0, 0.0, WHITE, DrawTextureParams { dest_size: Some(vec2(*tw as f32 * scale, *th as f32 * scale)), flip_y: true, ..Default::default() });
}
}
// ---- 2D overlay in device pixels: health bars, sleep marks, levels, gate HP
@@ -3228,12 +3255,14 @@ mod tests {
#[test]
fn pixel_size_follows_the_window() {
assert_eq!(effective_div(1, 400.0), 1);
assert_eq!(effective_div(4, 1080.0), 4);
assert_eq!(effective_div(4, 540.0), 2);
assert_eq!(effective_div(4, 2160.0), 8);
assert_eq!(effective_div(16, 200.0), 3);
assert_eq!(effective_div(2, 100.0), 1);
// the same rows on every screen; the width follows the aspect ratio
assert_eq!(pixel_target(360, 1920.0, 1080.0), Some((640, 360, 3.0)));
assert_eq!(pixel_target(360, 3840.0, 2160.0), Some((640, 360, 6.0)));
let (w, h, s) = pixel_target(360, 1366.0, 768.0).unwrap();
assert_eq!((w, h), (640, 360));
assert!((s - 2.1333).abs() < 0.01);
// a screen hardly bigger than the pixel look renders at full resolution
assert_eq!(pixel_target(360, 600.0, 400.0), None);
}
#[test]
+3 -1
View File
@@ -148,7 +148,8 @@ pub struct Settings {
pub wall_detail: u8, // 1 low, 2 medium, 3 high
pub max_lights: u8, // 8, 16 or 24
pub ui_factor: u8, // interface size: 0 = automatic, 1..4 = fixed integer factor
pub render_scale: u8, // 3D world resolution divisor: 1 = full, 2..16 = pixel look
pub render_scale: u8, // old pixel-look divisor (no longer used, kept for old settings files)
pub pixel_rows: u16, // pixel look: rows of the world on every screen (270, 360, 480)
pub touch_ui: u8, // touch interface: 0 = automatic (after the first touch), 1 = on, 2 = off
pub debug_fps: bool, // show FPS and rendering backend
pub debug_frame_time: bool, // show the frame-time line and graph
@@ -186,6 +187,7 @@ impl Default for Settings {
max_lights: 24,
ui_factor: 0,
render_scale: 1,
pixel_rows: 360,
touch_ui: 0,
debug_fps: false,
debug_frame_time: false,