{
  "slug": "threejs-fps-weapon-feel",
  "skill": "threejs-fps-weapon-feel",
  "upstream": "GameSkill original (onsite/<slug>/original)",
  "sourceHash": "6f912124ed0d59747a29434ea0364cf205251d084a6f1f9b56e6cf9f18cf0ccd",
  "date": "2026-10-28",
  "engine": "three.js r186 (0.186.1) in Chrome 156 headless",
  "env": [
    "Node v20.19.2",
    "three.js r186 vendored in vendor/three (no CDN)",
    "puppeteer-core 24 from package-lock.json",
    "Google Chrome 156 headless (SwiftShader WebGL), touch emulation on"
  ],
  "trigger": "static",
  "passed": 40,
  "total": 40,
  "commands": [
    "(cd ../original && sh -c npm ci --silent --no-audit --no-fund && node --test --test-reporter=tap test/logic.test.mjs)",
    "(cd ../original && node test/browser.test.mjs)",
    "(cd . && cp ../original/shots/fps-range.png screenshot.png)"
  ],
  "checks": [
    {
      "criterion": "trigger",
      "name": "SKILL.md description says when to use it",
      "pass": true,
      "detail": "First-person weapon feel for a three.js (or any) browser shooter, as one engine-free module with tests. A learnable per-"
    },
    {
      "criterion": "trigger",
      "name": "triggers on: \"My three.js FPS gun feels floaty, add recoil, recovery and aim down sights\"",
      "pass": true,
      "detail": "rank 1/118, overlap 10"
    },
    {
      "criterion": "trigger",
      "name": "triggers on: \"Recoil recovery pulls my aim below the target after I compensate, how do I fix it\"",
      "pass": true,
      "detail": "rank 1/118, overlap 7"
    },
    {
      "criterion": "trigger",
      "name": "triggers on: \"Add mobile touch controls with a look zone and precision aim to my browser FPS\"",
      "pass": true,
      "detail": "rank 1/118, overlap 7"
    },
    {
      "criterion": "trigger",
      "name": "does not trigger on: \"Export my Godot game for the web\"",
      "pass": true,
      "detail": "rank 31/118, overlap 0"
    },
    {
      "criterion": "trigger",
      "name": "does not trigger on: \"Make pixel art sprites with Aseprite\"",
      "pass": true,
      "detail": "rank 23/118, overlap 0"
    },
    {
      "criterion": "runs",
      "name": "weapon feel and touch tests (fire rate, frame-rate independence, pattern, recovery debt, ADS, spread, viewmodel, stick, touch aim): fire rate: 10 shots per second while held, from the first frame",
      "pass": true
    },
    {
      "criterion": "runs",
      "name": "weapon feel and touch tests (fire rate, frame-rate independence, pattern, recovery debt, ADS, spread, viewmodel, stick, touch aim): 30 fps and 144 fps fire the same shots to the same place",
      "pass": true
    },
    {
      "criterion": "runs",
      "name": "weapon feel and touch tests (fire rate, frame-rate independence, pattern, recovery debt, ADS, spread, viewmodel, stick, touch aim): the pattern is learnable: the first 16 kicks are the same for every seed",
      "pass": true
    },
    {
      "criterion": "runs",
      "name": "weapon feel and touch tests (fire rate, frame-rate independence, pattern, recovery debt, ADS, spread, viewmodel, stick, touch aim): the shot leaves along the aim before its own kick",
      "pass": true
    },
    {
      "criterion": "runs",
      "name": "weapon feel and touch tests (fire rate, frame-rate independence, pattern, recovery debt, ADS, spread, viewmodel, stick, touch aim): no recovery during a spray; after the delay the camera returns to where it started",
      "pass": true
    },
    {
      "criterion": "runs",
      "name": "weapon feel and touch tests (fire rate, frame-rate independence, pattern, recovery debt, ADS, spread, viewmodel, stick, touch aim): player compensation is not undone by recovery (no drag below the target)",
      "pass": true
    },
    {
      "criterion": "runs",
      "name": "weapon feel and touch tests (fire rate, frame-rate independence, pattern, recovery debt, ADS, spread, viewmodel, stick, touch aim): half compensation: recovery returns only the other half",
      "pass": true
    },
    {
      "criterion": "runs",
      "name": "weapon feel and touch tests (fire rate, frame-rate independence, pattern, recovery debt, ADS, spread, viewmodel, stick, touch aim): ADS: eased FOV blend and a tan-ratio sensitivity scale",
      "pass": true
    },
    {
      "criterion": "runs",
      "name": "weapon feel and touch tests (fire rate, frame-rate independence, pattern, recovery debt, ADS, spread, viewmodel, stick, touch aim): ADS scales recoil by adsRecoil",
      "pass": true
    },
    {
      "criterion": "runs",
      "name": "weapon feel and touch tests (fire rate, frame-rate independence, pattern, recovery debt, ADS, spread, viewmodel, stick, touch aim): spread: aiming tightens, firing blooms to a cap and decays, moving and jumping widen",
      "pass": true
    },
    {
      "criterion": "runs",
      "name": "weapon feel and touch tests (fire rate, frame-rate independence, pattern, recovery debt, ADS, spread, viewmodel, stick, touch aim): magazine empties, then reload",
      "pass": true
    },
    {
      "criterion": "runs",
      "name": "weapon feel and touch tests (fire rate, frame-rate independence, pattern, recovery debt, ADS, spread, viewmodel, stick, touch aim): viewmodel: bob shrinks when aiming, kick returns, sway lags and settles",
      "pass": true
    },
    {
      "criterion": "runs",
      "name": "weapon feel and touch tests (fire rate, frame-rate independence, pattern, recovery debt, ADS, spread, viewmodel, stick, touch aim): stick: radial deadzone, rescale and curve",
      "pass": true
    },
    {
      "criterion": "runs",
      "name": "weapon feel and touch tests (fire rate, frame-rate independence, pattern, recovery debt, ADS, spread, viewmodel, stick, touch aim): touch: left moves, right looks, first move ignored, aiming is more precise",
      "pass": true
    },
    {
      "criterion": "runs",
      "name": "weapon feel and touch tests (fire rate, frame-rate independence, pattern, recovery debt, ADS, spread, viewmodel, stick, touch aim): touch: the fire button fires and aims at the same time",
      "pass": true
    },
    {
      "criterion": "runs",
      "name": "weapon feel and touch tests (fire rate, frame-rate independence, pattern, recovery debt, ADS, spread, viewmodel, stick, touch aim): exits 0 with no script errors",
      "pass": true,
      "detail": "exit 0"
    },
    {
      "criterion": "runs",
      "name": "demo in headless Chrome: page loads; WebGL renders the range (50 colour bins)",
      "pass": true
    },
    {
      "criterion": "runs",
      "name": "demo in headless Chrome: demo hip spray: shots land on the pattern board and climb",
      "pass": true
    },
    {
      "criterion": "runs",
      "name": "demo in headless Chrome: demo aims down sights: FOV blends to 50°, crosshair hides",
      "pass": true
    },
    {
      "criterion": "runs",
      "name": "demo in headless Chrome: aimed, compensated burst stays near the bull",
      "pass": true
    },
    {
      "criterion": "runs",
      "name": "demo in headless Chrome: touch: first touch switches to phone controls",
      "pass": true
    },
    {
      "criterion": "runs",
      "name": "demo in headless Chrome: touch: drag on the right half turns the camera (+18.5°)",
      "pass": true
    },
    {
      "criterion": "runs",
      "name": "demo in headless Chrome: touch: holding FIRE shoots with recoil; flash shows (3 shots)",
      "pass": true
    },
    {
      "criterion": "runs",
      "name": "demo in headless Chrome: recovery returns the camera after the burst",
      "pass": true
    },
    {
      "criterion": "runs",
      "name": "demo in headless Chrome: touch: ADS button toggles aim; look gets slower (precision + FOV scale) (+5.05°)",
      "pass": true
    },
    {
      "criterion": "runs",
      "name": "demo in headless Chrome: touch: left-half stick walks forward",
      "pass": true
    },
    {
      "criterion": "runs",
      "name": "demo in headless Chrome: keyboard: R reloads",
      "pass": true
    },
    {
      "criterion": "runs",
      "name": "demo in headless Chrome: exits 0 with no script errors",
      "pass": true,
      "detail": "exit 0"
    },
    {
      "criterion": "output",
      "name": "screenshot screenshot.png rendered",
      "pass": true
    },
    {
      "criterion": "output",
      "name": "self-check list has at least 3 items",
      "pass": true,
      "detail": "6 items"
    },
    {
      "criterion": "single",
      "name": "one job per skill (reviewed)",
      "pass": true,
      "detail": "One job: how a first-person weapon feels (recoil pattern and recovery, ADS, spread, viewmodel motion, flash) plus the phone controls that aim it. Movement, enemies and level design are out of scope; the range exists only to show the weapon."
    },
    {
      "criterion": "bilingual",
      "name": "English SKILL.md",
      "pass": true
    },
    {
      "criterion": "bilingual",
      "name": "Chinese SKILL.zh.md shipped next to SKILL.md",
      "pass": true,
      "detail": "2806 chars"
    },
    {
      "criterion": "bilingual",
      "name": "Chinese guide written",
      "pass": true,
      "detail": "1058 chars"
    }
  ]
}
