본문 바로가기
C.W.K.
Stream
Lesson 02 of 05 · published

seek 은 상태 기계야, boolean 이 아니라

~14 min · observed-state, state-machine, seek, tolerance

Level 0릴 입문자
0 XP0/39 lessons0/12 achievements
0/100 XP to next level100 XP to go0% complete
"명령을 큐에 넣는 건 pending 이지 성공이 아냐. 엔진이 보고한 시간이 applied, clamped, failed 를 정해."

참도 거짓도 되기를 거부하는 seek

순진한 플레이어한테 "seek 됐어?" 물으면 true 라고 답해. 명령 보냈잖아, 안 그래? 근데 진짜 seek 은 boolean 이 담을 수 있는 것보다 결말이 많아. 요청한 데 딱 떨어질 수도 있고. duration 을 넘겨 요청해서 끝에 떨어질 수도 있고. 파일이 멈춰서 영영 확인 안 될 수도 있고. 아예 실패할 수도 있고. 그걸 전부 truefalse 로 욱여넣는 게, 플레이어가 엉뚱한 초에 앉은 채 성공을 우기는 방식이야.

Ashen Reel 은 seek 을 작은 상태 기계로 다뤄. pending 으로 시작하고, 엔진이 보고한 위치가 그걸 정확히 하나의 정직한 결말로 옮겨.

  • applied. 관찰된 위치가 타겟 tolerance 안. 진짜 성공.
  • clamped. 타겟이 끝을 넘어서 duration 에 snap 됨. UI 는 요청한 것도, 실제로 간 데도 둘 다 보여줘.
  • timeout. 엔진이 제때 위치를 확인 안 함. 요청 시간은 그대로 두고, 앱은 exact 열기를 우기지 않아.
  • failed. seek 이 에러남. 보이고, 이유도 남고.

정직한 정확함은 프레임 하나, 아니면 80밀리초

뭘 '요청한 데 떨어짐' 으로 쳐? 0밀리초는 아냐. 그건 매체가 못 지키는 환상이거든. 영상은 프레임으로 이뤄지고, 화면에 존재하는 제일 작은 건 프레임 하나야. 그래서 tolerance 를 정직하게 디코드된 프레임 하나 또는 80 ms 중 큰 쪽으로 정의해. 'exact' 는 어떤 포맷도 표현 못 하는 sub-frame 정밀도가 아니라 '맞는 프레임' 을 뜻하고. 매체의 결에 맞춰 성공을 정의하는 게, 그 주장을 진실되게 지켜.

결말을 boolean 이 아니라 이름 붙은 상태로 다뤄. 결과에 의미 있는 결말이 둘 넘게 생기는 순간, bool 은 안 맞는 것들에 대해 거짓말하라고 강요해. 정직한 결말의 enum 은 UI 랑 로그랑 테스트가 전부 같은 현실을 말하게 해주고.

해석 로직은 작고, 모든 분기가 실제로 일어날 수 있는 진짜야.

clamp 도 정직하게 보여줘야 하는 성공이야. 끝을 넘겨 점프하라고 하면 duration 에 snap 하는 게 맞는 동작이야. 근데 UI 는 요청 시간이랑 적용 시간을 둘 다 보여줘. 사용자가 그림이 왜 자기가 친 데 없는지 알 수 있게. 조용한 clamp 은 버그처럼 보이고, 라벨 붙은 clamp 은 배려처럼 보이거든.

왜 이 기계가 남는 장사인가

seek 이 이름 붙은 상태를 가지면, 아래쪽 전부가 공짜로 정직해져. 오버레이는 pending 동안 "seeking…" 을, 그 뒤엔 진짜 위치를 말하고. 로그는 수수께끼 같은 틀린 숫자 대신 clamped 를 기록하고. 테스트는 추측 대신 appliedtimeout 을 딱 단언하고. 그리고 맞는 초에 떨어지느냐에 사활이 걸린 Recall handoff 는 마침내 "네가 기억한 그 순간에 있어""시도했고 실제론 이렇게 됐어" 를 구분할 수 있어.

Code

seek 결말을 정직한 상태로, 매체에 맞춰 해석·swift
enum SeekResult {
    case pending                       // sent, not yet confirmed
    case applied(observed: Double)     // landed within tolerance of target
    case clamped(to: Double)           // target was past the end -> snapped
    case timeout(target: Double)       // engine never confirmed in time
    case failed(reason: String)
}

/// Compare the engine's OBSERVED position to the target, honestly.
func resolveSeek(target: Double,
                 observed: Double,
                 duration: Double,
                 frameDuration: Double) -> SeekResult {
    let tolerance = max(frameDuration, 0.080)   // one frame OR 80 ms
    if target > duration { return .clamped(to: duration) }
    if abs(observed - target) <= tolerance { return .applied(observed: observed) }
    return .pending   // still off-target: keep observing, or time out elsewhere
}
// Nowhere does this return `true`. 'It worked' is one of five honest states.

External links

Exercise

네 코드에서 boolean '됐어?' 를 돌려주는데 실제론 결말이 둘 넘는 연산을 찾아. 결제(승인/거절/pending/3DS-필요), 저장(저장됨/충돌/오프라인-큐잉), fetch(ok/not-modified/rate-limited/실패) 같은 거. 반환을 이름 붙은 상태의 enum 으로 다시 설계해. 그다음 tolerance 질문을 골라. '충분히 가까움' 개념이 있는 거라면, 도메인의 진짜 결에 맞춘 정직한 임계값이 뭐야?
Hint
bool 을 넘어섰다는 신호 둘. 하나, true 를 돌려주면서 그게 어떤 종류의 true 였는지 warning 을 로깅하는 자신을 발견함. 둘, 호출자가 진짜 무슨 일이 났는지 알려고 두 번째 필드를 들여다봐야 함. 둘 다 결말이 이름을 원한다는 뜻이야. 그리고 '성공 tolerance' 는 이상화된 0 이 아니라 매체의 진짜 해상도에 맞춰 정의해.

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고

댓글 0

🔔 답글 알림 (로그인 필요)
로그인댓글을 남기려면 로그인해 주세요.

아직 댓글이 없어요. 첫 댓글을 남겨보세요.