Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,12 @@ All notable changes to TowerCab 3D will be documented in this file.
- New **Show Moon** setting under Settings → Graphics lets you hide the moon from the sky.
- vNAS can join your CRC session on a YAAT training server: YAAT1, or YAAT Local when a local YAAT server is running.
- 3D buildings now show rows of windows on their walls, some of them lit at night.
- FSLTL and AIG aircraft now deploy spoilers and thrust reversers on landing, and their wheels turn as they roll.

### Changed
- 3D buildings and aircraft no longer have an olive-green cast on their walls and fuselages; their reflected ground light is now a neutral grey.
- Remote access accepts only devices on your local network, unless you turn on **Allow devices outside the local network** in Settings → Configuration.
- vNAS aircraft now touch down and lift off when the simulator's aircraft does, and raise their gear a few seconds after liftoff.

### Fixed
- The remote access server no longer serves or writes files outside its own folders.
Expand All @@ -23,6 +25,7 @@ All notable changes to TowerCab 3D will be documented in this file.
- The flight phase badge now shows "Go Around" after a go-around, and leaves "Short Final" as soon as the aircraft climbs away.
- Aircraft passing over an airport at altitude are no longer labelled "Final" or "Short Final".
- Datablocks on iPads and other high-resolution screens now sit on their aircraft at full size, instead of half size and in the wrong place.
- vNAS aircraft holding short or lined up no longer creep forward while vNAS is quiet, and their takeoff roll no longer plays in fast motion.

