From 13169698cfbb824f0fe5ef3b0921ae08dd981252 Mon Sep 17 00:00:00 2001 From: bQUARKz Date: Tue, 13 Jan 2026 06:30:32 +0000 Subject: [PATCH] tile, layers and bank implementation --- crates/core/src/color.rs | 32 ----- crates/core/src/lib.rs | 4 +- crates/core/src/machine.rs | 66 ++++++--- .../core/src/{c_button.rs => model/button.rs} | 6 +- crates/core/src/model/color.rs | 59 ++++++++ crates/core/src/model/mod.rs | 11 ++ crates/core/src/model/tile.rs | 7 + crates/core/src/model/tile_bank.rs | 45 ++++++ crates/core/src/model/tile_layer.rs | 109 ++++++++++++++ crates/core/src/peripherals/gfx.rs | 133 ++++++++++++++---- crates/core/src/peripherals/pad.rs | 26 ++-- crates/core/src/peripherals/touch.rs | 4 +- docs/topics/chapter-4.md | 10 +- 13 files changed, 406 insertions(+), 106 deletions(-) delete mode 100644 crates/core/src/color.rs rename crates/core/src/{c_button.rs => model/button.rs} (82%) create mode 100644 crates/core/src/model/color.rs create mode 100644 crates/core/src/model/mod.rs create mode 100644 crates/core/src/model/tile.rs create mode 100644 crates/core/src/model/tile_bank.rs create mode 100644 crates/core/src/model/tile_layer.rs diff --git a/crates/core/src/color.rs b/crates/core/src/color.rs deleted file mode 100644 index c09a526a..00000000 --- a/crates/core/src/color.rs +++ /dev/null @@ -1,32 +0,0 @@ -/// Cor simples em RGB565 (0bRRRRRGGGGGGBBBBB). -/// - 5 bits Red -/// - 6 bits Green -/// - 5 bits Blue -/// -/// Não há canal alpha. -/// Transparência é tratada via Color Key ou Blend Mode. -#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] -pub struct Color(pub u16); - -impl Color { - /// Cria uma cor RGB565 a partir de componentes 8-bit. - pub const fn rgb(r: u8, g: u8, b: u8) -> Self { - let r5 = (r as u16 >> 3) & 0x1F; - let g6 = (g as u16 >> 2) & 0x3F; - let b5 = (b as u16 >> 3) & 0x1F; - - Self((r5 << 11) | (g6 << 5) | b5) - } - - pub const fn gray_scale(c: u8) -> Self { - Self::rgb(c, c, c) - } - - pub const fn from_raw(raw: u16) -> Self { - Self(raw) - } - - pub const fn raw(self) -> u16 { - self.0 - } -} diff --git a/crates/core/src/lib.rs b/crates/core/src/lib.rs index e3050d0d..08a0e5c2 100644 --- a/crates/core/src/lib.rs +++ b/crates/core/src/lib.rs @@ -1,7 +1,5 @@ pub mod machine; pub mod peripherals; -mod color; -mod c_button; +mod model; pub use machine::Machine; -pub use color::Color; diff --git a/crates/core/src/machine.rs b/crates/core/src/machine.rs index ef7a9026..10cfc656 100644 --- a/crates/core/src/machine.rs +++ b/crates/core/src/machine.rs @@ -1,5 +1,5 @@ -use crate::peripherals::{BlendMode, Gfx, InputSignals, Pad, Touch}; -use crate::Color; +use crate::model::Color; +use crate::peripherals::{Gfx, InputSignals, Pad, Touch}; /// PROMETEU "hardware lógico" (v0). /// O Host alimenta INPUT SIGNALS e chama `step_frame()` em 60Hz. @@ -44,20 +44,50 @@ impl Machine { /// Lógica do frame (demo hardcoded por enquanto). pub fn tick(&mut self) { - self.gfx.clear(Color::rgb(0x10, 0x10, 0x10)); + // self.gfx.clear(Color::rgb(0x10, 0x10, 0x10)); + // + // let (x, y) = self.demo_pos(); + // + // let color = if self.pad.a.down || self.touch.f.down { + // Color::rgb(0x00, 0xFF, 0x00) + // } else { + // Color::rgb(0xFF, 0x40, 0x40) + // }; + // + // // game + // self.gfx.fill_rect(x, y, 20, 20, color); + // // smoke + // self.gfx.fill_rect_blend(140, 0, 40, 180, Color::gray_scale(0x88), BlendMode::Half) - let (x, y) = self.demo_pos(); - let color = if self.pad.a.down || self.touch.f.down { - Color::rgb(0x00, 0xFF, 0x00) - } else { - Color::rgb(0xFF, 0x40, 0x40) - }; + // Limpa a tela com um azul escuro "estilo console" + self.gfx.clear(Color::rgb(0x20, 0x20, 0x40)); - // game - self.gfx.fill_rect(x, y, 20, 20, color); - // smoke - self.gfx.fill_rect_blend(140, 0, 40, 180, Color::gray_scale(0x88), BlendMode::Half) + // Simula o carregamento de um banco se estiver vazio (apenas para teste) + if self.gfx.banks[0].is_none() { + let mut test_bank = crate::model::TileBank::new(crate::model::TileSize::Size8, 128, 128); + // Pinta o primeiro tile (ID 1) de Branco para teste + // Ele começa no pixel (8, 0) pois o ID 0 é o primeiro (0,0) + let tile_size = 8; + let start_x = 8; // ID 1 está na segunda posição da primeira linha + let start_y = 0; + + for y in 0..tile_size { + for x in 0..tile_size { + let px = start_x + x; + let py = start_y + y; + let idx = py * 128 + px; + test_bank.pixels[idx] = Color::WHITE; + } + } + self.gfx.banks[0] = Some(test_bank); + } + + // Coloca o Tile ID 1 no mapa do HUD (canto superior esquerdo) + self.gfx.hud.map.set_tile(0, 0, crate::model::Tile { id: 1, ..Default::default() }); + + // Executa o pipeline gráfico + self.gfx.render_all(); } /// Final do frame: troca buffers. @@ -65,9 +95,9 @@ impl Machine { self.gfx.present(); } - fn demo_pos(&self) -> (i32, i32) { - let x = (self.frame_index % 300) as i32; - let y = (self.pad.a.hold_frames % 160) as i32; - (x, y) - } + // fn demo_pos(&self) -> (i32, i32) { + // let x = (self.frame_index % 300) as i32; + // let y = (self.pad.a.hold_frames % 160) as i32; + // (x, y) + // } } diff --git a/crates/core/src/c_button.rs b/crates/core/src/model/button.rs similarity index 82% rename from crates/core/src/c_button.rs rename to crates/core/src/model/button.rs index edb89c03..00809d9a 100644 --- a/crates/core/src/c_button.rs +++ b/crates/core/src/model/button.rs @@ -1,21 +1,19 @@ #[derive(Default, Clone, Copy, Debug)] -pub struct CButton { +pub struct Button { pub pressed: bool, pub released: bool, pub down: bool, pub hold_frames: u32, } -impl CButton { +impl Button { pub fn begin_frame(&mut self, is_down_now: bool) { let was_down = self.down; self.down = is_down_now; - // Detecta transições self.pressed = !was_down && self.down; self.released = was_down && !self.down; - // Atualiza contador de frames if self.down { self.hold_frames = self.hold_frames.saturating_add(1); } else { diff --git a/crates/core/src/model/color.rs b/crates/core/src/model/color.rs new file mode 100644 index 00000000..5009807f --- /dev/null +++ b/crates/core/src/model/color.rs @@ -0,0 +1,59 @@ +/// Cor simples em RGB565 (0bRRRRRGGGGGGBBBBB). +/// - 5 bits Red +/// - 6 bits Green +/// - 5 bits Blue +/// +/// Não há canal alpha. +/// Transparência é tratada via Color Key ou Blend Mode. +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] +pub struct Color(pub u16); + +impl Color { + pub const BLACK: Self = Self::rgb(0, 0, 0); // 0x0000 + pub const WHITE: Self = Self::rgb(255, 255, 255); // 0xFFFF + pub const RED: Self = Self::rgb(255, 0, 0); // 0xF800 + pub const GREEN: Self = Self::rgb(0, 255, 0); // 0x07E0 + pub const BLUE: Self = Self::rgb(0, 0, 255); // 0x001F + pub const YELLOW: Self = Self::rgb(255, 255, 0); // 0xFFE0 + pub const CYAN: Self = Self::rgb(0, 255, 255); // 0x07FF + pub const MAGENTA: Self = Self::rgb(255, 0, 255); // 0xF81F + pub const COLOR_KEY: Self = Self::MAGENTA; + + /// Extrai canais já na faixa nativa do RGB565: + /// R: 0..31, G: 0..63, B: 0..31 + #[inline(always)] + pub const fn unpack_to_native(px: u16) -> (u8, u8, u8) { + let r = ((px >> 11) & 0x1F) as u8; + let g = ((px >> 5) & 0x3F) as u8; + let b = (px & 0x1F) as u8; + (r, g, b) + } + + /// Incorpora canais já na faixa nativa do RGB565 em um pixel: + /// R: 0..31, G: 0..63, B: 0..31 + #[inline(always)] + pub const fn pack_from_native(r: u8, g: u8, b: u8) -> u16 { + ((r as u16 & 0x1F) << 11) | ((g as u16 & 0x3F) << 5) | (b as u16 & 0x1F) + } + + /// Cria uma cor RGB565 a partir de componentes 8-bit (0..255). + pub const fn rgb(r: u8, g: u8, b: u8) -> Self { + let r5 = (r as u16 >> 3) & 0x1F; + let g6 = (g as u16 >> 2) & 0x3F; + let b5 = (b as u16 >> 3) & 0x1F; + + Self((r5 << 11) | (g6 << 5) | b5) + } + + pub const fn gray_scale(c: u8) -> Self { + Self::rgb(c, c, c) + } + + pub const fn from_raw(raw: u16) -> Self { + Self(raw) + } + + pub const fn raw(self) -> u16 { + self.0 + } +} diff --git a/crates/core/src/model/mod.rs b/crates/core/src/model/mod.rs new file mode 100644 index 00000000..ee0d4cc3 --- /dev/null +++ b/crates/core/src/model/mod.rs @@ -0,0 +1,11 @@ +mod color; +mod button; +mod tile; +mod tile_layer; +mod tile_bank; + +pub use button::Button; +pub use color::Color; +pub use tile::Tile; +pub use tile_bank::{TileBank, TileSize}; +pub use tile_layer::{HudTileLayer, ScrollableTileLayer, TileMap}; diff --git a/crates/core/src/model/tile.rs b/crates/core/src/model/tile.rs new file mode 100644 index 00000000..337116bd --- /dev/null +++ b/crates/core/src/model/tile.rs @@ -0,0 +1,7 @@ +#[derive(Clone, Copy, Debug, Default)] +pub struct Tile { + pub id: u16, + pub flip_x: bool, + pub flip_y: bool, + pub palette_id: u8, +} \ No newline at end of file diff --git a/crates/core/src/model/tile_bank.rs b/crates/core/src/model/tile_bank.rs new file mode 100644 index 00000000..372173fc --- /dev/null +++ b/crates/core/src/model/tile_bank.rs @@ -0,0 +1,45 @@ +use crate::model::Color; + +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum TileSize { + Size8 = 8, + Size16 = 16, + Size32 = 32, +} + +pub struct TileBank { + pub tile_size: TileSize, + pub width: usize, // em pixels + pub height: usize, // em pixels + pub pixels: Vec, +} + +impl TileBank { + pub fn new(tile_size: TileSize, width: usize, height: usize) -> Self { + Self { + tile_size, + width, + height, + pixels: vec![Color::BLACK; width * height], + } + } + + /// Retorna a cor de um pixel específico dentro de um tile. + /// tile_id: o índice do tile no banco + /// local_x, local_y: a posição do pixel dentro do tile (0 até tile_size-1) + pub fn get_pixel(&self, tile_id: u16, local_x: usize, local_y: usize) -> Color { + let tile_size = self.tile_size as usize; + let tiles_per_row = self.width / tile_size; + let tile_x = (tile_id as usize % tiles_per_row) * tile_size; + let tile_y = (tile_id as usize / tiles_per_row) * tile_size; + + let pixel_x = tile_x + local_x; + let pixel_y = tile_y + local_y; + + if pixel_x < self.width && pixel_y < self.height { + self.pixels[pixel_y * self.width + pixel_x] + } else { + Color::BLACK + } + } +} \ No newline at end of file diff --git a/crates/core/src/model/tile_layer.rs b/crates/core/src/model/tile_layer.rs new file mode 100644 index 00000000..e0e0e8b7 --- /dev/null +++ b/crates/core/src/model/tile_layer.rs @@ -0,0 +1,109 @@ +use crate::model::Tile; +use crate::model::tile_bank::TileSize; +use crate::model::TileSize::Size8; + +pub struct TileMap { + pub width: usize, + pub height: usize, + pub tiles: Vec, +} + +impl TileMap { + fn create(width: usize, height: usize) -> Self { + Self { + width, + height, + tiles: vec![Tile::default(); width * height], + } + } + + pub fn set_tile(&mut self, x: usize, y: usize, tile: Tile) { + if x < self.width && y < self.height { + self.tiles[y * self.width + x] = tile; + } + } +} + + +pub struct TileLayer { + pub bank_id: u8, + pub tile_size: TileSize, + pub map: TileMap, +} + +impl TileLayer { + fn create(width: usize, height: usize, tile_size: TileSize) -> Self { + Self { + bank_id: 0, + tile_size: tile_size, + map: TileMap::create(width, height), + } + } +} + +impl std::ops::Deref for TileLayer { + type Target = TileMap; + fn deref(&self) -> &Self::Target { + &self.map + } +} + +impl std::ops::DerefMut for TileLayer { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.map + } +} + +pub struct ScrollableTileLayer { + pub layer: TileLayer, + pub scroll_x: i32, + pub scroll_y: i32, +} + +impl ScrollableTileLayer { + pub fn new(width: usize, height: usize, tile_size: TileSize) -> Self { + Self { + layer: TileLayer::create(width, height, tile_size), + scroll_x: 0, + scroll_y: 0, + } + } +} + +impl std::ops::Deref for ScrollableTileLayer { + type Target = TileLayer; + fn deref(&self) -> &Self::Target { + &self.layer + } +} + +impl std::ops::DerefMut for ScrollableTileLayer { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.layer + } +} + +pub struct HudTileLayer { + pub layer: TileLayer, +} + +impl HudTileLayer { + pub fn new(width: usize, height: usize) -> Self { + Self { + layer: TileLayer::create(width, height, Size8), + } + } +} + +impl std::ops::Deref for HudTileLayer { + type Target = TileLayer; + fn deref(&self) -> &Self::Target { + &self.layer + } +} + +impl std::ops::DerefMut for HudTileLayer { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.layer + } +} diff --git a/crates/core/src/peripherals/gfx.rs b/crates/core/src/peripherals/gfx.rs index 05436838..f37a31c4 100644 --- a/crates/core/src/peripherals/gfx.rs +++ b/crates/core/src/peripherals/gfx.rs @@ -1,4 +1,4 @@ -use crate::color::Color; +use crate::model::{Color, HudTileLayer, ScrollableTileLayer, TileBank, TileMap, TileSize}; #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] pub enum BlendMode { @@ -20,16 +20,30 @@ pub struct Gfx { h: usize, front: Vec, back: Vec, + + pub layers: [ScrollableTileLayer; 4], + pub hud: HudTileLayer, + pub banks: [Option; 16], } -impl Gfx { +impl Gfx { pub fn new(w: usize, h: usize) -> Self { + const EMPTY_BANK: Option = None; + let len = w * h; Self { w, h, front: vec![0; len], back: vec![0; len], + layers: [ + ScrollableTileLayer::new(64, 64, TileSize::Size16), + ScrollableTileLayer::new(64, 64, TileSize::Size16), + ScrollableTileLayer::new(64, 64, TileSize::Size16), + ScrollableTileLayer::new(64, 64, TileSize::Size16), + ], + hud: HudTileLayer::new(64, 32), + banks: [EMPTY_BANK; 16], } } @@ -85,6 +99,82 @@ impl Gfx { pub fn present(&mut self) { std::mem::swap(&mut self.front, &mut self.back); } + + /// Pipeline principal de renderização do frame. + /// Segue a ordem de prioridade do manual (Capítulo 4.11). + pub fn render_all(&mut self) { + // 1. Layers de jogo (0 a 3) + for i in 0..self.layers.len() { + self.render_layer(i); + } + + // 2. Sprites (Ainda vamos implementar) + + // 3. HUD (Sempre por cima) + self.render_hud(); + } + + pub fn render_layer(&mut self, layer_idx: usize) { + if layer_idx >= self.layers.len() { return; } + + let bank_id = self.layers[layer_idx].bank_id as usize; + let scroll_x = self.layers[layer_idx].scroll_x; + let scroll_y = self.layers[layer_idx].scroll_y; + + let bank = match self.banks.get(bank_id) { + Some(Some(b)) => b, + _ => return, + }; + + Self::draw_tile_map(&mut self.back, self.w, self.h, &self.layers[layer_idx].map, bank, scroll_x, scroll_y); + } + + /// Renderiza o HUD (sem scroll). + pub fn render_hud(&mut self) { + let bank_id = self.hud.bank_id as usize; + let bank = match self.banks.get(bank_id) { + Some(Some(b)) => b, + _ => return, + }; + + Self::draw_tile_map(&mut self.back, self.w, self.h, &self.hud.map, bank, 0, 0); + } + + fn draw_tile_map( + back: &mut [u16], + screen_w: usize, + screen_h: usize, + map: &TileMap, + bank: &TileBank, + scroll_x: i32, + scroll_y: i32 + ) { + let size = bank.tile_size as usize; + + for screen_y in 0..screen_h { + for screen_x in 0..screen_w { + let world_x = screen_x as i32 + scroll_x; + let world_y = screen_y as i32 + scroll_y; + + let tile_x = (world_x / size as i32) as usize; + let tile_y = (world_y / size as i32) as usize; + + if tile_x >= map.width || tile_y >= map.height { continue; } + + let tile = map.tiles[tile_y * map.width + tile_x]; + if tile.id == 0 { continue; } + + let local_x = (world_x % size as i32) as usize; + let local_y = (world_y % size as i32) as usize; + + let color = bank.get_pixel(tile.id, local_x, local_y); + if color == Color::COLOR_KEY { continue; } + + let idx = (screen_y * screen_w) + screen_x; + back[idx] = color.0; + } + } + } } /// Faz blend em RGB565 por canal com saturação. @@ -94,60 +184,45 @@ fn blend_rgb565(dst: u16, src: u16, mode: BlendMode) -> u16 { BlendMode::None => src, BlendMode::Half => { - let (dr, dg, db) = unpack_rgb565(dst); - let (sr, sg, sb) = unpack_rgb565(src); + let (dr, dg, db) = Color::unpack_to_native(dst); + let (sr, sg, sb) = Color::unpack_to_native(src); let r = ((dr as u16 + sr as u16) >> 1) as u8; let g = ((dg as u16 + sg as u16) >> 1) as u8; let b = ((db as u16 + sb as u16) >> 1) as u8; - pack_rgb565(r, g, b) + Color::pack_from_native(r, g, b) } BlendMode::HalfPlus => { - let (dr, dg, db) = unpack_rgb565(dst); - let (sr, sg, sb) = unpack_rgb565(src); + let (dr, dg, db) = Color::unpack_to_native(dst); + let (sr, sg, sb) = Color::unpack_to_native(src); let r = (dr as u16 + ((sr as u16) >> 1)).min(31) as u8; let g = (dg as u16 + ((sg as u16) >> 1)).min(63) as u8; let b = (db as u16 + ((sb as u16) >> 1)).min(31) as u8; - pack_rgb565(r, g, b) + Color::pack_from_native(r, g, b) } BlendMode::HalfMinus => { - let (dr, dg, db) = unpack_rgb565(dst); - let (sr, sg, sb) = unpack_rgb565(src); + let (dr, dg, db) = Color::unpack_to_native(dst); + let (sr, sg, sb) = Color::unpack_to_native(src); let r = (dr as i16 - ((sr as i16) >> 1)).max(0) as u8; let g = (dg as i16 - ((sg as i16) >> 1)).max(0) as u8; let b = (db as i16 - ((sb as i16) >> 1)).max(0) as u8; - pack_rgb565(r, g, b) + Color::pack_from_native(r, g, b) } BlendMode::Full => { - let (dr, dg, db) = unpack_rgb565(dst); - let (sr, sg, sb) = unpack_rgb565(src); + let (dr, dg, db) = Color::unpack_to_native(dst); + let (sr, sg, sb) = Color::unpack_to_native(src); let r = (dr as u16 + sr as u16).min(31) as u8; let g = (dg as u16 + sg as u16).min(63) as u8; let b = (db as u16 + sb as u16).min(31) as u8; - pack_rgb565(r, g, b) + Color::pack_from_native(r, g, b) } } } - -/// Extrai canais já na faixa nativa do RGB565: -/// R: 0..31, G: 0..63, B: 0..31 -#[inline(always)] -fn unpack_rgb565(px: u16) -> (u8, u8, u8) { - let r = ((px >> 11) & 0x1F) as u8; - let g = ((px >> 5) & 0x3F) as u8; - let b = (px & 0x1F) as u8; - (r, g, b) -} - -#[inline(always)] -fn pack_rgb565(r: u8, g: u8, b: u8) -> u16 { - ((r as u16 & 0x1F) << 11) | ((g as u16 & 0x3F) << 5) | (b as u16 & 0x1F) -} diff --git a/crates/core/src/peripherals/pad.rs b/crates/core/src/peripherals/pad.rs index a2d297ee..ca047f7d 100644 --- a/crates/core/src/peripherals/pad.rs +++ b/crates/core/src/peripherals/pad.rs @@ -1,22 +1,22 @@ -use crate::c_button::CButton; +use crate::model::Button; use crate::peripherals::input_signal::InputSignals; #[derive(Default, Clone, Copy, Debug)] pub struct Pad { - pub up: CButton, - pub down: CButton, - pub left: CButton, - pub right: CButton, + pub up: Button, + pub down: Button, + pub left: Button, + pub right: Button, - pub a: CButton, // ps: square - pub d: CButton, // ps: circle - pub w: CButton, // ps: triangle - pub s: CButton, // ps: cross - pub q: CButton, // ps: R - pub e: CButton, // ps: L + pub a: Button, // ps: square + pub d: Button, // ps: circle + pub w: Button, // ps: triangle + pub s: Button, // ps: cross + pub q: Button, // ps: R + pub e: Button, // ps: L - pub start: CButton, - pub select: CButton, + pub start: Button, + pub select: Button, } impl Pad { diff --git a/crates/core/src/peripherals/touch.rs b/crates/core/src/peripherals/touch.rs index 54c1678f..2b4d7076 100644 --- a/crates/core/src/peripherals/touch.rs +++ b/crates/core/src/peripherals/touch.rs @@ -1,9 +1,9 @@ -use crate::c_button::CButton; +use crate::model::Button; use crate::peripherals::input_signal::InputSignals; #[derive(Default, Clone, Copy, Debug)] pub struct Touch { - pub f: CButton, + pub f: Button, pub x: i32, pub y: i32, } diff --git a/docs/topics/chapter-4.md b/docs/topics/chapter-4.md index f8d10b00..3fb1a2d3 100644 --- a/docs/topics/chapter-4.md +++ b/docs/topics/chapter-4.md @@ -72,7 +72,7 @@ Isso garante: O mundo gráfico é composto por: - Até **16 Tile Banks** -- **4 Game Layers** (scrolláveis) +- **4 Tile Layers** (scrolláveis) - **1 HUD Layer** (fixa, sempre por cima) - Sprites com prioridade entre layers @@ -90,7 +90,7 @@ O mundo gráfico é composto por: ### 4.2 Layers - Existem: - - 4 Game Layers + - 4 Tile Layers - 1 HUD Layer - Cada layer aponta para **um único bank** - Sprites podem usar **qualquer bank** @@ -296,7 +296,7 @@ Por design: O PROMETEU suporta **fade gradual** como um PostFX especial, com dois controles independentes: -- **Scene Fade**: afeta toda a cena (Game Layers 0–3 + Sprites) +- **Scene Fade**: afeta toda a cena (Tile Layers 0–3 + Sprites) - **HUD Fade**: afeta apenas o HUD Layer (sempre composto por último) O fade é implementado sem alpha contínuo por pixel e sem floats. @@ -371,7 +371,7 @@ Observações: A composição do frame segue esta ordem: -1. Rasterizar **Game Layers 0–3** → Back Buffer +1. Rasterizar **Tile Layers 0–3** → Back Buffer 2. Rasterizar **Sprites** conforme prioridade 3. (Opcional) Pipeline extra (Emission/Light/Glow etc.) 4. Aplicar **Scene Fade** usando: @@ -415,7 +415,7 @@ O GFX do PROMETEU é simples **por escolha**, não por limitação. - Color key para transparência - Blending discreto estilo SNES - Até 16 tile banks -- 4 game layers + 1 HUD +- 4 Tile Layers + 1 HUD - Layer = tilemap + cache + scroll - Projeção rasterizada por frame - Profundidade definida por ordem de desenho