mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-11-08 05:23:41 +08:00
02ae25b975
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.
147 lines
4.4 KiB
Python
147 lines
4.4 KiB
Python
"""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
|