이 글은 Today I Learn 시리즈의 131번째 기록입니다. (총 133개)

나선의 “높이” 슬라이더가 사실 상승 속도를 바꿔서 속도와 겹쳤다. 높이는 “어디까지 오를지(목표)”로, 속도는 “얼마나 빨리”로 나누고 싶었다. 그 과정에서 파생값·클램프·편차에 대해 배웠다.


높이=목표, 수명은 파생

세 값 높이 = 속도 × 수명이 한 식에 묶여 있어 셋을 다 독립으로 못 준다(코일 간격 = 상승 ÷ 회전이 같은 계열 — TIL 23). 그래서 둘만 손잡이로 두고 나머지는 파생시킨다. 높이(목표)와 속도를 정하고 수명을 뽑았다.

const vy = speed * (0.24 + r() * 0.12);        // 속도 = 상승 빠르기
const targetHeight = (rise / 3) * SPAWN_Y * 1.1; // 높이 = 도달 목표(preview 상단까지)
const life = targetHeight / vy;                 // 수명은 파생 — vy·life = 목표

vy·life = 목표가 성립하니 속도가 빠르든 느리든 같은 높이에서 죽는다. 빠르면 짧게 살고 느리면 오래 산다. 슬라이더를 화면 실측(SPAWN_Y=스폰~상단)에 매핑해 최대치가 미리보기 꼭대기 살짝 위가 되게 했다.


파생값을 클램프하면 불변식이 깨진다

처음엔 수명이 너무 짧거나 길까 봐 클램프(값을 상·하한 안에 가두기)를 걸었다. 그런데 이게 vy·life = 목표를 무너뜨렸다.

// ❌ 하한 0.4 → 빨리 닿아 짧게 죽어야 할 게 억지로 살아남아 그 시간 계속 올라가 목표 초과
// ❌ 상한 12 → 느린 속도서 목표에 닿기 전에 죽어 미달
const life = Math.min(Math.max(targetHeight / vy, 0.4), 12);

// ✅ 클램프 제거 — 입력 범위로 자연 한계에 맡긴다
const life = targetHeight / vy;

“높이 낮추고 속도 높였는데 더 높이 올라감”이 딱 하한 클램프 탓이었다. 파생값을 억지로 자르면, 그 값이 지키고 있던 관계(vy·life=목표)가 함께 틀어진다. 대신 입력의 범위로 한계를 준다 — 속도 하한(0.2)이 vy를 받쳐 수명이 무한정 커지지 않고(최대 ~19초), 총량은 별도 상한(MAX_PARTICLES)이 막는다.


너무 정확하면 오히려 부자연스럽다

그렇게 정확히 맞추니 이번엔 꼭대기가 자로 자른 듯 평평했다. life = 목표 ÷ vy가 모든 파티클에 정확히 성립해서, 속도 지터(파티클마다 주는 미세한 무작위 흔들림)가 상쇄되고 전부 똑같은 높이에서 죽은 것이다.

// 도달 목표에 파티클별 편차 → 조금씩 다른 높이에서 죽어 유기적 taper
const targetHeight = (rise / 3) * SPAWN_Y * 1.1 * (0.6 + 0.4 * r());

정밀함이 오히려 인공적으로 보이는 사례였다. 자연스러움은 딱 맞는 값이 아니라 적당한 흩어짐에서 온다. 각 파티클이 제 목표에서 죽는 성질(속도 무관)은 그대로 두고, 목표 자체만 흔들었다.


단일 토글을 독립 축으로

“발광 영역에 가두기” 하나였던 토글을 폭·높이 둘로 쪼갰다. 내부가 이미 높이(ceilY)와 폭(bx0/bx1)으로 나뉘어 있어서, 각 축을 따로 켜는 것만으로 독립 동작이 된다.

const ceilY = params.boundH ? topFrac - keyGlowLen : -Infinity; // 높이 = 천장
const bx0   = params.boundW ? centerX - keyWidth / 2 : -Infinity; // 폭 = 좌우

폭만 켜면 좌우만 좁고 높이는 파티클대로(발광 밴드보다 위까지), 높이만 켜면 세로만 천장. UI는 section + 자식 체크박스 2개. 켠 축만 클램프하고, 끈 축은 ±Infinity라 비교가 항상 통과한다(플래그 없이).


요약

  • 세 값이 한 식에 묶이면(높이=속도×수명) 둘만 손잡이로, 나머지는 파생(TIL 23의 커플링을 높이에 적용).
  • 파생값을 클램프하면 지키려던 불변식(vy·life=목표)이 깨진다 — 한계는 클램프가 아니라 입력 범위로.
  • life=목표÷vy가 정확히 성립하면 다 같은 높이서 죽어 평평 — 자연스러움은 의도적 편차에서.
  • 단일 토글도 내부가 축으로 나뉘어 있으면 독립 체크박스로 쪼갤 수 있다(끈 축은 ±Infinity 센티넬).

Series: Today I Learn

