Add scout constellation mode for APK venue persona packs.
Some checks failed
CI Docker Mining Proof / Linux agent hashrate proof (push) Has been cancelled
Some checks failed
CI Docker Mining Proof / Linux agent hashrate proof (push) Has been cancelled
Cluster 3+ scout_report hits on the same SSID within 10 minutes; server infers airport/campus/retail venue class and pushes persona spread_policy. Emberwake weather-map merges active scout biomes. Includes agent, server API, and Vitest coverage.
This commit is contained in:
90
server/internal/ai/scout_constellation_test.go
Normal file
90
server/internal/ai/scout_constellation_test.go
Normal file
@@ -0,0 +1,90 @@
|
||||
package ai
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestInferVenueClassSSIDHints(t *testing.T) {
|
||||
cases := []struct {
|
||||
ssid string
|
||||
want string
|
||||
svc int
|
||||
agents int
|
||||
}{
|
||||
{"SFO-Airport-Free-WiFi", VenueAirport, 8, 3},
|
||||
{"StateUniversity-Campus", VenueCampus, 6, 3},
|
||||
{"Target-Guest", VenueRetail, 4, 3},
|
||||
{"MyHomeNetwork", VenueUnknown, 2, 3},
|
||||
{"dense-mobile", VenueAirport, 25, 4},
|
||||
{"busy-lan", VenueCampus, 14, 3},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
got := InferVenueClass(tc.ssid, tc.svc, tc.agents)
|
||||
if got != tc.want {
|
||||
t.Fatalf("InferVenueClass(%q)=%q want %q", tc.ssid, got, tc.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestVenuePersonaPackMapping(t *testing.T) {
|
||||
if VenuePersonaPack(VenueAirport) != PersonaPersuasive {
|
||||
t.Fatal("airport should be persuasive")
|
||||
}
|
||||
if VenuePersonaPack(VenueRetail) != PersonaAggressive {
|
||||
t.Fatal("retail should be aggressive")
|
||||
}
|
||||
if VenuePersonaPack(VenueCampus) != PersonaBalanced {
|
||||
t.Fatal("campus should be balanced")
|
||||
}
|
||||
}
|
||||
|
||||
func TestScoutConstellationFormsAtThreeAgents(t *testing.T) {
|
||||
reg := NewScoutConstellationRegistry()
|
||||
now := time.Date(2026, 6, 7, 12, 0, 0, 0, time.UTC)
|
||||
ssid := "Campus-Guest"
|
||||
|
||||
_, changed := reg.Record("scout-1", ssid, 4, now)
|
||||
if changed {
|
||||
t.Fatal("should not form with one scout")
|
||||
}
|
||||
_, changed = reg.Record("scout-2", ssid, 6, now.Add(time.Minute))
|
||||
if changed {
|
||||
t.Fatal("should not form with two scouts")
|
||||
}
|
||||
c, changed := reg.Record("scout-3", ssid, 8, now.Add(2*time.Minute))
|
||||
if !changed {
|
||||
t.Fatal("expected constellation formation")
|
||||
}
|
||||
if c.VenueClass != VenueCampus {
|
||||
t.Fatalf("venue=%q", c.VenueClass)
|
||||
}
|
||||
if c.PersonaPack != PersonaBalanced {
|
||||
t.Fatalf("persona=%q", c.PersonaPack)
|
||||
}
|
||||
if len(c.AgentIDs) != 3 {
|
||||
t.Fatalf("agents=%v", c.AgentIDs)
|
||||
}
|
||||
|
||||
policy := BuildScoutSpreadPolicy(c)
|
||||
if policy["persona_pack"] != PersonaBalanced {
|
||||
t.Fatalf("policy persona=%v", policy["persona_pack"])
|
||||
}
|
||||
if policy["scout_constellation"] != true {
|
||||
t.Fatal("missing scout_constellation flag")
|
||||
}
|
||||
}
|
||||
|
||||
func TestScoutConstellationPrunesOldHits(t *testing.T) {
|
||||
reg := NewScoutConstellationRegistry()
|
||||
base := time.Date(2026, 6, 7, 12, 0, 0, 0, time.UTC)
|
||||
ssid := "Retail-Free"
|
||||
|
||||
reg.Record("a1", ssid, 3, base.Add(-11*time.Minute))
|
||||
reg.Record("a2", ssid, 3, base.Add(-11*time.Minute))
|
||||
reg.Record("a3", ssid, 3, base)
|
||||
_, changed := reg.Record("a4", ssid, 3, base.Add(time.Minute))
|
||||
if changed {
|
||||
t.Fatal("stale hits should not count toward constellation")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user