이클립스 흐름 — 배너 진입 · 이벤트 버튼 · 오버레이 · 방어 진행 표시 · 흔들기 감지 통일
This commit is contained in:
@@ -48,22 +48,62 @@ typedef PlaceProbe = Future<PlaceFix> Function();
|
||||
/// 흔들기 한 번마다 그 시각을 내는 스트림.
|
||||
typedef ShakeProbe = Stream<DateTime> Function();
|
||||
|
||||
/// 흔들기 감지 — 가속도 크기가 중력(9.8)을 크게 넘는 순간. 이클립스 방어 화면과 같은 문턱 ·
|
||||
/// 간격이다(같은 동작을 같은 기준으로 센다).
|
||||
Stream<DateTime> deviceShakes() async* {
|
||||
var last = DateTime.fromMillisecondsSinceEpoch(0);
|
||||
await for (final e in accelerometerEventStream()) {
|
||||
final magnitude = math.sqrt(e.x * e.x + e.y * e.y + e.z * e.z);
|
||||
final now = DateTime.now();
|
||||
if (!magnitude.isFinite || magnitude < 18 ||
|
||||
now.difference(last) < const Duration(milliseconds: 220)) {
|
||||
continue;
|
||||
/// 흔들기 한 번을 세는 규칙 — Music Shake · 이클립스 방어 · HERO TIME 이 함께 쓴다.
|
||||
///
|
||||
/// **단위는 두 플랫폼 모두 m/s²(중력 포함)다.** sensors_plus 가 iOS 의 g 값에 9.81 을
|
||||
/// 곱해 넘긴다. 플랫폼 차이는 **표본 주기**였다(2026-10-05 실기기 — 같은 시간 흔들어
|
||||
/// iPhone 16 · Galaxy 63). 기본 주기(200ms)를 iOS 는 그대로 지켜 초당 5 표본만 오고,
|
||||
/// Android 는 요청을 힌트로만 받아 훨씬 촘촘히 보낸다. 흔들기의 가속도 정점은 수십 ms
|
||||
/// 라 5Hz 표본은 대부분을 놓친다. 그래서 [sampling] 을 명시하고(약 50Hz),
|
||||
/// 정점 하나를 한 번만 세도록 [rearm] 아래로 내려와야 다시 센다(히스테리시스).
|
||||
class ShakeDetector {
|
||||
ShakeDetector({
|
||||
this.threshold = 18,
|
||||
this.rearm = 13,
|
||||
this.gap = const Duration(milliseconds: 220),
|
||||
});
|
||||
|
||||
/// 흔들기로 보는 가속도 크기(m/s²). 정지 상태는 중력 9.8 이다.
|
||||
final double threshold;
|
||||
|
||||
/// 이 아래로 내려와야 다음 흔들기를 센다. 한 정점이 표본 여럿에 걸쳐도 한 번이다.
|
||||
final double rearm;
|
||||
|
||||
/// 두 흔들기 사이 최소 간격. 이보다 촘촘한 값은 같은 동작의 진동이다.
|
||||
final Duration gap;
|
||||
|
||||
/// 표본 주기. 두 플랫폼이 같은 주기로 보도록 기본값(200ms)을 쓰지 않는다.
|
||||
static const sampling = SensorInterval.gameInterval;
|
||||
|
||||
bool _armed = true;
|
||||
DateTime _last = DateTime.fromMillisecondsSinceEpoch(0);
|
||||
|
||||
/// 표본 하나를 넣는다. 흔들기 한 번으로 셀 표본이면 true.
|
||||
bool add(double x, double y, double z, DateTime at) {
|
||||
final magnitude = math.sqrt(x * x + y * y + z * z);
|
||||
if (!magnitude.isFinite) return false;
|
||||
if (magnitude < rearm) {
|
||||
_armed = true;
|
||||
return false;
|
||||
}
|
||||
last = now;
|
||||
yield now;
|
||||
if (!_armed || magnitude < threshold || at.difference(_last) < gap) {
|
||||
return false;
|
||||
}
|
||||
_armed = false;
|
||||
_last = at;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/// 단말 가속도계에서 흔들기 한 번마다 그 시각을 낸다.
|
||||
Stream<DateTime> deviceShakes() {
|
||||
final detector = ShakeDetector();
|
||||
return accelerometerEventStream(samplingPeriod: ShakeDetector.sampling)
|
||||
.map((e) => (e, DateTime.now()))
|
||||
.where((s) => detector.add(s.$1.x, s.$1.y, s.$1.z, s.$2))
|
||||
.map((s) => s.$2);
|
||||
}
|
||||
|
||||
/// Music Shake 구간 판정표 — `fn-quest-verify-music-shake` 가 받는 **증거**(원장 B-24).
|
||||
///
|
||||
/// 곡의 박(`quests.bpm`)마다 한 구간이다. 구간 박 시각 ±반 박 안에 흔들기가 있으면 맞춘 것,
|
||||
|
||||
Reference in New Issue
Block a user