Catalog
fairoosansar-coder/controls

Player-facing input signs for browser games: WASD, vehicles, flight, FPS mouse-look, and the #1 failure mode (inverted A/D). Mandatory control self-tests and a tiny test interface so you can verify A turns left before shipping. Load for ANY game with movement, steering, flying, driving, tanks, boats, mechs, drones, planes, karts, third-person follow cams — not only racing. Triggers on "controls", "WASD", "inverted", "steer", "flight", "airplane", "kart", "vehicle", "yaw", "roll", "pitch", "mouse look", "A/D".

v1.0LATEST
NewUpdated Oct 29, 2026

Controls (player-visible signs — do not ship inverted)

Read this end-to-end before writing movement/steer/flight code for any game that uses WASD, arrows, a chase camera, or a flying craft. Do not skip this and only open racing-kart or fps — those genre files assume you already know these signs. Inverted A/D is the most common ship-blocker in vehicle and flight demos.

Pair with building-games (loop, camera, 3D orientation) and building-games/references/input.md (keydown state, gamepad, touch). This skill owns what left/right/up mean to the player and how you prove it.


0. Hard rules (fail the build if broken)

  1. Player-visible left/right is law. From a chase / behind camera while the craft moves forward:
    • A / ← → nose (or bank) turns left on screen
    • D / → → nose (or bank) turns right on screen
  2. Never reuse FPS strafe as vehicle steer. FPS “D → +right vector” is position on the ground plane. Vehicle A/D is yaw (or roll) rate, not a strafe offset. Mixing them is the #1 cause of inverted A/D.
  3. You must run a control self-test (§5) before saying done. Screenshot-only QA is not enough. If A turns right, flip the steer/roll sign, retest, then ship — do not invent a new coordinate story.

1. Shared 3D basis (use this everywhere)

three.js: right-handed, +Y up, meshes face +Z, cameras look −Z.

Yaw-only heading on XZ (ground vehicles, walkers, most arcade craft):

// yaw = 0 faces world −Z; +yaw is CCW about +Y (nose moves toward −X)
forward = (-sin(yaw), 0, -cos(yaw))
right   = ( cos(yaw), 0, -sin(yaw))   // = normalize(cross(forward, worldUp))

With a chase cam behind the craft (camera near position - forward * dist):

Player sees World (this basis) Input
Nose left +yaw A / ← must produce +yaw (or equivalent bank-left for planes)
Nose right −yaw D / → must produce −yaw

If your basis differs, keep one consistent pair — but the player-visible row above is mandatory.


2. Genre maps

2a. FPS / on-foot (strafe, not steer)

W = +forward, S = −forward, D = +right, A = −right   // position, not yaw
mouse: yaw -= movementX * sens; pitch -= movementY * sens; clamp pitch

Body yaw for look; movement uses yaw only (do not apply pitch to walk).

2b. Ground / water vehicle (kart, bike, jetski, boat, tank, rover, snowmobile)

Arcade body with heading/yaw and forward speed:

// Input (held keys → actions once per frame)
let steer = 0; // -1..+1, player-visible
if (keys.has('KeyA') || keys.has('ArrowLeft'))  steer += 1;  // LEFT
if (keys.has('KeyD') || keys.has('ArrowRight')) steer -= 1;  // RIGHT
// Optional: steer = clamp(steer + gamepadX, -1, 1) with same sign convention

// Integrate (speedFactor ~ 0..1 from |speed|)
const reverse = speed >= 0 ? 1 : -1; // wheel-left still feels left in reverse
yaw += steer * turnRate * speedFactor * reverse * dt;

// Move along heading
const fx = -Math.sin(yaw), fz = -Math.cos(yaw);
position.x += fx * speed * dt;
position.z += fz * speed * dt;

Canonical bug (do not copy):

// WRONG — this is what ships inverted A/D in the wild
if (KeyA) steer -= 1;
if (KeyD) steer += 1;
yaw += steer * turnRate * dt;  // A → −yaw → nose RIGHT on chase cam