### Removed
- The experimental VR mode and its VR button in the controls bar.
Expand Down
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -375,7 +375,7 @@ if (version < 7) {
1. Interpolation logic (60 Hz smooth motion): `useAircraftInterpolation.ts`
2. 3D model rendering: `CesiumViewer.tsx` (Cesium entities)
3. Datablock labels and leader lines: `useBabylonOverlay.ts` (Babylon.js GUI)
4. Regression suite: `tests/motion/` drives the interpolation hook, the timeline store, the phase detector and the gear controller with synthetic VATSIM (15 s) and vNAS (1 Hz) flights and compares each frame with truth (`pnpm vitest run tests/motion`; `MOTION_REPORT=1` writes `.tmp/motion/report.txt`). A motion fix lands with a red-first assertion there, turning its finding's `it.todo` into an `it`.
4. Regression suite: `tests/motion/` drives the interpolation hook, the timeline store, the phase detector and the gear and surface (spoiler, reverser, wheel) controllers with synthetic VATSIM (15 s) and vNAS (1 Hz) flights and compares each frame with truth (`pnpm vitest run tests/motion`; `MOTION_REPORT=1` writes `.tmp/motion/report.txt`). A motion fix lands with a red-first assertion there, turning its finding's `it.todo` into an `it`.

### Modifying Camera Behavior

Expand Down
6 changes: 6 additions & 0 deletions MODDING.md
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,12 @@ One mod can match multiple aircraft types by listing them in `aircraftTypes`.
4. **File Size**: Keep models under 5MB for best performance
5. **Format**: GLB recommended; DAE and OBJ also supported

### Animation Clips

Only converted FSLTL and AIG models play animation clips; a custom mod model is drawn static. The viewer picks a clip by its glTF animation name: gear clips contain `GEAR`; spoiler clips are names like `l_spoiler_key`, `r_spoiler_key` or `custom_anim_SPOILERS_LEFT_POSITION_n`; reverser clips are names like `custom_anim_REV_THRUST`, `reverse_thrust` or `thrust_rev_n`; wheel-spin clips are names like `custom_anim_CENTER_WHEEL_ROTATION_ANGLE_n`, `WHEEL_ROLL_left` or `c_tire_anim`.

A wheel-spin clip must be exactly one revolution about a single axis, or it is not played, and it must not move a node a gear clip moves. Steering clips (any name containing `STEER`) are never played. The full list is in `docs/msfs-model-conversion.md`, "Animation Clips the Viewer Drives".

## Tower Mods

### Manifest Format
Expand Down
5 changes: 4 additions & 1 deletion USER_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -939,7 +939,10 @@ TowerCab 3D matches aircraft to models in this order:

### Features

- **Landing Gear Animation**: Gear extends below 2,000ft AGL, retracts above 500ft
- **Landing Gear Animation**: Gear is down on the ground and extends when descending below 2,000ft AGL; it retracts shortly after liftoff once the aircraft is climbing
- **Spoilers, Thrust Reversers and Wheels**: On models that have the matching animations, spoilers deploy at touchdown and stay out through the rollout, thrust reversers deploy while the aircraft decelerates and stay out until the rollout ends, and the wheels spin with the aircraft's groundspeed (backwards in a pushback) and coast to a stop after liftoff. A touch-and-go ends the rollout.

For VATSIM and RealTraffic aircraft, which report no height, the surfaces wait until the aircraft is drawn fully on the ground
- **Multiple Engine Variants**: Supports aircraft with GE, PW, or RR engine variants
- **Texture Scaling**: Choose texture resolution (Full 4K, 2K, 1K, or 512) to balance quality and storage
- **LRU Cache**: Automatic cleanup when cache limit is reached
Expand Down
18 changes: 18 additions & 0 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,24 @@ useAircraftInterpolation() [60 Hz singleton]
Note: AircraftPanel uses useAircraftFiltering for 1Hz UI updates
```

**Ground contact and gear.** vNAS observations carry the simulator's height above ground (`heightAboveGround`, metres, 1 Hz; built in `vnasStore.ts`, carried to remote browsers by the observation messages). `aircraftTimelineStore.ts` interpolates it to the displayed instant. `useAircraftInterpolation.ts` turns it into `inContact` per frame: entered below 3 ft, left above 5 ft. In contact the aircraft sits on the sampled terrain; airborne it is drawn at the reported height. The vNAS 50/80 ft `onGround` flag stays on the observation for labels only: a vNAS observation without a height (old replays, the brief mixed VATSIM/vNAS window) gives no drawing ground flag (`drawingGroundFlag`), so the landing and departure blends apply. A source without a height takes `inContact` from the drawn result of those blends.

**Stopped vNAS aircraft.** vNAS sends nothing for an aircraft that stands still, so `aircraftTimelineStore.ts` treats the silence after a stopped vNAS sample as parking (`isStoppedVnasSample`). A sample is stopped when the aircraft is on the ground (height above ground at most the contact exit height, 5 ft; a sample without a height is never held) and its last step is under `STOPPED_SPEED_KTS` (2 kt), and either the step before it or its speed over `VNAS_STOP_WINDOW_MS` (3 s) is too. Speeds are measured between vNAS samples, never taken from the reported groundspeed. The newest observation, if stopped, is held through the silence: no extrapolation, groundspeed 0, no ground track, vertical rate 0. A receive interval over `VNAS_PARKED_GAP_MS` (3 s) that starts at a stopped vNAS sample and ends at a vNAS sample is left out of the display-delay statistics, so a line-up wait neither raises the delay nor plays the takeoff roll fast. The restart needs no special case: reconciliation moves the aircraft from the held spot to the first moving sample. After 30 s of vNAS silence the timeline falls back to the mixed VATSIM list. Moving vNAS aircraft that go quiet and all VATSIM and RealTraffic aircraft still extrapolate. `tests/motion/` covers it (`vnasSilence`).

`useAircraftModels.ts` feeds `inContact` and the data-source time to `updateGearAnimation` (`utils/gearAnimationController.ts`; inputs typed in `types/gear.ts`). In contact the gear is down. After contact ends, the gear retracts on positive climb once the aircraft has been out of contact 2 s of data time, over 8 s with an ease-out; that retract fires once per contact, after which the gear holds and extends only when descending below 2000 ft. The 150 ft climb rule takes over once the aircraft passes 150 ft out of contact or after a backward seek in replay. An aircraft not in contact since it was drawn (an airborne spawn, a go-around without touchdown) uses the 150 ft rule. `tests/motion/` covers it (`contact`, `gearRule`, `heightAboveGround`, `heightlessVnas`).

Right after the gear, `useAircraftModels.ts` feeds `updateSurfaceAnimation` (`utils/surfaceAnimationController.ts`) and applies the pose with `applySurfaceAnimation` (`gearAnimationController.ts`), for FSLTL/AIG models only. The controller runs on the data-source time of the frame (`aircraft.timestamp`), so a paused replay stops the wheels and holds a reverser mid-travel; the gear's own transition stays on the wall clock.

`surfaceFrameFor` builds the controller's frame from the aircraft state alone (its data time, `isReplay` and contact inputs), so the replay flag always matches the frame's data. A source with a height above ground (vNAS) uses the shared `inContact`. A source without one (VATSIM, RealTraffic) is in contact when the shared contact holds and either the blend weight did not come from the landing blend (`InterpolatedAircraftState.groundBlendDescending` false: the departure blend or the ground cases) or it is at or above `SURFACE_HEIGHTLESS_CONTACT_BLEND_WEIGHT` (0.99). The landing case needs the stricter weight because the shared contact passes about 30 m up on a landing; the other cases do not, because on the rollout and taxi the weight falls below 0.99 whenever the reported altitude sits more than about 5 m above the sampled terrain. The rule keeps no state between frames, so first sight and a seek onto a rollout or taxi keep contact. The gear keeps the shared contact.

Spoilers deploy at a touchdown above 60 kt (`SURFACE_DEPLOY_MIN_GROUNDSPEED_KTS`) on every landing and stay out through the rollout. Reversers deploy 1.5 s after touchdown while the aircraft is above 60 kt and decelerating, and latch out until the rollout ends. Deceleration is the groundspeed change over a window of at least 0.5 s of data time (`SURFACE_DECELERATION_WINDOW_SECONDS`) measured by the controller itself, so it holds at low frame rates and in fast replay.

The rollout ends at 60 kt or when the groundspeed rises 10 kt above its minimum since touchdown (a touch-and-go). A touchdown is a contact after at least 2 s out of contact, so a contact flicker is not a second one. Wheels spin at groundspeed over a radius set by the type's wingspan (`wheelRadiusForWingspan`), backwards in a pushback, and coast linearly to zero over 5 s after liftoff. The pushback rule (`isPushingBack`, `utils/aircraft/pushback.ts`) is shared with the phase detector.

A seek snaps the spoilers and reversers to their targets and keeps the wheel phase. A step back in data time is a seek; so is a step forward of more than 5 s (`SURFACE_SEEK_SECONDS`) in replay, and a switch between replay and live; live, a longer forward gap just continues.

After a seek, or on first sight, an aircraft in contact deploys nothing for that rollout; one first seen out of contact is armed, so its next contact is a touchdown. `tests/motion/` covers it (`surfaceRule`, `surfaceScenario`, `surfaceClips`, `surfaceClipPlayback`).

### Weather Data Flow

```
Expand Down
14 changes: 14 additions & 0 deletions docs/msfs-model-conversion.md
Original file line number Diff line number Diff line change
Expand Up @@ -748,6 +748,20 @@ fn is_attachment_gltf(filename: &str) -> bool {

**Cache Invalidation**: Bumped `MODEL_CACHE_VERSION` to 9 and `CONVERTER_VERSION` to 8 to force re-indexing and re-conversion of affected models.

## Animation Clips the Viewer Drives

The converter keeps the glTF animation clips; the viewer picks which ones to play by name (`classifyAnimationClip` in `utils/gltfAnimationParser.ts`). Gear progress drives gear clips, and the surface controller (`utils/surfaceAnimationController.ts`) drives spoiler, reverser and wheel-spin clips.

- **Excluded first** (driven by nothing): any name containing `STEER`, `custom_anim_C_WHEEL_LR`, `c_wheel` and `c_wheel_key`, `custom_anim_ROTATION_VELOCITY_BODY_Y_*`, `custom_anim_{LEFT,RIGHT,CENTER}_WHEEL_RPM_*`, `fx_tire_contactPoint_*`, `custom_anim_GROUND_VELOCITY_*` and `spoiler_UTT787_aileron`.
- **Wheel spin**: `custom_anim_{CENTER,LEFT,RIGHT}_WHEEL_ROTATION_ANGLE_*`, `WHEEL_ROLL_{center,left,right}`, `{c,l,r}_tire` with the suffixes `_anim`, `_blurred_key`, `_still_key`, `_roll` or `_key`, `center_tire_roll`, `tire_anim` and `GEAR_{Center,Left,Right}_Tire`.
- **Spoiler**: `custom_anim_SPOILERS_{LEFT,RIGHT}_POSITION_*`, `{l,r}_spoiler_key`, `SPOILERS` (also spelled `SPOLIERS`, with an optional `_left` or `_right`), `HANDLING_Spoilers_{Ground,l,r}`, `*_Spoiler_{L,R,LR}[n]`, `Spoiler_{L,R}n`, `spoiler_Q_new`, the `TFS_777`, `TFS_A380` and `UTT_787` spoiler sets, and `AIA_Fokker_TailSpeedBrake`.
- **Reverser**: `custom_anim_REV_THRUST`, `reverse_thrust`, `REVERSER` and `REVERSERS`, `thrust_rev_n`, `engine_rev` and `custom_anim_TURB_ENG_REVERSE_NOZZLE_PERCENT_*`.
- **Gear**: every other clip with `GEAR` in its name (case-insensitive).

The patterns are an allowlist checked against the converted FSLTL and AIG models, so a new model family adds its names to the pattern lists in `gltfAnimationParser.ts`.

A wheel-spin clip plays only when every rotation track turns exactly one revolution (within 4 degrees), monotonic about one axis with no half-turn step; a clip that does not is left out. A wheel clip that targets a node a gear clip of the same model moves is dropped and logged once, since the two would overwrite each other's pose.

## Embedded Source Metadata

Converted GLB files contain embedded source metadata in `asset.extras.towercab3d`:
Expand Down
Loading