1 C++ 자료형(Data Type) 2 MD5 vs pHash 3 C++에서 함수의 선언과 정의 4 Tkinter padx, pady 5 메모리와 포인터 변수 6 Call by Value, Call by Reference, Call by Pointer 비교 7 const 8 Gemfile — Jekyll 프로젝트의 의존성 파일 9 kramdown-parser-gfm — Jekyll의 GFM 파서 10 파서(Parser) 11 AHU vs OHU 12 I might try it vs I'll try it 뉘앙스 차이 13 SESSION_EXPIRE_AT_BROWSER_CLOSE=True 14 configuration key 15 Git stash vs discard 16 subprocess.Popen으로 Windows 탐색기에 명령어를 전달 17 Post 잔디 분석하기 18 Google Sheets Sync 최적화 19 DSL (Domain Specific Language)과 GPL (General Purpose Language) 20 마크다운 표 그리는 방법 21 쿼리 파라미터(Query Parameter). 기존 QR코드 재활용 22 Django 보안 취약점 점검 및 수정 23 OOP Object-Oriented Programming 객체 지향 프로그래밍 24 Fernet 대칭 암호화 25 Jekyll 코드블록 안의 Liquid 태그 26 습관만들기 Pawbit TIL 1 27 insertOnConflictUpdate vs DoUpdate(target) 28 세션 필터 29 아코디언(Accordiaon) UI를 펼친상태로 만들기 30 input의 step 31 습관만들기 Pawbit TIL 2 32 습관만들기 Pawbit TIL 3 33 Word Cloud 34 Google Sheets를 데이터 버스로(with AppSheet) 35 Django 모델 텍스트 필드 자동 수집 패턴 36 localStorage로 섹션 토글 상태 유지 37 순차 ID 생성(`select_for_update()` + `max()` 조합) 38 역참조 검색과 distinct() 39 xlsx 다운로드와 로딩 오버레이 충돌 40 Android 파일 공유 MIME 타입 41 AssetManifest — Flutter 빌드 타임 asset 목록 런타임 조회 42 크레센도 Phaser + TypeScript TIL 1 43 크레센도 Phaser + TypeScript TIL 2 44 크레센도 Phaser + TypeScript TIL 3 45 크레센도 Phaser + TypeScript TIL 4 46 크레센도 Phaser + TypeScript TIL 5 47 여둘까 Office Layout TIL 1 48 습관만들기 Pawbit TIL 4 49 여둘까 Office Layout TIL 2 50 여둘까 Office Layout TIL 3 51 크레센도 Phaser + TypeScript TIL 6 52 UTF-8 BOM과 PowerShell 파일 쓰기 53 소리꽃 KeyBloom TIL 1 54 소리꽃 KeyBloom TIL 2 55 소리꽃 KeyBloom TIL 3 56 소리꽃 KeyBloom TIL 4 57 소리꽃 KeyBloom TIL 5 58 습관만들기 Pawbit TIL 5 59 여둘까 Office Layout TIL 4 60 소리꽃 KeyBloom TIL 6 61 소리꽃 KeyBloom TIL 7 62 소리꽃 KeyBloom TIL 8 63 메트로놈 Simple Metronome TIL 1 64 소리꽃 KeyBloom TIL 9 65 메트로놈 Simple Metronome TIL 2 66 소리꽃 KeyBloom TIL 10 67 습관만들기 Pawbit TIL 6 68 크레센도 Phaser + TypeScript TIL 7 69 메트로놈 Simple Metronome TIL 3 70 습관만들기 Pawbit TIL 7 71 정적 블로그 SEO 정비와 Pagefind 검색 도입 72 크레센도 Phaser + TypeScript TIL 8 73 메트로놈 Simple Metronome TIL 4 74 습관만들기 Pawbit TIL 8 75 안드로이드 AudioTrack 연속 재생, 실측 피커 정렬, 카메라 토치 플래시 76 메트로놈 Simple Metronome TIL 5 77 모래게임 Sandrop TIL 1 78 습관만들기 Pawbit TIL 9 79 메트로놈 Simple Metronome TIL 6 80 모래게임 Sandrop TIL 2 81 모래게임 Sandrop TIL 3 82 습관만들기 Pawbit TIL 10 83 습관만들기 Pawbit TIL 11 84 메트로놈 Simple Metronome TIL 7 85 모래게임 Sandrop TIL 4 86 모래게임 Sandrop TIL 5 87 습관만들기 Pawbit TIL 12 88 모래게임 Sandrop TIL 6 89 소리꽃 KeyBloom TIL 11 90 모래게임 Sandrop TIL 7 91 모래게임 Sandrop TIL 8 92 소리꽃 KeyBloom TIL 12 93 모래게임 Sandrop TIL 9 94 소리꽃 KeyBloom TIL 13 95 모래게임 Sandrop TIL 10 96 소리꽃 KeyBloom TIL 14 97 소리꽃 KeyBloom TIL 15 98 소리꽃 KeyBloom TIL 16 99 콘티온 Conti On TIL 1 100 소리꽃 KeyBloom TIL 17 101 콘티온 Conti On TIL 2 102 습관만들기 Pawbit TIL 13 103 콘티온 Conti On TIL 3 104 모래게임 Sandrop TIL 11 105 소리꽃 KeyBloom TIL 18 106 여둘까 Office Layout TIL 5 107 모래게임 Sandrop TIL 12 108 모래게임 Sandrop TIL 13 109 콘티온 Conti On TIL 4 110 모래게임 Sandrop TIL 14 111 소리꽃 KeyBloom TIL 19 112 소리꽃 KeyBloom TIL 20 113 소리꽃 KeyBloom TIL 21 114 소리꽃 KeyBloom TIL 22 115 습관만들기 Pawbit TIL 14 116 모래게임 Sandrop TIL 15 117 모래게임 Sandrop TIL 16 118 소리꽃 KeyBloom TIL 23 119 소리꽃 KeyBloom TIL 24 120 소리꽃 KeyBloom TIL 25 121 소리꽃 KeyBloom TIL 26 122 온실 GreenHouse TIL 1 123 콘티온 Conti On TIL 5 124 모래게임 Sandrop TIL 17 125 소리꽃 KeyBloom TIL 27 126 온실 GreenHouse TIL 2 127 콘티온 Conti On TIL 6 128 모래게임 Sandrop TIL 18 129 콘티온 Conti On TIL 7 130 모래게임 Sandrop TIL 19 131 소리꽃 KeyBloom TIL 28 읽는 중 132 모래게임 Sandrop TIL 20 133 모래게임 Sandrop TIL 21