Skip to content
Back to skills

Tinyworld Flight Sim

ASecurity

Flyable plane flight, ported from the standalone `ships/flight-sim3.html` arcade model. **The plane is NOT a bespoke tool or object kind** — it is the existing crop-duster / stunt-plane that the codebase already ships as a **model-stamp** (`models/stunt_plane.glb`, stamp id `stunt-plane`, label "Stunt Plane"), placed through the **Stamps** system. The placed cell is therefore `kind: 'model-stamp'` with `appearance.modelStampId === 'stunt-plane'`. Do not reintroduce a separate `plane` tool/kin...

  • 1,587 stars
  • 0 votes
  • 0 copies
  • 3 views
  • Added September 3, 2026
devopsgorails

Works with

  • cli

Security analysis

A100/100

Scanned September 3, 2026

npx -y skills add jasonkneen/tiny-world-builder --skill tinyworld-flight-sim --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Tinyworld Flight Sim?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Tinyworld Flight Sim
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/jasonkneen-tinyworld-flight-sim/badge)](https://www.skillsdirectory.com/skills/jasonkneen-tinyworld-flight-sim)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
# tinyworld-flight-sim

Flyable plane flight, ported from the standalone `ships/flight-sim3.html` arcade
model. **The plane is NOT a bespoke tool or object kind** — it is the existing
crop-duster / stunt-plane that the codebase already ships as a **model-stamp**
(`models/stunt_plane.glb`, stamp id `stunt-plane`, label "Stunt Plane"), placed
through the **Stamps** system. The placed cell is therefore
`kind: 'model-stamp'` with `appearance.modelStampId === 'stunt-plane'`.

Do not reintroduce a separate `plane` tool/kind — that was a wrong turn. Flight
attaches to whatever model-stamp the player already placed, detected by signature.

## Where it lives
- `engine/world/34-flight-sim.js` — the whole system: trimmed physics, flight cameras,
  `enterFlight`/`exitFlight`, `tickFlight`, HUD, Enter/Fly menu, key capture, and
  `isFlyableStampCell(cell)` (the detector). Loaded between `33-radial-menu.js`
  and `99-late-boot.js`.
- `isFlyableStampCell(cell)` returns true when `cell.kind === 'model-stamp'` and
  the resolved `getModelStamp(appearance.modelStampId)` signature matches
  `/plane|aircraft|airplane|stunt|crop-?duster|jet/`. Exposed as
  `window.isFlyableStampCell`.
- Wiring hooks:
  - `20-input-place-erase.js` — `applyTool` gains `if (window.__flightActive) return;`.
    In the `pointerup` click-resolution block, a **plain (unmodified) click on a
    flyable stamp opens the Enter/Fly menu regardless of the active tool** (you
    still erase it with the eraser). This runs BEFORE the place/select branches so
    a click never stacks/replaces the plane. NOTE: a plain Select click resolves
    here, NOT in `applyTool` (`applyTool` is only reached by non-select tools).
  - `25-animation-loop-schema.js` — `if (window.tickFlight) window.tickFlight(dt);`
    right before `renderScene()`.
  - `styles/tiny-world.css` — `.flight-hud`, `.flight-menu`, `.flight-menu-btn`,
    `body.flight-active`.

## The scale trick (do not "fix" this)
The ships model is tuned for a kilometre-scale world (cruise ~77 m/s). Physics
runs untouched in **sim space**; the flown object (the placed stamp group) position
is mapped into the scene via a single **similarity transform**:
`scenePos = sceneOrigin + yawQuat * ((simPos - simOrigin) * FLIGHT_SIM_TO_SCENE)`
(`FLIGHT_SIM_TO_SCENE = 0.09`). `sceneOrigin`/yaw are captured from the parked
stamp at `enterFlight`. Ground is a flat sim plane at spawn height.
- **Camera is the exception**: the chase cam is framed in **scene units** (~3–5.5
  units behind, ~1.45 up — the plane is ~1.35 units wide), NOT run through the sim-scale
  transform. Running the camera offset through the sim scale parks it inside the
  tail (the original bug). Controls: arrows Up/Down = throttle, Left/Right = rudder.

## Flow
- Place the **Stunt Plane** from Stamps (it is a model-stamp).
- Plain-click the placed plane → `showFlightMenu` → "Enter / Fly" →
  `enterFlight(x,z)`: swaps the global `camera` to a `flightCam` (FOV 60), captures
  keys, and uses the placed stamp group as the flown mesh (`flightJet`); spins
  the named propeller mesh in place and adds the Dusty-style translucent strobe
  disc whose opacity flickers with throttle.
- Controls: W = nose down, S or X = nose up (pitch), A/D roll, Q/E yaw, Shift/Ctrl OR
  ArrowUp/ArrowDown = throttle, ArrowLeft/ArrowRight = rudder, B brake.
- Combat controls live in `41-flight-combat.js` while flying: left mouse or
  Space holds guns, `X` fires a missile, and right-click fires one missile via
  `window.__flightMissilePressed` / `window.__flightMissileHeld`. The pointer
  handler in `34-flight-sim.js` must prevent the context menu only while
  `flightActive` is true. Missile hits and destruction should use the pooled
  flight explosion sprites (`tw_flight_explosions`) plus smoke trails rather
  than adding DOM effects or build-mode controls.
- Default view while flying is the LOS drone camera, not the chase camera. It
  captures a fixed observer anchor near the launch/island position and then pans
  and adjusts FOV to keep filming the plane. The enlarged map panel carries the
  close chase/cockpit feed, and the swap button can make either feed primary.
- `Escape` → `exitFlight()`: restores the previous camera, calls `updateCamera()`,
  re-renders the cell to re-park the stamp.
- Lobby vehicles are self-replenishing: `enterFlight()` detaches the clicked
  stamp mesh as the active flyer, clears `entry.object`, and immediately
  `renderCellObject()`s a fresh parked copy in the same cell. Do not mutate the
  world cell or broadcast a cell edit for this handoff; the placed stamp remains
  the shared lobby spawn so every player can take a plane independently.
- Flight combat has finite gun and missile ammo. `41-flight-combat.js` owns the
  ammo counters plus the `tw_flight_resupply_rewards` pool: glowing airborne
  rings placed ahead of the flyer that refill guns and missiles when flown
  through, then respawn farther ahead.

## Gotchas
- Module 34 shares the global scope with every other `engine/world` module — all
  its top-level scratch globals are `_fl…`-prefixed to avoid the duplicate-
  identifier instantiation failure (see AGENTS.md "Project shape"). A duplicate
  top-level `const`/`let`/`function` name silently kills the whole module.
- The tool picker is a **search palette** (`#palette-search` → `#palette-results`),
  not a fixed toolbar; the stunt plane is found under Stamps, not a tool button.
- The stunt plane propeller mesh (`SM_Veh_Plane_Stunt_01_Prop`) already has its
  local origin at the hub. Match the crop-duster path by rotating that mesh in
  place around local Z; do not wrap it in a new AABB-centred pivot, or it orbits
  off-centre instead of spinning like the ambient dusting plane.
- The flight propeller must read like the ambient Dusty/crop-duster prop while
  borrowing the duplicate-island engine tint: high apparent RPM, a dark
  translucent disc, and mostly faded physical blades at cruise throttle. A solid
  blade screenshot means the visual balance is wrong even if the mesh is
  technically rotating.
- The blur shape should be a round camera-facing disc, not a projected oval from
  a tilted local mesh. Use the circular sprite treatment for flight-camera
  readability.
- Flight is arcade-scale for a tiny world: `enterFlight` launches the plane
  already cruising just above the board (initial forward speed + throttle 0.6)
  so there is no runway taxi phase. Collision and landing are checked in scene space against the
  TinyWorld board surface plus object bounds, then converted back to sim-space Y.
  The collision hot path must stay candidate-based: collect the small 3x3-ish
  cell window around the plane in home/world coordinates plus the matching local
  window for each editable island, then test only those rendered `cellMeshes`
  entries. Do not return to splitting and scanning every `cellMeshes` key per
  frame. Shallow, upright touchdowns become `ROLLING` / `LANDED`; hard terrain
  strikes or object hits stop the plane and show a collision/hard-landing status.
  Tuning lives in `FCFG`, `FLIGHT_SIM_TO_SCENE` (0.09), and the
  `FLIGHT_SCENE_*` collision constants.

## Verify (real app, real pointer pipeline — not synthetic shortcuts)
`npm run dev`, then via agent-browser `eval`:
1. Place: `setCell(x,z,{terrain:'grass',terrainFloors:1,kind:'model-stamp',floors:1,appearance:{modelStampId:'stunt-plane'}})`;
   `isFlyableStampCell(getWorldCell(x,z))` is true once the GLB loads.
2. Find a screen point where `pickTile(px,py)` resolves to the plane cell (project +
   search a small window — projecting the centre alone misses, terrain raycast is
   angled), then dispatch real `pointerdown`/`pointerup` there → `.flight-menu` appears.
3. Click `.flight-menu-btn` → `camera.fov === 60`, `window.__flightActive === true`.
4. Dispatch `keydown` ShiftLeft+KeyW, run ~2.5s → the stamp's world position moves.
5. `Escape` → `camera.fov === 28`, `__flightActive === false`, stamp re-parked.

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.