ASELSANMicrokernel
S551 · SOURCE-BOUND GATE EVIDENCE

S551 · R1 ekran: Goodix GT911 dokunmatik I2C rapor çözücü modeli

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

S551Focused kod testiOperations id exactsource SHA exacttest target exact

operation: g8l-s551-r1-gt911-touch-i2c-report-decoder-model

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–L544
kernel/src/g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_migration_lifecycle_s551_r1_gt911_touch_i2c_report_decoder_model.rs::S551 r1 gt911 touch i2c report decoder model implementation
#![allow(dead_code)]

//! S551 models the Goodix GT911 capacitive touch controller report protocol
//! as pure decode logic for the R1 720x1280 panel.
//!
//! The model covers the I2C slave addresses `0x5D`/`0x14`, the coordinate
//! status register `0x814E` (buffer-ready bit 7, large-detect bit 6, touch
//! count bits 0..3), the eight-byte per-point records starting at `0x814F`
//! (track id, x lo/hi, y lo/hi, size lo/hi, reserved), the configuration
//! block `0x8047..=0x80FE` with its 8-bit sum-complement checksum at `0x80FF`,
//! the ASCII product id at `0x8140` (`"911"`), an axis swap/invert transform
//! and the status write-back that clears `0x814E` to `0` after a frame is
//! consumed.  The decoder produces a `G8lS551TouchFrame` from a byte slice
//! and fails closed on a missing buffer-ready bit, a touch count above five,
//! out-of-range coordinates, a bad configuration checksum, a truncated or
//! oversized frame, a non-zero reserved byte, and unordered track ids.
//!
//! This gate is a source/host model only.  No I2C controller, no touch panel
//! and no board exist for it; nothing here performs a device operation, is
//! wired into a boot, IRQ, scheduler or driver path, or claims a physical
//! observation.  S540 and S543 physical verdicts remain immutable RED.
//!
//! Predecessor: S550 (ILI9881 panel DCS init sequence model).
//! Next gate: S552 (input focus and touch event routing model).

use alloc::vec::Vec;

pub const S551_SEQUENCE: usize = 551;
pub const S551_EXPECTED_PREDECESSOR: usize = 550;
pub const S551_R1_STAGE: u8 = 2;
pub const S551_R1_RANGE_FIRST: usize = 536;
pub const S551_R1_RANGE_LAST: usize = 568;
pub const S551_SUPPORTED_PROFILE_RUNTIME_OBSERVATIONS: usize = 0;
pub const S551_PHYSICAL_OBSERVATIONS: usize = 0;
pub const S551_PHYSICAL_OR_DEVICE_OPERATIONS: usize = 0;
pub const S551_SD_WRITES: usize = 0;
pub const S551_UART_OPENS: usize = 0;
pub const S551_POWER_TRANSITIONS: usize = 0;
pub const S551_NEW_IMMUTABLE_RAW_CAPTURES: usize = 0;
pub const S551_S540_PHYSICAL_VERDICT_RETAINED_RED: bool = true;
pub const S551_S543_PHYSICAL_VERDICT_RETAINED_RED: bool = true;
pub const S551_AUTOMATIC_PROMOTION: bool = false;
pub const S551_BOOT_TO_UI_PHYSICALLY_OBSERVED: bool = false;
pub const S551_HARDWARE_PRESENT: bool = false;
pub const S551_R1_ACCEPTANCE_COMPLETE: bool = false;
pub const RUNBOOK_EXECUTED_IN_S551: bool = false;

/// GT911 7-bit I2C slave addresses selected by the INT pin level at reset.
pub const S551_GT911_I2C_ADDRESS_PRIMARY: u8 = 0x5D;
pub const S551_GT911_I2C_ADDRESS_SECONDARY: u8 = 0x14;
/// Coordinate status register: bit 7 buffer ready, bit 6 large detect,
/// bits 0..3 touch count.
pub const S551_GT911_STATUS_REGISTER: u16 = 0x814E;
pub const S551_GT911_STATUS_BUFFER_READY_BIT: u8 = 0x80;
pub const S551_GT911_STATUS_LARGE_DETECT_BIT: u8 = 0x40;
pub const S551_GT911_STATUS_TOUCH_COUNT_MASK: u8 = 0x0F;
/// Value written back to the status register after a frame is consumed.
pub const S551_GT911_STATUS_WRITE_BACK_VALUE: u8 = 0x00;
/// First per-point record register; records are eight bytes each.
pub const S551_GT911_POINT_BASE_REGISTER: u16 = 0x814F;
pub const S551_GT911_POINT_RECORD_BYTES: usize = 8;
pub const S551_GT911_MAX_POINTS: usize = 5;
pub const S551_GT911_MAX_TRACK_ID: u8 = 9;
/// Largest report the decoder accepts: status byte plus five records.
pub const S551_GT911_MAX_REPORT_BYTES: usize =
    1 + S551_GT911_MAX_POINTS * S551_GT911_POINT_RECORD_BYTES;
/// Configuration block and checksum registers.
pub const S551_GT911_CONFIG_FIRST_REGISTER: u16 = 0x8047;
pub const S551_GT911_CONFIG_LAST_REGISTER: u16 = 0x80FE;
pub const S551_GT911_CONFIG_CHECKSUM_REGISTER: u16 = 0x80FF;
pub const S551_GT911_CONFIG_BYTES: usize =
    (S551_GT911_CONFIG_LAST_REGISTER - S551_GT911_CONFIG_FIRST_REGISTER + 1) as usize;
/// Offsets inside the configuration block (relative to `0x8047`).
pub const S551_GT911_CONFIG_OFFSET_VERSION: usize = 0x00;
pub const S551_GT911_CONFIG_OFFSET_X_MAX_LO: usize = 0x01;
pub const S551_GT911_CONFIG_OFFSET_X_MAX_HI: usize = 0x02;
pub const S551_GT911_CONFIG_OFFSET_Y_MAX_LO: usize = 0x03;
pub const S551_GT911_CONFIG_OFFSET_Y_MAX_HI: usize = 0x04;
pub const S551_GT911_CONFIG_OFFSET_TOUCH_NUMBER: usize = 0x05;
pub const S551_GT911_CONFIG_OFFSET_MODULE_SWITCH1: usize = 0x06;
/// Module_Switch1 bit 3 selects the controller-side X/Y exchange.
pub const S551_GT911_MODULE_SWITCH1_X2Y_BIT: u8 = 0x08;
pub const S551_GT911_CONFIG_VERSION: u8 = 0x41;
/// Product id registers `0x8140..=0x8143` hold ASCII `"911"` plus NUL.
pub const S551_GT911_PRODUCT_ID_REGISTER: u16 = 0x8140;
pub const S551_GT911_PRODUCT_ID: [u8; 4] = *b"911\0";
/// R1 panel geometry and the inclusive coordinate limits.
pub const S551_PANEL_WIDTH: u16 = 720;
pub const S551_PANEL_HEIGHT: u16 = 1280;
pub const S551_MAX_X: u16 = S551_PANEL_WIDTH - 1;
pub const S551_MAX_Y: u16 = S551_PANEL_HEIGHT - 1;

/// Raw point exactly as the controller records it (before axis transform).
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct G8lS551RawTouchPoint {
    pub track_id: u8,
    pub x: u16,
    pub y: u16,
    pub size: u16,
}

/// Decoded point in panel coordinates (after swap/invert).
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct G8lS551TouchPoint {
    pub track_id: u8,
    pub x: u16,
    pub y: u16,
    pub size: u16,
}

#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct G8lS551AxisTransform {
    pub swap_xy: bool,
    pub invert_x: bool,
    pub invert_y: bool,
}

#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct G8lS551TouchFrame {
    pub points: [G8lS551TouchPoint; S551_GT911_MAX_POINTS],
    pub count: u8,
    pub large_detect: bool,
}

