@@ -2735,7 +2735,15 @@ impl Desktop {
27352735 // Initialize double buffering
27362736 crate::serial_println!("[Desktop] init_double_buffer...");
27372737 framebuffer::init_double_buffer();
2738- framebuffer::set_double_buffer_mode(true);
2738+ // Verify backbuffer was actually allocated — if it failed (OOM),
2739+ // fall back to direct framebuffer mode to avoid invisible desktop
2740+ if framebuffer::get_backbuffer_ptr().is_some() {
2741+ framebuffer::set_double_buffer_mode(true);
2742+ crate::serial_println!("[Desktop] double buffer: OK");
2743+ } else {
2744+ framebuffer::set_double_buffer_mode(false);
2745+ crate::serial_println!("[Desktop] WARNING: backbuffer alloc failed, using direct FB mode");
2746+ }
27392747
27402748 // Initialize background cache for fast redraws
27412749 crate::serial_println!("[Desktop] init_background_cache...");
@@ -3014,22 +3022,28 @@ struct AppConfig {
30143022 // Estimate CPU speed via TSC (in MHz)
30153023 let tsc_mhz = crate::cpu::tsc_frequency() / 1_000_000;
30163024
3017- // Compute a capability score:
3018- // RAM contribution: 1 point per 128 MB
3019- // CPU contribution: 1 point per 500 MHz
3020- // Core contribution: 1 point per core
3025+ // Compute a capability score weighted toward CPU throughput.
3026+ // Full tier requires real rendering power — RAM alone isn't enough.
3027+ // RAM contribution: 1 point per 256 MB (capped at 8 — diminishing returns)
3028+ // CPU contribution: 1 point per 400 MHz (weighted more than RAM)
3029+ // Core contribution: 2 points per core (parallelism matters for rendering)
30213030 // Resolution penalty: -1 per million pixels above 1M
3022- let ram_score = (phys_mb / 128 ) as i64;
3023- let cpu_score = if tsc_mhz > 0 { (tsc_mhz / 500 ) as i64 } else { 2 }; // assume moderate if unknown
3024- let core_score = cpus as i64;
3031+ let ram_score = (( phys_mb / 256 ) as i64).min(8) ;
3032+ let cpu_score = if tsc_mhz > 0 { (tsc_mhz / 400 ) as i64 } else { 2 };
3033+ let core_score = ( cpus as i64) * 2 ;
30253034 let res_penalty = ((pixels as i64) - 1_000_000) / 1_000_000;
30263035 let score = ram_score + cpu_score + core_score - res_penalty;
30273036
3037+ // Hard cap: old dual-core CPUs (< 3 GHz) cannot sustain Full tier.
3038+ // The 4-layer matrix rain + visualizer + drone swarm need at least
3039+ // ~3 GHz × 4 cores or equivalent throughput to hit stable 30 FPS.
3040+ let cpu_limited = tsc_mhz > 0 && tsc_mhz < 3000 && cpus <= 2;
3041+
30283042 let tier = if phys_mb < 128 || heap_free_mb < 8 {
30293043 DesktopTier::CliOnly
3030- } else if score <= 3 || phys_mb < 256 {
3044+ } else if score <= 4 || phys_mb < 256 {
30313045 DesktopTier::Minimal
3032- } else if score <= 6 || phys_mb < 512 {
3046+ } else if score <= 8 || phys_mb < 512 || cpu_limited {
30333047 DesktopTier::Standard
30343048 } else {
30353049 DesktopTier::Full
@@ -3041,8 +3055,8 @@ struct AppConfig {
30413055 self.fps_high_count = 0;
30423056
30433057 crate::serial_println!(
3044- "[Desktop] Tier={:?} (score={}, RAM={}MB, heap={}MB, CPUs={}, TSC={}MHz, {}x{})",
3045- tier, score, phys_mb, heap_free_mb, cpus, tsc_mhz, self.width, self.height
3058+ "[Desktop] Tier={:?} (score={}, RAM={}MB, heap={}MB, CPUs={}, TSC={}MHz, {}x{}, cpu_limited={} )",
3059+ tier, score, phys_mb, heap_free_mb, cpus, tsc_mhz, self.width, self.height, cpu_limited
30463060 );
30473061 }
30483062
@@ -7201,6 +7215,33 @@ struct AppConfig {
72017215 pub fn draw(&mut self) {
72027216 self.frame_count += 1;
72037217
7218+ // ── Safe startup: first 3 frames do MINIMAL rendering ──
7219+ // Eliminates all heavy code (matrix rain, audio analysis, game ticks,
7220+ // animations) from the initial frames. If this fixes the T61 freeze,
7221+ // the bug is in one of the skipped subsystems.
7222+ if self.frame_count <= 3 {
7223+ crate::serial_println!("[Desktop] safe frame {} / 3", self.frame_count);
7224+ // Get mouse state for cursor
7225+ let mouse = crate::mouse::get_state();
7226+
7227+ framebuffer::clear_backbuffer(0xFF010200);
7228+ framebuffer::begin_frame();
7229+ // Just draw icons + taskbar + cursor — no rain, no audio, no effects
7230+ self.draw_desktop_icons();
7231+ self.draw_taskbar();
7232+ self.draw_cursor();
7233+ // Update tracking state
7234+ self.last_cursor_x = mouse.x;
7235+ self.last_cursor_y = mouse.y;
7236+ self.last_window_count = self.windows.len();
7237+ self.last_start_menu_open = self.start_menu_open;
7238+ self.last_context_menu_visible = self.context_menu.visible;
7239+ framebuffer::end_frame();
7240+ framebuffer::swap_buffers();
7241+ crate::serial_println!("[Desktop] safe frame {} done", self.frame_count);
7242+ return;
7243+ }
7244+
72047245 // ── Auto-adjust tier based on sustained FPS ──
72057246 self.auto_adjust_tier();
72067247
@@ -14803,7 +14844,33 @@ pub fn run() {
1480314844
1480414845 crate::serial_println!("[GUI] Starting desktop environment...");
1480514846 crate::serial_println!("[GUI] Hotkeys: Alt+Tab, Win+Arrows, Alt+F4, Win=Start");
14806- crate::serial_println!("[GUI] Target: ~60 FPS (16.6ms) with HLT-based frame limiting");
14847+ crate::serial_println!("[GUI] Target: ~60 FPS (16.6ms) with spin-loop frame limiting");
14848+
14849+ // ── Visual proof-of-life: write directly to framebuffer (bypass backbuffer) ──
14850+ // If this shows on screen but the desktop doesn't, the issue is in drawing/swap.
14851+ // If this doesn't show, the code never reaches run().
14852+ {
14853+ let fb = crate::framebuffer::get_framebuffer();
14854+ let w = crate::framebuffer::FB_WIDTH.load(core::sync::atomic::Ordering::Relaxed) as usize;
14855+ let pitch = crate::framebuffer::FB_PITCH.load(core::sync::atomic::Ordering::Relaxed) as usize;
14856+ if !fb.is_null() && w > 0 && pitch > 0 {
14857+ // Draw a bright green bar at top-left corner (16×4 pixels)
14858+ for y in 0..4usize {
14859+ for x in 0..16usize {
14860+ if x < w {
14861+ unsafe {
14862+ let dst = (fb as *mut u8).add(y * pitch).add(x * 4) as *mut u32;
14863+ dst.write_volatile(0xFF00FF00); // bright green
14864+ }
14865+ }
14866+ }
14867+ }
14868+ crate::serial_println!("[GUI] Proof-of-life pixels written to framebuffer");
14869+ }
14870+ }
14871+
14872+ // Frame counter for safe startup (skip heavy work on first few frames)
14873+ let mut loop_frame: u32 = 0;
1480714874
1480814875 loop {
1480914876 // Check exit flag
@@ -15303,7 +15370,7 @@ pub fn run() {
1530315370 }
1530415371
1530515372 // ═══════════════════════════════════════════════════════════════
15306- // VSync frame pacing (adaptive HLT sleep + spin for precision )
15373+ // VSync frame pacing (spin-loop sleep with bail-out )
1530715374 // ═══════════════════════════════════════════════════════════════
1530815375 let render_time_us = engine::now_us().saturating_sub(frame_start);
1530915376 // Log FPS to serial every 120 frames for performance monitoring
@@ -15317,6 +15384,7 @@ pub fn run() {
1531715384 }
1531815385 }
1531915386 crate::gui::vsync::frame_end(frame_start);
15387+ loop_frame = loop_frame.saturating_add(1);
1532015388 }
1532115389
1532215390 // ═══════════════════════════════════════════════════════════════
0 commit comments