ASELSANMicrokernel
S548 · SOURCE-BOUND GATE EVIDENCE

S548 · R1 ekran: test deseni ve FramebufferCap bağlama modeli

tam S548 implementation modülü → Operations --test hedefi ile bağlı tam focused test → ayrı Operations kaydı Bu sayfa yalnız S548 kapısına aittir; komşu kapıların kaynakları bu kabulün içine katılmaz.

S548Focused kod testiOperations id exactsource SHA exacttest target exact

operation: g8l-s548-r1-display-test-pattern-framebuffer-capability-binding

uygulama/model · focused test · Operations · 3 exact excerpt

sequence-bound=true · implementation-bound=true
01 · Yürütme / doğrulama kodu

Kapının gerçek repository sözleşmesi

tam dosyaL1–L592
kernel/src/g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_migration_lifecycle_s548_r1_display_test_pattern_framebuffer_capability_binding.rs::S548 r1 display test pattern framebuffer capability binding implementation
#![allow(unexpected_cfgs)]

//! S548 models a deterministic display test-pattern renderer bound to the
//! framebuffer capability model (R1 stage 2: display, touch and basic UI).
//!
//! The model mirrors the M4.4 `PixelFormat` / `FramebufferRights` shapes so it
//! compiles identically in the `no_std` kernel and the host simulation.  A
//! `G8lS548FramebufferBinding` carries the capability geometry (width, height,
//! byte pitch, pixel format) and the rights actually granted to the renderer.
//! Four patterns are rendered into an `alloc::vec::Vec<u32>` word buffer with
//! explicit pitch handling (the pitch may exceed `width * bytes_per_pixel`;
//! padding words stay zero and are covered by the checksum), BGRA8888 and
//! RGB565 packing, bounds-checked pixel writes and an FNV-1a 64-bit checksum
//! of the produced buffer so tests can pin exact expected values.
//!
//! The renderer requires the WRITE right and fails closed without it; VSYNC
//! and DMA are not required.  Every malformed geometry, oversized buffer,
//! unaligned pitch, invalid checkerboard cell or out-of-range pixel access is
//! rejected before any buffer is produced.
//!
//! What this gate does NOT claim: there is no panel, no MMIO, no mailbox
//! transaction, no real framebuffer mapping and no production callsite.  No
//! hardware exists for this gate; `physical observations = 0`,
//! `RUNBOOK_EXECUTED_IN_S548=NO`, Boot-to-UI physically observed = false and
//! R1 acceptance complete = false.  S540 and S543 remain immutable physical
//! RED verdicts.  Predecessor: S547 (mailbox framebuffer allocation contract).
//! Next: S549 (RP1 DSI host register map and D-PHY timing model).

use alloc::vec::Vec;

pub const S548_SEQUENCE: usize = 548;
pub const S548_EXPECTED_PREDECESSOR: usize = 547;
pub const S548_R1_STAGE: u8 = 2;
pub const S548_R1_RANGE_FIRST: usize = 536;
pub const S548_R1_RANGE_LAST: usize = 568;
pub const S548_SUPPORTED_PROFILE_RUNTIME_OBSERVATIONS: usize = 0;
pub const S548_PHYSICAL_OBSERVATIONS: usize = 0;
pub const S548_PHYSICAL_OR_DEVICE_OPERATIONS: usize = 0;
pub const S548_SD_WRITES: usize = 0;
pub const S548_UART_OPENS: usize = 0;
pub const S548_POWER_TRANSITIONS: usize = 0;
pub const S548_NEW_IMMUTABLE_RAW_CAPTURES: usize = 0;
pub const S548_S540_PHYSICAL_VERDICT_RETAINED_RED: bool = true;
pub const S548_S543_PHYSICAL_VERDICT_RETAINED_RED: bool = true;
pub const S548_AUTOMATIC_PROMOTION: bool = false;
pub const S548_BOOT_TO_UI_PHYSICALLY_OBSERVED: bool = false;
pub const S548_HARDWARE_PRESENT: bool = false;
pub const S548_R1_ACCEPTANCE_COMPLETE: bool = false;
pub const RUNBOOK_EXECUTED_IN_S548: bool = false;

/// Number of vertical colour bars in the colour-bar pattern.
pub const S548_COLOUR_BAR_COUNT: u32 = 8;
/// Reference phone panel geometry used by the pinned checksum cases.
pub const S548_PHONE_WIDTH: u32 = 720;
pub const S548_PHONE_HEIGHT: u32 = 1280;
/// Small reference geometry used by the pinned checksum cases.
pub const S548_SMALL_WIDTH: u32 = 64;
pub const S548_SMALL_HEIGHT: u32 = 64;
/// Hard geometry limits of the model (fail-closed above them).
pub const S548_MAX_DIMENSION: u32 = 4096;
pub const S548_MAX_PITCH_BYTES: u32 = S548_MAX_DIMENSION * 4;
pub const S548_MAX_BUFFER_BYTES: u64 = 64 * 1024 * 1024;
/// Border thickness of the border+crosshair pattern in pixels.
pub const S548_BORDER_THICKNESS_PX: u32 = 4;
/// FNV-1a 64-bit parameters.
pub const S548_FNV1A_OFFSET_BASIS: u64 = 0xcbf2_9ce4_8422_2325;
pub const S548_FNV1A_PRIME: u64 = 0x0000_0100_0000_01b3;

/// Mirror of the M4.4 `ui::framebuffer::PixelFormat` (kept local so the model
/// compiles in both the kernel and the host simulation).
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum G8lS548PixelFormat {
    Bgra8888,
    Rgb565,
}

impl G8lS548PixelFormat {
    pub const fn bytes_per_pixel(self) -> u32 {
        match self {
            Self::Bgra8888 => 4,
            Self::Rgb565 => 2,
        }
    }

    pub const fn format_code(self) -> u8 {
        match self {
            Self::Bgra8888 => 1,
            Self::Rgb565 => 2,
        }
    }
}

/// Mirror of the M4.4 `ui::framebuffer::FramebufferRights` (bitmask).
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
pub struct G8lS548FramebufferRights(u8);

impl G8lS548FramebufferRights {
    pub const READ: Self = Self(0b0001);
    pub const WRITE: Self = Self(0b0010);
    pub const VSYNC: Self = Self(0b0100);
    pub const DMA: Self = Self(0b1000);
    pub const FULL: Self = Self(Self::READ.0 | Self::WRITE.0 | Self::VSYNC.0 | Self::DMA.0);

    pub const fn empty() -> Self {
        Self(0)
    }

    pub const fn from_bits(bits: u8) -> Self {
        Self(bits & Self::FULL.0)
    }

    pub const fn contains(self, other: Self) -> bool {
        (self.0 & other.0) == other.0
    }

    pub const fn union(self, other: Self) -> Self {
        Self(self.0 | other.0)
    }

    pub const fn as_u8(self) -> u8 {
        self.0
    }
}

/// An 8-bit-per-channel colour used by the pattern tables.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct G8lS548Rgb {
    pub r: u8,
    pub g: u8,
    pub b: u8,
}

impl G8lS548Rgb {
    pub const fn new(r: u8, g: u8, b: u8) -> Self {
        Self { r, g, b }
    }
}

pub const S548_WHITE: G8lS548Rgb = G8lS548Rgb::new(0xff, 0xff, 0xff);
pub const S548_BLACK: G8lS548Rgb = G8lS548Rgb::new(0x00, 0x00, 0x00);
pub const S548_RED: G8lS548Rgb = G8lS548Rgb::new(0xff, 0x00, 0x00);
pub const S548_BACKGROUND_GREY: G8lS548Rgb = G8lS548Rgb::new(0x20, 0x20, 0x20);

/// Full-intensity colour bars, left to right: white, yellow, cyan, green,
/// magenta, red, blue, black.
pub const S548_COLOUR_BARS: [G8lS548Rgb; S548_COLOUR_BAR_COUNT as usize] = [
    G8lS548Rgb::new(0xff, 0xff, 0xff),
    G8lS548Rgb::new(0xff, 0xff, 0x00),
    G8lS548Rgb::new(0x00, 0xff, 0xff),
    G8lS548Rgb::new(0x00, 0xff, 0x00),
    G8lS548Rgb::new(0xff, 0x00, 0xff),
    G8lS548Rgb::new(0xff, 0x00, 0x00),
    G8lS548Rgb::new(0x00, 0x00, 0xff),
    G8lS548Rgb::new(0x00, 0x00, 0x00),
];

/// The deterministic patterns the renderer can produce.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum G8lS548TestPattern {
    ColourBars,
    Checkerboard { cell: u32 },
    Gradient,
    BorderCrosshair,
}

impl G8lS548TestPattern {
    pub const fn pattern_code(self) -> u8 {
        match self {
            Self::ColourBars => 1,
            Self::Checkerboard { .. } => 2,
            Self::Gradient => 3,
            Self::BorderCrosshair => 4,
        }
    }

