Skip to content

Comments

Add visual approach requests (controller-initiated and pilot-spontaneous) #583#807

Open
jordw wants to merge 17 commits intommp:masterfrom
jordw:visual-approach-583
Open

Add visual approach requests (controller-initiated and pilot-spontaneous) #583#807
jordw wants to merge 17 commits intommp:masterfrom
jordw:visual-approach-583

Conversation

@jordw
Copy link
Contributor

@jordw jordw commented Feb 19, 2026

I took a stab at adding visual approach support. I tried to make some reasonable assumptions, but happy to iterate based on feedback.

Pilots spontaneously request the visual when they have the field in sight, controllers can ask (FS), and controllers can clear direct visual approaches (CV). Charted visual approaches (e.g., Belmont Visual) remain separate via the C command. STT equivalents are included for all new commands.

  • Pilots report "field in sight, requesting the visual" when VMC, within 15nm, and airport visible. Probability scales with distance and visibility. The controller decides whether to clear a plain visual (CV) or a charted visual procedure (C).
  • FS command queries the pilot for field in sight.
  • CV clears a direct visual: flies straight-in when aligned, inserts a base-turn waypoint when offset >1.5nm from centerline.

Assumptions
Pilot behavior

  • Pilots always request "the visual" generically - the controller decides whether to clear a plain visual (CV) or a charted visual procedure (C).
  • Pilots have roughly a 3% chance per second of requesting the visual when close in clear weather, lower when further out or in marginal VFR. Once they've requested, they don't ask again.
  • Field-in-sight is modeled as VMC weather + within 15nm + airport within 120° of the nose. No terrain or obstruction modeling for now.

Flight path

  • No downwind/base/final pattern - in Class B airspace the controller has hopefully somewhat positioned the aircraft, so we just need to get them turned onto final.
  • Aircraft offset more than 1.5nm from the centerline get a base-turn waypoint so they don't cut diagonally across to final. It's placed far enough out (~4.5nm) that they roll out on a normal 3nm final.
  • The 3nm final point has a "don't be below 900ft AGL" restriction matching a standard 3° glideslope. The base-turn point has no altitude constraint - pilot manages their own descent like they would in real life.
  • If the aircraft can't set up a stable approach (first waypoint is behind it), CV triggers a go-around rather than returning an error.

Test plan

  • Clear a visual approach (CV13L) for an aircraft on or near the extended centerline — it should fly straight to a 3nm final and land normally.
  • Clear a visual for an aircraft well off to the side — it should turn toward a base leg first, then turn onto final, rather than cutting diagonally.
  • Ask a pilot if they have the field in sight (FS) in VMC and IMC — they should say yes and no respectively.
  • Fly some arrivals in VMC and wait — pilots should eventually call in requesting the visual on their own.

@jessie846
Copy link
Contributor

jessie846 commented Feb 19, 2026

Would it be possible to make this a probability as well "Pilots always request "the visual" generically - the controller decides whether to clear a plain visual (CV) or a charted visual procedure (C).". Becuase I feel like, not every pilot requets a visual or calls the field in sight. I think that might be a bit more realistic than having them call every time.

@jordw
Copy link
Contributor Author

jordw commented Feb 20, 2026

Sorry if that wasn't clear, but it's a probability like so:

Base rate: 3% per second (~30 second expected wait time if conditions are perfect)

Distance: linear taper from 3nm to 15nm:

  • ≤3nm: 1.0 (full probability)
  • 9nm: 0.625
  • 15nm: 0.25

Visibility: linear ramp from 3SM to 10SM:

  • ≤3SM: 0.3 (marginal VFR, unlikely to request)
  • 5SM: 0.5
  • ≥10SM: 1.0 (clear day)

Combined: prob = 0.03 × distFactor × visFactor

Happy to adjust this - thoughts?

@jordw jordw marked this pull request as ready for review February 20, 2026 01:35
@jordw jordw force-pushed the visual-approach-583 branch from 910efd6 to b99d5c7 Compare February 20, 2026 01:58
@jordw
Copy link
Contributor Author

