Skip to content

Commit

Permalink
Correct content of SPIKED response composition
Browse files Browse the repository at this point in the history
  • Loading branch information
dharmab committed Jan 31, 2025
1 parent 5b87495 commit 3bcf69e
Show file tree
Hide file tree
Showing 9 changed files with 94 additions and 57 deletions.
2 changes: 1 addition & 1 deletion internal/application/compose.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ func (a *Application) composeCall(ctx context.Context, call any, out chan<- Mess
response = a.composer.ComposeShoppingResponse(c)
case brevity.SnaplockResponse:
response = a.composer.ComposeSnaplockResponse(c)
case brevity.SpikedResponse:
case brevity.SpikedResponseV2:
response = a.composer.ComposeSpikedResponse(c)
case brevity.TripwireResponse:
response = a.composer.ComposeTripwireResponse(c)
Expand Down
4 changes: 4 additions & 0 deletions pkg/brevity/aspect.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,7 @@ func AspectFromAngle(bearing bearings.Bearing, track bearings.Bearing) Aspect {
return UnknownAspect
}
}

func (a Aspect) IsCardinal() bool {
return a == Flank || a == Beam || a == Drag
}
8 changes: 4 additions & 4 deletions pkg/brevity/group.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,13 @@ type Group interface {
// Altitude is the group's highest altitude. This may be zero for BOGEY DOPE, SNAPLOCK, and THREAT calls.
Altitude() unit.Length
// Stacks are the group's altitude STACKS, ordered from highest to lowest in intervals of at least 10,000 feet.
// This may be empty for BOGEY DOPE, SNAPLOCK, and THREAT calls.
// This may be empty for BOGEY DOPE, SNAPLOCK, SPIKED and THREAT calls.
Stacks() []Stack
// Track is the group's track direction. This may be UnknownDirection for BOGEY DOPE, SNAPLOCK, and THREAT calls.
// Track is the group's track direction. This may be UnknownDirection for BOGEY DOPE, SNAPLOCK, SPIKED and THREAT calls.
Track() Track
// Aspect is the group's aspect angle relative to another aircraft. This may be nil for BOGEY DOPE, SNAPLOCK, and some THREAT calls.
// Aspect is the group's aspect angle relative to another aircraft. This may be nil for BOGEY DOPE, SNAPLOCK, SPIKED and some THREAT calls.
Aspect() Aspect
// BRAA is an alternate format for the group's location. This is nil except for BOGEY DOPE, SNAPLOCK, and some THREAT calls.
// BRAA is an alternate format for the group's location. This is nil except for BOGEY DOPE, SNAPLOCK, SPIKED, and some THREAT calls.
BRAA() BRAA
// Declaration of the group's friend or foe status.
Declaration() Declaration
Expand Down
15 changes: 15 additions & 0 deletions pkg/brevity/spiked.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ func (r SpikedRequest) String() string {

// SpikedResponse reports any contacts within ±30 degrees of a reported radar spike.
// Reference: ATP 3-52.4 Chapter V section 13.
//
// Deprecated: Use SpikedResponseV2 instead.
type SpikedResponse struct {
// Callsign of the friendly aircraft calling SPIKED.
Callsign string
Expand All @@ -42,3 +44,16 @@ type SpikedResponse struct {
// Reported spike bearing. This is used if the response did not correlate to a group.
Bearing bearings.Bearing
}

// SpikedResponseV2 reports any contacts within ±30 degrees of a reported radar spike.
// Reference: ATP 3-52.4 Chapter V section 13.
type SpikedResponseV2 struct {
// Callsign of the friendly aircraft calling SPIKED.
Callsign string
// Reported spike bearing. This is used if the response did not correlate to a group.
Bearing bearings.Bearing
// True if the spike was correlated to a contact. False otherwise.
Status bool
// Correleted contact group. If Status is false, this may be nil.
Group Group
}
6 changes: 6 additions & 0 deletions pkg/composer/composer.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
package composer

import (
"fmt"
"unicode"
)

Expand Down Expand Up @@ -31,6 +32,11 @@ func (r *NaturalLanguageResponse) WriteBoth(s string) {
r.Write(s, s)
}

// WriteBothf appends the formatted string to the subtitle and speech fields.
func (r *NaturalLanguageResponse) WriteBothf(format string, a ...any) {
r.WriteBoth(fmt.Sprintf(format, a...))
}

// WriteResponse appends the given response's subtitle and speech to this response.
func (r *NaturalLanguageResponse) WriteResponse(response NaturalLanguageResponse) {
r.Write(response.Speech, response.Subtitle)
Expand Down
8 changes: 3 additions & 5 deletions pkg/composer/faded.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package composer

import (
"fmt"

"github.com/dharmab/skyeye/pkg/brevity"
)

Expand All @@ -12,7 +10,7 @@ func (c *Composer) ComposeFadedCall(call brevity.FadedCall) (response NaturalLan
if call.Group.Contacts() == 1 {
response.WriteBoth("single contact faded,")
} else {
response.WriteBoth(fmt.Sprintf("%d contacts faded,", call.Group.Contacts()))
response.WriteBothf("%d contacts faded,", call.Group.Contacts())
}

if bullseye := call.Group.Bullseye(); bullseye != nil {
Expand All @@ -21,11 +19,11 @@ func (c *Composer) ComposeFadedCall(call brevity.FadedCall) (response NaturalLan
}

if call.Group.Track() != brevity.UnknownDirection {
response.WriteBoth(fmt.Sprintf(", track %s", call.Group.Track()))
response.WriteBothf(", track %s", call.Group.Track())
}

if call.Group.Declaration() != brevity.Unable {
response.WriteBoth(fmt.Sprintf(", %s", call.Group.Declaration()))
response.WriteBothf(", %s", call.Group.Declaration())
}

for _, platform := range call.Group.Platforms() {
Expand Down
32 changes: 22 additions & 10 deletions pkg/composer/group.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package composer
import (
"fmt"
"math"
"slices"
"strconv"
"strings"

Expand Down Expand Up @@ -59,31 +58,44 @@ func (c *Composer) composeGroup(group brevity.Group) (response NaturalLanguageRe
fmt.Sprintf("%s %s, %s", label, bullseye.Subtitle, altitude),
)
if isTrackKnown {
response.WriteBoth(fmt.Sprintf(", track %s", group.Track()))
response.WriteBothf(", track %s", group.Track())
}
} else if group.BRAA() != nil {
braa := c.composeBRAA(group.BRAA(), group.Declaration())
response.Write(
fmt.Sprintf("%s %s", label, braa.Speech),
fmt.Sprintf("%s %s", label, braa.Subtitle),
)
isCardinalAspect := slices.Contains([]brevity.Aspect{brevity.Flank, brevity.Beam, brevity.Drag}, group.BRAA().Aspect())
if isCardinalAspect && isTrackKnown {
response.WriteBoth(fmt.Sprintf(" %s", group.Track()))
if group.BRAA().Aspect().IsCardinal() && isTrackKnown {
response.WriteBothf(" %s", group.Track())
}
}

// Declaration
response.WriteBoth(fmt.Sprintf(", %s", group.Declaration()))
response.WriteBoth(", ")
declaration := c.composeDeclaration(group)
response.WriteResponse(declaration)

// Fill-in information
fillIns := c.composeFillIns(group)
response.WriteResponse(fillIns)

response.WriteBoth(".")
return
}

func (*Composer) composeDeclaration(group brevity.Group) (response NaturalLanguageResponse) {
response.WriteBoth(string(group.Declaration()))
if group.MergedWith() == 1 {
response.WriteBoth(", merged with 1 friendly")
}
if group.MergedWith() > 1 {
response.WriteBoth(fmt.Sprintf(", merged with %d friendlies", group.MergedWith()))
response.WriteBothf(", merged with %d friendlies", group.MergedWith())
}
return
}

// Fill-in information

func (c *Composer) composeFillIns(group brevity.Group) (response NaturalLanguageResponse) {
isFurball := group.Declaration() == brevity.Furball

if !isFurball {
Expand All @@ -95,6 +107,7 @@ func (c *Composer) composeGroup(group brevity.Group) (response NaturalLanguageRe
response.WriteResponse(contacts)

if !group.High() {
stacks := group.Stacks()
if len(stacks) > 1 {
response.WriteBoth(", " + c.composeAltitudeFillIns(stacks))
}
Expand All @@ -121,7 +134,6 @@ func (c *Composer) composeGroup(group brevity.Group) (response NaturalLanguageRe
}
}

response.WriteBoth(".")
return
}

Expand Down
58 changes: 32 additions & 26 deletions pkg/composer/spiked.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,45 +2,51 @@ package composer

import (
"fmt"
"slices"

"github.com/dharmab/skyeye/pkg/brevity"
)

// ComposeSpikedResponse constructs natural language brevity for responding to a SPIKED call.
func (c *Composer) ComposeSpikedResponse(response brevity.SpikedResponse) NaturalLanguageResponse {
func (c *Composer) ComposeSpikedResponse(response brevity.SpikedResponseV2) NaturalLanguageResponse {
if response.Status {
reply := fmt.Sprintf(
"%s, spike range %d, %s, %s",
c.composeCallsigns(response.Callsign),
int(response.Range.NauticalMiles()),
c.composeAltitude(response.Altitude, brevity.Bogey),
response.Aspect)
isCardinalAspect := slices.Contains([]brevity.Aspect{brevity.Flank, brevity.Beam, brevity.Drag}, response.Aspect)
isTrackKnown := response.Track != brevity.UnknownDirection
if isCardinalAspect && isTrackKnown {
reply = fmt.Sprintf("%s %s", reply, response.Track)
}
reply = fmt.Sprintf("%s, %s", reply, response.Declaration)
if response.Contacts == 1 {
reply += ", single contact."
} else if response.Contacts > 1 {
reply = fmt.Sprintf("%s, %d contacts.", reply, response.Contacts)
nlr := NaturalLanguageResponse{}

callsigns := c.composeCallsigns(response.Callsign)
nlr.WriteBoth(callsigns)

grp := response.Group

_range := int(grp.BRAA().Range().NauticalMiles())
nlr.WriteBothf(", spike range %d", _range)

nlr.WriteBoth(", ")
altitude := c.composeAltitudeStacks(grp.Stacks(), grp.Declaration())
nlr.WriteBoth(altitude)

nlr.WriteBothf(", %s", grp.BRAA().Aspect())

if grp.BRAA().Aspect().IsCardinal() && grp.Track() != brevity.UnknownDirection {
nlr.WriteBothf(" %s", grp.Track())
}
return NaturalLanguageResponse{
Subtitle: reply,
Speech: reply,
declaration := c.composeDeclaration(grp)
nlr.WriteBoth(", ")
nlr.WriteResponse(declaration)

fillIns := c.composeFillIns(grp)
if len(fillIns.Subtitle) > 0 {
nlr.WriteResponse(fillIns)
}
nlr.WriteBoth(".")
return nlr
}
if response.Bearing == nil {
nlr := NaturalLanguageResponse{}
message := fmt.Sprintf("%s, %s", c.composeCallsigns(response.Callsign), brevity.Unable)
return NaturalLanguageResponse{
Subtitle: message,
Speech: message,
}
nlr.WriteBoth(message)
return nlr
}
return NaturalLanguageResponse{
Subtitle: fmt.Sprintf("%s, %s clean %d.", c.composeCallsigns(response.Callsign), c.composeCallsigns(c.Callsign), int(response.Bearing.Degrees())),
Speech: fmt.Sprintf("%s, %s, clean - %s", c.composeCallsigns(response.Callsign), c.composeCallsigns(c.Callsign), pronounceBearing(response.Bearing)),
Speech: fmt.Sprintf("%s, %s, clean %s", c.composeCallsigns(response.Callsign), c.composeCallsigns(c.Callsign), pronounceBearing(response.Bearing)),
}
}
18 changes: 7 additions & 11 deletions pkg/controller/spiked.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,25 +39,21 @@ func (c *Controller) HandleSpiked(ctx context.Context, request *brevity.SpikedRe

if nearestGroup == nil {
logger.Info().Msg("no hostile groups found within spike cone")
c.calls <- NewCall(ctx, brevity.SpikedResponse{
c.calls <- NewCall(ctx, brevity.SpikedResponseV2{
Callsign: foundCallsign,
Status: false,
Bearing: request.Bearing,
})
return
}
nearestGroup.SetDeclaration(brevity.Hostile)

logger = logger.With().Stringer("group", nearestGroup).Logger()
logger.Debug().Msg("hostile group found within spike cone")
c.calls <- NewCall(ctx, brevity.SpikedResponse{
Callsign: foundCallsign,
Status: true,
Bearing: request.Bearing,
Range: nearestGroup.BRAA().Range(),
Altitude: nearestGroup.BRAA().Altitude(),
Aspect: nearestGroup.BRAA().Aspect(),
Track: nearestGroup.Track(),
Declaration: brevity.Hostile,
Contacts: nearestGroup.Contacts(),
c.calls <- NewCall(ctx, brevity.SpikedResponseV2{
Callsign: foundCallsign,
Status: true,
Bearing: request.Bearing,
Group: nearestGroup,
})
}

0 comments on commit 3bcf69e

Please sign in to comment.