조작 (플레이어가 확인 가능한 신호 — 반전된 채로 출고하지 마세요)
WASD, 화살표, 추적 카메라, 또는 비행체를 사용하는 모든 게임에서 이동/조향/비행 코드를 작성하기 전에 이 문서를 끝까지 읽으세요. racing-kart나 fps 같은 장르 파일만 열고 나머지를 건너뛰지 마세요 — 해당 파일들은 이미 이 신호들을 알고 있다고 가정합니다. 반전된 A/D는 차량 및 비행 데모에서 가장 흔한 출시 차단 요인입니다.
building-games (루프, 카메라, 3D 방향) 및 building-games/references/input.md (키 다운 상태, 게임패드, 터치)와 함께 사용하세요. 이 스킬은 플레이어에게 왼쪽/오른쪽/위가 무엇을 의미하는지와 이를 증명하는 방법을 담당합니다.
0. 하드 규칙 (위반 시 빌드 실패)
- 플레이어가 보는 왼쪽/오른쪽이 법입니다. 차량이 전진하는 동안 추적 / 후방 카메라 기준:
- A / ← → 기수(또는 기체의 기울기)가 화면 왼쪽으로 회전
- D / → → 기수(또는 기체의 기울기)가 화면 오른쪽으로 회전
- FPS 스트레이프를 차량 조향으로 재사용하지 마세요. FPS의 "D → +오른쪽 벡터"는 지면 평면상의 위치입니다. 차량의 A/D는 요(yaw) (또는 롤(roll) 속도) 이지, 스트레이프 오프셋이 아닙니다. 이 둘을 혼동하는 것이 반전된 A/D의 가장 큰 원인입니다.
- 작업 완료를 선언하기 전에 반드시 조작 자체 테스트(§5)를 실행해야 합니다. 스크린샷만으로는 QA가 충분하지 않습니다. A가 오른쪽으로 회전한다면 조향/롤 부호를 반전시키고, 다시 테스트한 후 출고하세요 — 새로운 좌표계 이야기를 지어내지 마세요.
1. 공통 3D 기준 (모든 곳에서 사용)
three.js: 오른손 좌표계, +Y 위쪽, 메시는 +Z 방향, 카메라는 −Z 방향.
XZ 평면의 요(Yaw) 전용 방위각 (지상 차량, 보행자, 대부분의 아케이드 비행체):
// yaw = 0이면 세계 −Z 방향; +yaw는 +Y 축 기준 시계 반대 방향 (기수가 −X 방향으로 이동)
forward = (-sin(yaw), 0, -cos(yaw))
right = ( cos(yaw), 0, -sin(yaw)) // = normalize(cross(forward, worldUp))
차량 후방의 추적 카메라 사용 시 (카메라는 position - forward * dist 근처):
| 플레이어가 보는 방향 | 세계 (이 기준) | 입력 |
|---|---|---|
| 기수 왼쪽 | +yaw | **A / ←**는 반드시 +yaw (또는 비행기의 경우 이에 상응하는 왼쪽 기울기)를 생성해야 함 |
| 기수 오른쪽 | −yaw | **D / →**는 반드시 −yaw를 생성해야 함 |
기준이 다르다면 일관된 한 쌍을 유지하세요 — 하지만 위의 플레이어가 보는 행은 필수입니다.
2. 장르별 매핑
2a. FPS / 도보 (스트레이프, 조향 아님)
W = +전진, S = -전진, D = +오른쪽, A = -오른쪽 // 위치, 요(yaw) 아님
마우스: yaw -= movementX * sens; pitch -= movementY * sens; pitch 제한
몸통 요(yaw)는 시야 전환용; 이동은 요(yaw)만 사용 (걷기에 피치(pitch)를 적용하지 마세요).
2b. 지상 / 수상 차량 (카트, 자전거, 제트스키, 보트, 탱크, 로버, 스노모빌)
heading/yaw 및 전진 speed를 사용하는 아케이드 바디:
// 입력 (키를 누르고 있으면 → 프레임당 한 번 실행)
let steer = 0; // -1..+1, 플레이어가 보는 방향 기준
if (keys.has('KeyA') || keys.has('ArrowLeft')) steer += 1; // 왼쪽
if (keys.has('KeyD') || keys.has('ArrowRight')) steer -= 1; // 오른쪽
// 선택 사항: steer = clamp(steer + gamepadX, -1, 1) (동일한 부호 규칙 사용)
// 적분 (speedFactor ~ 0..1, |speed|에서 계산)
const reverse = speed >= 0 ? 1 : -1; // 후진 시에도 왼쪽 바퀴는 여전히 왼쪽으로 느껴짐
yaw += steer * turnRate * speedFactor * reverse * dt;
// 방위각을 따라 이동
const fx = -Math.sin(yaw), fz = -Math.cos(yaw);
position.x += fx * speed * dt;
position.z += fz * speed * dt;
일반적인 버그 (복사하지 마세요):
// 잘못된 코드 — 실제 환경에서 반전된 A/D가 출고되는 원인
if (KeyA) steer -= 1;
if (KeyD) steer += 1;
yaw += steer * turnRate * dt; // A → -yaw → 추적 카메라에서 기수 오른쪽
이미 KeyA → steer-- 로 작성했다면, 키 매핑을 바꾸거나 적분 시 한 번 부호를 반전시키고 (yaw += -steer * …) §5를 실행하세요. 두 번 반전시키지 마세요 (키 + 적분 + 메시 기울기).
2c. 고정익 비행 (비행기, 글라이더, RC 비행기)
| 입력 | 동작 | 플레이어 기대치 |
|---|---|---|
| A / ← | 왼쪽으로 롤 (에일러론) | 왼쪽 날개 하강 / 왼쪽으로 기울기 |
| D / → | 오른쪽으로 롤 | 오른쪽 날개 하강 / 오른쪽으로 기울기 |
| W / ↑ | 피치 (하나의 방식을 선택하고 HUD에 표시) | 일반적으로 기수 하강 또는 상승 — 일관성 유지 |
| S / ↓ | 반대 피치 | |
| Q / E | 요 / 러더 (선택 사항) | Q 왼쪽, E 오른쪽 |
- A/D는 스트레이프가 아니며 "FPS 부호를 사용한 지상 조향"도 아닙니다.
- 비행체의 로컬 전진 축을 기준으로 롤을 적용하며, A 입력 시 왼쪽으로 기울어지는 방향과 일치하도록 부호를 맞추세요. 메시가 잘못된 방향으로 기울어진다면, 전체 기준을 바꾸지 말고 롤 적용(또는 A/D → 롤 매핑)에서 하나의 부호만 반전시키세요.
- 조정된 선회(coordinate turn): 양의 뱅크(positive bank)는 추적/외부 카메라 아래에서 뱅크 방향과 일치하는 선회를 생성해야 합니다.
2d. 헬리콥터 / 드론 / 6DOF
시작 오버레이에 방식을 문서화하세요. 최소 요구 사항:
- 개별 키를 사용하는 스로틀/고도는 한 번 누른 후 "항상 위"로 고정되어서는 안 됩니다 (누르고 있는 상태 사용 또는 명시적인 위/아래 사용).
- 스트레이프/요: 비행체의 수평 프레임 기준으로 A 왼쪽, D 오른쪽 (플레이어가 보는 방향 기준).
2e. 2D 사이드스크롤러 / 플랫포머
**D / →**는 화면에서 오른쪽으로 이동; **A / ←**는 왼쪽으로 이동. 중력은 장르가 명시적으로 거꾸로 되어 있지 않은 한 반전시키지 마세요.
3. 카메라는 일치해야 함
- 추적 카메라:
desired = craftPos + up*height + forward*(-followDist); 보간(lerp);lookAt(craft). - 이동과 카메라 모두에 대해
forward를 한 번만 계산하세요 — 카메라 경로에서 반대 요(yaw) 부호로 다시 생성하지 마세요. - 디버그 순서: (1) 키 등록 → (2) 부호 정확성(§5) → (3) 카메라 일치.
4. 입력 파이프라인 (간략히)
keydown/keyup시event.code를 사용하여Set으로 키를 추적하고,blur/visibilitychange시 초기화하세요. 키 핸들러가 아닌 게임 루프에서 dt를 사용하여 이동하세요.- 키보드 + 터치 + 게임패드를 액션(
throttle,steer,pitch,roll, ...)으로 통일하세요.building-games/references/input.md를 참조하세요. - 터치: 왼쪽 스틱 = 이동/조향, 오른쪽 = 액션; ≥44px 타겟.
5. 필수 조작 자체 테스트 ("완료" 전에)
A/D가 있는 모든 비행체에 대해 스크린샷만으로는 충분하지 않습니다.
5a. 플레이어 확인 체크리스트 (모든 차량 / 비행 빌드)
전진 중 (속도 > 작은 엡실론), 후방 추적 카메라:
| 누르기 | 약 0.5초 이내에 관찰해야 할 사항 |
|---|---|
| A | 기수 또는 기울기가 화면에서 왼쪽으로 이동 |
| D | 기수 또는 기울기가 화면에서 오른쪽으로 이동 |
| W (지상) | 속도 증가 / 향하는 방향으로 이동 |
| S (지상) | 제동 또는 후진 (설계된 대로) |
A 테스트 실패 시: 조향/롤 부호를 한 번 반전시키고, A와 D를 모두 다시 테스트하세요.
5b. 최소 테스트 인터페이스 (구현 필수)
비공개 클로저를 추측하지 않고도 부호를 증명할 수 있도록 작은 훅을 노출하세요:
// 예: src/game/controlsTest.ts — 개발/QA 전용으로 충분
// ... (ControlsProbe 타입 정의는 생략) ...
게임 루프에서 import.meta.env.DEV 또는 ?qa=1 플래그가 설정된 경우 window.__controlsTest를 연결하세요.
5c. 자동화된 스모크 테스트 (실행 필수)
agent-browser CLI로 §5b 프로브를 구동하세요 — 이것이 첫 번째 단계이며, 수동 스크립트가 아닙니다. 던져진 eval은 0이 아닌 값으로 종료되고, 단순히 거짓인 값은 그렇지 않으므로, throw를 통해 단언하세요. 한 번의 batch로 실행하세요 — 동사당 하나가 아닌 하나의 CLI 호출로, stdin에서 JSON을 받습니다; --bail은 첫 번째 실패 단계에서 멈추고 0이 아닌 값으로 종료됩니다.
agent-browser batch --bail <<'JSON'
# ... (JSON 명령어 목록은 생략) ...
JSON
keydown 대신 프로브를 통해 키를 누르고 있어야 합니다 — agent-browser keydown은 §4의 Set에 도달하지 못하므로, 올바른 게임이라도 깨진 것으로 읽힙니다. 프로브에 setKeys가 없나요? 추가하거나(§5b) 직접 키 이벤트를 디스패치하세요 — 브라우저-QA 참조의 Keys 노트에 메커니즘과 형식이 있습니다.
키를 누르고 있는 동작은 하나의 eval 내부에 있어야 합니다: 여러 명령에 걸쳐 있으면 명령이 실행되는 시간 동안만 지속되며, Math.atan2(Math.sin(d), Math.cos(d))의 ±π 래핑은 π 이상 회전한 비행체를 반대 방향으로 회전한 것으로 읽습니다. IIFE는 단계를 재실행 가능하게 만듭니다 — 순수 const d = …는 두 번째 시도에서 "이미 선언됨" 오류와 함께 실패합니다 — 그리고 async는 페이지 시간 내에 키를 누르고 있는 동작을 가능하게 합니다. D에 대해 'KeyD'와 두 비교를 반전시켜(w > 0.05는 반전, w >= -0.05는 거의 회전 안 함) 반복하세요. 작은따옴표를 사용하여 JSON에 이스케이프가 필요하지 않도록 하세요; 비행기의 경우 롤을 단언하세요: A ⇒ 왼쪽으로 기울기.
A turns RIGHT 는 부호 오류입니다: 하나의 부호를 반전시키고 다시 실행하세요. A barely turned 는 그렇지 않습니다 — 비행체가 올바른 방향으로 움직였지만 300ms 동안 0.05라디안 미만으로 움직인 것이며, 보트나 무거운 로버는 그럴 수 있습니다; 키를 누르고 있는 시간을 늘리거나 시뮬레이션이 실행 중인지 확인하고, 아무것도 반전시키지 마세요. 0이 아닌 다른 종료 코드 — 프로브 없음, 멈춘 데몬 — 는 검사가 실행되지 않았음을 의미합니다: 먼저 메시지를 읽으세요. 첫 번째 흐름 전에 브라우저-QA 참조의 AGENTS.md 링크를 읽으세요 — 동사, 인수 형태 및 대체 방법이 있습니다.
5d. 하지 말아야 할 것
- "D가 어떤 내부 변수를 증가시키는지" 만 테스트하지 마세요.
- FPS의 "−Z 방향을 바라볼 때 D → +X"를 차량 통과 조건으로 사용하지 마세요.
- 수정 시 메시 기울기, 카메라, 조향을 모두 한 번에 반전시키지 마세요 — 하나의 부호를 바꾸고 다시 테스트하세요.
6. 완료 기준 (조작)
- 이동/조향/비행 코드를 작성하기 전에 이 스킬을 열었습니다.
- 장르 맵을 선택하고(§2) 시작 화면 / HUD 라벨이 이와 일치합니다.
- 추적 카메라 A/D 플레이어 확인 테스트 통과(§5a).
-
window.__controlsTest(또는 이에 상응하는 것)가 개발/QA에서 사용 가능하며 한 번 사용되었습니다. - 롤/조향 입력 대비 반전된 기울기 메시가 없습니다.
- 키는 누르고 있는 상태 + dt 스케일링으로 처리됩니다. 의도적이고 라벨이 붙은 경우가 아니라면, Space를 한 번 탭했다고 추력이 고정되지 않습니다.
하나라도 체크되지 않은 상자가 있다면, 게임은 완료되지 않은 것입니다.