From 1d80c9ba368f11a64f99b64b4e0f052868fdb60d Mon Sep 17 00:00:00 2001 From: jedarden Date: Fri, 10 Apr 2026 21:16:39 -0400 Subject: [PATCH] fix: resolve simulator test compilation errors - Fix SimulateCSIData to accept []*Walker instead of []*SimWalker - Remove unused imports (path/filepath, time) from virtual_state_test.go - Fix assignment mismatch for CreateVirtualNode error handling - Fix deadlock in VirtualNodeStore by using saveLocked() when mutex is held - Refactor CreateAPNode to avoid race condition with state modification Co-Authored-By: Claude Opus 4.6 --- mothership/internal/simulator/propagation.go | 27 +++++++ .../internal/simulator/virtual_state.go | 72 +++++++++++++------ .../internal/simulator/virtual_state_test.go | 4 +- 3 files changed, 78 insertions(+), 25 deletions(-) diff --git a/mothership/internal/simulator/propagation.go b/mothership/internal/simulator/propagation.go index ec504af..83eab22 100644 --- a/mothership/internal/simulator/propagation.go +++ b/mothership/internal/simulator/propagation.go @@ -486,3 +486,30 @@ func IsInFirstFresnelZone(tx, rx, point Point) bool { return FresnelZoneNumber(tx, rx, point) == 1 } +// SimulateCSIData simulates CSI data for a set of links and walkers. +// Returns a map of link IDs to their maximum deltaRMS value across all walkers, +// but only includes links where deltaRMS is >= threshold. +func (pm *PropagationModel) SimulateCSIData(links []Link, walkers []*Walker, threshold float64) map[string]float64 { + results := make(map[string]float64) + + for _, link := range links { + maxDeltaRMS := 0.0 + + // Compute deltaRMS for each walker position + for _, walker := range walkers { + deltaRMS := pm.ComputeLinkActivity(link, walker.Position, threshold) + if deltaRMS > maxDeltaRMS { + maxDeltaRMS = deltaRMS + } + } + + // Only include links that meet the threshold + if maxDeltaRMS >= threshold { + linkID := link.TX.ID + "->" + link.RX.ID + results[linkID] = maxDeltaRMS + } + } + + return results +} + diff --git a/mothership/internal/simulator/virtual_state.go b/mothership/internal/simulator/virtual_state.go index 87bca6c..14470f5 100644 --- a/mothership/internal/simulator/virtual_state.go +++ b/mothership/internal/simulator/virtual_state.go @@ -117,8 +117,8 @@ func (s *VirtualNodeStore) CreateNode(id, name string, nodeType NodeType, positi s.nodes[id] = state - // Persist to disk - if err := s.save(); err != nil { + // Persist to disk (mutex already held) + if err := s.saveLocked(); err != nil { delete(s.nodes, id) return nil, fmt.Errorf("save node: %w", err) } @@ -133,19 +133,47 @@ func (s *VirtualNodeStore) CreateVirtualNode(id, name string, position Point) (* // CreateAPNode creates a new access point node (for passive radar) func (s *VirtualNodeStore) CreateAPNode(id, name, bssid string, channel int, position Point) (*VirtualNodeState, error) { - state, err := s.CreateNode(id, name, NodeTypeAP, position) - if err != nil { - return nil, err + s.mu.Lock() + defer s.mu.Unlock() + + if s.closed { + return nil, fmt.Errorf("store is closed") } - s.mu.Lock() - state.Role = RoleTX - state.APBSSID = bssid - state.APChannel = channel - state.UpdatedAt = time.Now() - s.mu.Unlock() + if _, exists := s.nodes[id]; exists { + return nil, fmt.Errorf("node %s already exists", id) + } - if err := s.save(); err != nil { + // Validate position is within space bounds + minX, minY, minZ, maxX, maxY, maxZ := s.space.Bounds() + if position.X < minX || position.X > maxX || + position.Y < minY || position.Y > maxY || + position.Z < minZ || position.Z > maxZ { + return nil, fmt.Errorf("position (%f, %f, %f) is outside space bounds [%f, %f, %f] to [%f, %f, %f]", + position.X, position.Y, position.Z, minX, minY, minZ, maxX, maxY, maxZ) + } + + now := time.Now() + state := &VirtualNodeState{ + ID: id, + Name: name, + Type: NodeTypeAP, + Role: RoleTX, + Position: position, + Enabled: true, + CreatedAt: now, + UpdatedAt: now, + Metadata: make(map[string]interface{}), + Tags: make([]string, 0), + APBSSID: bssid, + APChannel: channel, + } + + s.nodes[id] = state + + // Persist to disk (mutex already held) + if err := s.saveLocked(); err != nil { + delete(s.nodes, id) return nil, fmt.Errorf("save AP node: %w", err) } @@ -196,7 +224,7 @@ func (s *VirtualNodeStore) UpdateNodePosition(id string, position Point) error { state.Position = position state.UpdatedAt = time.Now() - return s.save() + return s.saveLocked() } // UpdateNodeRole updates a node's role @@ -216,7 +244,7 @@ func (s *VirtualNodeStore) UpdateNodeRole(id string, role NodeRole) error { state.Role = role state.UpdatedAt = time.Now() - return s.save() + return s.saveLocked() } // SetNodeEnabled enables or disables a node @@ -236,7 +264,7 @@ func (s *VirtualNodeStore) SetNodeEnabled(id string, enabled bool) error { state.Enabled = enabled state.UpdatedAt = time.Now() - return s.save() + return s.saveLocked() } // UpdateNodeMetadata updates a node's metadata @@ -256,7 +284,7 @@ func (s *VirtualNodeStore) UpdateNodeMetadata(id string, metadata map[string]int state.Metadata = metadata state.UpdatedAt = time.Now() - return s.save() + return s.saveLocked() } // AddTag adds a tag to a node @@ -283,7 +311,7 @@ func (s *VirtualNodeStore) AddTag(id, tag string) error { state.Tags = append(state.Tags, tag) state.UpdatedAt = time.Now() - return s.save() + return s.saveLocked() } // RemoveTag removes a tag from a node @@ -315,7 +343,7 @@ func (s *VirtualNodeStore) RemoveTag(id, tag string) error { state.Tags = newTags state.UpdatedAt = time.Now() - return s.save() + return s.saveLocked() } // DeleteNode removes a node from the store @@ -333,7 +361,7 @@ func (s *VirtualNodeStore) DeleteNode(id string) error { delete(s.nodes, id) - return s.save() + return s.saveLocked() } // ListNodes returns all nodes @@ -448,7 +476,7 @@ func (s *VirtualNodeStore) UpdateSpace(space *Space) error { } } - return s.save() + return s.saveLocked() } // Clear removes all nodes from the store @@ -462,7 +490,7 @@ func (s *VirtualNodeStore) Clear() error { s.nodes = make(map[string]*VirtualNodeState) - return s.save() + return s.saveLocked() } // ToNodeSet converts the stored nodes to a NodeSet for simulation @@ -540,7 +568,7 @@ func (s *VirtualNodeStore) ImportFromNodeSet(nodeSet *NodeSet) error { s.nodes[node.ID] = state } - return s.save() + return s.saveLocked() } // Close closes the store and releases resources diff --git a/mothership/internal/simulator/virtual_state_test.go b/mothership/internal/simulator/virtual_state_test.go index 796ccd1..3e797da 100644 --- a/mothership/internal/simulator/virtual_state_test.go +++ b/mothership/internal/simulator/virtual_state_test.go @@ -2,9 +2,7 @@ package simulator import ( "os" - "path/filepath" "testing" - "time" ) // Test helper to create a temporary store @@ -804,7 +802,7 @@ func TestVirtualNodeStore_Close(t *testing.T) { } // Operations should fail after close - err = store.CreateVirtualNode("node-2", "Should Fail", NewPoint(2.0, 3.0, 1.5)) + _, err = store.CreateVirtualNode("node-2", "Should Fail", NewPoint(2.0, 3.0, 1.5)) if err == nil { t.Error("Expected error when creating node after close") }