impl G8lS551TouchFrame {
    pub fn active_points(&self) -> &[G8lS551TouchPoint] {
        let count = (self.count as usize).min(S551_GT911_MAX_POINTS);
        &self.points[..count]
    }
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct G8lS551TouchI2cReportDecoderReceipt {
    pub sequence: usize,
    pub predecessor_sequence: usize,
    pub r1_stage: u8,
    pub i2c_address: u8,
    pub product_id: [u8; 4],
    pub config_bytes: usize,
    pub config_checksum: u8,
    pub config_x_max: u16,
    pub config_y_max: u16,
    pub config_touch_number: u8,
    pub transform: G8lS551AxisTransform,
    pub status_register: u16,
    pub status_before: u8,
    pub status_after_write_back: u8,
    pub report_bytes: usize,
    pub frame: G8lS551TouchFrame,
    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 runbook_executed: bool,
}

#[derive(Debug)]
pub struct G8lS551TouchI2cReportDecoderState {
    receipt: Option<G8lS551TouchI2cReportDecoderReceipt>,
    status_shadow: u8,
    write_backs: usize,
}

impl G8lS551TouchI2cReportDecoderState {
    pub const fn new() -> Self {
        Self {
            receipt: None,
            status_shadow: S551_GT911_STATUS_WRITE_BACK_VALUE,
            write_backs: 0,
        }
    }

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

    /// Modelled content of `0x814E` after the last service call.
    pub const fn status_register_shadow(&self) -> u8 {
        self.status_shadow
    }

