ASELSANMicrokernel
S552 · SOURCE-BOUND GATE EVIDENCE

S552 · R1 ekran: input focus ve dokunma olayı yönlendirme modeli

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

S552Focused kod testiOperations id exactsource SHA exacttest target exact

operation: g8l-s552-r1-input-focus-touch-event-routing-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–L754
kernel/src/g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_migration_lifecycle_s552_r1_input_focus_touch_event_routing_model.rs::S552 r1 input focus touch event routing model implementation
//! S552 models the R1 input-focus and touch-event routing layer that sits
//! between a GT911-like touch report decoder (S551 abstraction) and the
//! task-facing input queues.
//!
//! The model owns a capability-gated focus stack of task ids, a z-ordered
//! rectangular surface table, per-track Down/Move/Up conversion of decoded
//! touch frames, and bounded per-task event queues (capacity 32, overflow
//! drops the oldest event and is counted).  Focus grant requires the owner
//! task's GRANT right and the target's WAIT right; focus revoke flushes the
//! target's queue and releases its active tracks.  Nothing is delivered to an
//! unfocused task, event sequence numbers are strictly monotonic, and every
//! unknown task, unknown surface, missing right or malformed frame fails
//! closed.  Rights naming mirrors the notification SIGNAL/WAIT/GRANT/REVOKE
//! vocabulary of `ipc.rs` and `ui/capability.rs` without calling into them.
//!
//! This is a source/host model gate only.  No touch controller, panel, I2C
//! bus, IRQ path, scheduler or UART is touched; there is no production
//! callsite, no hardware and no physical observation.  S540 and S543 remain
//! immutable physical RED.  Predecessor: S551 (GT911 report decoder model).
//! Next: S553 (system UI lock/status/settings scene flow model).

use alloc::collections::VecDeque;
use alloc::vec::Vec;

pub const S552_SEQUENCE: usize = 552;
pub const S552_EXPECTED_PREDECESSOR: usize = 551;
pub const S552_R1_STAGE: u8 = 2;
pub const S552_R1_RANGE_FIRST: usize = 536;
pub const S552_R1_RANGE_LAST: usize = 568;
pub const S552_EVENT_QUEUE_CAPACITY: usize = 32;
pub const S552_MAX_FOCUS_DEPTH: usize = 8;
pub const S552_MAX_TASKS: usize = 12;
pub const S552_MAX_SURFACES: usize = 8;
pub const S552_MAX_TOUCH_POINTS: usize = 5;
pub const S552_MAX_TRACK_ID: u8 = 9;
pub const S552_PANEL_WIDTH: u16 = 720;
pub const S552_PANEL_HEIGHT: u16 = 1280;
pub const S552_INVALID_TASK: usize = 0;
pub const S552_INPUT_OWNER_TASK: usize = 1;
pub const S552_SUPPORTED_PROFILE_RUNTIME_OBSERVATIONS: usize = 0;
pub const S552_PHYSICAL_OBSERVATIONS: usize = 0;
pub const S552_PHYSICAL_OR_DEVICE_OPERATIONS: usize = 0;
pub const S552_SD_WRITES: usize = 0;
pub const S552_UART_OPENS: usize = 0;
pub const S552_POWER_TRANSITIONS: usize = 0;
pub const S552_NEW_IMMUTABLE_RAW_CAPTURES: usize = 0;
pub const S552_S540_PHYSICAL_VERDICT_RETAINED_RED: bool = true;
pub const S552_S543_PHYSICAL_VERDICT_RETAINED_RED: bool = true;
pub const S552_AUTOMATIC_PROMOTION: bool = false;
pub const S552_BOOT_TO_UI_PHYSICALLY_OBSERVED: bool = false;
pub const S552_HARDWARE_PRESENT: bool = false;
pub const S552_R1_ACCEPTANCE_COMPLETE: bool = false;
pub const RUNBOOK_EXECUTED_IN_S552: bool = false;

/// Input rights, named after the notification right vocabulary in
/// `ui/capability.rs` (SIGNAL / WAIT / GRANT / REVOKE).
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct G8lS552InputRights(pub u8);

impl G8lS552InputRights {
    pub const NONE: Self = Self(0);
    /// May deliver (signal) events into task queues.  Owner only.
    pub const SIGNAL: Self = Self(0b0001);
    /// May receive (wait on) routed events.  Grantable to any task.
    pub const WAIT: Self = Self(0b0010);
    /// May grant focus and WAIT rights.
    pub const GRANT: Self = Self(0b0100);
    /// May revoke focus and flush queues.
    pub const REVOKE: Self = Self(0b1000);
    pub const OWNER: Self = Self(Self::SIGNAL.0 | Self::GRANT.0 | Self::REVOKE.0);
    pub const GRANTABLE_MASK: Self = Self::WAIT;
    pub const VALID_MASK: Self = Self(0b1111);

    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)
    }
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct G8lS552Surface {
    pub id: u32,
    pub task: usize,
    pub x: u16,
    pub y: u16,
    pub width: u16,
    pub height: u16,
    pub z: u8,
}

impl G8lS552Surface {
    pub const fn contains(&self, x: u16, y: u16) -> bool {
        x >= self.x
            && y >= self.y
            && (x as u32) < self.x as u32 + self.width as u32
            && (y as u32) < self.y as u32 + self.height as u32
    }
}