If you already wrote KeyA → steer--, either swap the key mapping or negate once at integrate (yaw += -steer * …) — then run §5. Do not flip twice (keys + integrate + bank mesh).

2c. Fixed-wing flight (airplane, glider, RC plane)

Input Action Player expectation
A / ← Roll left (aileron) Left wing down / bank left
D / → Roll right Right wing down / bank right
W / ↑ Pitch (pick one scheme and label HUD) Usually nose down or pull-up — be consistent
S / ↓ Opposite pitch
Q / E Yaw / rudder (optional) Q left, E right
  • A/D are not strafe and not “ground steer with FPS signs.”
  • Apply roll in the craft’s local forward axis with a sign that matches bank left on A. If the mesh banks the wrong way, flip one sign on the roll apply (or on the A/D → roll mapping), not the whole basis.
  • Coordinated turn: positive bank should produce a turn that matches the bank direction under the chase/external cam.

2d. Heli / drone / 6DOF

Document the scheme on a start overlay. Minimum:

  • Throttle/altitude on discrete keys must not stick “always up” after one press (use held state or explicit up/down).
  • Strafe/yaw: A left, D right in the craft’s horizontal frame (player-visible).

2e. 2D side-scroller / platformer

D / → moves right on screen; A / ← moves left. Gravity only inverted if the genre is explicitly upside-down.


3. Camera must agree

  • Chase cam: desired = craftPos + up*height + forward*(-followDist); lerp; lookAt(craft).
  • Compute forward once for both movement and camera — do not rebuild with opposite yaw sign in the camera path.
  • Debug order: (1) keys register → (2) signs correct (§5) → (3) camera agrees.

4. Input plumbing (brief)

  • Track keys with a Set on keydown/keyup using event.code; clear on blur / visibilitychange. Move in the game loop with dt, not in the key handler.
  • Unify keyboard + touch + gamepad into actions (throttle, steer, pitch, roll, …). See building-games/references/input.md.
  • Touch: left stick = move/steer, right = actions; ≥44px targets.

5. Mandatory control self-test (before “done”)

Screenshot-only is not enough for any craft with A/D.

5a. Player-visible checklist (every vehicle / flight build)

While moving forward (speed > small epsilon), chase cam behind:

Hold Must observe within ~0.5s
A Nose or bank moves left on screen
D Nose or bank moves right on screen
W (ground) Speed increases / moves along facing
S (ground) Brakes or reverse (as designed)

If A fails: flip steer/roll sign once, retest both A and D.

5b. Minimal test interface (implement this)

Expose a tiny hook so you (and automated QA) can prove signs without guessing private closures:

// e.g. src/game/controlsTest.ts — dev/QA only is fine
export type ControlsProbe = {
  getYaw: () => number;       // radians; or getHeading()
  getSpeed: () => number;
  /** Inject held actions for one frame path; clear after test */
  setSteer?: (v: number) => void; // -1..1, same sign as production
  setKeys?: (codes: string[]) => void;
};

declare global {
  interface Window {
    __controlsTest?: ControlsProbe;
  }
}

Wire window.__controlsTest from the game loop when import.meta.env.DEV or a ?qa=1 flag is set.

5c. Automated smoke (run it)

In a short Playwright / agent script against the local preview:

  1. Start / click-to-play so the sim runs.
  2. Set forward throttle; wait until getSpeed() > threshold.
  3. Record y0 = getYaw().
  4. Hold A (or setSteer(+1) if that is your left convention) for 500ms.
  5. Assert deltaAngle(getYaw(), y0) has the left sign for your basis (with the §1 basis: left ⇒ yaw increased).
  6. Repeat for D with opposite sign.
  7. Fail the build if either assert fails; fix signs; re-run.

Pseudo-assert for the §1 basis:

const wrap = (a) => Math.atan2(Math.sin(a), Math.cos(a));
// After holding A (left) while moving forward:
expect(wrap(yawAfter - yawBefore)).toBeGreaterThan(0.05);
// After holding D:
expect(wrap(yawAfter - yawBefore)).toBeLessThan(-0.05);