jordw commented Feb 20, 2026

I play tested this a bit more, and came up with a simpler probability model for requesting the visual that's also easier to tune.

@jordw
Copy link
Contributor Author

jordw commented Feb 21, 2026

Add command documentation

@jordw jordw force-pushed the visual-approach-583 branch 2 times, most recently from c388aa3 to b4ceafb Compare February 21, 2026 20:30
@jordw
Copy link
Contributor Author

jordw commented Feb 21, 2026

Removed the charted visual fallback to a direct straight-in when the aircraft can't intercept the charted waypoints — it now reports "unable" instead, so the controller can re-clear.

@mmp
Copy link
Owner

mmp commented Feb 22, 2026

This will be fantastic to have in there. Broadly speaking the code looks good. Some initial feedback / thoughts before properly digging into the code (which will probably have to wait until tomorrow at this point):

  • Rather than FS we might have something like AP/{oclock}/{miles}, similar to the traffic-call command. (And assuming STT is the typical path now, we'd have that info from the controller transmission anyway.) Then whether or not the pilot sees the field could have a rough relationship to the accuracy of those (also like traffic calls). I don't think we should be too picky, but if it's said to be at their 2 o'clock and it's actually at their 10 o'clock we probably shouldn't have them say they see it.
  • That'd mean adding a case where sometimes they report "looking" and we're not able to clear them for the visual. In that case, we could have them come back a little while later with "we have the field in sight now".
  • I haven't digested fully, but it looks like maybe a pilot can be cleared for a visual approach before reporting the field in sight?
  • Is a pilot requesting the visual approach typical? I'd have expected the flow would be that they'd spontaneously call in with "we have the field in sight" and the controller would have the option to clear them for the visual.
  • It would be nice to use more of the METAR have a more nuanced model for the probability of seeing the field: e.g. if it's 8 miles visibility and they're 12 miles out, presumably they're not going to report the field in sight. Similarly, it can be VMC but they could still be in the clouds in which case they won't be able to see the field.

@jordw jordw force-pushed the visual-approach-583 branch from d4e7001 to 8ca5942 Compare February 22, 2026 03:47
@jordw
Copy link
Contributor Author

jordw commented Feb 22, 2026

Great feedback! I took another stab at it.

Rather than FS we might have something like AP/{oclock}/{miles}, similar to the traffic-call command. Then whether or not the pilot sees the field could have a rough relationship to the accuracy of those.

Done. FS is replaced with AP/{oclock}/{miles}. The clock position gets compared against the actual bearing to the airport, and if it's off by more than about 120 degrees they'll always say "looking." Otherwise there's a distance scaled probability they spot it.

That'd mean adding a case where sometimes they report "looking" and we're not able to clear them for the visual. In that case, we could have them come back a little while later with "we have the field in sight now".

Added. When they say "looking" a 10-20 second timer starts, and once it expires they re-check and call back with "field in sight" if they can see it, or negative if they can't.

I haven't digested fully, but it looks like maybe a pilot can be cleared for a visual approach before reporting the field in sight?

Good catch, fixed. CV now requires field in sight, a spontaneous visual request, or traffic in sight before accepting the clearance. That last one means you can also clear a visual after a traffic advisory: point out preceding traffic, pilot calls traffic in sight, then clear the visual. This is also valid per the 7110.65, and was easy enough to add.

Is a pilot requesting the visual approach typical? I'd have expected the flow would be that they'd spontaneously call in with "we have the field in sight" and the controller would have the option to clear them for the visual.

I researched this more and reworked this. It does happen, but it's seemingly rare on both counts, so I adjusted the probabilities: 10% of pilots spontaneously report field in sight, and of those about 10% also request the visual. The rest only report field/traffic in sight when asked. These are easily tunable if there's more feedback as well.

It would be nice to use more of the METAR and have a more nuanced model for the probability of seeing the field.

Done. The actual METAR visibility (converted from statute to nautical miles, capped at 15nm) is now used as the max range instead of a flat 15. Also added a ceiling check: if the aircraft is above airport elevation plus the reported ceiling, they're in the clouds regardless of surface weather.

@mmp
Copy link
Owner

mmp commented Feb 22, 2026

I checked out the PR branch and spent some time with it. First and foremost, apologies for that crash in the wx/atmos code; that is now fixed in master and merging that to this branch takes care of that.

I did successfully clear someone for a visual approach in the end which was great, but hit a few bumps (this was all via STT and not the keyboard commands):

  • On the JFK 22s scenario, if I said "expect visual approach runway 22L", they'd read back that they expected the Belmont visual
  • I switched to the JFK 31s to work around that and tried "fly heading 040 vectors visual approach runway 31R"; the STT transcript was clean but I got a "we'll plan for ILS 31R" readback
  • I then wasn't able to get the aircraft to report the field in sight, even at the point of 11 o'clock, 8 miles out. METAR was 10 mile visibility, BKN150 the lowest cloud layer, so no issues there.
  • They did successfully see it when I vectored them onto the approach course and gave them 12 o'clock, 6 miles.
  • It didn't like "kennedy is at your eleven o'clock now, 8 miles"; it looks like airport names aren't wired up as an option in stt/
  • TTS readbacks are a bit off with e.g. "approach" said twice; it looks like this is the issue:
+               rt = av.MakeReadbackTransmission("[approach|], {callsign}, [we have the field in sight now|field in sight|we have the airport in sight now]",

In general, don't include "approach" in transmission strings; that will be added automatically. Also, use av.MakeReadbackTransmission for pilot readbacks immediately after a command is issued, but then use av.MakeContactTransmission if the pilot wasn't just spoken to. (The former makes transmissions of the form "blah blah, Delta 123", while the latter does "Approach, Delta 123, blah blah".)

(I'll look at the code a bit now.)

Copy link
Owner

@mmp mmp left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

First pass; I haven't dug into everything deeply yet, but here are some initial comments.

@jordw
Copy link
Contributor Author

jordw commented Feb 22, 2026

Thanks for the playtesting and feedback. No worries about the crash, I was able to fix it locally last night and keep moving. I didn’t expect this feature to be so nuanced when I started, but it’s been a great way to learn the codebase, and I appreciate your patience.

It’s clear what I need to do to polish this up. Everything you ran into looks straightforward to address, so I’ll do another pass.

jordw and others added 13 commits February 22, 2026 20:19
…ous) mmp#583

Pilots spontaneously report "field in sight, requesting the visual" when VMC, within 15nm, and the airport is visible (≤120° off nose). Probability scales with distance and visibility.

  Controllers can query "do you have the field in sight?" (FS command). The CV command (e.g., CV13L) clears a direct visual approach: when roughly aligned, flies a 3nm final to the threshold; when offset, inserts a base-turn waypoint for a
  realistic turn onto final. Charted visual intercept failures also fall back to the direct visual path.
Replace per-second probability scaling (distance/visibility factors)
with a one-time coin flip: ~30% of pilots prefer the visual and
request it after a short random delay (2-8s) once the field is in
sight.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…rence

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…mp#583

Charted visual approaches have specific waypoints for separation and
obstacle clearance. If the aircraft can't intercept, it should report
unable so the controller can re-clear, not silently degrade to a
straight-in visual.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…mp#583

- Replace FS command with AP/{oclock}/{miles} (airport advisory): controller
  tells pilot where to look, pilot responds "field in sight", "looking", or
  IMC indication based on bearing accuracy, distance, and weather
- Add "looking" → delayed "field in sight" flow mirroring traffic advisory
  pattern (checkDelayedFieldInSight)
- Require field-in-sight before CV clearance; pilot responds "unable" without it
- Change spontaneous calls: ~90% report "field in sight" only, ~10% also
  request the visual approach
- Update STT handlers, website docs, and tests

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Scale visual range with actual METAR visibility (capped at 15nm) instead
of a fixed 15nm constant, and reject field-in-sight when the aircraft is
above the reported ceiling. Adds effectiveVisualRange() helper to keep
the logic DRY between checkVisualEligibility and handleAirportAdvisory.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…backs mmp#583

checkDelayedTrafficInSight and checkDelayedFieldInSight now fire
deterministically when SimTime passes the deadline instead of randomly
within a window. The initial 10-20s random delay is sufficient variation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
METAR reports visibility in statute miles but distances are computed in
nautical miles. Add StatuteMilesToNauticalMiles constant and apply the
conversion in effectiveVisualRange.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…bability, update UI help mmp#583

- Merge duplicate airport/field STT commands into one pattern and add
  "report field in sight" variant
- Make visual request conditional on field-in-sight flip (10% of 10% = 1%)
- Update UI help to show AP/{oclock}/{miles} instead of old FS command

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ack mmp#583

- CV gate now accepts TrafficInSight so controllers can clear a visual
  after a traffic advisory (per 7110.65)
- When the looking timer expires and the pilot still can't see the field,
  they call back "negative field" instead of going silent
- Update UI help and website docs

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>


Add e2e tests that chain stt.DecodeTranscript → sim.RunAircraftControlCommands,
catching bugs at the seam between layers that unit tests miss. Uses an
export_test.go bridge to expose unexported Sim fields to the external test
package.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@jordw jordw force-pushed the visual-approach-583 branch 2 times, most recently from 589bc8d to c68c630 Compare February 23, 2026 04:44
jordw and others added 3 commits February 22, 2026 23:48
METAR visibility is a ground-level measurement. Aerosol extinction decays
exponentially with altitude: σ(z) = σ₀·exp(-z/H). Integrating along the
slant path from the aircraft to the airport (Beer-Lambert) gives:
effectiveRange = surfaceVis × h / (H × (1 - exp(-h/H))), using a 2500 ft
haze scale height. Cap raised from 15 to 25 NM. mmp#583

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Change approach name to "Visual Approach Runway XX" (title case, with
"approach" before runway) matching real pilot phrasing. Skip appending
"approach" in ClearedApproachIntent and ApproachExpect renderers when
the name already contains it. mmp#583

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@jordw jordw force-pushed the visual-approach-583 branch from c68c630 to 01b7a37 Compare February 23, 2026 04:48
@jordw
Copy link
Contributor Author

jordw commented Feb 23, 2026

Updates from today:

  1. Visual approach commands are now CVA/EVA (CVA replaces the old CV; EVA is new for "expect visual approach"). A digit check after the prefix prevents collisions with fix-based commands like CVORA.

  2. Added STT patterns for "expect visual", "vectors visual", "cleared visual", and airport position callouts like "Kennedy at your 12 o'clock 8 miles". These match at higher priority than the generic approach matcher, so "expect visual 22L" produces EVA22L instead of matching a charted visual like the Belmont.

  3. Visibility now factors in altitude. METAR reports statute miles at ground level, so we convert to nautical miles, then account for the fact that haze thins out as you go up. In practice: at 3000ft with 10SM vis, a pilot can spot the field from about 15nm out instead of the 8.7nm you'd get just converting the surface number. The previous model was too rigid in this regard.

  4. Readbacks say "cleared visual approach runway 22L" instead of "cleared visual runway 22L approach". No more doubled "approach approach".

  5. Added tests that feed spoken transcripts through STT and into the sim to verify the right command runs and the right readback comes out, with 6 cases covering the bugs you hit during playtesting. This might be something to expand upon as STT becomes more important.

  6. CVA go-arounds now actually produce a pilot response instead of silent discard.

I still need to do more play testing tomorrow with STT, but good progress.

GetSTTFixes() was missing the arrival and departure airport ICAO codes,
so STT couldn't match airport names like "Kennedy" in commands such as
"Kennedy 12 o'clock 12 miles" for the AP (airport advisory) command.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@jordw
Copy link
Contributor Author

jordw commented Feb 24, 2026

I smoke tested this in the sim with STT via the test bench tool I submitted in #816 (the visual approach scenarios for now are at jordw@ee904d8 since they depend on this logic, and I wanted to keep this clean) and it seems to be in decent shape now.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants