feat(simulator): seed spread geometry for default virtual nodes in registry bridge (bf-xrej)
Some checks are pending
CI Benchmark - Fusion Loop Timing / Fusion Loop Timing Benchmark (push) Waiting to run

Virtual nodes created without an explicit position carry the nodes-table DB
defaults (pos_x=0, pos_y=0, pos_z=1), so SyncToRegistry/SyncOneNode wrote
co-located origin positions into the fleet registry — and downstream into the
fusion engine — collapsing Fresnel excess paths (core symptom in bf-18yn).

Resolve positions through a new effectivePositions helper: nodes still at the
default origin are reassigned distinct, spread-out geometry from
DefaultNodePositions (sized to the full node count, across the store's space),
while explicitly-placed nodes keep their position. Slot assignment is keyed by
sorted node ID so it is deterministic regardless of map iteration order, and a
single-node sync writes the same position a full sync would. The effective
positions flow through SyncToRegistry, SyncOneNode, and ToRegistryRecords, so
the registry and fusion-engine seeding observe non-degenerate geometry.

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
jedarden 2026-07-03 19:00:37 -04:00
parent d048987d61
commit 307ab0e194
2 changed files with 468 additions and 24 deletions

View file

@ -3,8 +3,22 @@ package simulator
import (
"fmt"
"sort"
)
// DefaultNodeOrigin is the degenerate position virtual nodes carry when they
// are created without an explicit placement — it matches the fleet registry's
// nodes-table schema defaults (pos_x=0, pos_y=0, pos_z=1). Nodes still sitting
// at this origin are treated as "unset": the bridge reassigns them spread-out
// geometry at sync time so the registry and downstream fusion engine never see
// co-located / all-at-origin virtual nodes (core symptom in bf-18yn / bf-4q5w).
var DefaultNodeOrigin = Point{X: 0, Y: 0, Z: 1}
// isDefaultOrigin reports whether p is the unset DB-default origin position.
func isDefaultOrigin(p Point) bool {
return p.X == DefaultNodeOrigin.X && p.Y == DefaultNodeOrigin.Y && p.Z == DefaultNodeOrigin.Z
}
// FleetRegistryBridge integrates virtual nodes with the fleet registry.
// This allows virtual nodes to participate in coverage planning and role assignment.
type FleetRegistryBridge struct {
@ -12,6 +26,78 @@ type FleetRegistryBridge struct {
registryKey string // Prefix for MAC addresses in registry
}
// space returns the geometry the bridge spreads default nodes across. It
// prefers the store's configured space and falls back to DefaultSpace() so the
// bridge is safe even when the store has no space attached.
func (b *FleetRegistryBridge) space() *Space {
if b.store != nil {
if s := b.store.GetSpace(); s != nil {
return s
}
}
return DefaultSpace()
}
// effectivePositions returns the registry position to sync for each node ID.
// Nodes still at the default DB origin (DefaultNodeOrigin) are reassigned
// distinct, spread-out geometry from DefaultNodePositions — sized to the full
// node count so even when every node is unset the geometry spans the room —
// while explicitly-placed (non-origin) nodes keep their position. Default-origin
// nodes are assigned successive spread slots in sorted-ID order, so the mapping
// is deterministic regardless of map iteration order and a single-node sync
// produces the same position as a full sync. The result therefore has no two
// co-located points and is never entirely at the origin.
func (b *FleetRegistryBridge) effectivePositions(nodes []*VirtualNodeState) map[string]Point {
effective := make(map[string]Point, len(nodes))
var defaults []*VirtualNodeState
for _, n := range nodes {
if isDefaultOrigin(n.Position) {
defaults = append(defaults, n)
} else {
effective[n.ID] = n.Position
}
}
if len(defaults) == 0 {
return effective
}
// Deterministic slot assignment independent of map iteration order.
sort.Slice(defaults, func(i, j int) bool { return defaults[i].ID < defaults[j].ID })
space := b.space()
spread := DefaultNodePositions(space, len(nodes))
// Track positions already in use so default-origin nodes never collide
// with each other or with an explicitly-placed node.
taken := make(map[Point]bool, len(effective)+len(defaults))
for _, p := range effective {
taken[p] = true
}
si := 0
for _, n := range defaults {
for si < len(spread) && taken[spread[si]] {
si++
}
var pos Point
if si < len(spread) {
pos = spread[si]
si++
} else {
// Spread set exhausted: more default nodes than slots. The set is
// sized to len(nodes) so this is effectively unreachable; fall back
// to the room center, which is still distinct from the origin for
// any non-degenerate room.
minX, minY, minZ, maxX, maxY, maxZ := space.Bounds()
pos = Point{X: (minX + maxX) / 2, Y: (minY + maxY) / 2, Z: (minZ + maxZ) / 2}
}
effective[n.ID] = pos
taken[pos] = true
}
return effective
}
// NewFleetRegistryBridge creates a new bridge between virtual nodes and fleet registry
func NewFleetRegistryBridge(store *VirtualNodeStore) *FleetRegistryBridge {
return &FleetRegistryBridge{
@ -42,16 +128,24 @@ type NodeRecord struct {
Enabled bool
}
// SyncToRegistry synchronizes all virtual nodes to the fleet registry
// SyncToRegistry synchronizes all virtual nodes to the fleet registry.
//
// Positions are resolved through effectivePositions: any node still at the
// default DB origin (DefaultNodeOrigin) is reassigned distinct, spread-out
// geometry across the store's space so the registry — and the fusion engine
// fed from it via the existing wiring — never observes co-located or
// all-at-origin virtual nodes.
func (b *FleetRegistryBridge) SyncToRegistry(registry RegistryNodeAdapter) error {
if registry == nil {
return fmt.Errorf("registry is nil")
}
nodes := b.store.ListNodes()
positions := b.effectivePositions(nodes)
for _, node := range nodes {
mac := b.virtualMAC(node.ID)
pos := positions[node.ID]
// Check if node exists in registry
existing, err := registry.GetNode(mac)
@ -60,21 +154,21 @@ func (b *FleetRegistryBridge) SyncToRegistry(registry RegistryNodeAdapter) error
if err := registry.AddVirtualNode(
mac,
node.Name,
node.Position.X,
node.Position.Y,
node.Position.Z,
pos.X,
pos.Y,
pos.Z,
); err != nil {
return fmt.Errorf("add virtual node %s: %w", node.ID, err)
}
} else {
// Node exists, update position/role if changed
if existing.PosX != node.Position.X ||
existing.PosY != node.Position.Y ||
existing.PosZ != node.Position.Z {
if existing.PosX != pos.X ||
existing.PosY != pos.Y ||
existing.PosZ != pos.Z {
if err := registry.SetNodePosition(mac,
node.Position.X,
node.Position.Y,
node.Position.Z,
pos.X,
pos.Y,
pos.Z,
); err != nil {
return fmt.Errorf("update position for %s: %w", node.ID, err)
}
@ -94,7 +188,12 @@ func (b *FleetRegistryBridge) SyncToRegistry(registry RegistryNodeAdapter) error
return nil
}
// SyncOneNode syncs a single virtual node to the registry
// SyncOneNode syncs a single virtual node to the registry.
//
// The effective position is resolved over the full node set (not just this
// node) so a single-node sync produces the same spread geometry a full sync
// would write: default-origin nodes receive deterministic spread slots keyed
// by their rank among the unset nodes.
func (b *FleetRegistryBridge) SyncOneNode(registry RegistryNodeAdapter, nodeID string) error {
if registry == nil {
return fmt.Errorf("registry is nil")
@ -105,6 +204,10 @@ func (b *FleetRegistryBridge) SyncOneNode(registry RegistryNodeAdapter, nodeID s
return fmt.Errorf("get node %s: %w", nodeID, err)
}
// Resolve over the full set so the slot assignment matches SyncToRegistry.
positions := b.effectivePositions(b.store.ListNodes())
pos := positions[nodeID]
mac := b.virtualMAC(nodeID)
existing, err := registry.GetNode(mac)
@ -113,20 +216,20 @@ func (b *FleetRegistryBridge) SyncOneNode(registry RegistryNodeAdapter, nodeID s
return registry.AddVirtualNode(
mac,
node.Name,
node.Position.X,
node.Position.Y,
node.Position.Z,
pos.X,
pos.Y,
pos.Z,
)
}
// Update existing node
if existing.PosX != node.Position.X ||
existing.PosY != node.Position.Y ||
existing.PosZ != node.Position.Z {
if existing.PosX != pos.X ||
existing.PosY != pos.Y ||
existing.PosZ != pos.Z {
if err := registry.SetNodePosition(mac,
node.Position.X,
node.Position.Y,
node.Position.Z,
pos.X,
pos.Y,
pos.Z,
); err != nil {
return fmt.Errorf("update position: %w", err)
}
@ -180,19 +283,23 @@ func (b *FleetRegistryBridge) VirtualNodeID(mac string) (string, bool) {
return "", true // TODO: implement reverse mapping
}
// ToRegistryRecords converts virtual nodes to fleet registry records
// ToRegistryRecords converts virtual nodes to fleet registry records. Positions
// are resolved through effectivePositions so the records reflect exactly what
// SyncToRegistry would write (spread geometry for default-origin nodes).
func (b *FleetRegistryBridge) ToRegistryRecords() []NodeRecord {
nodes := b.store.ListNodes()
positions := b.effectivePositions(nodes)
records := make([]NodeRecord, 0, len(nodes))
for _, node := range nodes {
pos := positions[node.ID]
records = append(records, NodeRecord{
MAC: b.virtualMAC(node.ID),
Name: node.Name,
Role: string(node.Role),
PosX: node.Position.X,
PosY: node.Position.Y,
PosZ: node.Position.Z,
PosX: pos.X,
PosY: pos.Y,
PosZ: pos.Z,
Virtual: true,
Enabled: node.Enabled,
})

View file

@ -0,0 +1,337 @@
package simulator
import (
"fmt"
"testing"
)
// fakeRegistry is an in-memory RegistryNodeAdapter that records every mutation
// so bridge tests can assert what was written to the registry (and in what order).
type fakeRegistry struct {
nodes map[string]NodeRecord
addCalls []NodeRecord
setPosCalls []NodeRecord
setRoleCalls []macRole
}
type macRole struct {
MAC, Role string
}
func newFakeRegistry() *fakeRegistry {
return &fakeRegistry{nodes: make(map[string]NodeRecord)}
}
func (f *fakeRegistry) AddVirtualNode(mac, name string, x, y, z float64) error {
rec := NodeRecord{MAC: mac, Name: name, PosX: x, PosY: y, PosZ: z, Virtual: true, Enabled: true}
f.nodes[mac] = rec
f.addCalls = append(f.addCalls, rec)
return nil
}
func (f *fakeRegistry) SetNodePosition(mac string, x, y, z float64) error {
rec := f.nodes[mac]
rec.PosX, rec.PosY, rec.PosZ = x, y, z
f.nodes[mac] = rec
f.setPosCalls = append(f.setPosCalls, NodeRecord{MAC: mac, PosX: x, PosY: y, PosZ: z})
return nil
}
func (f *fakeRegistry) SetNodeRole(mac, role string) error {
rec := f.nodes[mac]
rec.Role = role
f.nodes[mac] = rec
f.setRoleCalls = append(f.setRoleCalls, macRole{MAC: mac, Role: role})
return nil
}
func (f *fakeRegistry) DeleteNode(mac string) error {
delete(f.nodes, mac)
return nil
}
func (f *fakeRegistry) GetNode(mac string) (*NodeRecord, error) {
rec, ok := f.nodes[mac]
if !ok {
return nil, fmt.Errorf("not found: %s", mac)
}
return &rec, nil
}
func (f *fakeRegistry) GetAllNodes() ([]NodeRecord, error) {
out := make([]NodeRecord, 0, len(f.nodes))
for _, r := range f.nodes {
out = append(out, r)
}
return out, nil
}
// storeWithNodes builds a fresh store (DefaultSpace) holding one virtual node
// per entry. Each entry gives (id, position). Positions at the default origin
// are created as-is (the origin is in-bounds for DefaultSpace).
func storeWithNodes(t *testing.T, nodes ...struct{ ID string; Pos Point }) *VirtualNodeStore {
t.Helper()
store, _ := tempStore(t)
for _, n := range nodes {
if _, err := store.CreateVirtualNode(n.ID, n.ID, n.Pos); err != nil {
t.Fatalf("create %s: %v", n.ID, err)
}
}
return store
}
// originStore builds a store with count nodes all at the default DB origin.
func originStore(t *testing.T, count int) *VirtualNodeStore {
t.Helper()
entries := make([]struct{ ID string; Pos Point }, 0, count)
for i := 0; i < count; i++ {
entries = append(entries, struct{ ID string; Pos Point }{
ID: fmt.Sprintf("node-%d", i+1),
Pos: DefaultNodeOrigin,
})
}
return storeWithNodes(t, entries...)
}
// assertDistinctNonOrigin fails the test if any record is still at the default
// origin or if any two records share the same position.
func assertDistinctNonOrigin(t *testing.T, recs []NodeRecord) {
t.Helper()
seen := make(map[Point]bool, len(recs))
for _, r := range recs {
p := Point{X: r.PosX, Y: r.PosY, Z: r.PosZ}
if isDefaultOrigin(p) {
t.Errorf("node %s still at default origin %v", r.MAC, p)
}
if seen[p] {
t.Errorf("co-located registry nodes at %v (mac %s)", p, r.MAC)
}
seen[p] = true
}
}
func TestIsDefaultOrigin(t *testing.T) {
cases := []struct {
name string
p Point
want bool
}{
{"db default origin", DefaultNodeOrigin, true},
{"explicit zero", Point{X: 0, Y: 0, Z: 0}, false},
{"origin wrong Z", Point{X: 0, Y: 0, Z: 1.5}, false},
{"origin shifted X", Point{X: 1, Y: 0, Z: 1}, false},
{"placed node", Point{X: 3, Y: 2, Z: 1.5}, false},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
if got := isDefaultOrigin(c.p); got != c.want {
t.Errorf("isDefaultOrigin(%v) = %v, want %v", c.p, got, c.want)
}
})
}
}
// TestSyncToRegistry_OriginNodesGetSpreadGeometry is the core acceptance check:
// a fleet of nodes created at the default origin must be written to the
// registry at distinct, non-degenerate positions (spread across the room).
func TestSyncToRegistry_OriginNodesGetSpreadGeometry(t *testing.T) {
for _, count := range []int{1, 2, 3, 4, 6, 9} {
t.Run(fmt.Sprintf("count=%d", count), func(t *testing.T) {
store := originStore(t, count)
bridge := NewFleetRegistryBridge(store)
reg := newFakeRegistry()
if err := bridge.SyncToRegistry(reg); err != nil {
t.Fatalf("SyncToRegistry: %v", err)
}
if len(reg.nodes) != count {
t.Fatalf("expected %d registry nodes, got %d", count, len(reg.nodes))
}
if len(reg.addCalls) != count {
t.Fatalf("expected %d AddVirtualNode calls, got %d", count, len(reg.addCalls))
}
recs := make([]NodeRecord, 0, len(reg.nodes))
for _, r := range reg.nodes {
recs = append(recs, r)
}
assertDistinctNonOrigin(t, recs)
})
}
}
// TestSyncToRegistry_ExplicitPositionsPreserved verifies nodes placed at a real
// (non-origin) position are written through unchanged — the bridge only
// substitutes geometry for unset nodes.
func TestSyncToRegistry_ExplicitPositionsPreserved(t *testing.T) {
a := Point{X: 1, Y: 1, Z: 1.5}
b := Point{X: 4, Y: 4, Z: 2}
store := storeWithNodes(t,
struct{ ID string; Pos Point }{"a", a},
struct{ ID string; Pos Point }{"b", b},
)
bridge := NewFleetRegistryBridge(store)
reg := newFakeRegistry()
if err := bridge.SyncToRegistry(reg); err != nil {
t.Fatalf("SyncToRegistry: %v", err)
}
for id, want := range map[string]Point{"a": a, "b": b} {
rec, ok := reg.nodes[bridge.virtualMAC(id)]
if !ok {
t.Fatalf("node %s missing from registry", id)
}
got := Point{X: rec.PosX, Y: rec.PosY, Z: rec.PosZ}
if got != want {
t.Errorf("node %s: expected explicit %v, got %v", id, want, got)
}
}
}
// TestSyncToRegistry_MixedNodes keeps explicit placements while spreading the
// unset ones, and asserts the final set is distinct and non-origin.
func TestSyncToRegistry_MixedNodes(t *testing.T) {
store := storeWithNodes(t,
struct{ ID string; Pos Point }{"explicit", Point{X: 5, Y: 4, Z: 2}},
struct{ ID string; Pos Point }{"o1", DefaultNodeOrigin},
struct{ ID string; Pos Point }{"o2", DefaultNodeOrigin},
struct{ ID string; Pos Point }{"o3", DefaultNodeOrigin},
)
bridge := NewFleetRegistryBridge(store)
reg := newFakeRegistry()
if err := bridge.SyncToRegistry(reg); err != nil {
t.Fatalf("SyncToRegistry: %v", err)
}
// Explicit node keeps its position.
rec := reg.nodes[bridge.virtualMAC("explicit")]
if (Point{X: rec.PosX, Y: rec.PosY, Z: rec.PosZ}) != (Point{X: 5, Y: 4, Z: 2}) {
t.Errorf("explicit node moved: got %v", rec)
}
recs := make([]NodeRecord, 0, len(reg.nodes))
for _, r := range reg.nodes {
recs = append(recs, r)
}
assertDistinctNonOrigin(t, recs)
}
// TestSyncToRegistry_Idempotent asserts a second sync is a no-op: because the
// spread slot assignment is deterministic, the registry already holds the
// effective positions and no further writes occur.
func TestSyncToRegistry_Idempotent(t *testing.T) {
store := originStore(t, 4)
bridge := NewFleetRegistryBridge(store)
reg := newFakeRegistry()
if err := bridge.SyncToRegistry(reg); err != nil {
t.Fatalf("first SyncToRegistry: %v", err)
}
firstAdds, firstSets := len(reg.addCalls), len(reg.setPosCalls)
if err := bridge.SyncToRegistry(reg); err != nil {
t.Fatalf("second SyncToRegistry: %v", err)
}
if len(reg.addCalls) != firstAdds {
t.Errorf("second sync added nodes again: %d != %d", len(reg.addCalls), firstAdds)
}
if len(reg.setPosCalls) != firstSets {
t.Errorf("second sync rewrote positions: %d != %d", len(reg.setPosCalls), firstSets)
}
}
// TestSyncOneNode_MatchesFullSync asserts a single-node sync writes the same
// effective position that a full sync would — including spread geometry for a
// default-origin node.
func TestSyncOneNode_MatchesFullSync(t *testing.T) {
store := originStore(t, 3)
fullReg := newFakeRegistry()
bridge := NewFleetRegistryBridge(store)
if err := bridge.SyncToRegistry(fullReg); err != nil {
t.Fatalf("SyncToRegistry: %v", err)
}
target := "node-2"
mac := bridge.virtualMAC(target)
wantRec, ok := fullReg.nodes[mac]
if !ok {
t.Fatalf("node %s missing from full sync", target)
}
oneReg := newFakeRegistry()
if err := bridge.SyncOneNode(oneReg, target); err != nil {
t.Fatalf("SyncOneNode: %v", err)
}
gotRec, ok := oneReg.nodes[mac]
if !ok {
t.Fatalf("node %s not written by SyncOneNode", target)
}
want := Point{X: wantRec.PosX, Y: wantRec.PosY, Z: wantRec.PosZ}
got := Point{X: gotRec.PosX, Y: gotRec.PosY, Z: gotRec.PosZ}
if got != want {
t.Errorf("SyncOneNode position %v != SyncToRegistry position %v", got, want)
}
if isDefaultOrigin(got) {
t.Errorf("SyncOneNode left default-origin node at the origin: %v", got)
}
}
// TestSyncOneNode_ExplicitPreserved asserts SyncOneNode passes an explicit
// (non-origin) position through unchanged.
func TestSyncOneNode_ExplicitPreserved(t *testing.T) {
store := storeWithNodes(t, struct{ ID string; Pos Point }{"a", Point{X: 2, Y: 3, Z: 1.2}})
bridge := NewFleetRegistryBridge(store)
reg := newFakeRegistry()
if err := bridge.SyncOneNode(reg, "a"); err != nil {
t.Fatalf("SyncOneNode: %v", err)
}
rec := reg.nodes[bridge.virtualMAC("a")]
got := Point{X: rec.PosX, Y: rec.PosY, Z: rec.PosZ}
if got != (Point{X: 2, Y: 3, Z: 1.2}) {
t.Errorf("expected explicit position preserved, got %v", got)
}
}
// TestSyncToRegistry_NilRegistry guards the nil-adapter guard.
func TestSyncToRegistry_NilRegistry(t *testing.T) {
store := originStore(t, 2)
bridge := NewFleetRegistryBridge(store)
if err := bridge.SyncToRegistry(nil); err == nil {
t.Fatal("expected error syncing to nil registry")
}
if err := bridge.SyncOneNode(nil, "node-1"); err == nil {
t.Fatal("expected error syncing one node to nil registry")
}
}
// TestToRegistryRecords_UsesEffectivePositions ensures the record view matches
// what SyncToRegistry writes (spread geometry for origin nodes).
func TestToRegistryRecords_UsesEffectivePositions(t *testing.T) {
store := originStore(t, 3)
bridge := NewFleetRegistryBridge(store)
records := bridge.ToRegistryRecords()
if len(records) != 3 {
t.Fatalf("expected 3 records, got %d", len(records))
}
assertDistinctNonOrigin(t, records)
// Cross-check against an actual sync.
reg := newFakeRegistry()
if err := bridge.SyncToRegistry(reg); err != nil {
t.Fatalf("SyncToRegistry: %v", err)
}
for _, r := range records {
synced := reg.nodes[r.MAC]
if synced.PosX != r.PosX || synced.PosY != r.PosY || synced.PosZ != r.PosZ {
t.Errorf("record %s %v != synced %v", r.MAC, r, synced)
}
}
}