    pub const fn parameter(self) -> u32 {
        match self {
            Self::Checkerboard { cell } => cell,
            _ => 0,
        }
    }
}

/// Framebuffer capability geometry plus the rights granted to the renderer.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct G8lS548FramebufferBinding {
    pub cap_id: u64,
    pub width: u32,
    pub height: u32,
    pub pitch_bytes: u32,
    pub format: G8lS548PixelFormat,
    pub cap_max_rights: G8lS548FramebufferRights,
    pub granted_rights: G8lS548FramebufferRights,
}

impl G8lS548FramebufferBinding {
    /// Tight-pitch binding with the WRITE right granted from a FULL cap.
    pub const fn tight(cap_id: u64, width: u32, height: u32, format: G8lS548PixelFormat) -> Self {
        Self {
            cap_id,
            width,
            height,
            pitch_bytes: width * format.bytes_per_pixel(),
            format,
            cap_max_rights: G8lS548FramebufferRights::FULL,
            granted_rights: G8lS548FramebufferRights::WRITE,
        }
    }
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct G8lS548DisplayTestPatternReceipt {
    pub sequence: usize,
    pub predecessor_sequence: usize,
    pub r1_stage: u8,
    pub cap_id: u64,
    pub width: u32,
    pub height: u32,
    pub pitch_bytes: u32,
    pub format_code: u8,
    pub granted_rights: u8,
    pub pattern_code: u8,
    pub pattern_parameter: u32,
    pub pixels_written: u64,
    pub padding_bytes_per_row: u32,
    pub buffer_words: usize,
    pub checksum: u64,
    pub hardware_present: bool,
    pub s540_physical_verdict_retained_red: bool,
    pub s543_physical_verdict_retained_red: bool,
    pub automatic_promotion: bool,
    pub supported_profile_runtime_observations: usize,
    pub physical_observations: usize,
    pub boot_to_ui_physically_observed: bool,
    pub r1_acceptance_complete: bool,
    pub runbook_executed: bool,
}

#[derive(Debug)]
pub struct G8lS548DisplayTestPatternState {
    receipt: Option<G8lS548DisplayTestPatternReceipt>,
}

impl G8lS548DisplayTestPatternState {
    pub const fn new() -> Self {
        Self { receipt: None }
    }

