Files
StarPilot/openpilot/starpilot/ui/spacepilot/state.py
T
firestar5683 02ae25b975 Make SpacePilot entry reliable at natural tapping speeds
Count distinct touch releases by their original timestamps, retain taps
during valid Park dwell, and let preparation earn its own admission.
Cancel only the pending C4 startup camera redirect for a freshly parked
open game. All session, physical, alert, control, noOutput, ignition and
input authority checks remain unchanged.

231 focused native cases, original before regressions, current successor
entry/decoder qualification, canonical lint/type checks and independent
review. Host functional evidence; no device changes or headroom claim.
2026-10-26 14:35:25 -05:00

147 lines
4.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Bounded game simulation and input state; no vehicle writes or UI dependencies."""
from dataclasses import dataclass
from enum import StrEnum
import math
import random
class FiveTaps:
"""Five separate releases at a natural pace; use original touch timestamps."""
def __init__(self):
self.reset()
def reset(self) -> None:
self.count = 0
self.first = self.last = None
def tap(self, now: float) -> bool:
if not math.isfinite(now):
self.reset()
return False
if self.last is not None and 0 <= now - self.last < .04:
return False
if self.last is None or not 0 <= now - self.last <= 1.5 or self.first is None or now - self.first > 6.:
self.count, self.first = 0, now
self.count += 1
self.last = now
if self.count == 5:
self.reset()
return True
return False
class WheelInput:
"""Observe original events once. A release arms each key; held/repeated keys do nothing.
Unknown pre-admission button state cannot be inferred from an empty event list.
The first wheel press/release primes a key; its next press can launch the game.
"""
def __init__(self, stamp: int = 0):
self.stamp = stamp
self.armed: set[str] = set()
self.last_action = -math.inf
def update(self, stamp: int, events: tuple[tuple[str, bool], ...], now: float) -> bool:
if stamp <= self.stamp:
return False
self.stamp = stamp
fire = False
for kind, pressed in events:
if kind not in ('accelCruise', 'decelCruise'):
continue
if not pressed:
self.armed.add(kind)
else:
if kind in self.armed and now - self.last_action >= .12:
self.last_action, fire = now, True
self.armed.discard(kind)
return fire
class Phase(StrEnum):
READY = 'ready'
FLIGHT = 'flight'
OVER = 'over'
@dataclass
class Gate:
x: float
center: float
scored: bool = False
class Flight:
"""1072 × 480 C4 canvas, fixed 120 Hz physics, capped catch-up and storage."""
WIDTH, HEIGHT = 1072., 480.
SHIP_X, RADIUS = 270., 12.
TOP, BOTTOM = 64., 438.
GATE_WIDTH, GAP = 72., 174.
STEP = 1 / 120
def __init__(self, seed: int = 0):
self.rng = random.Random(seed)
self.best = 0
self.phase = Phase.READY
self.y, self.vy = 250., 0.
self.score = 0
self.age = self.over_age = self.accumulator = 0.
self.distance = 0.
self.gates: list[Gate] = []
self.last_input = -math.inf
def act(self, now: float) -> bool:
if not math.isfinite(now) or now - self.last_input < .12 or (self.phase == Phase.OVER and self.over_age < .65):
return False
self.last_input = now
if self.phase != Phase.FLIGHT:
self.phase = Phase.FLIGHT
self.y, self.vy, self.score, self.distance, self.accumulator = 250., 0., 0, 0., 0.
self.gates = [Gate(1100., 250.)]
self.vy = -268.
return True
def _crash(self) -> None:
self.phase, self.over_age = Phase.OVER, 0.
self.best = max(self.best, self.score)
def update(self, dt: float) -> None:
if not math.isfinite(dt) or dt <= 0:
return
dt = min(dt, .05)
self.age += dt
if self.phase == Phase.OVER:
self.over_age += dt
if self.phase != Phase.FLIGHT:
return
self.accumulator += dt
while self.accumulator >= self.STEP and self.phase == Phase.FLIGHT:
self.accumulator -= self.STEP
speed = min(238., 168. + self.score * 3.)
self.vy = min(420., self.vy + 760. * self.STEP)
self.y += self.vy * self.STEP
self.distance += speed * self.STEP
if self.y - self.RADIUS <= self.TOP or self.y + self.RADIUS >= self.BOTTOM:
self._crash()
break
for gate in self.gates:
gate.x -= speed * self.STEP
if gate.x - self.RADIUS < self.SHIP_X < gate.x + self.GATE_WIDTH + self.RADIUS:
if abs(self.y - gate.center) + self.RADIUS >= self.GAP / 2:
self._crash()
break
if not gate.scored and gate.x + self.GATE_WIDTH + self.RADIUS < self.SHIP_X:
gate.scored = True
self.score += 1
if self.phase != Phase.FLIGHT:
break
self.gates = [gate for gate in self.gates if gate.x > -self.GATE_WIDTH]
if self.gates[-1].x < 835.:
center = max(170., min(330., self.gates[-1].center + self.rng.uniform(-80., 80.)))
self.gates.append(Gate(self.gates[-1].x + 325., center))
def close(self) -> None:
self.gates.clear()
self.phase = Phase.READY