    /// Number of modelled write-backs to `0x814E`.
    pub const fn write_backs(&self) -> usize {
        self.write_backs
    }
}

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

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum G8lS551TouchI2cReportDecoderOutcome {
    FrameDecoded(G8lS551TouchI2cReportDecoderReceipt),
    FrameRetained(G8lS551TouchI2cReportDecoderReceipt),
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum G8lS551TouchI2cReportDecoderError {
    InvalidI2cAddress,
    ProductIdMismatch,
    ConfigLengthMismatch,
    ConfigChecksumMismatch,
    ConfigVersionMismatch,
    ConfigResolutionMismatch,
    ConfigTouchNumberMismatch,
    AxisSwapConfigMismatch,
    TruncatedFrame,
    FrameLengthMismatch,
    BufferNotReady,
    TouchCountOverflow,
    TrackIdOutOfRange,
    TrackIdNotAscending,
    ReservedByteNonZero,
    CoordinateOutOfRange,
    ArithmeticOverflow,
    PublishedStateDrift,
}

impl G8lS551TouchI2cReportDecoderError {
    pub const fn diagnostic_code(self) -> u64 {
        match self {
            Self::InvalidI2cAddress => 1,
            Self::ProductIdMismatch => 2,
            Self::ConfigLengthMismatch => 3,
            Self::ConfigChecksumMismatch => 4,
            Self::ConfigVersionMismatch => 5,
            Self::ConfigResolutionMismatch => 6,
            Self::ConfigTouchNumberMismatch => 7,
            Self::AxisSwapConfigMismatch => 8,
            Self::TruncatedFrame => 9,
            Self::FrameLengthMismatch => 10,
            Self::BufferNotReady => 11,
            Self::TouchCountOverflow => 12,
            Self::TrackIdOutOfRange => 13,
            Self::TrackIdNotAscending => 14,
            Self::ReservedByteNonZero => 15,
            Self::CoordinateOutOfRange => 16,
            Self::ArithmeticOverflow => 17,
            Self::PublishedStateDrift => 18,
        }
    }
}

pub const fn gt911_i2c_address_is_valid(address: u8) -> bool {
    address == S551_GT911_I2C_ADDRESS_PRIMARY || address == S551_GT911_I2C_ADDRESS_SECONDARY
}

/// 8-bit sum-complement over the `0x8047..=0x80FE` block: the byte that makes
/// `sum(config) + checksum == 0 (mod 256)`.
pub fn compute_gt911_config_checksum(
    config: &[u8],
) -> Result<u8, G8lS551TouchI2cReportDecoderError> {
    if config.len() != S551_GT911_CONFIG_BYTES {
        return Err(G8lS551TouchI2cReportDecoderError::ConfigLengthMismatch);
    }
    let mut sum: u32 = 0;
    for byte in config {
        sum = sum
            .checked_add(*byte as u32)
            .ok_or(G8lS551TouchI2cReportDecoderError::ArithmeticOverflow)?;
    }
    let low = sum & 0xFF;
    Ok(((0x100 - low) & 0xFF) as u8)
}

/// Canonical R1 configuration block: version `0x41`, 720x1280 output range,
/// five touch points, Module_Switch1 with the X2Y bit chosen by `swap_xy`,
/// remaining bytes zero.
pub fn canonical_s551_config(swap_xy: bool) -> [u8; S551_GT911_CONFIG_BYTES] {
    let mut config = [0u8; S551_GT911_CONFIG_BYTES];
    config[S551_GT911_CONFIG_OFFSET_VERSION] = S551_GT911_CONFIG_VERSION;
    let [x_lo, x_hi] = S551_PANEL_WIDTH.to_le_bytes();
    let [y_lo, y_hi] = S551_PANEL_HEIGHT.to_le_bytes();
    config[S551_GT911_CONFIG_OFFSET_X_MAX_LO] = x_lo;
    config[S551_GT911_CONFIG_OFFSET_X_MAX_HI] = x_hi;
    config[S551_GT911_CONFIG_OFFSET_Y_MAX_LO] = y_lo;
    config[S551_GT911_CONFIG_OFFSET_Y_MAX_HI] = y_hi;
    config[S551_GT911_CONFIG_OFFSET_TOUCH_NUMBER] = S551_GT911_MAX_POINTS as u8;
    config[S551_GT911_CONFIG_OFFSET_MODULE_SWITCH1] = if swap_xy {
        S551_GT911_MODULE_SWITCH1_X2Y_BIT
    } else {
        0
    };
    config
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct G8lS551ConfigSummary {
    pub checksum: u8,
    pub x_max: u16,
    pub y_max: u16,
    pub touch_number: u8,
    pub swap_xy: bool,
}

/// Validates the configuration block against the R1 panel and the checksum
/// byte read from `0x80FF`.
pub fn validate_gt911_config(
    config: &[u8],
    checksum_register: u8,
) -> Result<G8lS551ConfigSummary, G8lS551TouchI2cReportDecoderError> {
    let checksum = compute_gt911_config_checksum(config)?;
    if checksum != checksum_register {
        return Err(G8lS551TouchI2cReportDecoderError::ConfigChecksumMismatch);
    }
    if config[S551_GT911_CONFIG_OFFSET_VERSION] != S551_GT911_CONFIG_VERSION {
        return Err(G8lS551TouchI2cReportDecoderError::ConfigVersionMismatch);
    }
    let x_max = u16::from_le_bytes([
        config[S551_GT911_CONFIG_OFFSET_X_MAX_LO],
        config[S551_GT911_CONFIG_OFFSET_X_MAX_HI],
    ]);
    let y_max = u16::from_le_bytes([
        config[S551_GT911_CONFIG_OFFSET_Y_MAX_LO],
        config[S551_GT911_CONFIG_OFFSET_Y_MAX_HI],
    ]);
    if x_max != S551_PANEL_WIDTH || y_max != S551_PANEL_HEIGHT {
        return Err(G8lS551TouchI2cReportDecoderError::ConfigResolutionMismatch);
    }
    let touch_number = config[S551_GT911_CONFIG_OFFSET_TOUCH_NUMBER];
    if touch_number as usize != S551_GT911_MAX_POINTS {
        return Err(G8lS551TouchI2cReportDecoderError::ConfigTouchNumberMismatch);
    }
    let swap_xy =
        config[S551_GT911_CONFIG_OFFSET_MODULE_SWITCH1] & S551_GT911_MODULE_SWITCH1_X2Y_BIT != 0;
    Ok(G8lS551ConfigSummary {
        checksum,
        x_max,
        y_max,
        touch_number,
        swap_xy,
    })
}

/// Encodes one raw point into the eight-byte GT911 record layout.
pub const fn encode_gt911_point(
    point: G8lS551RawTouchPoint,
) -> [u8; S551_GT911_POINT_RECORD_BYTES] {
    let x = point.x.to_le_bytes();
    let y = point.y.to_le_bytes();
    let size = point.size.to_le_bytes();
    [
        point.track_id,
        x[0],
        x[1],
        y[0],
        y[1],
        size[0],
        size[1],
        0x00,
    ]
}

/// Encodes a status byte plus records as the controller would present them
/// from `0x814E` onwards.  Used by the focused test to build wire frames.
pub fn encode_gt911_report(
    points: &[G8lS551RawTouchPoint],
    buffer_ready: bool,
    large_detect: bool,
) -> Result<Vec<u8>, G8lS551TouchI2cReportDecoderError> {
    if points.len() > S551_GT911_MAX_POINTS {
        return Err(G8lS551TouchI2cReportDecoderError::TouchCountOverflow);
    }
    let mut status = points.len() as u8 & S551_GT911_STATUS_TOUCH_COUNT_MASK;
    if buffer_ready {
        status |= S551_GT911_STATUS_BUFFER_READY_BIT;
    }
    if large_detect {
        status |= S551_GT911_STATUS_LARGE_DETECT_BIT;
    }
    let mut report = Vec::with_capacity(S551_GT911_MAX_REPORT_BYTES);
    report.push(status);
    for point in points {
        report.extend_from_slice(&encode_gt911_point(*point));
    }
    Ok(report)
}

/// Applies the controller-native to panel transform and range-checks.
pub fn transform_gt911_point(
    raw: G8lS551RawTouchPoint,
    transform: G8lS551AxisTransform,
) -> Result<G8lS551TouchPoint, G8lS551TouchI2cReportDecoderError> {
    let (mut x, mut y) = if transform.swap_xy {
        (raw.y, raw.x)
    } else {
        (raw.x, raw.y)
    };
    if x > S551_MAX_X || y > S551_MAX_Y {
        return Err(G8lS551TouchI2cReportDecoderError::CoordinateOutOfRange);
    }
    if transform.invert_x {
        x = S551_MAX_X
            .checked_sub(x)
            .ok_or(G8lS551TouchI2cReportDecoderError::ArithmeticOverflow)?;
    }
    if transform.invert_y {
        y = S551_MAX_Y
            .checked_sub(y)
            .ok_or(G8lS551TouchI2cReportDecoderError::ArithmeticOverflow)?;
    }
    Ok(G8lS551TouchPoint {
        track_id: raw.track_id,
        x,
        y,
        size: raw.size,
    })
}

/// Pure decoder: `report[0]` is the status register, followed by
/// `count` eight-byte records.  The slice must be exactly
/// `1 + count * 8` bytes long.
pub fn decode_gt911_report(
    report: &[u8],
    transform: G8lS551AxisTransform,
) -> Result<G8lS551TouchFrame, G8lS551TouchI2cReportDecoderError> {
    let status = *report
        .first()
        .ok_or(G8lS551TouchI2cReportDecoderError::TruncatedFrame)?;
    if status & S551_GT911_STATUS_BUFFER_READY_BIT == 0 {
        return Err(G8lS551TouchI2cReportDecoderError::BufferNotReady);
    }
    let count = status & S551_GT911_STATUS_TOUCH_COUNT_MASK;
    if count as usize > S551_GT911_MAX_POINTS {
        return Err(G8lS551TouchI2cReportDecoderError::TouchCountOverflow);
    }
    let expected_len = (count as usize)
        .checked_mul(S551_GT911_POINT_RECORD_BYTES)
        .and_then(|records| records.checked_add(1))
        .ok_or(G8lS551TouchI2cReportDecoderError::ArithmeticOverflow)?;
    if report.len() < expected_len {
        return Err(G8lS551TouchI2cReportDecoderError::TruncatedFrame);
    }
    if report.len() != expected_len {
        return Err(G8lS551TouchI2cReportDecoderError::FrameLengthMismatch);
    }
    let mut frame = G8lS551TouchFrame {
        points: [G8lS551TouchPoint::default(); S551_GT911_MAX_POINTS],
        count,
        large_detect: status & S551_GT911_STATUS_LARGE_DETECT_BIT != 0,
    };
    let mut previous_track: Option<u8> = None;
    for (index, record) in report[1..]
        .chunks_exact(S551_GT911_POINT_RECORD_BYTES)
        .enumerate()
    {
        let track_id = record[0];
        if track_id > S551_GT911_MAX_TRACK_ID {
            return Err(G8lS551TouchI2cReportDecoderError::TrackIdOutOfRange);
        }
        if let Some(previous) = previous_track {
            if track_id <= previous {
                return Err(G8lS551TouchI2cReportDecoderError::TrackIdNotAscending);
            }
        }
        if record[7] != 0 {
            return Err(G8lS551TouchI2cReportDecoderError::ReservedByteNonZero);
        }
        let raw = G8lS551RawTouchPoint {
            track_id,
            x: u16::from_le_bytes([record[1], record[2]]),
            y: u16::from_le_bytes([record[3], record[4]]),
            size: u16::from_le_bytes([record[5], record[6]]),
        };
        frame.points[index] = transform_gt911_point(raw, transform)?;
        previous_track = Some(track_id);
    }
    Ok(frame)
}

/// Full model service: validates the controller identity and configuration,
/// decodes one report, models the `0x814E` write-back to `0`, and retains an
/// exact receipt.  Exact replay is retained; any divergence after the first
/// publication fails closed.
pub fn service_s551_model_decode_touch_report(
    state: &mut G8lS551TouchI2cReportDecoderState,
    i2c_address: u8,
    product_id: [u8; 4],
    config: &[u8],
    config_checksum_register: u8,
    transform: G8lS551AxisTransform,
    report: &[u8],
) -> Result<G8lS551TouchI2cReportDecoderOutcome, G8lS551TouchI2cReportDecoderError> {
    if !gt911_i2c_address_is_valid(i2c_address) {
        return Err(G8lS551TouchI2cReportDecoderError::InvalidI2cAddress);
    }
    if product_id != S551_GT911_PRODUCT_ID {
        return Err(G8lS551TouchI2cReportDecoderError::ProductIdMismatch);
    }
    let summary = validate_gt911_config(config, config_checksum_register)?;
    if summary.swap_xy != transform.swap_xy {
        return Err(G8lS551TouchI2cReportDecoderError::AxisSwapConfigMismatch);
    }
    let frame = decode_gt911_report(report, transform)?;
    let status_before = report[0];
    let receipt = G8lS551TouchI2cReportDecoderReceipt {
        sequence: S551_SEQUENCE,
        predecessor_sequence: S551_EXPECTED_PREDECESSOR,
        r1_stage: S551_R1_STAGE,
        i2c_address,
        product_id,
        config_bytes: config.len(),
        config_checksum: summary.checksum,
        config_x_max: summary.x_max,
        config_y_max: summary.y_max,
        config_touch_number: summary.touch_number,
        transform,
        status_register: S551_GT911_STATUS_REGISTER,
        status_before,
        status_after_write_back: S551_GT911_STATUS_WRITE_BACK_VALUE,
        report_bytes: report.len(),
        frame,
        hardware_present: S551_HARDWARE_PRESENT,
        s540_physical_verdict_retained_red: S551_S540_PHYSICAL_VERDICT_RETAINED_RED,
        s543_physical_verdict_retained_red: S551_S543_PHYSICAL_VERDICT_RETAINED_RED,
        automatic_promotion: S551_AUTOMATIC_PROMOTION,
        supported_profile_runtime_observations: S551_SUPPORTED_PROFILE_RUNTIME_OBSERVATIONS,
        physical_observations: S551_PHYSICAL_OBSERVATIONS,
        runbook_executed: RUNBOOK_EXECUTED_IN_S551,
    };
    if let Some(published) = state.receipt {
        if published != receipt {
            return Err(G8lS551TouchI2cReportDecoderError::PublishedStateDrift);
        }
        return Ok(G8lS551TouchI2cReportDecoderOutcome::FrameRetained(
            published,
        ));
    }
    // Write-back model: the consumed status byte is replaced by `0x00` so the
    // controller may publish the next buffer.
    state.status_shadow = S551_GT911_STATUS_WRITE_BACK_VALUE;
    state.write_backs = state
        .write_backs
        .checked_add(1)
        .ok_or(G8lS551TouchI2cReportDecoderError::ArithmeticOverflow)?;
    state.receipt = Some(receipt);
    Ok(G8lS551TouchI2cReportDecoderOutcome::FrameDecoded(receipt))
}
snippet sha256: f55ae2ccc19cfile sha256: f55ae2ccc19c
02 · Doğrulayan test kodu

Operations komutuna bağlı focused test

tam dosyaL1–L759
simulation/tests/g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_migration_lifecycle_s551_r1_gt911_touch_i2c_report_decoder_model.rs::S551 r1 gt911 touch i2c report decoder model focused tests
use aselsan_microkernel_simulation::g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_migration_lifecycle_s551_r1_gt911_touch_i2c_report_decoder_model::*;
use std::collections::BTreeSet;

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

const IDENTITY: G8lS551AxisTransform = G8lS551AxisTransform {
    swap_xy: false,
    invert_x: false,
    invert_y: false,
};

fn raw(track_id: u8, x: u16, y: u16, size: u16) -> G8lS551RawTouchPoint {
    G8lS551RawTouchPoint {
        track_id,
        x,
        y,
        size,
    }
}

fn report(points: &[G8lS551RawTouchPoint]) -> Vec<u8> {
    encode_gt911_report(points, true, false).unwrap()
}

fn config_and_checksum(swap_xy: bool) -> ([u8; S551_GT911_CONFIG_BYTES], u8) {
    let config = canonical_s551_config(swap_xy);
    let checksum = compute_gt911_config_checksum(&config).unwrap();
    (config, checksum)
}

fn decode(
    state: &mut G8lS551TouchI2cReportDecoderState,
    transform: G8lS551AxisTransform,
    report: &[u8],
) -> Result<G8lS551TouchI2cReportDecoderOutcome, G8lS551TouchI2cReportDecoderError> {
    let (config, checksum) = config_and_checksum(transform.swap_xy);
    service_s551_model_decode_touch_report(
        state,
        S551_GT911_I2C_ADDRESS_PRIMARY,
        S551_GT911_PRODUCT_ID,
        &config,
        checksum,
        transform,
        report,
    )
}

fn single_point_report() -> Vec<u8> {
    report(&[raw(0, 100, 200, 12)])
}

#[test]
fn sequence_scope_and_nonpromotion_are_exact() {
    assert_eq!(S551_SEQUENCE, 551);
    assert_eq!(S551_EXPECTED_PREDECESSOR, 550);
    assert_eq!(S551_R1_STAGE, 2);
    assert_eq!(S551_R1_RANGE_FIRST, 536);
    assert_eq!(S551_R1_RANGE_LAST, 568);
    assert_eq!(S551_SUPPORTED_PROFILE_RUNTIME_OBSERVATIONS, 0);
    assert_eq!(S551_PHYSICAL_OBSERVATIONS, 0);
    assert_eq!(S551_PHYSICAL_OR_DEVICE_OPERATIONS, 0);
    assert_eq!(S551_SD_WRITES, 0);
    assert_eq!(S551_UART_OPENS, 0);
    assert_eq!(S551_POWER_TRANSITIONS, 0);
    assert_eq!(S551_NEW_IMMUTABLE_RAW_CAPTURES, 0);
    assert!(S551_S540_PHYSICAL_VERDICT_RETAINED_RED);
    assert!(S551_S543_PHYSICAL_VERDICT_RETAINED_RED);
    assert!(!S551_AUTOMATIC_PROMOTION);
    assert!(!S551_BOOT_TO_UI_PHYSICALLY_OBSERVED);
    assert!(!S551_HARDWARE_PRESENT);
    assert!(!S551_R1_ACCEPTANCE_COMPLETE);
    assert!(!RUNBOOK_EXECUTED_IN_S551);
}

#[test]
fn protocol_constants_match_the_gt911_register_map() {
    assert_eq!(S551_GT911_I2C_ADDRESS_PRIMARY, 0x5D);
    assert_eq!(S551_GT911_I2C_ADDRESS_SECONDARY, 0x14);
    assert_eq!(S551_GT911_STATUS_REGISTER, 0x814E);
    assert_eq!(S551_GT911_POINT_BASE_REGISTER, 0x814F);
    assert_eq!(S551_GT911_POINT_BASE_REGISTER, S551_GT911_STATUS_REGISTER + 1);
    assert_eq!(S551_GT911_STATUS_BUFFER_READY_BIT, 0x80);
    assert_eq!(S551_GT911_STATUS_LARGE_DETECT_BIT, 0x40);
    assert_eq!(S551_GT911_STATUS_TOUCH_COUNT_MASK, 0x0F);
    assert_eq!(S551_GT911_STATUS_WRITE_BACK_VALUE, 0x00);
    assert_eq!(S551_GT911_POINT_RECORD_BYTES, 8);
    assert_eq!(S551_GT911_MAX_POINTS, 5);
    assert_eq!(S551_GT911_MAX_REPORT_BYTES, 41);
    assert_eq!(S551_GT911_CONFIG_FIRST_REGISTER, 0x8047);
    assert_eq!(S551_GT911_CONFIG_LAST_REGISTER, 0x80FE);
    assert_eq!(S551_GT911_CONFIG_CHECKSUM_REGISTER, 0x80FF);
    assert_eq!(S551_GT911_CONFIG_BYTES, 184);
    assert_eq!(S551_GT911_PRODUCT_ID_REGISTER, 0x8140);
    assert_eq!(&S551_GT911_PRODUCT_ID[..3], b"911");
    assert_eq!(S551_GT911_PRODUCT_ID[3], 0);
    assert_eq!(S551_PANEL_WIDTH, 720);
    assert_eq!(S551_PANEL_HEIGHT, 1280);
    assert_eq!(S551_MAX_X, 719);
    assert_eq!(S551_MAX_Y, 1279);
    assert!(gt911_i2c_address_is_valid(0x5D));
    assert!(gt911_i2c_address_is_valid(0x14));
    assert!(!gt911_i2c_address_is_valid(0x5E));
}

#[test]
fn canonical_config_checksum_is_an_eight_bit_sum_complement() {
    for swap_xy in [false, true] {
        let (config, checksum) = config_and_checksum(swap_xy);
        let sum: u32 = config.iter().map(|byte| *byte as u32).sum::<u32>() + checksum as u32;
        assert_eq!(sum % 256, 0);
        assert_eq!(config[0], 0x41);
        assert_eq!(u16::from_le_bytes([config[1], config[2]]), 720);
        assert_eq!(u16::from_le_bytes([config[3], config[4]]), 1280);
        assert_eq!(config[5], 5);
        assert_eq!(config[6] & 0x08 != 0, swap_xy);
        let summary = validate_gt911_config(&config, checksum).unwrap();
        assert_eq!(summary.checksum, checksum);
        assert_eq!(summary.x_max, 720);
        assert_eq!(summary.y_max, 1280);
        assert_eq!(summary.touch_number, 5);
        assert_eq!(summary.swap_xy, swap_xy);
    }
    // Table-driven spot checks of the complement arithmetic.
    let zeros = [0u8; S551_GT911_CONFIG_BYTES];
    assert_eq!(compute_gt911_config_checksum(&zeros), Ok(0x00));
    let mut one = zeros;
    one[0] = 0x01;
    assert_eq!(compute_gt911_config_checksum(&one), Ok(0xFF));
    let all_ff = [0xFFu8; S551_GT911_CONFIG_BYTES];
    // 184 * 255 = 46920 = 0xB748 -> low byte 0x48 -> complement 0xB8.
    assert_eq!(compute_gt911_config_checksum(&all_ff), Ok(0xB8));
    assert_eq!(
        compute_gt911_config_checksum(&zeros[..183]),
        Err(G8lS551TouchI2cReportDecoderError::ConfigLengthMismatch)
    );
}

#[test]
fn single_point_report_decodes_and_write_back_clears_status() {
    let mut state = G8lS551TouchI2cReportDecoderState::new();
    assert_eq!(state.status_register_shadow(), 0);
    assert_eq!(state.write_backs(), 0);
    let wire = single_point_report();
    assert_eq!(wire.len(), 9);
    assert_eq!(wire[0], 0x81);
    let G8lS551TouchI2cReportDecoderOutcome::FrameDecoded(receipt) =
        decode(&mut state, IDENTITY, &wire).unwrap()
    else {
        panic!("first S551 frame must be decoded")
    };
    assert_eq!(state.receipt(), Some(receipt));
    assert_eq!(state.status_register_shadow(), 0x00);
    assert_eq!(state.write_backs(), 1);
    assert_eq!(receipt.sequence, 551);
    assert_eq!(receipt.predecessor_sequence, 550);
    assert_eq!(receipt.r1_stage, 2);
    assert_eq!(receipt.i2c_address, 0x5D);
    assert_eq!(receipt.product_id, *b"911\0");
    assert_eq!(receipt.config_bytes, 184);
    assert_eq!(receipt.config_x_max, 720);
    assert_eq!(receipt.config_y_max, 1280);
    assert_eq!(receipt.config_touch_number, 5);
    assert_eq!(receipt.status_register, 0x814E);
    assert_eq!(receipt.status_before, 0x81);
    assert_eq!(receipt.status_after_write_back, 0x00);
    assert_eq!(receipt.report_bytes, 9);
    assert_eq!(receipt.frame.count, 1);
    assert!(!receipt.frame.large_detect);
    assert_eq!(
        receipt.frame.active_points(),
        &[G8lS551TouchPoint {
            track_id: 0,
            x: 100,
            y: 200,
            size: 12
        }]
    );
    assert_eq!(receipt.frame.points[1], G8lS551TouchPoint::default());
    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.runbook_executed);
}

#[test]
fn five_point_report_decodes_all_records_in_track_order() {
    let points = [
        raw(0, 0, 0, 1),
        raw(1, 719, 0, 2),
        raw(2, 0, 1279, 3),
        raw(5, 719, 1279, 4),
        raw(9, 360, 640, 0xFFFF),
    ];
    let wire = report(&points);
    assert_eq!(wire.len(), 41);
    assert_eq!(wire[0], 0x85);
    let frame = decode_gt911_report(&wire, IDENTITY).unwrap();
    assert_eq!(frame.count, 5);
    assert_eq!(frame.active_points().len(), 5);
    for (index, point) in points.iter().enumerate() {
        let decoded = frame.points[index];
        assert_eq!(decoded.track_id, point.track_id);
        assert_eq!(decoded.x, point.x);
        assert_eq!(decoded.y, point.y);
        assert_eq!(decoded.size, point.size);
    }
    let mut state = G8lS551TouchI2cReportDecoderState::new();
    let G8lS551TouchI2cReportDecoderOutcome::FrameDecoded(receipt) =
        decode(&mut state, IDENTITY, &wire).unwrap()
    else {
        panic!("five-point frame must be decoded")
    };
    assert_eq!(receipt.frame, frame);
    assert_eq!(receipt.report_bytes, 41);
}

#[test]
fn zero_point_release_report_decodes_an_empty_frame() {
    let wire = report(&[]);
    assert_eq!(wire, vec![0x80]);
    let frame = decode_gt911_report(&wire, IDENTITY).unwrap();
    assert_eq!(frame.count, 0);
    assert!(frame.active_points().is_empty());
    assert_eq!(frame, G8lS551TouchFrame::default());
    let mut state = G8lS551TouchI2cReportDecoderState::new();
    assert!(matches!(
        decode(&mut state, IDENTITY, &wire),
        Ok(G8lS551TouchI2cReportDecoderOutcome::FrameDecoded(_))
    ));
    assert_eq!(state.status_register_shadow(), 0);
}

#[test]
fn exact_replay_retains_the_same_receipt() {
    let mut state = G8lS551TouchI2cReportDecoderState::new();
    let wire = single_point_report();
    let G8lS551TouchI2cReportDecoderOutcome::FrameDecoded(receipt) =
        decode(&mut state, IDENTITY, &wire).unwrap()
    else {
        panic!("first publication missing")
    };
    assert_eq!(
        decode(&mut state, IDENTITY, &wire),
        Ok(G8lS551TouchI2cReportDecoderOutcome::FrameRetained(receipt))
    );
    assert_eq!(state.write_backs(), 1);
    assert_eq!(state.receipt(), Some(receipt));
}

#[test]
fn divergent_input_after_publication_fails_closed() {
    let mut state = G8lS551TouchI2cReportDecoderState::new();
    decode(&mut state, IDENTITY, &single_point_report()).unwrap();
    let moved = report(&[raw(0, 101, 200, 12)]);
    assert_eq!(
        decode(&mut state, IDENTITY, &moved),
        Err(G8lS551TouchI2cReportDecoderError::PublishedStateDrift)
    );
    let inverted = G8lS551AxisTransform {
        invert_x: true,
        ..IDENTITY
    };
    assert_eq!(
        decode(&mut state, inverted, &single_point_report()),
        Err(G8lS551TouchI2cReportDecoderError::PublishedStateDrift)
    );
    let (config, checksum) = config_and_checksum(false);
    assert_eq!(
        service_s551_model_decode_touch_report(
            &mut state,
            S551_GT911_I2C_ADDRESS_SECONDARY,
            S551_GT911_PRODUCT_ID,
            &config,
            checksum,
            IDENTITY,
            &single_point_report(),
        ),
        Err(G8lS551TouchI2cReportDecoderError::PublishedStateDrift)
    );
    assert_eq!(state.write_backs(), 1);
}

#[test]
fn buffer_not_ready_status_is_rejected() {
    let not_ready = encode_gt911_report(&[raw(0, 1, 1, 1)], false, false).unwrap();
    assert_eq!(not_ready[0], 0x01);
    assert_eq!(
        decode_gt911_report(&not_ready, IDENTITY),
        Err(G8lS551TouchI2cReportDecoderError::BufferNotReady)
    );
    assert_eq!(
        decode_gt911_report(&[0x00], IDENTITY),
        Err(G8lS551TouchI2cReportDecoderError::BufferNotReady)
    );
    let mut state = G8lS551TouchI2cReportDecoderState::new();
    assert_eq!(
        decode(&mut state, IDENTITY, &not_ready),
        Err(G8lS551TouchI2cReportDecoderError::BufferNotReady)
    );
    assert_eq!(state.receipt(), None);
    assert_eq!(state.write_backs(), 0);
}

#[test]
fn touch_count_above_five_is_rejected() {
    for count in 6u8..=15 {
        let mut wire = vec![0x80 | count];
        wire.extend(std::iter::repeat(0u8).take(count as usize * 8));
        assert_eq!(
            decode_gt911_report(&wire, IDENTITY),
            Err(G8lS551TouchI2cReportDecoderError::TouchCountOverflow),
            "count {count}"
        );
    }
    let six = [raw(0, 0, 0, 0); 6];
    assert_eq!(
        encode_gt911_report(&six, true, false),
        Err(G8lS551TouchI2cReportDecoderError::TouchCountOverflow)
    );
}

#[test]
fn truncated_and_oversized_frames_are_rejected() {
    assert_eq!(
        decode_gt911_report(&[], IDENTITY),
        Err(G8lS551TouchI2cReportDecoderError::TruncatedFrame)
    );
    let two = report(&[raw(0, 1, 2, 3), raw(1, 4, 5, 6)]);
    assert_eq!(two.len(), 17);
    for cut in 1..two.len() {
        assert_eq!(
            decode_gt911_report(&two[..cut], IDENTITY),
            Err(G8lS551TouchI2cReportDecoderError::TruncatedFrame),
            "cut {cut}"
        );
    }
    let mut oversized = two.clone();
    oversized.push(0x00);
    assert_eq!(
        decode_gt911_report(&oversized, IDENTITY),
        Err(G8lS551TouchI2cReportDecoderError::FrameLengthMismatch)
    );
    let mut full_block = two.clone();
    full_block.resize(S551_GT911_MAX_REPORT_BYTES, 0);
    assert_eq!(
        decode_gt911_report(&full_block, IDENTITY),
        Err(G8lS551TouchI2cReportDecoderError::FrameLengthMismatch)
    );
    let mut state = G8lS551TouchI2cReportDecoderState::new();
    assert_eq!(
        decode(&mut state, IDENTITY, &two[..9]),
        Err(G8lS551TouchI2cReportDecoderError::TruncatedFrame)
    );
    assert_eq!(state.receipt(), None);
}

#[test]
fn out_of_range_coordinates_are_rejected_at_the_exact_boundary() {
    assert!(decode_gt911_report(&report(&[raw(0, 719, 1279, 1)]), IDENTITY).is_ok());
    for bad in [
        raw(0, 720, 0, 1),
        raw(0, 0, 1280, 1),
        raw(0, 720, 1280, 1),
        raw(0, 0xFFFF, 0, 1),
        raw(0, 0, 0xFFFF, 1),
    ] {
        assert_eq!(
            decode_gt911_report(&report(&[bad]), IDENTITY),
            Err(G8lS551TouchI2cReportDecoderError::CoordinateOutOfRange),
            "{bad:?}"
        );
    }
    // A later bad record still fails the whole frame.
    let mixed = report(&[raw(0, 1, 1, 1), raw(1, 2, 2, 2), raw(2, 719, 1280, 3)]);
    assert_eq!(
        decode_gt911_report(&mixed, IDENTITY),
        Err(G8lS551TouchI2cReportDecoderError::CoordinateOutOfRange)
    );
    let mut state = G8lS551TouchI2cReportDecoderState::new();
    assert_eq!(
        decode(&mut state, IDENTITY, &report(&[raw(0, 720, 0, 1)])),
        Err(G8lS551TouchI2cReportDecoderError::CoordinateOutOfRange)
    );
    assert_eq!(state.receipt(), None);
}

#[test]
fn axis_swap_and_invert_transform_coordinates() {
    let swap = G8lS551AxisTransform {
        swap_xy: true,
        invert_x: false,
        invert_y: false,
    };
    // Controller-native (x=1279, y=719) becomes panel (719, 1279).
    let swapped = transform_gt911_point(raw(3, 1279, 719, 7), swap).unwrap();
    assert_eq!((swapped.x, swapped.y, swapped.track_id, swapped.size), (719, 1279, 3, 7));
    // Without the swap the same raw point is out of range.
    assert_eq!(
        transform_gt911_point(raw(3, 1279, 719, 7), IDENTITY),
        Err(G8lS551TouchI2cReportDecoderError::CoordinateOutOfRange)
    );
    // Range is checked after the swap, before inversion.
    assert_eq!(
        transform_gt911_point(raw(0, 1280, 0, 0), swap),
        Err(G8lS551TouchI2cReportDecoderError::CoordinateOutOfRange)
    );
    let invert_both = G8lS551AxisTransform {
        swap_xy: false,
        invert_x: true,
        invert_y: true,
    };
    let inverted = transform_gt911_point(raw(0, 0, 0, 0), invert_both).unwrap();
    assert_eq!((inverted.x, inverted.y), (719, 1279));
    let inverted = transform_gt911_point(raw(0, 719, 1279, 0), invert_both).unwrap();
    assert_eq!((inverted.x, inverted.y), (0, 0));
    let inverted = transform_gt911_point(raw(0, 100, 200, 0), invert_both).unwrap();
    assert_eq!((inverted.x, inverted.y), (619, 1079));
    let swap_invert_x = G8lS551AxisTransform {
        swap_xy: true,
        invert_x: true,
        invert_y: false,
    };
    let point = transform_gt911_point(raw(0, 1000, 20, 0), swap_invert_x).unwrap();
    assert_eq!((point.x, point.y), (699, 1000));
    // The full service accepts the swap only when the config X2Y bit agrees.
    let mut state = G8lS551TouchI2cReportDecoderState::new();
    let G8lS551TouchI2cReportDecoderOutcome::FrameDecoded(receipt) =
        decode(&mut state, swap, &report(&[raw(0, 1279, 719, 1)])).unwrap()
    else {
        panic!("swapped frame must decode")
    };
    assert_eq!(receipt.transform, swap);
    assert_eq!((receipt.frame.points[0].x, receipt.frame.points[0].y), (719, 1279));
}

#[test]
fn axis_swap_must_match_the_config_x2y_bit() {
    let swap = G8lS551AxisTransform {
        swap_xy: true,
        invert_x: false,
        invert_y: false,
    };
    let (config_no_swap, checksum_no_swap) = config_and_checksum(false);
    let (config_swap, checksum_swap) = config_and_checksum(true);
    assert_ne!(checksum_no_swap, checksum_swap);
    let mut state = G8lS551TouchI2cReportDecoderState::new();
    assert_eq!(
        service_s551_model_decode_touch_report(
            &mut state,
            0x5D,
            S551_GT911_PRODUCT_ID,
            &config_no_swap,
            checksum_no_swap,
            swap,
            &single_point_report(),
        ),
        Err(G8lS551TouchI2cReportDecoderError::AxisSwapConfigMismatch)
    );
    assert_eq!(
        service_s551_model_decode_touch_report(
            &mut state,
            0x5D,
            S551_GT911_PRODUCT_ID,
            &config_swap,
            checksum_swap,
            IDENTITY,
            &single_point_report(),
        ),
        Err(G8lS551TouchI2cReportDecoderError::AxisSwapConfigMismatch)
    );
    assert_eq!(state.receipt(), None);
}

#[test]
fn bad_config_checksum_length_version_resolution_and_touch_number_are_rejected() {
    let (config, checksum) = config_and_checksum(false);
    let mut state = G8lS551TouchI2cReportDecoderState::new();
    let wire = single_point_report();
    let attempt = |state: &mut G8lS551TouchI2cReportDecoderState, config: &[u8], checksum: u8| {
        service_s551_model_decode_touch_report(
            state,
            0x5D,
            S551_GT911_PRODUCT_ID,
            config,
            checksum,
            IDENTITY,
            &wire,
        )
    };
    for wrong in [checksum.wrapping_add(1), checksum ^ 0x80, 0x00] {
        if wrong == checksum {
            continue;
        }
        assert_eq!(
            attempt(&mut state, &config, wrong),
            Err(G8lS551TouchI2cReportDecoderError::ConfigChecksumMismatch)
        );
    }
    let mut corrupted = config;
    corrupted[0x40] ^= 0x01;
    assert_eq!(
        attempt(&mut state, &corrupted, checksum),
        Err(G8lS551TouchI2cReportDecoderError::ConfigChecksumMismatch)
    );
    assert_eq!(
        attempt(&mut state, &config[..183], checksum),
        Err(G8lS551TouchI2cReportDecoderError::ConfigLengthMismatch)
    );
    let mut longer = config.to_vec();
    longer.push(0);
    assert_eq!(
        attempt(&mut state, &longer, checksum),
        Err(G8lS551TouchI2cReportDecoderError::ConfigLengthMismatch)
    );
    let mut version = config;
    version[S551_GT911_CONFIG_OFFSET_VERSION] = 0x42;
    let version_checksum = compute_gt911_config_checksum(&version).unwrap();
    assert_eq!(
        attempt(&mut state, &version, version_checksum),
        Err(G8lS551TouchI2cReportDecoderError::ConfigVersionMismatch)
    );
    let mut resolution = config;
    resolution[S551_GT911_CONFIG_OFFSET_X_MAX_LO] = 0x00;
    resolution[S551_GT911_CONFIG_OFFSET_X_MAX_HI] = 0x04;
    let resolution_checksum = compute_gt911_config_checksum(&resolution).unwrap();
    assert_eq!(
        attempt(&mut state, &resolution, resolution_checksum),
        Err(G8lS551TouchI2cReportDecoderError::ConfigResolutionMismatch)
    );
    let mut touches = config;
    touches[S551_GT911_CONFIG_OFFSET_TOUCH_NUMBER] = 10;
    let touches_checksum = compute_gt911_config_checksum(&touches).unwrap();
    assert_eq!(
        attempt(&mut state, &touches, touches_checksum),
        Err(G8lS551TouchI2cReportDecoderError::ConfigTouchNumberMismatch)
    );
    assert_eq!(state.receipt(), None);
    assert_eq!(state.write_backs(), 0);
}

#[test]
fn wrong_i2c_address_and_product_id_are_rejected() {
    let (config, checksum) = config_and_checksum(false);
    let wire = single_point_report();
    let mut state = G8lS551TouchI2cReportDecoderState::new();
    for address in [0x00, 0x15, 0x5C, 0x5E, 0x7F, 0xFF] {
        assert_eq!(
            service_s551_model_decode_touch_report(
                &mut state,
                address,
                S551_GT911_PRODUCT_ID,
                &config,
                checksum,
                IDENTITY,
                &wire,
            ),
            Err(G8lS551TouchI2cReportDecoderError::InvalidI2cAddress),
            "address {address:#04x}"
        );
    }
    for product in [*b"912\0", *b"911 ", *b"\0911", *b"9110", *b"\0\0\0\0"] {
        assert_eq!(
            service_s551_model_decode_touch_report(
                &mut state,
                0x14,
                product,
                &config,
                checksum,
                IDENTITY,
                &wire,
            ),
            Err(G8lS551TouchI2cReportDecoderError::ProductIdMismatch),
            "{product:?}"
        );
    }
    assert_eq!(state.receipt(), None);
    let G8lS551TouchI2cReportDecoderOutcome::FrameDecoded(receipt) =
        service_s551_model_decode_touch_report(
            &mut state,
            S551_GT911_I2C_ADDRESS_SECONDARY,
            S551_GT911_PRODUCT_ID,
            &config,
            checksum,
            IDENTITY,
            &wire,
        )
        .unwrap()
    else {
        panic!("secondary address must decode")
    };
    assert_eq!(receipt.i2c_address, 0x14);
}

#[test]
fn track_id_ordering_and_range_are_enforced() {
    assert!(decode_gt911_report(&report(&[raw(9, 1, 1, 1)]), IDENTITY).is_ok());
    assert_eq!(
        decode_gt911_report(&report(&[raw(10, 1, 1, 1)]), IDENTITY),
        Err(G8lS551TouchI2cReportDecoderError::TrackIdOutOfRange)
    );
    assert_eq!(
        decode_gt911_report(&report(&[raw(0xFF, 1, 1, 1)]), IDENTITY),
        Err(G8lS551TouchI2cReportDecoderError::TrackIdOutOfRange)
    );
    assert_eq!(
        decode_gt911_report(&report(&[raw(1, 1, 1, 1), raw(0, 2, 2, 2)]), IDENTITY),
        Err(G8lS551TouchI2cReportDecoderError::TrackIdNotAscending)
    );
    assert_eq!(
        decode_gt911_report(&report(&[raw(2, 1, 1, 1), raw(2, 2, 2, 2)]), IDENTITY),
        Err(G8lS551TouchI2cReportDecoderError::TrackIdNotAscending)
    );
    let sparse = decode_gt911_report(
        &report(&[raw(1, 1, 1, 1), raw(4, 2, 2, 2), raw(8, 3, 3, 3)]),
        IDENTITY,
    )
    .unwrap();
    assert_eq!(
        sparse
            .active_points()
            .iter()
            .map(|point| point.track_id)
            .collect::<Vec<_>>(),
        vec![1, 4, 8]
    );
}

#[test]
fn reserved_byte_and_large_detect_bit_are_modelled() {
    let mut wire = single_point_report();
    wire[8] = 0x01;
    assert_eq!(
        decode_gt911_report(&wire, IDENTITY),
        Err(G8lS551TouchI2cReportDecoderError::ReservedByteNonZero)
    );
    let large = encode_gt911_report(&[raw(0, 5, 6, 700)], true, true).unwrap();
    assert_eq!(large[0], 0xC1);
    let frame = decode_gt911_report(&large, IDENTITY).unwrap();
    assert!(frame.large_detect);
    assert_eq!(frame.count, 1);
    assert_eq!(frame.points[0].size, 700);
    let mut state = G8lS551TouchI2cReportDecoderState::new();
    let G8lS551TouchI2cReportDecoderOutcome::FrameDecoded(receipt) =
        decode(&mut state, IDENTITY, &large).unwrap()
    else {
        panic!("large-detect frame must decode")
    };
    assert_eq!(receipt.status_before, 0xC1);
    assert_eq!(receipt.status_after_write_back, 0x00);
    assert!(receipt.frame.large_detect);
    // Bits 4 and 5 are not part of the modelled status word and are ignored
    // only if they do not alter count/ready/large semantics.
    let mut unmodelled = single_point_report();
    unmodelled[0] = 0x81 | 0x30;
    let frame = decode_gt911_report(&unmodelled, IDENTITY).unwrap();
    assert_eq!(frame.count, 1);
    assert!(!frame.large_detect);
}

#[test]
fn encoder_and_decoder_round_trip_exactly() {
    let point = raw(4, 0x02CF, 0x04FF, 0x1234);
    assert_eq!(
        encode_gt911_point(point),
        [0x04, 0xCF, 0x02, 0xFF, 0x04, 0x34, 0x12, 0x00]
    );
    let points = [raw(0, 10, 20, 30), raw(3, 400, 1000, 5), raw(7, 719, 1279, 65535)];
    let wire = report(&points);
    assert_eq!(wire.len(), 1 + 3 * 8);
    let frame = decode_gt911_report(&wire, IDENTITY).unwrap();
    let decoded: Vec<G8lS551RawTouchPoint> = frame
        .active_points()
        .iter()
        .map(|point| raw(point.track_id, point.x, point.y, point.size))
        .collect();
    assert_eq!(decoded, points.to_vec());
    let re_encoded = encode_gt911_report(&decoded, true, false).unwrap();
    assert_eq!(re_encoded, wire);
}

#[test]
fn diagnostic_codes_are_nonzero_and_unique() {
    let errors = [
        G8lS551TouchI2cReportDecoderError::InvalidI2cAddress,
        G8lS551TouchI2cReportDecoderError::ProductIdMismatch,
        G8lS551TouchI2cReportDecoderError::ConfigLengthMismatch,
        G8lS551TouchI2cReportDecoderError::ConfigChecksumMismatch,
        G8lS551TouchI2cReportDecoderError::ConfigVersionMismatch,
        G8lS551TouchI2cReportDecoderError::ConfigResolutionMismatch,
        G8lS551TouchI2cReportDecoderError::ConfigTouchNumberMismatch,
        G8lS551TouchI2cReportDecoderError::AxisSwapConfigMismatch,
        G8lS551TouchI2cReportDecoderError::TruncatedFrame,
        G8lS551TouchI2cReportDecoderError::FrameLengthMismatch,
        G8lS551TouchI2cReportDecoderError::BufferNotReady,
        G8lS551TouchI2cReportDecoderError::TouchCountOverflow,
        G8lS551TouchI2cReportDecoderError::TrackIdOutOfRange,
        G8lS551TouchI2cReportDecoderError::TrackIdNotAscending,
        G8lS551TouchI2cReportDecoderError::ReservedByteNonZero,
        G8lS551TouchI2cReportDecoderError::CoordinateOutOfRange,
        G8lS551TouchI2cReportDecoderError::ArithmeticOverflow,
        G8lS551TouchI2cReportDecoderError::PublishedStateDrift,
    ];
    let codes: BTreeSet<_> = errors
        .into_iter()
        .map(G8lS551TouchI2cReportDecoderError::diagnostic_code)
        .collect();
    assert_eq!(codes.len(), errors.len());
    assert_eq!(codes.len(), 18);
    assert!(!codes.contains(&0));
}

#[test]
fn module_is_registered_in_kernel_and_simulation() {
    let module = "g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_migration_lifecycle_s551_r1_gt911_touch_i2c_report_decoder_model";
    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",
        "read_volatile",
        "crate::uart",
        "crate::arch",
        "#[no_mangle]",
        "spin::",
        "std::",
        "/dev/",
        "kprintln!",
    ] {
        assert!(!SOURCE.contains(forbidden), "forbidden token: {forbidden}");
    }
    assert!(SOURCE.contains("No I2C controller, no touch panel"));
    assert!(SOURCE.contains("nothing here performs a device operation"));
}

#[test]
fn source_only_gate_keeps_runtime_physical_and_r1_claims_zero() {
    assert!(SOURCE.contains("S551_SUPPORTED_PROFILE_RUNTIME_OBSERVATIONS: usize = 0"));
    assert!(SOURCE.contains("S551_PHYSICAL_OBSERVATIONS: usize = 0"));
    assert!(SOURCE.contains("S551_PHYSICAL_OR_DEVICE_OPERATIONS: usize = 0"));
    assert!(SOURCE.contains("S551_HARDWARE_PRESENT: bool = false"));
    assert!(SOURCE.contains("S551_BOOT_TO_UI_PHYSICALLY_OBSERVED: bool = false"));
    assert!(SOURCE.contains("S551_R1_ACCEPTANCE_COMPLETE: bool = false"));
    assert!(SOURCE.contains("RUNBOOK_EXECUTED_IN_S551: bool = false"));
    assert!(SOURCE.contains("S551_S540_PHYSICAL_VERDICT_RETAINED_RED: bool = true"));
    assert!(SOURCE.contains("S551_S543_PHYSICAL_VERDICT_RETAINED_RED: bool = true"));
}
snippet sha256: 82093b5f1e0bfile sha256: 82093b5f1e0b
03 · Kapı kimlik kaydı

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

tam Operations kaydıL2841–L2901
website/src/lib/operations.ts::g8l-s551-r1-gt911-touch-i2c-report-decoder-model
  {
    id: "g8l-s551-r1-gt911-touch-i2c-report-decoder-model",
    date: "2026-08-30",
    sequence: 551,
    status: "passed",
    umbrella_status: "partial",
    title: "S551 · R1 ekran: Goodix GT911 dokunmatik I2C rapor çözücü modeli",
    summary:
      "S551 kaynak/host model kapısı PASS'tir: Goodix GT911 dokunmatik denetleyicinin I2C rapor protokolü (adres 0x5D/0x14, 0x814E durum yazmacı, 0x814F'ten başlayan 8 baytlık nokta kayıtları, 0x8047–0x80FE konfigürasyon bloğu ve 0x80FF toplam-tümleyen sağlaması, 0x8140 ürün kimliği '911') saf çözücü mantığı olarak modellendi. Çözücü bayt diliminden en fazla 5 noktalı TouchFrame üretir, buffer-ready bitini zorunlu kılar, eksen takas/ters çevirme dönüşümünü uygular ve 0x814E yazma-geri modelini 0'a temizler; count>5, aralık dışı koordinat, bozuk sağlama, kesik veya fazla uzun çerçeve fail-closed reddedilir. Focused 23/23 PASS'tir. S540 ve S543 fiziksel raw/verdict değişmez RED kalır; supported-profile runtime/physical observation=0/0, hiçbir I2C denetleyici, dokunmatik panel veya board mevcut değildir, Boot-to-UI=false ve R1 acceptance=false'dur. RUNBOOK_EXECUTED_IN_S551=NO. S552 çözülmüş dokunma çerçevelerini tüketen input focus ve touch event routing modelidir.",
    evidence: [
      "S551, S550'den ayrı source model module, 23-test focused binary, proof, status manifest, Operations kaydı ve complete Code kartına sahiptir; hiçbir production callsite'a bağlanmamıştır.",
      "Dar S551 source-model status=PASS; R1 umbrella=PARTIAL ve S540/S543 physical gate status=RED olarak ayrı tutulur.",
      "Model GT911 I2C 7-bit adreslerini 0x5D (primary) ve 0x14 (secondary) olarak tanımlar; başka her adres InvalidI2cAddress ile reddedilir.",
      "0x814E durum yazmacı bit 7 buffer-ready, bit 6 large-detect ve bit 0..3 dokunma sayısı olarak çözülür; buffer-ready biti temizse BufferNotReady döner ve receipt üretilmez.",
      "Nokta kayıtları 0x814F'ten itibaren 8 bayttır: track id, x lo/hi, y lo/hi, size lo/hi ve sıfır olması zorunlu reserved bayt; en fazla 5 nokta ve 41 baytlık rapor kabul edilir.",
      "Dokunma sayısı 6..15 TouchCountOverflow, çerçeve uzunluğu 1+count*8'den kısa ise TruncatedFrame, uzun ise FrameLengthMismatch ile fail-closed reddedilir.",
      "Koordinat aralığı eksen takasından sonra ve ters çevirmeden önce x 0..719 / y 0..1279 olarak denetlenir; 720/1280 ve 0xFFFF değerleri CoordinateOutOfRange döner, 719/1279 sınırı kabul edilir.",
      "Eksen dönüşümü swap_xy, invert_x ve invert_y bayraklarını uygular; swap_xy konfigürasyon Module_Switch1 X2Y biti ile uyuşmazsa AxisSwapConfigMismatch döner.",
      "Konfigürasyon bloğu 0x8047..0x80FE (184 bayt) üzerinde 8-bit toplam-tümleyen sağlaması checked u32 toplama ile hesaplanır; 0x80FF ile uyuşmazlık, yanlış uzunluk, sürüm, çözünürlük veya touch number ayrı kodlarla reddedilir.",
      "Ürün kimliği 0x8140'ta ASCII '911' + NUL olarak zorunludur; track id 0..9 aralığında ve çerçeve içinde kesin artan sırada olmalıdır.",
      "Yazma-geri modeli çerçeve tüketildikten sonra 0x814E gölgesini 0x00'a temizler ve write-back sayacını bir artırır; exact replay FrameRetained döner ve ikinci yazma-geri yapmaz.",
      "Yayından sonra farklı rapor, dönüşüm veya adres PublishedStateDrift ile reddedilir; 18 hata kodu sıfırdan farklı ve tekildir.",
      "Kodlayıcı (encode_gt911_point/encode_gt911_report) ile çözücü byte-exact round-trip verir.",
      "Focused target 1 grup / 23 passed / 0 failed / 0 ignored / 0 filtered verdi.",
      "Implementation 20474 B / f55ae2ccc19c6c623e246824b1456c9583bc123208930076b477e21959674d01; focused test 28049 B / 82093b5f1e0b89cc8cf7bcfd2d3a59bef25174a0aad843a128800d96ea6f6e43 SHA-256'dır.",
      "Proof 4952 B'dır.",
      "Kaynakta unsafe, asm!, write_volatile, crate::uart, crate::arch, spin:: veya #[no_mangle] yüzeyi yoktur; modül boot, IRQ, scheduler veya driver yoluna bağlı değildir.",
      "S551 sırasında I2C işlemi, dokunmatik panel gözlemi, SD write/read-back/eject, UART open/capture, power transition, physical retry veya yeni immutable raw üretimi yapılmadı.",
      "RUNBOOK_EXECUTED_IN_S551=NO; supported-profile runtime observations=0, physical observations=0, hardware present=false, Boot-to-UI physically observed=false ve R1 acceptance=false'dur.",
      "S552 yalnız host üzerinde çözülmüş G8lS551TouchFrame değerlerini tüketen input focus ve touch event routing modelidir; I2C, panel 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_s551_r1_gt911_touch_i2c_report_decoder_model -- --test-threads=1",
    ],
    terminalSessions: [
      {
        id: "s551-focused",
        title: "S551 GT911 dokunmatik I2C rapor çözücü modeli focused",
        commandLines: [
          "CARGO_INCREMENTAL=0 cargo test -p aselsan_microkernel_simulation --test g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_migration_lifecycle_s551_r1_gt911_touch_i2c_report_decoder_model -- --test-threads=1",
        ],
        outputLines: [
          "test result: ok. 23 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s",
          "S551 focused=1 group / 23 passed / 0 failed",
          "hardware=none physical=0 runbook=NO",
        ],
        exitCode: 0,
        outputMode: "complete",
      },
    ],
    terminalSessionsNote:
      "S551 kaynak/host model PASS'tir; I2C, dokunmatik panel veya fiziksel PASS değildir. S540 ve S543 RED raw ve kararları değişmez.",
    limitations: [
      "S551 yalnız GT911 rapor protokolünün saf çözücü modelidir; gerçek I2C transferi, INT pini veya dokunmatik panel davranışı gözlenmemiştir.",
      "S540 ve S543 fiziksel RED immutable kalır; hiçbir donanım/panel/modem/board gözlemi yoktur.",
      "Modül hiçbir production callsite'a, boot, IRQ, scheduler veya driver yoluna bağlı değildir; supported RPi5 runtime invocation yoktur.",
      "Konfigürasyon bloğunun yalnız sürüm, çözünürlük, touch number ve X2Y biti anlamlandırılır; kalan baytlar yalnız sağlamaya girer.",
      "Boot-to-UI gerçek UART'ta görülmedi; Boot-to-UI ve R1 acceptance false kalır.",
      "S552 çözülmüş dokunma çerçevelerini tüketen input focus ve touch event routing modelidir; yeni SD/UART/power/I2C koşusu ayrı kapı, fresh target revalidation, açık operatör yetkisi ve yeni immutable raw ister.",
    ],
  },
snippet sha256: 58faf23e0512file 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_s551_r1_gt911_touch_i2c_report_decoder_model -- --test-threads=1
proof: docs/M8.1-RPi5-G8l-S551-R1-GT911-Touch-I2C-Report-Decoder-Model-Proof.md
Registry schema v5 · generator website/scripts/generate-code-gates.mjs · Tam SHA-256: 3050638b71a684d8f8f947a8a6faa237a17fa8db5dc0db04fb207b668b462af9