    pub const fn receipt(&self) -> Option<G8lS548DisplayTestPatternReceipt> {
        self.receipt
    }
}

impl Default for G8lS548DisplayTestPatternState {
    fn default() -> Self {
        Self::new()
    }
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum G8lS548DisplayTestPatternOutcome {
    Rendered(G8lS548DisplayTestPatternReceipt),
    Retained(G8lS548DisplayTestPatternReceipt),
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum G8lS548DisplayTestPatternError {
    WriteRightMissing,
    GrantExceedsCapability,
    ZeroDimension,
    DimensionTooLarge,
    PitchNotWordAligned,
    PitchBelowRowBytes,
    PitchTooLarge,
    BufferSizeOverflow,
    WidthBelowBarCount,
    CheckerboardCellZero,
    CheckerboardCellExceedsFrame,
    FrameTooSmallForBorderCrosshair,
    PixelOutOfBounds,
    BufferIndexOutOfRange,
    PublishedStateDrift,
}

impl G8lS548DisplayTestPatternError {
    pub const fn diagnostic_code(self) -> u64 {
        match self {
            Self::WriteRightMissing => 1,
            Self::GrantExceedsCapability => 2,
            Self::ZeroDimension => 3,
            Self::DimensionTooLarge => 4,
            Self::PitchNotWordAligned => 5,
            Self::PitchBelowRowBytes => 6,
            Self::PitchTooLarge => 7,
            Self::BufferSizeOverflow => 8,
            Self::WidthBelowBarCount => 9,
            Self::CheckerboardCellZero => 10,
            Self::CheckerboardCellExceedsFrame => 11,
            Self::FrameTooSmallForBorderCrosshair => 12,
            Self::PixelOutOfBounds => 13,
            Self::BufferIndexOutOfRange => 14,
            Self::PublishedStateDrift => 15,
        }
    }
}

/// A rendered frame: the word buffer plus the receipt describing it.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct G8lS548RenderedFrame {
    pub receipt: G8lS548DisplayTestPatternReceipt,
    pub buffer: Vec<u32>,
}

/// FNV-1a 64-bit over a byte slice.
pub fn s548_fnv1a_64(bytes: &[u8]) -> u64 {
    let mut hash = S548_FNV1A_OFFSET_BASIS;
    for byte in bytes {
        hash ^= u64::from(*byte);
        hash = hash.wrapping_mul(S548_FNV1A_PRIME);
    }
    hash
}

/// FNV-1a 64-bit over the little-endian byte image of a word buffer.
pub fn s548_checksum_words(words: &[u32]) -> u64 {
    let mut hash = S548_FNV1A_OFFSET_BASIS;
    for word in words {
        for byte in word.to_le_bytes() {
            hash ^= u64::from(byte);
            hash = hash.wrapping_mul(S548_FNV1A_PRIME);
        }
    }
    hash
}

/// Packs a colour into the given format.  BGRA8888 is the little-endian
/// word `A<<24 | R<<16 | G<<8 | B` (memory order B, G, R, A) with A = 0xff.
/// RGB565 is `R5<<11 | G6<<5 | B5` in the low 16 bits.
pub const fn s548_pack_pixel(format: G8lS548PixelFormat, colour: G8lS548Rgb) -> u32 {
    match format {
        G8lS548PixelFormat::Bgra8888 => {
            0xff00_0000 | ((colour.r as u32) << 16) | ((colour.g as u32) << 8) | (colour.b as u32)
        }
        G8lS548PixelFormat::Rgb565 => {
            (((colour.r as u32) >> 3) << 11)
                | (((colour.g as u32) >> 2) << 5)
                | ((colour.b as u32) >> 3)
        }
    }
}

/// Validates the binding geometry and rights; returns the buffer word count.
pub fn s548_validate_binding(
    binding: &G8lS548FramebufferBinding,
) -> Result<usize, G8lS548DisplayTestPatternError> {
    if !binding.cap_max_rights.contains(binding.granted_rights) {
        return Err(G8lS548DisplayTestPatternError::GrantExceedsCapability);
    }
    if !binding
        .granted_rights
        .contains(G8lS548FramebufferRights::WRITE)
    {
        return Err(G8lS548DisplayTestPatternError::WriteRightMissing);
    }
    if binding.width == 0 || binding.height == 0 {
        return Err(G8lS548DisplayTestPatternError::ZeroDimension);
    }
    if binding.width > S548_MAX_DIMENSION || binding.height > S548_MAX_DIMENSION {
        return Err(G8lS548DisplayTestPatternError::DimensionTooLarge);
    }
    if binding.pitch_bytes % 4 != 0 {
        return Err(G8lS548DisplayTestPatternError::PitchNotWordAligned);
    }
    let row_bytes = binding
        .width
        .checked_mul(binding.format.bytes_per_pixel())
        .ok_or(G8lS548DisplayTestPatternError::BufferSizeOverflow)?;
    if binding.pitch_bytes < row_bytes {
        return Err(G8lS548DisplayTestPatternError::PitchBelowRowBytes);
    }
    if binding.pitch_bytes > S548_MAX_PITCH_BYTES {
        return Err(G8lS548DisplayTestPatternError::PitchTooLarge);
    }
    let total_bytes = u64::from(binding.pitch_bytes)
        .checked_mul(u64::from(binding.height))
        .ok_or(G8lS548DisplayTestPatternError::BufferSizeOverflow)?;
    if total_bytes > S548_MAX_BUFFER_BYTES {
        return Err(G8lS548DisplayTestPatternError::BufferSizeOverflow);
    }
    usize::try_from(total_bytes / 4).map_err(|_| G8lS548DisplayTestPatternError::BufferSizeOverflow)
}

/// Validates pattern parameters against the binding geometry.
pub fn s548_validate_pattern(
    binding: &G8lS548FramebufferBinding,
    pattern: G8lS548TestPattern,
) -> Result<(), G8lS548DisplayTestPatternError> {
    match pattern {
        G8lS548TestPattern::ColourBars => {
            if binding.width < S548_COLOUR_BAR_COUNT {
                return Err(G8lS548DisplayTestPatternError::WidthBelowBarCount);
            }
        }
        G8lS548TestPattern::Checkerboard { cell } => {
            if cell == 0 {
                return Err(G8lS548DisplayTestPatternError::CheckerboardCellZero);
            }
            if cell > binding.width || cell > binding.height {
                return Err(G8lS548DisplayTestPatternError::CheckerboardCellExceedsFrame);
            }
        }
        G8lS548TestPattern::Gradient => {}
        G8lS548TestPattern::BorderCrosshair => {
            let minimum = S548_BORDER_THICKNESS_PX * 2 + 1;
            if binding.width < minimum || binding.height < minimum {
                return Err(G8lS548DisplayTestPatternError::FrameTooSmallForBorderCrosshair);
            }
        }
    }
    Ok(())
}

/// Word index and bit shift of pixel `(x, y)` inside the pitch-addressed
/// buffer.  Bounds-checked against the binding geometry.
pub fn s548_pixel_location(
    binding: &G8lS548FramebufferBinding,
    x: u32,
    y: u32,
) -> Result<(usize, u32), G8lS548DisplayTestPatternError> {
    if x >= binding.width || y >= binding.height {
        return Err(G8lS548DisplayTestPatternError::PixelOutOfBounds);
    }
    let byte_offset = u64::from(y)
        .checked_mul(u64::from(binding.pitch_bytes))
        .and_then(|row| row.checked_add(u64::from(x) * u64::from(binding.format.bytes_per_pixel())))
        .ok_or(G8lS548DisplayTestPatternError::BufferSizeOverflow)?;
    let word = usize::try_from(byte_offset / 4)
        .map_err(|_| G8lS548DisplayTestPatternError::BufferSizeOverflow)?;
    let shift = ((byte_offset % 4) as u32) * 8;
    Ok((word, shift))
}

/// Bounds-checked pixel write.
pub fn s548_write_pixel(
    buffer: &mut [u32],
    binding: &G8lS548FramebufferBinding,
    x: u32,
    y: u32,
    packed: u32,
) -> Result<(), G8lS548DisplayTestPatternError> {
    let (word, shift) = s548_pixel_location(binding, x, y)?;
    let slot = buffer
        .get_mut(word)
        .ok_or(G8lS548DisplayTestPatternError::BufferIndexOutOfRange)?;
    match binding.format {
        G8lS548PixelFormat::Bgra8888 => *slot = packed,
        G8lS548PixelFormat::Rgb565 => {
            let mask = 0xffff_u32 << shift;
            *slot = (*slot & !mask) | ((packed & 0xffff) << shift);
        }
    }
    Ok(())
}

/// Bounds-checked pixel read (returns the packed value).
pub fn s548_read_pixel(
    buffer: &[u32],
    binding: &G8lS548FramebufferBinding,
    x: u32,
    y: u32,
) -> Result<u32, G8lS548DisplayTestPatternError> {
    let (word, shift) = s548_pixel_location(binding, x, y)?;
    let slot = buffer
        .get(word)
        .ok_or(G8lS548DisplayTestPatternError::BufferIndexOutOfRange)?;
    Ok(match binding.format {
        G8lS548PixelFormat::Bgra8888 => *slot,
        G8lS548PixelFormat::Rgb565 => (*slot >> shift) & 0xffff,
    })
}

/// The colour of pixel `(x, y)` for a pattern on a validated binding.
pub fn s548_pattern_colour(
    binding: &G8lS548FramebufferBinding,
    pattern: G8lS548TestPattern,
    x: u32,
    y: u32,
) -> G8lS548Rgb {
    match pattern {
        G8lS548TestPattern::ColourBars => {
            let bar = (u64::from(x) * u64::from(S548_COLOUR_BAR_COUNT)) / u64::from(binding.width);
            S548_COLOUR_BARS[(bar as usize).min(S548_COLOUR_BARS.len() - 1)]
        }
        G8lS548TestPattern::Checkerboard { cell } => {
            if ((x / cell) + (y / cell)) % 2 == 0 {
                S548_WHITE
            } else {
                S548_BLACK
            }
        }
        G8lS548TestPattern::Gradient => {
            let span_x = u64::from(binding.width.saturating_sub(1)).max(1);
            let span_y = u64::from(binding.height.saturating_sub(1)).max(1);
            let r = (u64::from(x) * 255 / span_x) as u8;
            let g = (u64::from(y) * 255 / span_y) as u8;
            G8lS548Rgb::new(r, g, 255 - r)
        }
        G8lS548TestPattern::BorderCrosshair => {
            let t = S548_BORDER_THICKNESS_PX;
            if x < t || y < t || x >= binding.width - t || y >= binding.height - t {
                S548_WHITE
            } else if x == binding.width / 2 || y == binding.height / 2 {
                S548_RED
            } else {
                S548_BACKGROUND_GREY
            }
        }
    }
}

/// Renders a pattern into a fresh word buffer and returns it with a receipt.
pub fn render_s548_test_pattern(
    binding: &G8lS548FramebufferBinding,
    pattern: G8lS548TestPattern,
) -> Result<G8lS548RenderedFrame, G8lS548DisplayTestPatternError> {
    let buffer_words = s548_validate_binding(binding)?;
    s548_validate_pattern(binding, pattern)?;
    let mut buffer: Vec<u32> = Vec::new();
    buffer.resize(buffer_words, 0);
    let mut pixels_written: u64 = 0;
    for y in 0..binding.height {
        for x in 0..binding.width {
            let colour = s548_pattern_colour(binding, pattern, x, y);
            let packed = s548_pack_pixel(binding.format, colour);
            s548_write_pixel(&mut buffer, binding, x, y, packed)?;
            pixels_written = pixels_written
                .checked_add(1)
                .ok_or(G8lS548DisplayTestPatternError::BufferSizeOverflow)?;
        }
    }
    let checksum = s548_checksum_words(&buffer);
    let receipt = G8lS548DisplayTestPatternReceipt {
        sequence: S548_SEQUENCE,
        predecessor_sequence: S548_EXPECTED_PREDECESSOR,
        r1_stage: S548_R1_STAGE,
        cap_id: binding.cap_id,
        width: binding.width,
        height: binding.height,
        pitch_bytes: binding.pitch_bytes,
        format_code: binding.format.format_code(),
        granted_rights: binding.granted_rights.as_u8(),
        pattern_code: pattern.pattern_code(),
        pattern_parameter: pattern.parameter(),
        pixels_written,
        padding_bytes_per_row: binding.pitch_bytes
            - binding.width * binding.format.bytes_per_pixel(),
        buffer_words,
        checksum,
        hardware_present: S548_HARDWARE_PRESENT,
        s540_physical_verdict_retained_red: S548_S540_PHYSICAL_VERDICT_RETAINED_RED,
        s543_physical_verdict_retained_red: S548_S543_PHYSICAL_VERDICT_RETAINED_RED,
        automatic_promotion: S548_AUTOMATIC_PROMOTION,
        supported_profile_runtime_observations: S548_SUPPORTED_PROFILE_RUNTIME_OBSERVATIONS,
        physical_observations: S548_PHYSICAL_OBSERVATIONS,
        boot_to_ui_physically_observed: S548_BOOT_TO_UI_PHYSICALLY_OBSERVED,
        r1_acceptance_complete: S548_R1_ACCEPTANCE_COMPLETE,
        runbook_executed: RUNBOOK_EXECUTED_IN_S548,
    };
    Ok(G8lS548RenderedFrame { receipt, buffer })
}

/// Fail-closed, idempotent render service.  The first successful render
/// publishes its receipt; an exact replay is retained; any divergent binding
/// or pattern after publication is rejected.  The buffer itself is not kept in
/// the state (the receipt's checksum identifies it).
pub fn service_s548_model_render_test_pattern(
    state: &mut G8lS548DisplayTestPatternState,
    binding: &G8lS548FramebufferBinding,
    pattern: G8lS548TestPattern,
) -> Result<G8lS548DisplayTestPatternOutcome, G8lS548DisplayTestPatternError> {
    let frame = render_s548_test_pattern(binding, pattern)?;
    let receipt = frame.receipt;
    if let Some(published) = state.receipt {
        if published != receipt {
            return Err(G8lS548DisplayTestPatternError::PublishedStateDrift);
        }
        return Ok(G8lS548DisplayTestPatternOutcome::Retained(published));
    }
    state.receipt = Some(receipt);
    Ok(G8lS548DisplayTestPatternOutcome::Rendered(receipt))
}
snippet sha256: eee6f8da27a9file sha256: eee6f8da27a9
02 · Doğrulayan test kodu

Operations komutuna bağlı focused test

tam dosyaL1–L605
simulation/tests/g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_migration_lifecycle_s548_r1_display_test_pattern_framebuffer_capability_binding.rs::S548 r1 display test pattern framebuffer capability binding focused tests
use aselsan_microkernel_simulation::g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_migration_lifecycle_s548_r1_display_test_pattern_framebuffer_capability_binding::*;
use std::collections::BTreeSet;

const SOURCE: &str = include_str!(
    "../../kernel/src/g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_migration_lifecycle_s548_r1_display_test_pattern_framebuffer_capability_binding.rs"
);
const MAIN: &str = include_str!("../../kernel/src/main.rs");
const SIMULATION_LIB: &str = include_str!("../src/lib.rs");

/// Expected FNV-1a 64 checksums computed by an independent reference
/// implementation of the same pattern/packing/pitch rules.
const PHONE_BARS_BGRA8888: u64 = 0x7c54_4deb_8157_9325;
const PHONE_GRADIENT_RGB565: u64 = 0x2c3b_4455_9487_9b71;
const PHONE_CHECKER40_BGRA8888: u64 = 0xbb4f_86ef_b3eb_0325;
const SMALL_CHECKER8_BGRA8888: u64 = 0xa546_9e63_bfa7_0325;
const SMALL_BARS_RGB565: u64 = 0x8e56_2bac_1128_4b25;
const SMALL_BORDER_BGRA8888: u64 = 0xf748_ab56_fde2_f1b2;
const SMALL_GRADIENT_BGRA8888: u64 = 0x10b5_b99e_81f4_aef5;
const SMALL_BARS_BGRA8888_PITCH320: u64 = 0xe645_3365_e450_3325;
const SMALL_BARS_RGB565_PITCH192: u64 = 0xde43_4cdc_99b5_8b25;

fn phone(format: G8lS548PixelFormat) -> G8lS548FramebufferBinding {
    G8lS548FramebufferBinding::tight(0x5480, S548_PHONE_WIDTH, S548_PHONE_HEIGHT, format)
}

fn small(format: G8lS548PixelFormat) -> G8lS548FramebufferBinding {
    G8lS548FramebufferBinding::tight(0x5481, S548_SMALL_WIDTH, S548_SMALL_HEIGHT, format)
}

fn render(
    binding: &G8lS548FramebufferBinding,
    pattern: G8lS548TestPattern,
) -> G8lS548RenderedFrame {
    render_s548_test_pattern(binding, pattern).unwrap()
}

fn bgra(r: u8, g: u8, b: u8) -> u32 {
    0xff00_0000 | (u32::from(r) << 16) | (u32::from(g) << 8) | u32::from(b)
}

#[test]
fn sequence_scope_and_nonpromotion_are_exact() {
    assert_eq!(S548_SEQUENCE, 548);
    assert_eq!(S548_EXPECTED_PREDECESSOR, 547);
    assert_eq!(S548_R1_STAGE, 2);
    assert_eq!(S548_R1_RANGE_FIRST, 536);
    assert_eq!(S548_R1_RANGE_LAST, 568);
    assert_eq!(S548_SUPPORTED_PROFILE_RUNTIME_OBSERVATIONS, 0);
    assert_eq!(S548_PHYSICAL_OBSERVATIONS, 0);
    assert_eq!(S548_PHYSICAL_OR_DEVICE_OPERATIONS, 0);
    assert_eq!(S548_SD_WRITES, 0);
    assert_eq!(S548_UART_OPENS, 0);
    assert_eq!(S548_POWER_TRANSITIONS, 0);
    assert_eq!(S548_NEW_IMMUTABLE_RAW_CAPTURES, 0);
    assert!(S548_S540_PHYSICAL_VERDICT_RETAINED_RED);
    assert!(S548_S543_PHYSICAL_VERDICT_RETAINED_RED);
    assert!(!S548_AUTOMATIC_PROMOTION);
    assert!(!S548_BOOT_TO_UI_PHYSICALLY_OBSERVED);
    assert!(!S548_HARDWARE_PRESENT);
    assert!(!S548_R1_ACCEPTANCE_COMPLETE);
    assert!(!RUNBOOK_EXECUTED_IN_S548);
    assert_eq!(S548_COLOUR_BAR_COUNT, 8);
    assert_eq!(S548_COLOUR_BARS.len(), 8);
    assert_eq!((S548_PHONE_WIDTH, S548_PHONE_HEIGHT), (720, 1280));
    assert_eq!((S548_SMALL_WIDTH, S548_SMALL_HEIGHT), (64, 64));
    assert_eq!(S548_BORDER_THICKNESS_PX, 4);
    assert_eq!(S548_FNV1A_OFFSET_BASIS, 0xcbf2_9ce4_8422_2325);
    assert_eq!(S548_FNV1A_PRIME, 0x0000_0100_0000_01b3);
}

#[test]
fn module_is_registered_in_kernel_and_simulation() {
    let module = "g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_migration_lifecycle_s548_r1_display_test_pattern_framebuffer_capability_binding";
    assert!(MAIN.contains(&format!("mod {module};")));
    assert!(SIMULATION_LIB.contains(&format!("pub mod {module};")));
}

#[test]
fn source_has_no_device_execution_or_uart_emission_surface() {
    for forbidden in [
        "unsafe",
        "asm!",
        "write_volatile",
        "crate::uart",
        "crate::arch",
        "crate::mm",
        "crate::ui",
        "#[no_mangle]",
        "spin::",
        "std::",
        "kprintln!",
    ] {
        assert!(!SOURCE.contains(forbidden), "forbidden token: {forbidden}");
    }
    assert!(SOURCE.contains("no panel, no MMIO, no mailbox"));
    assert!(SOURCE.contains("no production callsite"));
    assert!(SOURCE.contains("use alloc::vec::Vec;"));
}

#[test]
fn diagnostic_codes_are_nonzero_and_unique() {
    let errors = [
        G8lS548DisplayTestPatternError::WriteRightMissing,
        G8lS548DisplayTestPatternError::GrantExceedsCapability,
        G8lS548DisplayTestPatternError::ZeroDimension,
        G8lS548DisplayTestPatternError::DimensionTooLarge,
        G8lS548DisplayTestPatternError::PitchNotWordAligned,
        G8lS548DisplayTestPatternError::PitchBelowRowBytes,
        G8lS548DisplayTestPatternError::PitchTooLarge,
        G8lS548DisplayTestPatternError::BufferSizeOverflow,
        G8lS548DisplayTestPatternError::WidthBelowBarCount,
        G8lS548DisplayTestPatternError::CheckerboardCellZero,
        G8lS548DisplayTestPatternError::CheckerboardCellExceedsFrame,
        G8lS548DisplayTestPatternError::FrameTooSmallForBorderCrosshair,
        G8lS548DisplayTestPatternError::PixelOutOfBounds,
        G8lS548DisplayTestPatternError::BufferIndexOutOfRange,
        G8lS548DisplayTestPatternError::PublishedStateDrift,
    ];
    let codes: BTreeSet<_> = errors
        .into_iter()
        .map(G8lS548DisplayTestPatternError::diagnostic_code)
        .collect();
    assert_eq!(codes.len(), errors.len());
    assert!(!codes.contains(&0));
}

#[test]
fn exact_replay_retains_the_same_receipt() {
    let mut state = G8lS548DisplayTestPatternState::new();
    let binding = phone(G8lS548PixelFormat::Bgra8888);
    let G8lS548DisplayTestPatternOutcome::Rendered(receipt) =
        service_s548_model_render_test_pattern(
            &mut state,
            &binding,
            G8lS548TestPattern::ColourBars,
        )
        .unwrap()
    else {
        panic!("first S548 render missing")
    };
    assert_eq!(state.receipt(), Some(receipt));
    assert_eq!(receipt.checksum, PHONE_BARS_BGRA8888);
    assert_eq!(
        service_s548_model_render_test_pattern(
            &mut state,
            &binding,
            G8lS548TestPattern::ColourBars,
        ),
        Ok(G8lS548DisplayTestPatternOutcome::Retained(receipt))
    );
    assert_eq!(state.receipt(), Some(receipt));
}

#[test]
fn divergent_input_after_publication_fails_closed() {
    let mut state = G8lS548DisplayTestPatternState::new();
    let binding = small(G8lS548PixelFormat::Bgra8888);
    service_s548_model_render_test_pattern(
        &mut state,
        &binding,
        G8lS548TestPattern::Checkerboard { cell: 8 },
    )
    .unwrap();
    let published = state.receipt().unwrap();
    assert_eq!(
        service_s548_model_render_test_pattern(
            &mut state,
            &binding,
            G8lS548TestPattern::Checkerboard { cell: 16 },
        ),
        Err(G8lS548DisplayTestPatternError::PublishedStateDrift)
    );
    let mut other_cap = binding;
    other_cap.cap_id ^= 1;
    assert_eq!(
        service_s548_model_render_test_pattern(
            &mut state,
            &other_cap,
            G8lS548TestPattern::Checkerboard { cell: 8 },
        ),
        Err(G8lS548DisplayTestPatternError::PublishedStateDrift)
    );
    let mut more_rights = binding;
    more_rights.granted_rights = G8lS548FramebufferRights::FULL;
    assert_eq!(
        service_s548_model_render_test_pattern(
            &mut state,
            &more_rights,
            G8lS548TestPattern::Checkerboard { cell: 8 },
        ),
        Err(G8lS548DisplayTestPatternError::PublishedStateDrift)
    );
    assert_eq!(state.receipt(), Some(published));
}

#[test]
fn colour_bars_720x1280_bgra8888_pins_exact_checksum_and_bar_edges() {
    let binding = phone(G8lS548PixelFormat::Bgra8888);
    let frame = render(&binding, G8lS548TestPattern::ColourBars);
    assert_eq!(frame.buffer.len(), 720 * 1280);
    assert_eq!(frame.receipt.buffer_words, 720 * 1280);
    assert_eq!(frame.receipt.pixels_written, 720 * 1280);
    assert_eq!(frame.receipt.padding_bytes_per_row, 0);
    assert_eq!(frame.receipt.pitch_bytes, 2880);
    assert_eq!(frame.receipt.format_code, 1);
    assert_eq!(frame.receipt.pattern_code, 1);
    assert_eq!(frame.receipt.checksum, PHONE_BARS_BGRA8888);
    assert_eq!(s548_checksum_words(&frame.buffer), PHONE_BARS_BGRA8888);
    // Each bar is exactly 90 pixels wide.
    for bar in 0..8u32 {
        let expected = s548_pack_pixel(G8lS548PixelFormat::Bgra8888, S548_COLOUR_BARS[bar as usize]);
        assert_eq!(s548_read_pixel(&frame.buffer, &binding, bar * 90, 0), Ok(expected));
        assert_eq!(s548_read_pixel(&frame.buffer, &binding, bar * 90 + 89, 1279), Ok(expected));
    }
    assert_eq!(s548_read_pixel(&frame.buffer, &binding, 0, 640), Ok(bgra(0xff, 0xff, 0xff)));
    assert_eq!(s548_read_pixel(&frame.buffer, &binding, 719, 640), Ok(bgra(0, 0, 0)));
    assert_eq!(s548_read_pixel(&frame.buffer, &binding, 450, 0), Ok(bgra(0xff, 0, 0)));
}

#[test]
fn gradient_and_checkerboard_720x1280_pin_exact_checksums() {
    let rgb565 = phone(G8lS548PixelFormat::Rgb565);
    let gradient = render(&rgb565, G8lS548TestPattern::Gradient);
    assert_eq!(gradient.buffer.len(), 720 * 1280 / 2);
    assert_eq!(gradient.receipt.pitch_bytes, 1440);
    assert_eq!(gradient.receipt.format_code, 2);
    assert_eq!(gradient.receipt.pattern_code, 3);
    assert_eq!(gradient.receipt.checksum, PHONE_GRADIENT_RGB565);
    let bgra8888 = phone(G8lS548PixelFormat::Bgra8888);
    let checker = render(&bgra8888, G8lS548TestPattern::Checkerboard { cell: 40 });
    assert_eq!(checker.receipt.pattern_code, 2);
    assert_eq!(checker.receipt.pattern_parameter, 40);
    assert_eq!(checker.receipt.checksum, PHONE_CHECKER40_BGRA8888);
    assert_ne!(checker.receipt.checksum, gradient.receipt.checksum);
}

#[test]
fn checkerboard_64x64_cell8_alternates_and_pins_exact_checksum() {
    let binding = small(G8lS548PixelFormat::Bgra8888);
    let frame = render(&binding, G8lS548TestPattern::Checkerboard { cell: 8 });
    assert_eq!(frame.buffer.len(), 64 * 64);
    assert_eq!(frame.receipt.checksum, SMALL_CHECKER8_BGRA8888);
    let white = bgra(0xff, 0xff, 0xff);
    let black = bgra(0, 0, 0);
    assert_eq!(s548_read_pixel(&frame.buffer, &binding, 0, 0), Ok(white));
    assert_eq!(s548_read_pixel(&frame.buffer, &binding, 7, 7), Ok(white));
    assert_eq!(s548_read_pixel(&frame.buffer, &binding, 8, 0), Ok(black));
    assert_eq!(s548_read_pixel(&frame.buffer, &binding, 0, 8), Ok(black));
    assert_eq!(s548_read_pixel(&frame.buffer, &binding, 8, 8), Ok(white));
    assert_eq!(s548_read_pixel(&frame.buffer, &binding, 63, 63), Ok(white));
    assert_eq!(s548_read_pixel(&frame.buffer, &binding, 63, 0), Ok(black));
    let whites = frame.buffer.iter().filter(|word| **word == white).count();
    let blacks = frame.buffer.iter().filter(|word| **word == black).count();
    assert_eq!((whites, blacks), (2048, 2048));
    // A different cell size changes the checksum.
    let cell16 = render(&binding, G8lS548TestPattern::Checkerboard { cell: 16 });
    assert_ne!(cell16.receipt.checksum, frame.receipt.checksum);
}

#[test]
fn colour_bars_64x64_rgb565_packs_two_pixels_per_word_and_pins_checksum() {
    let binding = small(G8lS548PixelFormat::Rgb565);
    let frame = render(&binding, G8lS548TestPattern::ColourBars);
    assert_eq!(frame.buffer.len(), 64 * 64 / 2);
    assert_eq!(frame.receipt.pitch_bytes, 128);
    assert_eq!(frame.receipt.checksum, SMALL_BARS_RGB565);
    // White = 0xffff, yellow = 0xffe0, cyan = 0x07ff, green = 0x07e0,
    // magenta = 0xf81f, red = 0xf800, blue = 0x001f, black = 0x0000.
    let expected = [0xffff, 0xffe0, 0x07ff, 0x07e0, 0xf81f, 0xf800, 0x001f, 0x0000];
    for (bar, packed) in expected.into_iter().enumerate() {
        assert_eq!(
            s548_pack_pixel(G8lS548PixelFormat::Rgb565, S548_COLOUR_BARS[bar]),
            packed
        );
        assert_eq!(
            s548_read_pixel(&frame.buffer, &binding, bar as u32 * 8, 3),
            Ok(packed)
        );
    }
    // Word 0 holds pixels (0,0) low half and (1,0) high half, both white.
    assert_eq!(frame.buffer[0], 0xffff_ffff);
    // Word 4 holds pixels (8,0) and (9,0): yellow twice.
    assert_eq!(frame.buffer[4], 0xffe0_ffe0);
    assert_eq!(s548_pixel_location(&binding, 9, 0), Ok((4, 16)));
    assert_eq!(s548_pixel_location(&binding, 0, 1), Ok((32, 0)));
}

#[test]
fn gradient_64x64_corners_are_exact() {
    let binding = small(G8lS548PixelFormat::Bgra8888);
    let frame = render(&binding, G8lS548TestPattern::Gradient);
    assert_eq!(frame.receipt.checksum, SMALL_GRADIENT_BGRA8888);
    assert_eq!(s548_read_pixel(&frame.buffer, &binding, 0, 0), Ok(bgra(0, 0, 255)));
    assert_eq!(s548_read_pixel(&frame.buffer, &binding, 63, 0), Ok(bgra(255, 0, 0)));
    assert_eq!(s548_read_pixel(&frame.buffer, &binding, 0, 63), Ok(bgra(0, 255, 255)));
    assert_eq!(s548_read_pixel(&frame.buffer, &binding, 63, 63), Ok(bgra(255, 255, 0)));
    // Monotonic red ramp along the top row.
    let mut previous = 0u32;
    for x in 0..64 {
        let red = (s548_read_pixel(&frame.buffer, &binding, x, 0).unwrap() >> 16) & 0xff;
        assert!(red >= previous);
        previous = red;
    }
    // A 1x1 gradient is valid and degenerates to a single blue pixel.
    let one = G8lS548FramebufferBinding::tight(1, 1, 1, G8lS548PixelFormat::Bgra8888);
    let dot = render(&one, G8lS548TestPattern::Gradient);
    assert_eq!(dot.buffer, vec![bgra(0, 0, 255)]);
}

#[test]
fn border_crosshair_64x64_pixels_are_exact() {
    let binding = small(G8lS548PixelFormat::Bgra8888);
    let frame = render(&binding, G8lS548TestPattern::BorderCrosshair);
    assert_eq!(frame.receipt.pattern_code, 4);
    assert_eq!(frame.receipt.checksum, SMALL_BORDER_BGRA8888);
    let white = bgra(0xff, 0xff, 0xff);
    let red = bgra(0xff, 0, 0);
    let grey = bgra(0x20, 0x20, 0x20);
    assert_eq!(s548_read_pixel(&frame.buffer, &binding, 0, 0), Ok(white));
    assert_eq!(s548_read_pixel(&frame.buffer, &binding, 3, 30), Ok(white));
    assert_eq!(s548_read_pixel(&frame.buffer, &binding, 60, 30), Ok(white));
    assert_eq!(s548_read_pixel(&frame.buffer, &binding, 30, 3), Ok(white));
    assert_eq!(s548_read_pixel(&frame.buffer, &binding, 30, 60), Ok(white));
    assert_eq!(s548_read_pixel(&frame.buffer, &binding, 4, 30), Ok(grey));
    assert_eq!(s548_read_pixel(&frame.buffer, &binding, 59, 30), Ok(grey));
    assert_eq!(s548_read_pixel(&frame.buffer, &binding, 32, 10), Ok(red));
    assert_eq!(s548_read_pixel(&frame.buffer, &binding, 10, 32), Ok(red));
    assert_eq!(s548_read_pixel(&frame.buffer, &binding, 32, 32), Ok(red));
    assert_eq!(s548_read_pixel(&frame.buffer, &binding, 31, 31), Ok(grey));
    let reds = frame.buffer.iter().filter(|word| **word == red).count();
    // 56 interior pixels on each crosshair arm, centre counted once.
    assert_eq!(reds, 56 + 56 - 1);
    let whites = frame.buffer.iter().filter(|word| **word == white).count();
    assert_eq!(whites, 64 * 64 - 56 * 56);
}

#[test]
fn pitch_padding_stays_zero_and_changes_the_checksum() {
    let mut padded = small(G8lS548PixelFormat::Bgra8888);
    padded.pitch_bytes = 320;
    let frame = render(&padded, G8lS548TestPattern::ColourBars);
    assert_eq!(frame.buffer.len(), 320 / 4 * 64);
    assert_eq!(frame.receipt.padding_bytes_per_row, 64);
    assert_eq!(frame.receipt.pixels_written, 64 * 64);
    assert_eq!(frame.receipt.checksum, SMALL_BARS_BGRA8888_PITCH320);
    let tight = render(&small(G8lS548PixelFormat::Bgra8888), G8lS548TestPattern::ColourBars);
    assert_ne!(frame.receipt.checksum, tight.receipt.checksum);
    for y in 0..64usize {
        let row = &frame.buffer[y * 80..(y + 1) * 80];
        assert_eq!(&row[..64], &tight.buffer[y * 64..(y + 1) * 64]);
        assert!(row[64..].iter().all(|word| *word == 0));
    }
    assert_eq!(s548_pixel_location(&padded, 0, 1), Ok((80, 0)));
    let mut padded565 = small(G8lS548PixelFormat::Rgb565);
    padded565.pitch_bytes = 192;
    let frame565 = render(&padded565, G8lS548TestPattern::ColourBars);
    assert_eq!(frame565.buffer.len(), 192 / 4 * 64);
    assert_eq!(frame565.receipt.padding_bytes_per_row, 64);
    assert_eq!(frame565.receipt.checksum, SMALL_BARS_RGB565_PITCH192);
    assert_eq!(s548_pixel_location(&padded565, 1, 1), Ok((48, 16)));
}

#[test]
fn write_right_is_required_but_vsync_and_dma_are_not() {
    let mut state = G8lS548DisplayTestPatternState::new();
    for rights in [
        G8lS548FramebufferRights::empty(),
        G8lS548FramebufferRights::READ,
        G8lS548FramebufferRights::VSYNC,
        G8lS548FramebufferRights::DMA,
        G8lS548FramebufferRights::READ
            .union(G8lS548FramebufferRights::VSYNC)
            .union(G8lS548FramebufferRights::DMA),
    ] {
        let mut binding = small(G8lS548PixelFormat::Bgra8888);
        binding.granted_rights = rights;
        assert_eq!(
            service_s548_model_render_test_pattern(
                &mut state,
                &binding,
                G8lS548TestPattern::ColourBars
            ),
            Err(G8lS548DisplayTestPatternError::WriteRightMissing),
            "rights {:#06b}",
            rights.as_u8()
        );
        assert_eq!(state.receipt(), None);
    }
    for rights in [
        G8lS548FramebufferRights::WRITE,
        G8lS548FramebufferRights::READ.union(G8lS548FramebufferRights::WRITE),
        G8lS548FramebufferRights::FULL,
    ] {
        let mut binding = small(G8lS548PixelFormat::Bgra8888);
        binding.granted_rights = rights;
        let frame = render(&binding, G8lS548TestPattern::ColourBars);
        assert_eq!(frame.receipt.granted_rights, rights.as_u8());
        assert!(frame.receipt.granted_rights & G8lS548FramebufferRights::WRITE.as_u8() != 0);
    }
    assert_eq!(G8lS548FramebufferRights::FULL.as_u8(), 0b1111);
    assert_eq!(G8lS548FramebufferRights::from_bits(0xff), G8lS548FramebufferRights::FULL);
}

#[test]
fn grant_exceeding_capability_fails_closed_before_rights_check() {
    let mut state = G8lS548DisplayTestPatternState::new();
    let mut binding = small(G8lS548PixelFormat::Bgra8888);
    binding.cap_max_rights = G8lS548FramebufferRights::READ;
    binding.granted_rights = G8lS548FramebufferRights::WRITE;
    assert_eq!(
        service_s548_model_render_test_pattern(&mut state, &binding, G8lS548TestPattern::Gradient),
        Err(G8lS548DisplayTestPatternError::GrantExceedsCapability)
    );
    binding.cap_max_rights = G8lS548FramebufferRights::WRITE;
    binding.granted_rights = G8lS548FramebufferRights::WRITE.union(G8lS548FramebufferRights::DMA);
    assert_eq!(
        service_s548_model_render_test_pattern(&mut state, &binding, G8lS548TestPattern::Gradient),
        Err(G8lS548DisplayTestPatternError::GrantExceedsCapability)
    );
    binding.cap_max_rights = G8lS548FramebufferRights::empty();
    binding.granted_rights = G8lS548FramebufferRights::empty();
    assert_eq!(
        service_s548_model_render_test_pattern(&mut state, &binding, G8lS548TestPattern::Gradient),
        Err(G8lS548DisplayTestPatternError::WriteRightMissing)
    );
    assert_eq!(state.receipt(), None);
}

#[test]
fn malformed_geometry_fails_closed() {
    let base = small(G8lS548PixelFormat::Bgra8888);
    let cases: [(fn(&mut G8lS548FramebufferBinding), G8lS548DisplayTestPatternError); 8] = [
        (|b| b.width = 0, G8lS548DisplayTestPatternError::ZeroDimension),
        (|b| b.height = 0, G8lS548DisplayTestPatternError::ZeroDimension),
        (
            |b| b.width = S548_MAX_DIMENSION + 1,
            G8lS548DisplayTestPatternError::DimensionTooLarge,
        ),
        (
            |b| b.height = S548_MAX_DIMENSION + 1,
            G8lS548DisplayTestPatternError::DimensionTooLarge,
        ),
        (|b| b.pitch_bytes = 258, G8lS548DisplayTestPatternError::PitchNotWordAligned),
        (|b| b.pitch_bytes = 252, G8lS548DisplayTestPatternError::PitchBelowRowBytes),
        (
            |b| b.pitch_bytes = S548_MAX_PITCH_BYTES + 4,
            G8lS548DisplayTestPatternError::PitchTooLarge,
        ),
        (
            |b| {
                b.width = 4096;
                b.height = 4096;
                b.pitch_bytes = 4096 * 4 + 4;
            },
            G8lS548DisplayTestPatternError::PitchTooLarge,
        ),
    ];
    for (mutate, expected) in cases {
        let mut binding = base;
        mutate(&mut binding);
        assert_eq!(render_s548_test_pattern(&binding, G8lS548TestPattern::Gradient), Err(expected));
    }
    // Largest legal geometry (4096 x 4096 x 4 = 64 MiB) validates exactly at the limit.
    let mut maximal = base;
    maximal.width = 4096;
    maximal.height = 4096;
    maximal.pitch_bytes = 4096 * 4;
    assert_eq!(s548_validate_binding(&maximal), Ok(4096 * 4096));
    // Any RGB565 frame with a 4-aligned pitch one word above the row is legal.
    let mut odd565 = small(G8lS548PixelFormat::Rgb565);
    odd565.pitch_bytes = 132;
    assert_eq!(s548_validate_binding(&odd565), Ok(132 / 4 * 64));
}

#[test]
fn pattern_parameter_boundaries_fail_closed() {
    let binding = small(G8lS548PixelFormat::Bgra8888);
    assert_eq!(
        render_s548_test_pattern(&binding, G8lS548TestPattern::Checkerboard { cell: 0 }),
        Err(G8lS548DisplayTestPatternError::CheckerboardCellZero)
    );
    assert_eq!(
        render_s548_test_pattern(&binding, G8lS548TestPattern::Checkerboard { cell: 65 }),
        Err(G8lS548DisplayTestPatternError::CheckerboardCellExceedsFrame)
    );
    assert!(render_s548_test_pattern(&binding, G8lS548TestPattern::Checkerboard { cell: 64 }).is_ok());
    assert!(render_s548_test_pattern(&binding, G8lS548TestPattern::Checkerboard { cell: 1 }).is_ok());
    let narrow = G8lS548FramebufferBinding::tight(2, 7, 16, G8lS548PixelFormat::Bgra8888);
    assert_eq!(
        render_s548_test_pattern(&narrow, G8lS548TestPattern::ColourBars),
        Err(G8lS548DisplayTestPatternError::WidthBelowBarCount)
    );
    let eight = G8lS548FramebufferBinding::tight(3, 8, 1, G8lS548PixelFormat::Bgra8888);
    let bars = render(&eight, G8lS548TestPattern::ColourBars);
    for bar in 0..8usize {
        assert_eq!(bars.buffer[bar], s548_pack_pixel(G8lS548PixelFormat::Bgra8888, S548_COLOUR_BARS[bar]));
    }
    let tiny = G8lS548FramebufferBinding::tight(4, 8, 9, G8lS548PixelFormat::Bgra8888);
    assert_eq!(
        render_s548_test_pattern(&tiny, G8lS548TestPattern::BorderCrosshair),
        Err(G8lS548DisplayTestPatternError::FrameTooSmallForBorderCrosshair)
    );
    let minimal = G8lS548FramebufferBinding::tight(5, 9, 9, G8lS548PixelFormat::Bgra8888);
    let cross = render(&minimal, G8lS548TestPattern::BorderCrosshair);
    assert_eq!(cross.buffer[4 * 9 + 4], bgra(0xff, 0, 0));
    assert_eq!(cross.buffer.iter().filter(|w| **w == bgra(0x20, 0x20, 0x20)).count(), 0);
}

#[test]
fn bounds_checked_pixel_access_rejects_out_of_range() {
    let binding = small(G8lS548PixelFormat::Bgra8888);
    let mut buffer = vec![0u32; 64 * 64];
    assert_eq!(
        s548_write_pixel(&mut buffer, &binding, 64, 0, 1),
        Err(G8lS548DisplayTestPatternError::PixelOutOfBounds)
    );
    assert_eq!(
        s548_write_pixel(&mut buffer, &binding, 0, 64, 1),
        Err(G8lS548DisplayTestPatternError::PixelOutOfBounds)
    );
    assert_eq!(
        s548_read_pixel(&buffer, &binding, u32::MAX, u32::MAX),
        Err(G8lS548DisplayTestPatternError::PixelOutOfBounds)
    );
    assert!(buffer.iter().all(|word| *word == 0));
    let mut short = vec![0u32; 64 * 63];
    assert_eq!(
        s548_write_pixel(&mut short, &binding, 0, 63, 1),
        Err(G8lS548DisplayTestPatternError::BufferIndexOutOfRange)
    );
    assert_eq!(
        s548_read_pixel(&short, &binding, 63, 63),
        Err(G8lS548DisplayTestPatternError::BufferIndexOutOfRange)
    );
    assert_eq!(s548_write_pixel(&mut short, &binding, 63, 62, 0xdead_beef), Ok(()));
    assert_eq!(short[64 * 62 + 63], 0xdead_beef);
    // RGB565 writes touch only their own half-word.
    let binding565 = small(G8lS548PixelFormat::Rgb565);
    let mut words = vec![0u32; 64 * 64 / 2];
    assert_eq!(s548_write_pixel(&mut words, &binding565, 0, 0, 0x1_f800), Ok(()));
    assert_eq!(s548_write_pixel(&mut words, &binding565, 1, 0, 0x001f), Ok(()));
    assert_eq!(words[0], 0x001f_f800);
    assert_eq!(s548_read_pixel(&words, &binding565, 0, 0), Ok(0xf800));
    assert_eq!(s548_read_pixel(&words, &binding565, 1, 0), Ok(0x001f));
}

#[test]
fn fnv1a_64_matches_reference_vectors_and_word_byte_order() {
    assert_eq!(s548_fnv1a_64(b""), S548_FNV1A_OFFSET_BASIS);
    assert_eq!(s548_fnv1a_64(b"a"), 0xaf63_dc4c_8601_ec8c);
    assert_eq!(s548_fnv1a_64(b"foobar"), 0x8594_4171_f739_67e8);
    assert_eq!(s548_checksum_words(&[]), S548_FNV1A_OFFSET_BASIS);
    assert_eq!(
        s548_checksum_words(&[0x6162_6364]),
        s548_fnv1a_64(&[0x64, 0x63, 0x62, 0x61])
    );
    assert_eq!(
        s548_checksum_words(&[0x0403_0201, 0x0807_0605]),
        s548_fnv1a_64(&[1, 2, 3, 4, 5, 6, 7, 8])
    );
    assert_ne!(s548_checksum_words(&[1, 0]), s548_checksum_words(&[0, 1]));
}

#[test]
fn receipt_carries_binding_identity_and_zero_claims() {
    let binding = phone(G8lS548PixelFormat::Rgb565);
    let frame = render(&binding, G8lS548TestPattern::BorderCrosshair);
    let receipt = frame.receipt;
    assert_eq!(receipt.sequence, S548_SEQUENCE);
    assert_eq!(receipt.predecessor_sequence, S548_EXPECTED_PREDECESSOR);
    assert_eq!(receipt.r1_stage, S548_R1_STAGE);
    assert_eq!(receipt.cap_id, 0x5480);
    assert_eq!((receipt.width, receipt.height, receipt.pitch_bytes), (720, 1280, 1440));
    assert_eq!(receipt.format_code, 2);
    assert_eq!(receipt.granted_rights, G8lS548FramebufferRights::WRITE.as_u8());
    assert_eq!(receipt.pattern_code, 4);
    assert_eq!(receipt.pattern_parameter, 0);
    assert_eq!(receipt.pixels_written, 720 * 1280);
    assert_eq!(receipt.buffer_words, 720 * 1280 / 2);
    assert_eq!(receipt.checksum, s548_checksum_words(&frame.buffer));
    assert!(!receipt.hardware_present);
    assert!(receipt.s540_physical_verdict_retained_red);
    assert!(receipt.s543_physical_verdict_retained_red);
    assert!(!receipt.automatic_promotion);
    assert_eq!(receipt.supported_profile_runtime_observations, 0);
    assert_eq!(receipt.physical_observations, 0);
    assert!(!receipt.boot_to_ui_physically_observed);
    assert!(!receipt.r1_acceptance_complete);
    assert!(!receipt.runbook_executed);
    assert_eq!(G8lS548DisplayTestPatternState::default().receipt(), None);
}

#[test]
fn source_only_gate_keeps_runtime_physical_and_r1_claims_zero() {
    assert!(SOURCE.contains("S548_SUPPORTED_PROFILE_RUNTIME_OBSERVATIONS: usize = 0"));
    assert!(SOURCE.contains("S548_PHYSICAL_OBSERVATIONS: usize = 0"));
    assert!(SOURCE.contains("S548_PHYSICAL_OR_DEVICE_OPERATIONS: usize = 0"));
    assert!(SOURCE.contains("S548_HARDWARE_PRESENT: bool = false"));
    assert!(SOURCE.contains("S548_BOOT_TO_UI_PHYSICALLY_OBSERVED: bool = false"));
    assert!(SOURCE.contains("S548_R1_ACCEPTANCE_COMPLETE: bool = false"));
    assert!(SOURCE.contains("RUNBOOK_EXECUTED_IN_S548: bool = false"));
    assert!(SOURCE.contains("S548_S540_PHYSICAL_VERDICT_RETAINED_RED: bool = true"));
    assert!(SOURCE.contains("S548_S543_PHYSICAL_VERDICT_RETAINED_RED: bool = true"));
}
snippet sha256: 5a50432d0342file sha256: 5a50432d0342
03 · Kapı kimlik kaydı

Operations sıra, kimlik ve başlık bağı

tam Operations kaydıL3027–L3088
website/src/lib/operations.ts::g8l-s548-r1-display-test-pattern-framebuffer-capability-binding
  {
    id: "g8l-s548-r1-display-test-pattern-framebuffer-capability-binding",
    date: "2026-08-30",
    sequence: 548,
    status: "passed",
    umbrella_status: "partial",
    title: "S548 · R1 ekran: test deseni ve FramebufferCap bağlama modeli",
    summary:
      "S548 kaynak/host model kapısı PASS'tir: M4.4 FramebufferCap modelinin PixelFormat ve FramebufferRights şekillerine bağlı deterministik bir ekran test deseni renderer'ı modellenmiştir. Renk barları (8 bar), dama tahtası (ayarlanabilir hücre), gradyan ve çerçeve+artı desenleri; açık pitch işleme (pitch, genişlik×bpp'yi aşabilir), BGRA8888 ve RGB565 paketleme, sınır denetimli piksel yazımı ve üretilen tamponun FNV-1a 64 checksum'ı ile alloc::vec::Vec<u32> içine çizilir; 720x1280 ve 64x64 durumları için exact checksum'lar sabitlenmiştir. Grant hak denetimi fail-closed'dur: WRITE zorunludur, VSYNC/DMA gerekli değildir ve cap sınırını aşan grant reddedilir. Focused 21/21 PASS'tir; hiçbir MMIO, mailbox, gerçek framebuffer eşlemesi veya production çağrı noktası yoktur. S540 ve S543 fiziksel raw/verdict değişmez RED kalır; physical observation=0, RUNBOOK_EXECUTED_IN_S548=NO, Boot-to-UI=false ve R1 acceptance=false'dur. S549, S548 geometri/format şekillerini tüketen host-only RP1 DSI host register haritası ve D-PHY zamanlama modeli kapısıdır.",
    evidence: [
      "S548, S547'den ayrı kaynak modülü, 21-test focused binary, proof, status manifest, Operations kaydı ve complete Code kartına sahiptir; production callsite eklenmemiştir.",
      "Dar S548 source/host status=PASS; R1 umbrella=PARTIAL, R1 aşaması 2 (ekran, dokunma ve temel UI); S540 ve S543 physical gate status=RED olarak ayrı tutulur.",
      "Model M4.4 PixelFormat (Bgra8888=4 B/px, Rgb565=2 B/px) ve FramebufferRights (READ/WRITE/VSYNC/DMA bitmask) şekillerini kernel no_std ve host simülasyonda aynı derlenecek biçimde yansıtır.",
      "G8lS548FramebufferBinding cap_id, width, height, pitch_bytes, format, cap_max_rights ve granted_rights taşır; render pitch_bytes×height/4 kelimelik Vec<u32> üretir.",
      "Piksel (x,y) byte ofseti y×pitch+x×bpp'dir; RGB565 için yarım-kelime kayması (ofset%4)×8'dir; pitch dolgu kelimeleri hiç yazılmaz, sıfır kalır ve checksum'a dahildir.",
      "BGRA8888 paketi 0xff<<24|R<<16|G<<8|B (bellek sırası B,G,R,A); RGB565 paketi R5<<11|G6<<5|B5'tir.",
      "Dört desen tablo güdümlüdür: renk barları (bar=x×8/width, beyaz-sarı-camgöbeği-yeşil-macenta-kırmızı-mavi-siyah), dama tahtası ((x/cell+y/cell) çift ise beyaz), gradyan (r=x×255/(w-1), g=y×255/(h-1), b=255-r), çerçeve+artı (4 px beyaz çerçeve, kırmızı orta sütun/satır, 0x202020 zemin).",
      "Sabitlenen FNV-1a 64 checksum'lar bağımsız referans uygulamasıyla çapraz doğrulanmıştır: 720x1280 BGRA8888 renk barları 0x7c544deb81579325, 720x1280 RGB565 gradyan 0x2c3b445594879b71, 720x1280 BGRA8888 dama(40) 0xbb4f86efb3eb0325, 64x64 BGRA8888 dama(8) 0xa5469e63bfa70325, 64x64 RGB565 renk barları 0x8e562bac11284b25, 64x64 BGRA8888 çerçeve+artı 0xf748ab56fde2f1b2, 64x64 BGRA8888 gradyan 0x10b5b99e81f4aef5.",
      "Pitch dolgulu durumlar da sabitlenmiştir: 64x64 BGRA8888 pitch 320 renk barları 0xe6453365e4503325 ve 64x64 RGB565 pitch 192 renk barları 0xde434cdc99b58b25; dolgu checksum'ı değiştirir, piksel içeriğini değiştirmez.",
      "Hak denetimi fail-closed'dur: granted_rights cap_max_rights'ı aşarsa GrantExceedsCapability, WRITE yoksa WriteRightMissing döner; READ, VSYNC veya DMA tek başına veya birlikte WRITE'ın yerini tutmaz; VSYNC ve DMA gerekli değildir.",
      "Geometri denetimi fail-closed'dur: sıfır boyut, 4096 üstü boyut, 4'e hizasız pitch, satır byte'ının altındaki pitch, 16384 üstü pitch ve 64 MiB üstü veya checked çarpım taşması reddedilir; 4096x4096x4 tam sınırda kabul edilir.",
      "Desen parametreleri fail-closed'dur: 8 genişliğin altında renk barları, 0 veya kareyi aşan dama hücresi ve 9x9 altı çerçeve+artı reddedilir.",
      "Piksel okuma/yazma sınır denetimlidir: geometri dışı koordinat PixelOutOfBounds, tampon uzunluğunu aşan kelime BufferIndexOutOfRange döner; RGB565 yazımı yalnız kendi yarım-kelimesine dokunur.",
      "On beş hata kodu sıfırdan farklı ve benzersizdir; exact tekrar aynı receipt ile Retained döner, yayın sonrası farklı desen, cap_id veya hak kümesi PublishedStateDrift ile reddedilir.",
      "Focused target 1 grup / 21 passed / 0 failed / 0 ignored / 0 filtered verdi.",
      "Implementation 21461 B / eee6f8da27a9fb74c9590c8de21ea6c9409cdb2a2353ea1327d5a1dbee4baceb; focused test 27193 B / 5a50432d03423c286321f049818dda3fb5ca74234e2d7d54408e9749888f9f53 SHA-256'dır.",
      "Proof 5612 B'dir.",
      "S540 immutable raw 20525 B ve S543 immutable raw 20509 B byte-exact korunur; her ikisi RED, automatic promotion=false ve rerun=false'dur.",
      "S548 sırasında panel, DSI host, mailbox, MMIO, gerçek framebuffer eşlemesi, SD write/read-back/eject, UART open/capture, power transition veya yeni immutable raw üretimi yapılmadı.",
      "RUNBOOK_EXECUTED_IN_S548=NO; supported-profile runtime observations=0, physical observations=0, hardware present=false, Boot-to-UI physically observed=false ve R1 acceptance=false'dur.",
      "S549 yalnız host üzerinde RP1 DSI host register haritasını ve D-PHY zamanlama modelini S548 geometri/format şekilleri üzerinden modelleyecektir; aygıt veya fiziksel koşu yetkisi değildir.",
    ],
    commands: [
      "CARGO_INCREMENTAL=0 cargo test -p aselsan_microkernel_simulation --test g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_migration_lifecycle_s548_r1_display_test_pattern_framebuffer_capability_binding -- --test-threads=1",
    ],
    terminalSessions: [
      {
        id: "s548-focused",
        title: "S548 ekran test deseni ve FramebufferCap bağlama focused kabulü",
        commandLines: [
          "CARGO_INCREMENTAL=0 cargo test -p aselsan_microkernel_simulation --test g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_migration_lifecycle_s548_r1_display_test_pattern_framebuffer_capability_binding -- --test-threads=1",
        ],
        outputLines: [
          "test result: ok. 21 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.05s",
          "S548 focused=1 group / 21 passed / 0 failed",
          "hardware=none physical=0 runbook=NO",
        ],
        exitCode: 0,
        outputMode: "complete",
      },
    ],
    terminalSessionsNote:
      "S548 kaynak/host model PASS'tir; supported-profile runtime veya fiziksel PASS değildir. S540 ve S543 RED raw ve kararları değişmez.",
    limitations: [
      "S548 yalnız host üzerinde derlenen ve focused testle sürülen bir modeldir; hiçbir donanım/panel/modem/board gözlemi yoktur.",
      "Renderer hiçbir MMIO, mailbox veya gerçek framebuffer eşlemesine dokunmaz; üretilen Vec<u32> tamponu yalnız checksum ve piksel assertion'ları ile doğrulanır.",
      "Modül hiçbir boot, IRQ, scheduler veya driver yoluna bağlanmamıştır; production callsite wired=false'dur.",
      "S540 ve S543 fiziksel RED immutable kalır; S546 üçüncü fiziksel koşu ayrı bir kapıdır ve kararı burada varsayılmaz.",
      "Boot-to-UI fiziksel olarak gözlenmedi; Boot-to-UI ve R1 acceptance false kalır.",
      "S549 host-only RP1 DSI host register haritası ve D-PHY zamanlama modeli tamamlanmadan panel yolu için yeni bir fiziksel aday yoktur.",
    ],
  },
snippet sha256: f1a6c5b66334file sha256: 9726dbf00f84
Focused test komutu
CARGO_INCREMENTAL=0 cargo test -p aselsan_microkernel_simulation --test g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_migration_lifecycle_s548_r1_display_test_pattern_framebuffer_capability_binding -- --test-threads=1
proof: docs/M8.1-RPi5-G8l-S548-R1-Display-Test-Pattern-Framebuffer-Capability-Binding-Proof.md
Registry schema v5 · generator website/scripts/generate-code-gates.mjs · Tam SHA-256: 91d38c7b6222f0b4c117be786454853543da55a160e543d9b951057cc20dcc06