Complete analysis of two position data flow paths: 1. CSI Simulator (cmd/sim) → hello message → fleet manager → registry 2. Virtual Node Planning (simulator/) → registry bridge → adapter → registry Both paths verified as complete and functional. No breaks or missing connections found.
7.4 KiB
Simulator-to-Registry Position Wiring Trace
Overview
This document traces the complete data flow for position data from virtual node creation in the simulator to persistence in the fleet registry. There are two distinct paths depending on which simulator is being used.
Path 1: CSI Simulator (cmd/sim/main.go)
1. Position Creation
File: /home/coding/spaxel/cmd/sim/main.go
Virtual nodes are created with computed positions using generateNodePositions():
- Single node: center of room
- Two nodes: opposite corners (spans both X and Y)
- 3+ nodes: row-major grid with alternating Z heights
// Lines 198-253
func createVirtualNodes(count int, space *Space, rng *rand.Rand) []*VirtualNode {
positions := generateNodePositions(count, space)
// ... creates VirtualNode structs with Position field
}
2. Hello Message Transmission
File: /home/coding/spaxel/cmd/sim/main.go (Lines 345-365)
The simulator connects to mothership via WebSocket and sends a hello message:
hello := map[string]interface{}{
"type": "hello",
"mac": macToString(n.MAC),
"pos_x": n.Position.X,
"pos_y": n.Position.Y,
"pos_z": n.Position.Z,
// ... other fields
}
3. Hello Message Reception
File: /home/coding/spaxel/mothership/internal/ingestion/message.go (Lines 10-34)
The HelloMessage struct defines the position fields as pointers:
type HelloMessage struct {
// ...
PosX *float64 `json:"pos_x,omitempty"`
PosY *float64 `json:"pos_y,omitempty"`
PosZ *float64 `json:"pos_z,omitempty"`
}
Pointers distinguish "not announced" (nil) from "position is (0,0,0)".
4. Fleet Notification
File: /home/coding/spaxel/mothership/internal/ingestion/server.go (Lines 26-37)
The ingestion server calls the FleetNotifier callback:
type FleetNotifier interface {
OnNodeConnected(mac, firmware, chip string, posX, posY, posZ *float64)
OnNodeDisconnected(mac string)
}
5. Registry Persistence
File: /home/coding/spaxel/mothership/internal/fleet/manager.go (Lines 182-231)
The Fleet Manager's OnNodeConnected() method:
- Upserts the node record:
registry.UpsertNode(mac, firmware, chip) - Persists announced position (if all three axes present):
if posX != nil && posY != nil && posZ != nil { registry.SetNodePosition(mac, *posX, *posY, *posZ) } - Forwards position to fusion engine via
nodePositionSink
6. Storage
File: /home/coding/spaxel/mothership/internal/fleet/registry.go (Lines 179-183)
Positions are stored in SQLite:
func (r *Registry) SetNodePosition(mac string, x, y, z float64) error {
_, err := r.db.Exec(`UPDATE nodes SET pos_x=?, pos_y=?, pos_z=? WHERE mac=?`,
x, y, z, mac)
return err
}
Path 2: Virtual Node Planning Simulator (mothership/internal/simulator/)
This path is used for planning and coverage optimization, distinct from the CSI simulator.
1. Virtual Node Store
File: /home/coding/spaxel/mothership/internal/simulator/virtual_state.go
Virtual nodes are created and managed in VirtualNodeStore:
func (s *VirtualNodeStore) CreateVirtualNode(id, name string, position Point)
2. Registry Bridge
File: /home/coding/spaxel/mothership/internal/simulator/registry_bridge.go
The FleetRegistryBridge integrates virtual nodes with the fleet registry:
Key Method - effectivePositions() (Lines 50-99):
- Resolves final positions for all nodes
- Nodes at default origin (0,0,1) are reassigned spread-out geometry via
DefaultNodePositions() - Explicitly-placed nodes keep their position
- Deterministic assignment: sorted by ID, no collisions
Sync Method (Lines 131-189):
func (b *FleetRegistryBridge) SyncToRegistry(registry RegistryNodeAdapter) error
- Lists all nodes from store
- Resolves effective positions
- Creates or updates registry entries
- Sets position and role
3. Registry Adapter Interface
File: /home/coding/spaxel/mothership/internal/simulator/registry_bridge.go (Lines 109-129)
type RegistryNodeAdapter interface {
AddVirtualNode(mac, name string, x, y, z float64) error
SetNodePosition(mac string, x, y, z float64) error
SetNodeRole(mac, role string) error
DeleteNode(mac string) error
GetNode(mac string) (*NodeRecord, error)
GetAllNodes() ([]NodeRecord, error)
}
4. Fleet Registry Implementation
File: /home/coding/spaxel/mothership/internal/fleet/registry.go (Lines 273-287)
func (r *Registry) AddVirtualNode(mac, name string, x, y, z float64) error {
_, err := r.db.Exec(`
INSERT INTO nodes (mac, name, role, pos_x, pos_y, pos_z, virtual, ...)
VALUES (?, ?, 'virtual', ?, ?, ?, 1, ...)
`, mac, name, x, y, z, ...)
return err
}
Fusion Engine Integration
The registry positions flow to the 3D fusion engine for blob generation:
File: /home/coding/spaxel/mothership/internal/fleet/manager.go (Lines 110-120, 173-180)
// Sink for position updates
nodePositionSink func(mac string, x, y, z float64)
func (m *Manager) ForwardNodePosition(mac string, x, y, z float64) {
if sink != nil {
sink(mac, x, y, z)
}
}
This is called from:
OnNodeConnected()after persisting hello-announced position- REST API handler after PATCH /api/nodes/{mac}/position
File: /home/coding/spaxel/mothership/internal/fusion/fusion.go (Lines 64-73)
The fusion engine maintains a node position map:
type Engine struct {
nodePos map[string]NodePosition
// ...
}
Key Files in Pipeline
- Simulator CLI:
/home/coding/spaxel/cmd/sim/main.go - Hello Message:
/home/coding/spaxel/mothership/internal/ingestion/message.go - Ingestion Server:
/home/coding/spaxel/mothership/internal/ingestion/server.go - Fleet Manager:
/home/coding/spaxel/mothership/internal/fleet/manager.go - Fleet Registry:
/home/coding/spaxel/mothership/internal/fleet/registry.go - Virtual Node Store:
/home/coding/spaxel/mothership/internal/simulator/virtual_state.go - Registry Bridge:
/home/coding/spaxel/mothership/internal/simulator/registry_bridge.go - Fusion Engine:
/home/coding/spaxel/mothership/internal/fusion/fusion.go
Verified Data Flow
✅ Complete Path 1 (CSI Simulator):
cmd/sim/main.go:generateNodePositions()- computes positionscmd/sim/main.go:connectNodes()- sends hello with pos_x/y/zingestion/server.go- receives WebSocket hello messagefleet/manager.go:OnNodeConnected()- persists positionfleet/registry.go:SetNodePosition()- stores in SQLitefleet/manager.go:ForwardNodePosition()- forwards to fusion engine
✅ Complete Path 2 (Virtual Node Planning):
simulator/virtual_state.go:CreateVirtualNode()- creates virtual nodesimulator/registry_bridge.go:SyncToRegistry()- syncs to registryfleet/registry.go:AddVirtualNode()- stores with position- Position available to fusion engine via registry queries
Integration Points
- Ingestion → Fleet:
FleetNotifier.OnNodeConnected(mac, firmware, chip, posX, posY, posZ) - Fleet → Fusion:
nodePositionSink(mac, x, y, z)callback - Simulator Bridge → Registry:
RegistryNodeAdapterinterface
Acceptance Criteria Status
✅ Complete data flow path documented from simulator to registry ✅ Two distinct paths identified and documented ✅ List of files involved in the simulator-to-registry pipeline provided