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: 53f16cdfed1f…file 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: de18a2377493…file 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: 2b4c6ed6165a…file 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=1proof: 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: 91d38c7b6222f0b4c117be786454853543da55a160e543d9b951057cc20dcc06