For planes, assert roll (or on-screen bank) the same way: A ⇒ bank left.

5d. What not to do

  • Do not only test “D increases some internal variable.”
  • Do not use FPS “D → +X when facing −Z” as the vehicle pass condition.
  • Do not flip mesh bank, camera, and steer all at once when fixing — change one sign, retest.

6. Finish criteria (controls)

  • Opened this skill before writing movement/steer/flight.
  • Genre map chosen (§2) and start-screen / HUD labels match it.
  • Chase-cam A/D player-visible test passed (§5a).
  • window.__controlsTest (or equivalent) available in dev/QA and used once.
  • No inverted bank mesh relative to roll/steer input.
  • Keys are held-state + dt-scaled; no sticky thrust from a single Space tap unless intentional and labeled.

If any box is unchecked, the game is not done.

Overall Score

88/100

Grade

A

Excellent

Grades are signals, not a certification. Always review a skill yourself before use.

Safety

92

Quality

87

Clarity

89

Completeness

82

Summary

This skill teaches game developers how to implement player-facing game controls (WASD, vehicle steering, flight, mouse-look) with correct 3D orientation and a mandatory self-test protocol to catch the most common shipping bug: inverted A/D steering. It pairs technical foundation (3D basis vectors, genre-specific input maps) with a structured QA checklist and automated test patterns to ensure controls work as players expect before shipping.

Static Analysis Findings

1 finding

Patterns detected by deterministic static analysis before AI scoring. Hover over any finding code for detailed information and remediation guidance.

Credential Exposure
SEC-020Direct .env File Access

Direct .env file access

SKILL.md.env

Detected Capabilities

file readingcode analysistest harness guidancecoordinate system documentationinput plumbing patterns

Trigger Keywords

Phrases that agents use to match this skill to user intent.

inverted a/d steeringvehicle controls wasdflight controls aileronchase camera inputgame control self-testarcade steering bug

Risk Signals

INFO

Reference to .env file in context of import.meta.env.DEV check

SKILL.md section 5b

Use Cases

  • >Implementing arcade vehicle steering (kart, tank, boat) with correct left/right feel
  • Building fixed-wing flight controls (plane, glider) with aileron/pitch/rudder mapping
  • Setting up FPS or third-person camera movement with strafe vs. steer distinction
  • Creating a test harness to automatically verify A/D steering direction before shipping
  • Debugging why A/D controls feel inverted in a game with a chase camera
  • Implementing helicopter/drone/6DOF control schemes with correct player-visible axes

Quality Notes

  • Exceptionally clear coordinate system foundation (§1) with explicit three.js conventions (+Y up, +Z forward camera look direction) — removes ambiguity about 'left' and 'right'
  • Well-structured genre map (§2a–e) with concrete code examples for each control scheme and explicit prohibition on common anti-patterns (e.g., reusing FPS strafe logic for vehicle steering)
  • Canonical bug example (§2b) shows the exact wrong pattern shipped in the wild and contrasts it with the correct approach — invaluable for debugging
  • Mandatory self-test protocol (§5) is thorough and includes both manual checklist (5a) and automated Playwright smoke test pseudo-code (5c), addressing the skill's stated #1 failure mode
  • Finish criteria checklist (§6) is concrete and actionable — every box corresponds to an earlier section, making it easy to verify readiness before shipping
  • Extensive use of tables, code blocks, and visual callouts makes instructions scannable and unambiguous
  • Cross-references to paired skills (building-games, building-games/references/input.md) help users understand scope and prerequisites without duplication
  • Debug order (§3) and input plumbing summary (§4) provide a clear path through the skill for different experience levels
Model: claude-haiku-4-5-20251001Analyzed: Oct 29, 2026

Reviews

Add this skill to your library to leave a review.

No reviews yet

Be the first to share your experience.

Use fairoosansar-coder/controls in your dev environment

Command Palette

Search for a command to run...