/// One pressed contact from a decoded GT911-like frame.  Frames only carry
/// pressed contacts; a track absent from the next frame is a release.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct G8lS552TouchPoint {
    pub track_id: u8,
    pub x: u16,
    pub y: u16,
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum G8lS552TouchPhase {
    Down,
    Move,
    Up,
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct G8lS552InputEvent {
    pub sequence: u64,
    pub frame_sequence: u32,
    pub track_id: u8,
    pub phase: G8lS552TouchPhase,
    pub x: u16,
    pub y: u16,
    pub surface_id: u32,
    pub task: usize,
}

#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
pub struct G8lS552FrameRoutingSummary {
    pub frame_sequence: u32,
    pub delivered: usize,
    pub overflow_drops: usize,
    pub unfocused_drops: usize,
    pub missed_hits: usize,
    pub released_tracks: usize,
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct G8lS552InputFocusRoutingReceipt {
    pub sequence: usize,
    pub predecessor_sequence: usize,
    pub r1_stage: u8,
    pub queue_capacity: usize,
    pub max_focus_depth: usize,
    pub task_count: usize,
    pub surface_count: usize,
    pub focus_depth: usize,
    pub focused_task: usize,
    pub frames_routed: u64,
    pub events_delivered: u64,
    pub events_dequeued: u64,
    pub overflow_drops: u64,
    pub unfocused_drops: u64,
    pub missed_hits: u64,
    pub flushed_events: u64,
    pub last_event_sequence: u64,
    pub active_tracks: usize,
    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(Clone, Debug)]
struct TaskEntry {
    task: usize,
    rights: G8lS552InputRights,
    queue: VecDeque<G8lS552InputEvent>,
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
struct TrackGrab {
    task: usize,
    surface_id: u32,
    x: u16,
    y: u16,
}

#[derive(Clone, Debug)]
pub struct G8lS552InputFocusRoutingState {
    tasks: Vec<TaskEntry>,
    surfaces: Vec<G8lS552Surface>,
    focus_stack: Vec<usize>,
    tracks: [Option<TrackGrab>; S552_MAX_TRACK_ID as usize + 1],
    next_event_sequence: u64,
    last_frame_sequence: Option<u32>,
    frames_routed: u64,
    events_delivered: u64,
    events_dequeued: u64,
    overflow_drops: u64,
    unfocused_drops: u64,
    missed_hits: u64,
    flushed_events: u64,
    receipt: Option<G8lS552InputFocusRoutingReceipt>,
}

impl G8lS552InputFocusRoutingState {
    /// Starts with the input owner task registered and holding OWNER rights.
    pub fn new() -> Self {
        let mut tasks = Vec::new();
        tasks.push(TaskEntry {
            task: S552_INPUT_OWNER_TASK,
            rights: G8lS552InputRights::OWNER,
            queue: VecDeque::new(),
        });
        Self {
            tasks,
            surfaces: Vec::new(),
            focus_stack: Vec::new(),
            tracks: [None; S552_MAX_TRACK_ID as usize + 1],
            next_event_sequence: 1,
            last_frame_sequence: None,
            frames_routed: 0,
            events_delivered: 0,
            events_dequeued: 0,
            overflow_drops: 0,
            unfocused_drops: 0,
            missed_hits: 0,
            flushed_events: 0,
            receipt: None,
        }
    }

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

    pub fn focused_task(&self) -> Option<usize> {
        self.focus_stack.last().copied()
    }

    pub fn focus_stack(&self) -> &[usize] {
        &self.focus_stack
    }

    pub fn rights_of(&self, task: usize) -> Option<G8lS552InputRights> {
        self.tasks
            .iter()
            .find(|entry| entry.task == task)
            .map(|entry| entry.rights)
    }

    pub fn queue_len(&self, task: usize) -> Option<usize> {
        self.tasks
            .iter()
            .find(|entry| entry.task == task)
            .map(|entry| entry.queue.len())
    }

    pub fn active_tracks(&self) -> usize {
        self.tracks.iter().filter(|grab| grab.is_some()).count()
    }

    fn task_index(&self, task: usize) -> Result<usize, G8lS552InputFocusRoutingError> {
        if task == S552_INVALID_TASK {
            return Err(G8lS552InputFocusRoutingError::InvalidTaskId);
        }
        self.tasks
            .iter()
            .position(|entry| entry.task == task)
            .ok_or(G8lS552InputFocusRoutingError::UnknownTask)
    }

    fn require_right(
        &self,
        task: usize,
        right: G8lS552InputRights,
    ) -> Result<usize, G8lS552InputFocusRoutingError> {
        let index = self.task_index(task)?;
        if !self.tasks[index].rights.contains(right) {
            return Err(G8lS552InputFocusRoutingError::InputRightsMissing);
        }
        Ok(index)
    }

    fn snapshot(&self) -> G8lS552InputFocusRoutingReceipt {
        G8lS552InputFocusRoutingReceipt {
            sequence: S552_SEQUENCE,
            predecessor_sequence: S552_EXPECTED_PREDECESSOR,
            r1_stage: S552_R1_STAGE,
            queue_capacity: S552_EVENT_QUEUE_CAPACITY,
            max_focus_depth: S552_MAX_FOCUS_DEPTH,
            task_count: self.tasks.len(),
            surface_count: self.surfaces.len(),
            focus_depth: self.focus_stack.len(),
            focused_task: self.focused_task().unwrap_or(S552_INVALID_TASK),
            frames_routed: self.frames_routed,
            events_delivered: self.events_delivered,
            events_dequeued: self.events_dequeued,
            overflow_drops: self.overflow_drops,
            unfocused_drops: self.unfocused_drops,
            missed_hits: self.missed_hits,
            flushed_events: self.flushed_events,
            last_event_sequence: self.next_event_sequence - 1,
            active_tracks: self.active_tracks(),
            hardware_present: S552_HARDWARE_PRESENT,
            s540_physical_verdict_retained_red: S552_S540_PHYSICAL_VERDICT_RETAINED_RED,
            s543_physical_verdict_retained_red: S552_S543_PHYSICAL_VERDICT_RETAINED_RED,
            automatic_promotion: S552_AUTOMATIC_PROMOTION,
            supported_profile_runtime_observations: S552_SUPPORTED_PROFILE_RUNTIME_OBSERVATIONS,
            physical_observations: S552_PHYSICAL_OBSERVATIONS,
            runbook_executed: RUNBOOK_EXECUTED_IN_S552,
        }
    }
}

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

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum G8lS552InputFocusRoutingOutcome {
    TaskRegistered(usize),
    TaskRetained(usize),
    RightsGranted {
        task: usize,
        rights: G8lS552InputRights,
    },
    RightsRetained {
        task: usize,
        rights: G8lS552InputRights,
    },
    SurfaceRegistered(u32),
    SurfaceRetained(u32),
    FocusGranted {
        task: usize,
        depth: usize,
    },
    FocusRetained {
        task: usize,
        depth: usize,
    },
    FocusRevoked {
        task: usize,
        flushed: usize,
        released_tracks: usize,
        depth: usize,
    },
    FrameRouted(G8lS552FrameRoutingSummary),
    EventDequeued(G8lS552InputEvent),
    QueueEmpty(usize),
    Published(G8lS552InputFocusRoutingReceipt),
    Retained(G8lS552InputFocusRoutingReceipt),
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum G8lS552InputFocusRoutingError {
    InvalidTaskId,
    UnknownTask,
    TaskTableFull,
    InputRightsMissing,
    InvalidRightsMask,
    NonGrantableRights,
    UnknownSurface,
    SurfaceTableFull,
    DuplicateSurfaceZ,
    SurfaceOutsidePanel,
    EmptySurface,
    SurfaceDrift,
    FocusStackFull,
    TaskNotFocused,
    TooManyTouchPoints,
    DuplicateTrackId,
    TrackIdOutOfRange,
    TouchOutsidePanel,
    FrameSequenceNotMonotonic,
    EventSequenceExhausted,
    PublishedStateDrift,
}

impl G8lS552InputFocusRoutingError {
    pub const fn diagnostic_code(self) -> u64 {
        match self {
            Self::InvalidTaskId => 1,
            Self::UnknownTask => 2,
            Self::TaskTableFull => 3,
            Self::InputRightsMissing => 4,
            Self::InvalidRightsMask => 5,
            Self::NonGrantableRights => 6,
            Self::UnknownSurface => 7,
            Self::SurfaceTableFull => 8,
            Self::DuplicateSurfaceZ => 9,
            Self::SurfaceOutsidePanel => 10,
            Self::EmptySurface => 11,
            Self::SurfaceDrift => 12,
            Self::FocusStackFull => 13,
            Self::TaskNotFocused => 14,
            Self::TooManyTouchPoints => 15,
            Self::DuplicateTrackId => 16,
            Self::TrackIdOutOfRange => 17,
            Self::TouchOutsidePanel => 18,
            Self::FrameSequenceNotMonotonic => 19,
            Self::EventSequenceExhausted => 20,
            Self::PublishedStateDrift => 21,
        }
    }
}

type Outcome = G8lS552InputFocusRoutingOutcome;
type Error = G8lS552InputFocusRoutingError;
type State = G8lS552InputFocusRoutingState;

/// Registers a task with no input rights.  Exact replay is retained.
pub fn service_s552_model_register_task(state: &mut State, task: usize) -> Result<Outcome, Error> {
    if task == S552_INVALID_TASK {
        return Err(Error::InvalidTaskId);
    }
    if state.tasks.iter().any(|entry| entry.task == task) {
        return Ok(Outcome::TaskRetained(task));
    }
    if state.tasks.len() >= S552_MAX_TASKS {
        return Err(Error::TaskTableFull);
    }
    state.tasks.push(TaskEntry {
        task,
        rights: G8lS552InputRights::NONE,
        queue: VecDeque::new(),
    });
    Ok(Outcome::TaskRegistered(task))
}

/// The owner (GRANT right) grants WAIT rights to a registered task.
pub fn service_s552_model_grant_input_rights(
    state: &mut State,
    granter: usize,
    target: usize,
    rights: G8lS552InputRights,
) -> Result<Outcome, Error> {
    state.require_right(granter, G8lS552InputRights::GRANT)?;
    let target_index = state.task_index(target)?;
    if rights.0 & !G8lS552InputRights::VALID_MASK.0 != 0 || rights == G8lS552InputRights::NONE {
        return Err(Error::InvalidRightsMask);
    }
    if rights.0 & !G8lS552InputRights::GRANTABLE_MASK.0 != 0 {
        return Err(Error::NonGrantableRights);
    }
    let entry = &mut state.tasks[target_index];
    if entry.rights.contains(rights) {
        return Ok(Outcome::RightsRetained {
            task: target,
            rights: entry.rights,
        });
    }
    entry.rights = entry.rights.union(rights);
    Ok(Outcome::RightsGranted {
        task: target,
        rights: entry.rights,
    })
}

/// Registers a rectangular surface owned by a registered task.
pub fn service_s552_model_register_surface(
    state: &mut State,
    surface: G8lS552Surface,
) -> Result<Outcome, Error> {
    state.task_index(surface.task)?;
    if surface.width == 0 || surface.height == 0 {
        return Err(Error::EmptySurface);
    }
    let right = surface
        .x
        .checked_add(surface.width)
        .ok_or(Error::SurfaceOutsidePanel)?;
    let bottom = surface
        .y
        .checked_add(surface.height)
        .ok_or(Error::SurfaceOutsidePanel)?;
    if right > S552_PANEL_WIDTH || bottom > S552_PANEL_HEIGHT {
        return Err(Error::SurfaceOutsidePanel);
    }
    if let Some(existing) = state.surfaces.iter().find(|entry| entry.id == surface.id) {
        if *existing == surface {
            return Ok(Outcome::SurfaceRetained(surface.id));
        }
        return Err(Error::SurfaceDrift);
    }
    if state.surfaces.iter().any(|entry| entry.z == surface.z) {
        return Err(Error::DuplicateSurfaceZ);
    }
    if state.surfaces.len() >= S552_MAX_SURFACES {
        return Err(Error::SurfaceTableFull);
    }
    state.surfaces.push(surface);
    Ok(Outcome::SurfaceRegistered(surface.id))
}

/// Pushes `target` to the top of the focus stack.  Requires the granter's
/// GRANT right and the target's WAIT right.
pub fn service_s552_model_focus_grant(
    state: &mut State,
    granter: usize,
    target: usize,
) -> Result<Outcome, Error> {
    state.require_right(granter, G8lS552InputRights::GRANT)?;
    state.require_right(target, G8lS552InputRights::WAIT)?;
    if state.focused_task() == Some(target) {
        return Ok(Outcome::FocusRetained {
            task: target,
            depth: state.focus_stack.len(),
        });
    }
    if let Some(position) = state.focus_stack.iter().position(|task| *task == target) {
        state.focus_stack.remove(position);
    } else if state.focus_stack.len() >= S552_MAX_FOCUS_DEPTH {
        return Err(Error::FocusStackFull);
    }
    state.focus_stack.push(target);
    Ok(Outcome::FocusGranted {
        task: target,
        depth: state.focus_stack.len(),
    })
}

/// Removes `target` from the focus stack, flushes its queue and releases
/// its grabbed tracks.  Requires the revoker's REVOKE right.
pub fn service_s552_model_focus_revoke(
    state: &mut State,
    revoker: usize,
    target: usize,
) -> Result<Outcome, Error> {
    state.require_right(revoker, G8lS552InputRights::REVOKE)?;
    let target_index = state.task_index(target)?;
    let position = state
        .focus_stack
        .iter()
        .position(|task| *task == target)
        .ok_or(Error::TaskNotFocused)?;
    state.focus_stack.remove(position);
    let flushed = state.tasks[target_index].queue.len();
    state.tasks[target_index].queue.clear();
    state.flushed_events += flushed as u64;
    let mut released_tracks = 0;
    for grab in state.tracks.iter_mut() {
        if grab.map(|entry| entry.task) == Some(target) {
            *grab = None;
            released_tracks += 1;
        }
    }
    Ok(Outcome::FocusRevoked {
        task: target,
        flushed,
        released_tracks,
        depth: state.focus_stack.len(),
    })
}

fn validate_frame(
    state: &State,
    frame_sequence: u32,
    points: &[G8lS552TouchPoint],
) -> Result<(), Error> {
    if points.len() > S552_MAX_TOUCH_POINTS {
        return Err(Error::TooManyTouchPoints);
    }
    for (index, point) in points.iter().enumerate() {
        if point.track_id > S552_MAX_TRACK_ID {
            return Err(Error::TrackIdOutOfRange);
        }
        if point.x >= S552_PANEL_WIDTH || point.y >= S552_PANEL_HEIGHT {
            return Err(Error::TouchOutsidePanel);
        }
        if points[..index]
            .iter()
            .any(|earlier| earlier.track_id == point.track_id)
        {
            return Err(Error::DuplicateTrackId);
        }
    }
    match state.last_frame_sequence {
        Some(last) if frame_sequence <= last => Err(Error::FrameSequenceNotMonotonic),
        _ => Ok(()),
    }
}

fn enqueue(
    state: &mut State,
    summary: &mut G8lS552FrameRoutingSummary,
    task: usize,
    surface_id: u32,
    track_id: u8,
    phase: G8lS552TouchPhase,
    x: u16,
    y: u16,
) -> Result<(), Error> {
    let sequence = state.next_event_sequence;
    let next = sequence
        .checked_add(1)
        .ok_or(Error::EventSequenceExhausted)?;
    let index = state.task_index(task)?;
    let event = G8lS552InputEvent {
        sequence,
        frame_sequence: summary.frame_sequence,
        track_id,
        phase,
        x,
        y,
        surface_id,
        task,
    };
    let queue = &mut state.tasks[index].queue;
    if queue.len() >= S552_EVENT_QUEUE_CAPACITY {
        queue.pop_front();
        summary.overflow_drops += 1;
        state.overflow_drops += 1;
    }
    queue.push_back(event);
    state.next_event_sequence = next;
    state.events_delivered += 1;
    summary.delivered += 1;
    Ok(())
}

/// Routes one decoded touch frame.  Down events are hit-tested against the
/// topmost surface and delivered only when that surface belongs to the
/// focused task; Move/Up follow the track's grabbing task.
pub fn service_s552_model_route_touch_frame(
    state: &mut State,
    frame_sequence: u32,
    points: &[G8lS552TouchPoint],
) -> Result<Outcome, Error> {
    validate_frame(state, frame_sequence, points)?;
    let mut summary = G8lS552FrameRoutingSummary {
        frame_sequence,
        ..Default::default()
    };
    let focused = state.focused_task();
    for point in points {
        let slot = point.track_id as usize;
        match state.tracks[slot] {
            Some(grab) => {
                if focused == Some(grab.task) {
                    enqueue(
                        state,
                        &mut summary,
                        grab.task,
                        grab.surface_id,
                        point.track_id,
                        G8lS552TouchPhase::Move,
                        point.x,
                        point.y,
                    )?;
                    state.tracks[slot] = Some(TrackGrab {
                        x: point.x,
                        y: point.y,
                        ..grab
                    });
                } else {
                    state.tracks[slot] = None;
                    summary.unfocused_drops += 1;
                    state.unfocused_drops += 1;
                }
            }
            None => {
                let hit = state
                    .surfaces
                    .iter()
                    .filter(|surface| surface.contains(point.x, point.y))
                    .max_by_key(|surface| surface.z)
                    .copied();
                match hit {
                    None => {
                        summary.missed_hits += 1;
                        state.missed_hits += 1;
                    }
                    Some(surface) if focused == Some(surface.task) => {
                        enqueue(
                            state,
                            &mut summary,
                            surface.task,
                            surface.id,
                            point.track_id,
                            G8lS552TouchPhase::Down,
                            point.x,
                            point.y,
                        )?;
                        state.tracks[slot] = Some(TrackGrab {
                            task: surface.task,
                            surface_id: surface.id,
                            x: point.x,
                            y: point.y,
                        });
                    }
                    Some(_) => {
                        summary.unfocused_drops += 1;
                        state.unfocused_drops += 1;
                    }
                }
            }
        }
    }
    for slot in 0..state.tracks.len() {
        let Some(grab) = state.tracks[slot] else {
            continue;
        };
        if points.iter().any(|point| point.track_id as usize == slot) {
            continue;
        }
        state.tracks[slot] = None;
        summary.released_tracks += 1;
        if focused == Some(grab.task) {
            enqueue(
                state,
                &mut summary,
                grab.task,
                grab.surface_id,
                slot as u8,
                G8lS552TouchPhase::Up,
                grab.x,
                grab.y,
            )?;
        } else {
            summary.unfocused_drops += 1;
            state.unfocused_drops += 1;
        }
    }
    state.last_frame_sequence = Some(frame_sequence);
    state.frames_routed += 1;
    Ok(Outcome::FrameRouted(summary))
}

/// A task with WAIT rights dequeues its oldest routed event.
pub fn service_s552_model_dequeue_event(state: &mut State, task: usize) -> Result<Outcome, Error> {
    let index = state.require_right(task, G8lS552InputRights::WAIT)?;
    match state.tasks[index].queue.pop_front() {
        Some(event) => {
            state.events_dequeued += 1;
            Ok(Outcome::EventDequeued(event))
        }
        None => Ok(Outcome::QueueEmpty(task)),
    }
}

/// Publishes the routing receipt.  Exact replay is retained; any counter or
/// table divergence after publication fails closed.
pub fn service_s552_model_publish_routing_receipt(state: &mut State) -> Result<Outcome, Error> {
    let receipt = state.snapshot();
    if let Some(published) = state.receipt {
        if published != receipt {
            return Err(Error::PublishedStateDrift);
        }
        return Ok(Outcome::Retained(published));
    }
    state.receipt = Some(receipt);
    Ok(Outcome::Published(receipt))
}
snippet sha256: 53f16cdfed1ffile sha256: 53f16cdfed1f
02 · Doğrulayan test kodu

Operations komutuna bağlı focused test

tam dosyaL1–L585
simulation/tests/g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_migration_lifecycle_s552_r1_input_focus_touch_event_routing_model.rs::S552 r1 input focus touch event routing model focused tests
use aselsan_microkernel_simulation::g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_migration_lifecycle_s552_r1_input_focus_touch_event_routing_model::*;
use std::collections::BTreeSet;

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

const OWNER: usize = S552_INPUT_OWNER_TASK;
const APP_A: usize = 7;
const APP_B: usize = 9;
const SURFACE_A: u32 = 0x5520_0001;
const SURFACE_B: u32 = 0x5520_0002;
const SURFACE_A_TOP: u32 = 0x5520_0003;

type Outcome = G8lS552InputFocusRoutingOutcome;
type Error = G8lS552InputFocusRoutingError;

fn surface(id: u32, task: usize, x: u16, y: u16, width: u16, height: u16, z: u8) -> G8lS552Surface {
    G8lS552Surface { id, task, x, y, width, height, z }
}

fn point(track_id: u8, x: u16, y: u16) -> G8lS552TouchPoint {
    G8lS552TouchPoint { track_id, x, y }
}

/// Owner task 1, app A (task 7) full-screen surface z=1 focused, app B
/// (task 9) surface on the lower half z=2 registered but not focused.
fn scene() -> G8lS552InputFocusRoutingState {
    let mut state = G8lS552InputFocusRoutingState::new();
    assert_eq!(
        service_s552_model_register_task(&mut state, APP_A),
        Ok(Outcome::TaskRegistered(APP_A))
    );
    assert_eq!(
        service_s552_model_register_task(&mut state, APP_B),
        Ok(Outcome::TaskRegistered(APP_B))
    );
    for task in [APP_A, APP_B] {
        assert_eq!(
            service_s552_model_grant_input_rights(&mut state, OWNER, task, G8lS552InputRights::WAIT),
            Ok(Outcome::RightsGranted { task, rights: G8lS552InputRights::WAIT })
        );
    }
    assert_eq!(
        service_s552_model_register_surface(&mut state, surface(SURFACE_A, APP_A, 0, 0, 720, 1280, 1)),
        Ok(Outcome::SurfaceRegistered(SURFACE_A))
    );
    assert_eq!(
        service_s552_model_register_surface(&mut state, surface(SURFACE_B, APP_B, 0, 640, 720, 640, 2)),
        Ok(Outcome::SurfaceRegistered(SURFACE_B))
    );
    assert_eq!(
        service_s552_model_focus_grant(&mut state, OWNER, APP_A),
        Ok(Outcome::FocusGranted { task: APP_A, depth: 1 })
    );
    state
}

fn routed(state: &mut G8lS552InputFocusRoutingState, frame: u32, points: &[G8lS552TouchPoint]) -> G8lS552FrameRoutingSummary {
    match service_s552_model_route_touch_frame(state, frame, points) {
        Ok(Outcome::FrameRouted(summary)) => summary,
        other => panic!("frame {frame} must route: {other:?}"),
    }
}

fn dequeued(state: &mut G8lS552InputFocusRoutingState, task: usize) -> G8lS552InputEvent {
    match service_s552_model_dequeue_event(state, task) {
        Ok(Outcome::EventDequeued(event)) => event,
        other => panic!("task {task} must dequeue: {other:?}"),
    }
}

#[test]
fn sequence_scope_and_nonpromotion_are_exact() {
    assert_eq!(S552_SEQUENCE, 552);
    assert_eq!(S552_EXPECTED_PREDECESSOR, 551);
    assert_eq!(S552_R1_STAGE, 2);
    assert_eq!(S552_R1_RANGE_FIRST, 536);
    assert_eq!(S552_R1_RANGE_LAST, 568);
    assert_eq!(S552_EVENT_QUEUE_CAPACITY, 32);
    assert_eq!(S552_MAX_FOCUS_DEPTH, 8);
    assert_eq!(S552_MAX_TASKS, 12);
    assert_eq!(S552_MAX_SURFACES, 8);
    assert_eq!(S552_MAX_TOUCH_POINTS, 5);
    assert_eq!(S552_MAX_TRACK_ID, 9);
    assert_eq!((S552_PANEL_WIDTH, S552_PANEL_HEIGHT), (720, 1280));
    assert_eq!(S552_SUPPORTED_PROFILE_RUNTIME_OBSERVATIONS, 0);
    assert_eq!(S552_PHYSICAL_OBSERVATIONS, 0);
    assert_eq!(S552_PHYSICAL_OR_DEVICE_OPERATIONS, 0);
    assert_eq!(S552_SD_WRITES, 0);
    assert_eq!(S552_UART_OPENS, 0);
    assert_eq!(S552_POWER_TRANSITIONS, 0);
    assert_eq!(S552_NEW_IMMUTABLE_RAW_CAPTURES, 0);
    assert!(S552_S540_PHYSICAL_VERDICT_RETAINED_RED);
    assert!(S552_S543_PHYSICAL_VERDICT_RETAINED_RED);
    assert!(!S552_AUTOMATIC_PROMOTION);
    assert!(!S552_BOOT_TO_UI_PHYSICALLY_OBSERVED);
    assert!(!S552_HARDWARE_PRESENT);
    assert!(!S552_R1_ACCEPTANCE_COMPLETE);
    assert!(!RUNBOOK_EXECUTED_IN_S552);
}

#[test]
fn module_is_registered_in_kernel_and_simulation() {
    let module = "g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_migration_lifecycle_s552_r1_input_focus_touch_event_routing_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",
        "crate::uart",
        "crate::arch",
        "#[no_mangle]",
        "spin::",
        "std::",
        "crate::ipc",
    ] {
        assert!(!SOURCE.contains(forbidden), "forbidden token: {forbidden}");
    }
    assert!(SOURCE.contains("no production\n//! callsite"));
    assert!(SOURCE.contains("without calling into them"));
}

#[test]
fn diagnostic_codes_are_nonzero_and_unique() {
    let errors = [
        Error::InvalidTaskId,
        Error::UnknownTask,
        Error::TaskTableFull,
        Error::InputRightsMissing,
        Error::InvalidRightsMask,
        Error::NonGrantableRights,
        Error::UnknownSurface,
        Error::SurfaceTableFull,
        Error::DuplicateSurfaceZ,
        Error::SurfaceOutsidePanel,
        Error::EmptySurface,
        Error::SurfaceDrift,
        Error::FocusStackFull,
        Error::TaskNotFocused,
        Error::TooManyTouchPoints,
        Error::DuplicateTrackId,
        Error::TrackIdOutOfRange,
        Error::TouchOutsidePanel,
        Error::FrameSequenceNotMonotonic,
        Error::EventSequenceExhausted,
        Error::PublishedStateDrift,
    ];
    let codes: BTreeSet<_> = errors.into_iter().map(Error::diagnostic_code).collect();
    assert_eq!(codes.len(), errors.len());
    assert_eq!(codes.len(), 21);
    assert!(!codes.contains(&0));
}

#[test]
fn exact_replay_retains_the_same_receipt() {
    let mut state = scene();
    routed(&mut state, 1, &[point(0, 100, 100)]);
    routed(&mut state, 2, &[]);
    let Ok(Outcome::Published(receipt)) = service_s552_model_publish_routing_receipt(&mut state) else {
        panic!("first publication missing")
    };
    assert_eq!(state.receipt(), Some(receipt));
    assert_eq!(receipt.sequence, S552_SEQUENCE);
    assert_eq!(receipt.predecessor_sequence, S552_EXPECTED_PREDECESSOR);
    assert_eq!(receipt.r1_stage, S552_R1_STAGE);
    assert_eq!(receipt.queue_capacity, 32);
    assert_eq!(receipt.task_count, 3);
    assert_eq!(receipt.surface_count, 2);
    assert_eq!(receipt.focus_depth, 1);
    assert_eq!(receipt.focused_task, APP_A);
    assert_eq!(receipt.frames_routed, 2);
    assert_eq!(receipt.events_delivered, 2);
    assert_eq!(receipt.last_event_sequence, 2);
    assert_eq!(receipt.active_tracks, 0);
    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);
    assert_eq!(
        service_s552_model_publish_routing_receipt(&mut state),
        Ok(Outcome::Retained(receipt))
    );
    assert_eq!(
        service_s552_model_dequeue_event(&mut state, APP_B),
        Ok(Outcome::QueueEmpty(APP_B))
    );
    assert_eq!(
        service_s552_model_publish_routing_receipt(&mut state),
        Ok(Outcome::Retained(receipt))
    );
}

#[test]
fn divergent_input_after_publication_fails_closed() {
    let mut state = scene();
    routed(&mut state, 1, &[point(0, 100, 100)]);
    let Ok(Outcome::Published(receipt)) = service_s552_model_publish_routing_receipt(&mut state) else {
        panic!("first publication missing")
    };
    routed(&mut state, 2, &[point(0, 110, 100)]);
    assert_eq!(
        service_s552_model_publish_routing_receipt(&mut state),
        Err(Error::PublishedStateDrift)
    );
    assert_eq!(state.receipt(), Some(receipt));
    let mut dequeued_state = scene();
    routed(&mut dequeued_state, 1, &[point(0, 100, 100)]);
    service_s552_model_publish_routing_receipt(&mut dequeued_state).unwrap();
    dequeued(&mut dequeued_state, APP_A);
    assert_eq!(
        service_s552_model_publish_routing_receipt(&mut dequeued_state),
        Err(Error::PublishedStateDrift)
    );
}

#[test]
fn owner_grants_input_rights_and_focus_to_registered_task() {
    let mut state = G8lS552InputFocusRoutingState::new();
    assert_eq!(state.rights_of(OWNER), Some(G8lS552InputRights::OWNER));
    assert!(G8lS552InputRights::OWNER.contains(G8lS552InputRights::GRANT));
    assert!(G8lS552InputRights::OWNER.contains(G8lS552InputRights::REVOKE));
    assert!(G8lS552InputRights::OWNER.contains(G8lS552InputRights::SIGNAL));
    assert!(!G8lS552InputRights::OWNER.contains(G8lS552InputRights::WAIT));
    assert_eq!(service_s552_model_register_task(&mut state, APP_A), Ok(Outcome::TaskRegistered(APP_A)));
    assert_eq!(service_s552_model_register_task(&mut state, APP_A), Ok(Outcome::TaskRetained(APP_A)));
    assert_eq!(state.rights_of(APP_A), Some(G8lS552InputRights::NONE));
    assert_eq!(
        service_s552_model_grant_input_rights(&mut state, OWNER, APP_A, G8lS552InputRights::WAIT),
        Ok(Outcome::RightsGranted { task: APP_A, rights: G8lS552InputRights::WAIT })
    );
    assert_eq!(
        service_s552_model_grant_input_rights(&mut state, OWNER, APP_A, G8lS552InputRights::WAIT),
        Ok(Outcome::RightsRetained { task: APP_A, rights: G8lS552InputRights::WAIT })
    );
    assert_eq!(
        service_s552_model_focus_grant(&mut state, OWNER, APP_A),
        Ok(Outcome::FocusGranted { task: APP_A, depth: 1 })
    );
    assert_eq!(
        service_s552_model_focus_grant(&mut state, OWNER, APP_A),
        Ok(Outcome::FocusRetained { task: APP_A, depth: 1 })
    );
    assert_eq!(state.focused_task(), Some(APP_A));
    assert_eq!(state.focus_stack(), &[APP_A]);
}

#[test]
fn focus_grant_without_input_rights_fails_closed() {
    let mut state = G8lS552InputFocusRoutingState::new();
    service_s552_model_register_task(&mut state, APP_A).unwrap();
    assert_eq!(
        service_s552_model_focus_grant(&mut state, OWNER, APP_A),
        Err(Error::InputRightsMissing)
    );
    assert_eq!(state.focused_task(), None);
    assert_eq!(
        service_s552_model_grant_input_rights(&mut state, OWNER, APP_A, G8lS552InputRights::GRANT),
        Err(Error::NonGrantableRights)
    );
    assert_eq!(
        service_s552_model_grant_input_rights(&mut state, OWNER, APP_A, G8lS552InputRights::NONE),
        Err(Error::InvalidRightsMask)
    );
    assert_eq!(
        service_s552_model_grant_input_rights(&mut state, OWNER, APP_A, G8lS552InputRights(0b1_0000)),
        Err(Error::InvalidRightsMask)
    );
    assert_eq!(state.rights_of(APP_A), Some(G8lS552InputRights::NONE));
}

#[test]
fn non_owner_cannot_grant_rights_or_focus_or_revoke() {
    let mut state = scene();
    assert_eq!(
        service_s552_model_grant_input_rights(&mut state, APP_A, APP_B, G8lS552InputRights::WAIT),
        Err(Error::InputRightsMissing)
    );
    assert_eq!(
        service_s552_model_focus_grant(&mut state, APP_B, APP_B),
        Err(Error::InputRightsMissing)
    );
    assert_eq!(
        service_s552_model_focus_revoke(&mut state, APP_B, APP_A),
        Err(Error::InputRightsMissing)
    );
    assert_eq!(
        service_s552_model_focus_revoke(&mut state, OWNER, APP_B),
        Err(Error::TaskNotFocused)
    );
    assert_eq!(state.focused_task(), Some(APP_A));
    assert_eq!(
        service_s552_model_dequeue_event(&mut state, OWNER),
        Err(Error::InputRightsMissing)
    );
}

#[test]
fn unknown_or_invalid_task_and_surface_fail_closed() {
    let mut state = scene();
    assert_eq!(service_s552_model_register_task(&mut state, S552_INVALID_TASK), Err(Error::InvalidTaskId));
    assert_eq!(
        service_s552_model_grant_input_rights(&mut state, OWNER, 42, G8lS552InputRights::WAIT),
        Err(Error::UnknownTask)
    );
    assert_eq!(
        service_s552_model_grant_input_rights(&mut state, 42, APP_A, G8lS552InputRights::WAIT),
        Err(Error::UnknownTask)
    );
    assert_eq!(service_s552_model_focus_grant(&mut state, OWNER, 42), Err(Error::UnknownTask));
    assert_eq!(service_s552_model_focus_revoke(&mut state, OWNER, 0), Err(Error::InvalidTaskId));
    assert_eq!(service_s552_model_dequeue_event(&mut state, 42), Err(Error::UnknownTask));
    assert_eq!(
        service_s552_model_register_surface(&mut state, surface(0x99, 42, 0, 0, 10, 10, 5)),
        Err(Error::UnknownTask)
    );
    assert_eq!(
        service_s552_model_register_surface(&mut state, surface(SURFACE_A, APP_A, 0, 0, 10, 10, 1)),
        Err(Error::SurfaceDrift)
    );
    assert_eq!(
        service_s552_model_register_surface(&mut state, surface(SURFACE_A, APP_A, 0, 0, 720, 1280, 1)),
        Ok(Outcome::SurfaceRetained(SURFACE_A))
    );
    for task in 10..=18 {
        assert_eq!(service_s552_model_register_task(&mut state, task), Ok(Outcome::TaskRegistered(task)));
    }
    assert_eq!(service_s552_model_register_task(&mut state, 99), Err(Error::TaskTableFull));
    assert_eq!(service_s552_model_register_task(&mut state, 18), Ok(Outcome::TaskRetained(18)));
}

#[test]
fn surface_registration_rejects_out_of_panel_empty_and_duplicate_z() {
    let mut state = scene();
    assert_eq!(
        service_s552_model_register_surface(&mut state, surface(0x10, APP_A, 700, 0, 21, 10, 3)),
        Err(Error::SurfaceOutsidePanel)
    );
    assert_eq!(
        service_s552_model_register_surface(&mut state, surface(0x11, APP_A, 0, 1200, 10, 81, 3)),
        Err(Error::SurfaceOutsidePanel)
    );
    assert_eq!(
        service_s552_model_register_surface(&mut state, surface(0x12, APP_A, 65535, 0, 2, 2, 3)),
        Err(Error::SurfaceOutsidePanel)
    );
    assert_eq!(
        service_s552_model_register_surface(&mut state, surface(0x13, APP_A, 0, 0, 0, 10, 3)),
        Err(Error::EmptySurface)
    );
    assert_eq!(
        service_s552_model_register_surface(&mut state, surface(0x14, APP_A, 0, 0, 10, 10, 1)),
        Err(Error::DuplicateSurfaceZ)
    );
    assert_eq!(
        service_s552_model_register_surface(&mut state, surface(0x15, APP_A, 710, 1270, 10, 10, 3)),
        Ok(Outcome::SurfaceRegistered(0x15))
    );
    for z in 4..=S552_MAX_SURFACES as u8 + 1 {
        let _ = service_s552_model_register_surface(&mut state, surface(0x20 + z as u32, APP_A, 0, 0, 1, 1, z));
    }
    assert_eq!(
        service_s552_model_register_surface(&mut state, surface(0x40, APP_A, 0, 0, 1, 1, 50)),
        Err(Error::SurfaceTableFull)
    );
}

#[test]
fn malformed_frames_are_rejected_without_mutation() {
    let mut state = scene();
    let six: Vec<_> = (0..6u8).map(|track| point(track, 10, 10)).collect();
    assert_eq!(
        service_s552_model_route_touch_frame(&mut state, 1, &six),
        Err(Error::TooManyTouchPoints)
    );
    assert_eq!(
        service_s552_model_route_touch_frame(&mut state, 1, &[point(3, 1, 1), point(3, 2, 2)]),
        Err(Error::DuplicateTrackId)
    );
    assert_eq!(
        service_s552_model_route_touch_frame(&mut state, 1, &[point(10, 1, 1)]),
        Err(Error::TrackIdOutOfRange)
    );
    assert_eq!(
        service_s552_model_route_touch_frame(&mut state, 1, &[point(0, 720, 1)]),
        Err(Error::TouchOutsidePanel)
    );
    assert_eq!(
        service_s552_model_route_touch_frame(&mut state, 1, &[point(0, 1, 1280)]),
        Err(Error::TouchOutsidePanel)
    );
    routed(&mut state, 5, &[]);
    assert_eq!(
        service_s552_model_route_touch_frame(&mut state, 5, &[]),
        Err(Error::FrameSequenceNotMonotonic)
    );
    assert_eq!(
        service_s552_model_route_touch_frame(&mut state, 4, &[]),
        Err(Error::FrameSequenceNotMonotonic)
    );
    assert_eq!(state.active_tracks(), 0);
    assert_eq!(state.queue_len(APP_A), Some(0));
    let Ok(Outcome::Published(receipt)) = service_s552_model_publish_routing_receipt(&mut state) else {
        panic!("publication missing")
    };
    assert_eq!(receipt.frames_routed, 1);
    assert_eq!(receipt.events_delivered, 0);
}

#[test]
fn down_move_up_are_routed_per_track_with_monotonic_sequence() {
    let mut state = scene();
    let first = routed(&mut state, 1, &[point(0, 100, 100), point(1, 200, 200)]);
    assert_eq!(first.delivered, 2);
    assert_eq!(state.active_tracks(), 2);
    let second = routed(&mut state, 2, &[point(0, 105, 100)]);
    assert_eq!((second.delivered, second.released_tracks), (2, 1));
    let third = routed(&mut state, 3, &[]);
    assert_eq!((third.delivered, third.released_tracks), (1, 1));
    assert_eq!(state.active_tracks(), 0);
    assert_eq!(state.queue_len(APP_A), Some(5));
    let events: Vec<_> = (0..5).map(|_| dequeued(&mut state, APP_A)).collect();
    let sequences: Vec<_> = events.iter().map(|event| event.sequence).collect();
    assert_eq!(sequences, vec![1, 2, 3, 4, 5]);
    let shape: Vec<_> = events
        .iter()
        .map(|event| (event.frame_sequence, event.track_id, event.phase, event.x, event.y))
        .collect();
    assert_eq!(
        shape,
        vec![
            (1, 0, G8lS552TouchPhase::Down, 100, 100),
            (1, 1, G8lS552TouchPhase::Down, 200, 200),
            (2, 0, G8lS552TouchPhase::Move, 105, 100),
            (2, 1, G8lS552TouchPhase::Up, 200, 200),
            (3, 0, G8lS552TouchPhase::Up, 105, 100),
        ]
    );
    assert!(events.iter().all(|event| event.task == APP_A && event.surface_id == SURFACE_A));
    assert_eq!(service_s552_model_dequeue_event(&mut state, APP_A), Ok(Outcome::QueueEmpty(APP_A)));
}

#[test]
fn topmost_z_surface_wins_hit_test_and_misses_are_counted() {
    let mut state = scene();
    assert_eq!(
        service_s552_model_register_surface(&mut state, surface(SURFACE_A_TOP, APP_A, 300, 900, 100, 100, 7)),
        Ok(Outcome::SurfaceRegistered(SURFACE_A_TOP))
    );
    let summary = routed(&mut state, 1, &[point(2, 350, 950), point(3, 10, 10)]);
    assert_eq!((summary.delivered, summary.missed_hits, summary.unfocused_drops), (2, 0, 0));
    let on_top = dequeued(&mut state, APP_A);
    assert_eq!((on_top.surface_id, on_top.track_id), (SURFACE_A_TOP, 2));
    let on_base = dequeued(&mut state, APP_A);
    assert_eq!((on_base.surface_id, on_base.track_id), (SURFACE_A, 3));
    let mut bare = G8lS552InputFocusRoutingState::new();
    service_s552_model_register_task(&mut bare, APP_A).unwrap();
    service_s552_model_grant_input_rights(&mut bare, OWNER, APP_A, G8lS552InputRights::WAIT).unwrap();
    service_s552_model_focus_grant(&mut bare, OWNER, APP_A).unwrap();
    let miss = routed(&mut bare, 1, &[point(0, 5, 5)]);
    assert_eq!((miss.delivered, miss.missed_hits), (0, 1));
    assert_eq!(bare.active_tracks(), 0);
}

#[test]
fn touches_on_unfocused_task_surface_are_dropped_and_counted() {
    let mut state = scene();
    let summary = routed(&mut state, 1, &[point(0, 100, 1000)]);
    assert_eq!((summary.delivered, summary.unfocused_drops), (0, 1));
    assert_eq!(state.queue_len(APP_B), Some(0));
    assert_eq!(state.queue_len(APP_A), Some(0));
    assert_eq!(state.active_tracks(), 0);
    let mut unfocused = G8lS552InputFocusRoutingState::new();
    service_s552_model_register_task(&mut unfocused, APP_A).unwrap();
    service_s552_model_grant_input_rights(&mut unfocused, OWNER, APP_A, G8lS552InputRights::WAIT).unwrap();
    service_s552_model_register_surface(&mut unfocused, surface(SURFACE_A, APP_A, 0, 0, 720, 1280, 1)).unwrap();
    let dropped = routed(&mut unfocused, 1, &[point(0, 1, 1)]);
    assert_eq!((dropped.delivered, dropped.unfocused_drops), (0, 1));
    assert_eq!(service_s552_model_dequeue_event(&mut unfocused, APP_A), Ok(Outcome::QueueEmpty(APP_A)));
}

#[test]
fn queue_overflow_drops_oldest_and_counts() {
    let mut state = scene();
    routed(&mut state, 1, &[point(0, 10, 10)]);
    for frame in 2..=40u32 {
        routed(&mut state, frame, &[point(0, 10 + frame as u16, 10)]);
    }
    assert_eq!(state.queue_len(APP_A), Some(S552_EVENT_QUEUE_CAPACITY));
    let oldest = dequeued(&mut state, APP_A);
    assert_eq!(oldest.sequence, 9);
    assert_eq!(oldest.phase, G8lS552TouchPhase::Move);
    let Ok(Outcome::Published(receipt)) = service_s552_model_publish_routing_receipt(&mut state) else {
        panic!("publication missing")
    };
    assert_eq!(receipt.events_delivered, 40);
    assert_eq!(receipt.overflow_drops, 8);
    assert_eq!(receipt.events_dequeued, 1);
    assert_eq!(receipt.last_event_sequence, 40);
}

#[test]
fn focus_revoke_flushes_queue_and_releases_tracks() {
    let mut state = scene();
    routed(&mut state, 1, &[point(0, 10, 10), point(4, 20, 20)]);
    assert_eq!(state.queue_len(APP_A), Some(2));
    assert_eq!(
        service_s552_model_focus_revoke(&mut state, OWNER, APP_A),
        Ok(Outcome::FocusRevoked { task: APP_A, flushed: 2, released_tracks: 2, depth: 0 })
    );
    assert_eq!(state.queue_len(APP_A), Some(0));
    assert_eq!(state.focused_task(), None);
    assert_eq!(state.active_tracks(), 0);
    let after = routed(&mut state, 2, &[point(0, 11, 10)]);
    assert_eq!((after.delivered, after.unfocused_drops), (0, 1));
    assert_eq!(service_s552_model_dequeue_event(&mut state, APP_A), Ok(Outcome::QueueEmpty(APP_A)));
    let Ok(Outcome::Published(receipt)) = service_s552_model_publish_routing_receipt(&mut state) else {
        panic!("publication missing")
    };
    assert_eq!(receipt.flushed_events, 2);
    assert_eq!(receipt.unfocused_drops, 1);
}

#[test]
fn focus_stack_restores_previous_task_and_is_bounded() {
    let mut state = scene();
    routed(&mut state, 1, &[point(0, 100, 100)]);
    assert_eq!(
        service_s552_model_focus_grant(&mut state, OWNER, APP_B),
        Ok(Outcome::FocusGranted { task: APP_B, depth: 2 })
    );
    assert_eq!(state.focus_stack(), &[APP_A, APP_B]);
    let switched = routed(&mut state, 2, &[point(0, 100, 100), point(1, 100, 1000)]);
    assert_eq!((switched.delivered, switched.unfocused_drops), (1, 1));
    let event = dequeued(&mut state, APP_B);
    assert_eq!((event.task, event.surface_id, event.phase), (APP_B, SURFACE_B, G8lS552TouchPhase::Down));
    assert_eq!(
        service_s552_model_focus_revoke(&mut state, OWNER, APP_B),
        Ok(Outcome::FocusRevoked { task: APP_B, flushed: 0, released_tracks: 1, depth: 1 })
    );
    assert_eq!(state.focused_task(), Some(APP_A));
    assert_eq!(
        service_s552_model_focus_grant(&mut state, OWNER, APP_B),
        Ok(Outcome::FocusGranted { task: APP_B, depth: 2 })
    );
    assert_eq!(
        service_s552_model_focus_grant(&mut state, OWNER, APP_A),
        Ok(Outcome::FocusGranted { task: APP_A, depth: 2 })
    );
    assert_eq!(state.focus_stack(), &[APP_B, APP_A]);
    for task in 20..26 {
        service_s552_model_register_task(&mut state, task).unwrap();
        service_s552_model_grant_input_rights(&mut state, OWNER, task, G8lS552InputRights::WAIT).unwrap();
        service_s552_model_focus_grant(&mut state, OWNER, task).unwrap();
    }
    assert_eq!(state.focus_stack().len(), S552_MAX_FOCUS_DEPTH);
    assert_eq!(
        service_s552_model_grant_input_rights(&mut state, OWNER, OWNER, G8lS552InputRights::WAIT),
        Ok(Outcome::RightsGranted { task: OWNER, rights: G8lS552InputRights(0b1111) })
    );
    assert_eq!(service_s552_model_focus_grant(&mut state, OWNER, OWNER), Err(Error::FocusStackFull));
}

#[test]
fn source_only_gate_keeps_runtime_physical_and_r1_claims_zero() {
    assert!(SOURCE.contains("S552_SUPPORTED_PROFILE_RUNTIME_OBSERVATIONS: usize = 0"));
    assert!(SOURCE.contains("S552_PHYSICAL_OBSERVATIONS: usize = 0"));
    assert!(SOURCE.contains("S552_PHYSICAL_OR_DEVICE_OPERATIONS: usize = 0"));
    assert!(SOURCE.contains("S552_HARDWARE_PRESENT: bool = false"));
    assert!(SOURCE.contains("S552_BOOT_TO_UI_PHYSICALLY_OBSERVED: bool = false"));
    assert!(SOURCE.contains("S552_R1_ACCEPTANCE_COMPLETE: bool = false"));
    assert!(SOURCE.contains("RUNBOOK_EXECUTED_IN_S552: bool = false"));
    assert!(SOURCE.contains("S552_EVENT_QUEUE_CAPACITY: usize = 32"));
}
snippet sha256: de18a2377493file sha256: de18a2377493
03 · Kapı kimlik kaydı

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

tam Operations kaydıL2781–L2840
website/src/lib/operations.ts::g8l-s552-r1-input-focus-touch-event-routing-model
  {
    id: "g8l-s552-r1-input-focus-touch-event-routing-model",
    date: "2026-08-30",
    sequence: 552,
    status: "passed",
    umbrella_status: "partial",
    title: "S552 · R1 ekran: input focus ve dokunma olayı yönlendirme modeli",
    summary:
      "S552 kaynak/host model kapısı PASS'tir: GT911-benzeri decoder soyutlamasından gelen dokunma frame'lerini, capability ile korunan bir focus stack'i (owner task INPUT WAIT hakkını verir; GRANT/REVOKE yalnız owner'dadır), z-sıralı dikdörtgen surface hit-testing'ini ve track başına Down/Move/Up olay dönüşümünü modelleyen saf bir durum makinesi eklendi. Her task için kapasitesi 32 olan sınırlı olay kuyruğu taşmada en eski olayı düşürüp sayar; olay sıra numaraları kesin monoton artar; focus revoke kuyruğu boşaltıp aktif track'leri bırakır ve odaksız task'a hiçbir olay teslim edilmez. Bilinmeyen task/surface, eksik hak, bozuk frame (5'ten fazla nokta, tekrar eden track id, panel dışı koordinat, monoton olmayan frame sırası) ve yayın sonrası sapma 21 ayrı nonzero diagnostic kodla fail-closed reddedilir. Focused 19/19 PASS'tir; S540 ve S543 fiziksel RED immutable kalır; physical observation=0, donanım yok, production callsite yok ve RUNBOOK_EXECUTED_IN_S552=NO'dur. S553 yönlendirilen olayları tüketen sistem UI kilit/durum/ayarlar sahne akışı modelidir.",
    evidence: [
      "S552, S551'den ayrı kaynak modülü, 19-test focused binary, proof, status manifest, Operations kaydı ve complete Code kartına sahiptir; hiçbir production callsite, IRQ, scheduler veya sürücü yoluna bağlanmaz.",
      "Dar S552 source-model status=PASS; R1 umbrella=PARTIAL, R1 stage=2 (Ekran, dokunma ve temel UI) ve S540/S543 physical gate status=RED olarak ayrı tutulur.",
      "Focus stack derinliği 8'dir; focus grant, granter'da GRANT ve hedef task'ta WAIT hakkı ister; hedef stack'in tepesine taşınır ve dolu stack FocusStackFull ile reddedilir.",
      "Input hakları ipc.rs/ui/capability.rs notification sözlüğüyle hizalı SIGNAL/WAIT/GRANT/REVOKE bitleridir; yalnız WAIT grant edilebilir; NONE veya 4-bit maske dışı bitler InvalidRightsMask, GRANT/REVOKE/SIGNAL istekleri NonGrantableRights verir.",
      "Surface tablosu 8 dikdörtgenle sınırlıdır; kayıt checked add ile 720x1280 panel sınırını, boş olmayan boyutu, benzersiz id ve benzersiz z'yi zorlar; aynı id altında farklı dikdörtgen SurfaceDrift'tir.",
      "Frame doğrulaması mutasyondan önce yapılır: en fazla 5 nokta, track id 0..=9, panel içi koordinat, tekrar eden track id yok ve frame sırası son yönlendirilen frame'den kesin büyük.",
      "Yeni track en yüksek z'li kapsayan surface'a hit-test edilir; Down yalnız o surface odaklı task'a aitse teslim edilir, aksi halde unfocused_drops (surface yoksa missed_hits) sayılır; tutulan track odak sürdükçe Move üretir; frame'de bulunmayan track son konumunda Up üretip bırakılır.",
      "Task başına kuyruk kapasitesi 32'dir; taşmada en eski olay düşürülür ve overflow_drops hem frame özetinde hem receipt'te sayılır; 40 ardışık olayda 8 drop ve en eski kalan sıra=9 doğrulanmıştır.",
      "Olay sıra numarası u64 olarak 1'den başlar, checked add ile kesin monoton artar ve tükenme EventSequenceExhausted verir.",
      "Focus revoke REVOKE hakkı ister; hedef stack'ten çıkar, kuyruğu flushed_events sayacıyla boşaltılır, tuttuğu track'ler bırakılır ve önceki stack girdisi yeniden odak olur.",
      "Yayınlanan routing receipt tüm sayaçları anlık görüntüler; exact replay Retained döner, sonraki her sayaç veya tablo değişikliği PublishedStateDrift ile fail-closed reddedilir.",
      "21 hata varyantı 1..=21 aralığında nonzero ve benzersiz diagnostic kod taşır.",
      "Kaynakta unsafe, asm!, write_volatile, crate::uart, crate::arch, #[no_mangle], spin:: veya std:: yoktur; yalnız core ve alloc kullanılır.",
      "Focused target 1 grup / 19 passed / 0 failed / 0 ignored / 0 filtered verdi.",
      "Implementation 24732 B / 53f16cdfed1fb4a35d28657a6f1f7b89e8340598b1d2ca7757a3d6d84b23df03; focused test 24345 B / de18a23774933496cdfa181c6ea5e8d22eefd98370e12225f6fa9e23f8d96f9f SHA-256'dır.",
      "Proof 4753 B'dir.",
      "S540 immutable raw 20525 B ve S543 immutable raw 20509 B fiziksel RED olarak byte-exact korunur; automatic promotion=false ve rerun=false'dur.",
      "S552 sırasında SD write/read-back/eject, UART open/capture, power transition, dokunma denetleyicisi, panel, I2C veya board gözlemi yapılmadı; S546 kararı varsayılmaz.",
      "RUNBOOK_EXECUTED_IN_S552=NO; supported-profile runtime observations=0, physical observations=0, hardware present=false, Boot-to-UI physically observed=false ve R1 acceptance=false'dur.",
      "S553 yönlendirilen input olaylarını tüketen sistem UI kilit/durum/ayarlar sahne akışını yalnız host üzerinde 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_s552_r1_input_focus_touch_event_routing_model -- --test-threads=1",
    ],
    terminalSessions: [
      {
        id: "s552-focused",
        title: "S552 input focus ve dokunma olayı yönlendirme modeli focused",
        commandLines: [
          "CARGO_INCREMENTAL=0 cargo test -p aselsan_microkernel_simulation --test g8l_target_dispatch_scheduler_owner_scheduler_mutation_production_migration_lifecycle_s552_r1_input_focus_touch_event_routing_model -- --test-threads=1",
        ],
        outputLines: [
          "test result: ok. 19 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s",
          "S552 focused=1 group / 19 passed / 0 failed",
          "hardware=none physical=0 runbook=NO",
        ],
        exitCode: 0,
        outputMode: "complete",
      },
    ],
    terminalSessionsNote:
      "S552 kaynak/host model PASS'tir; supported-profile runtime veya fiziksel PASS değildir. S540 ve S543 RED raw ve kararları değişmez.",
    limitations: [
      "S552 yalnız host üzerinde derlenen ve focused testle sürülen bir modeldir; hiçbir donanım/panel/modem/board gözlemi yoktur.",
      "Model hiçbir production boot, IRQ, scheduler veya sürücü yoluna bağlanmamıştır; gerçek GT911 decoder veya I2C trafiği ile beslenmemiştir.",
      "S540 ve S543 fiziksel RED immutable kalır; otomatik yükseltme yoktur ve S546 kararı varsayılmaz.",
      "Boot-to-UI fiziksel gözlemi false ve R1 acceptance false kalır.",
      "S553 sistem UI kilit/durum/ayarlar sahne akışı modelini yalnız host üzerinde ekler; yeni SD/UART/power koşusu ayrı kapı, fresh target revalidation ve açık operatör yetkisi ister.",
    ],
  },
snippet sha256: 2b4c6ed6165afile 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_s552_r1_input_focus_touch_event_routing_model -- --test-threads=1
proof: docs/M8.1-RPi5-G8l-S552-R1-Input-Focus-Touch-Event-Routing-Model-Proof.md
Registry schema v5 · generator website/scripts/generate-code-gates.mjs · Tam SHA-256: 3050638b71a684d8f8f947a8a6faa237a17fa8db5dc0db04fb207b668b462af9