@@ -82,12 +82,7 @@ impl SpatialGrid {
8282/// have been REMOVED: those regions are the map's impassable mountain walls, not
8383/// accessible ocean. Treating them as free water caused the optimizer to inflate
8484/// scores at the western/northern map boundary and produce border-edge results.
85- fn distance_to_nearest_water (
86- x : f64 ,
87- y : f64 ,
88- opt_nodes : & [ OptNode ] ,
89- waterwell_idx : Option < usize > ,
90- ) -> f64 {
85+ fn distance_to_nearest_water ( x : f64 , y : f64 , waterwell_nodes : & [ ( f64 , f64 ) ] ) -> f64 {
9186 let mut min_dist_cm = f64:: MAX ;
9287
9388 // Major static bodies of water in Satisfactory (centres, half-widths, in cm)
@@ -122,16 +117,12 @@ fn distance_to_nearest_water(
122117 let mut min_dist_m = min_dist_cm / 100.0 ;
123118
124119 // Add dynamic waterwell checks
125- if let Some ( ww_idx) = waterwell_idx {
126- for node in opt_nodes {
127- if node. res_idx == ww_idx {
128- let dx = ( x - node. x ) / 100.0 ;
129- let dy = ( y - node. y ) / 100.0 ;
130- let dist = ( dx * dx + dy * dy) . sqrt ( ) ;
131- if dist < min_dist_m {
132- min_dist_m = dist;
133- }
134- }
120+ for & ( node_x, node_y) in waterwell_nodes {
121+ let dx = ( x - node_x) / 100.0 ;
122+ let dy = ( y - node_y) / 100.0 ;
123+ let dist = ( dx * dx + dy * dy) . sqrt ( ) ;
124+ if dist < min_dist_m {
125+ min_dist_m = dist;
135126 }
136127 }
137128
@@ -361,7 +352,7 @@ fn calculate_utility(
361352 weights_arr : & [ f64 ] ,
362353 epsilons_arr : & [ f64 ] ,
363354 res_to_idx : & HashMap < String , usize > ,
364- waterwell_idx : Option < usize > ,
355+ waterwell_nodes : & [ ( f64 , f64 ) ] ,
365356 land_mask : & LandMask ,
366357) -> f64 {
367358 // Reject points outside the practical landmass polygon.
@@ -475,7 +466,7 @@ fn calculate_utility(
475466
476467 // Add virtual water yield based on proximity to mapped lakes/ponds or waterwells.
477468 if let Some ( & water_idx) = res_to_idx. get ( "water" ) {
478- let water_dist = distance_to_nearest_water ( x, y, opt_nodes , waterwell_idx ) ;
469+ let water_dist = distance_to_nearest_water ( x, y, waterwell_nodes ) ;
479470 let water_decay = match config. decay_func {
480471 crate :: models:: DistanceDecay :: Gaussian => {
481472 let two_sigma_sq = 2.0 * config. sigma * config. sigma ;
@@ -627,7 +618,7 @@ fn run_hill_climbing(
627618 weights_arr : & [ f64 ] ,
628619 epsilons_arr : & [ f64 ] ,
629620 res_to_idx : & HashMap < String , usize > ,
630- waterwell_idx : Option < usize > ,
621+ waterwell_nodes : & [ ( f64 , f64 ) ] ,
631622 land_mask : & LandMask ,
632623) -> OptimizationResult {
633624 let mut curr_x = start_x;
@@ -646,7 +637,7 @@ fn run_hill_climbing(
646637 weights_arr,
647638 epsilons_arr,
648639 res_to_idx,
649- waterwell_idx ,
640+ waterwell_nodes ,
650641 land_mask,
651642 ) ;
652643
@@ -684,7 +675,7 @@ fn run_hill_climbing(
684675 weights_arr,
685676 epsilons_arr,
686677 res_to_idx,
687- waterwell_idx ,
678+ waterwell_nodes ,
688679 land_mask,
689680 ) ;
690681 if score > best_neighbor_score {
@@ -844,7 +835,7 @@ struct SearchContext {
844835 weights_arr : Vec < f64 > ,
845836 epsilons_arr : Vec < f64 > ,
846837 res_to_idx : HashMap < String , usize > ,
847- waterwell_idx : Option < usize > ,
838+ waterwell_nodes : Vec < ( f64 , f64 ) > ,
848839 land_mask : LandMask ,
849840}
850841
@@ -934,6 +925,15 @@ fn prepare_context(nodes: &[ResourceNode], config: &OptimizerConfig) -> SearchCo
934925
935926 let spatial_grid = SpatialGrid :: new ( & opt_nodes, 100000.0 ) ;
936927 let waterwell_idx = res_to_idx. get ( "waterwell" ) . copied ( ) ;
928+ let waterwell_nodes = waterwell_idx
929+ . map ( |idx| {
930+ opt_nodes
931+ . iter ( )
932+ . filter ( |node| node. res_idx == idx)
933+ . map ( |node| ( node. x , node. y ) )
934+ . collect ( )
935+ } )
936+ . unwrap_or_default ( ) ;
937937 let land_mask = LandMask :: from_nodes ( & opt_nodes) ;
938938
939939 SearchContext {
@@ -943,7 +943,7 @@ fn prepare_context(nodes: &[ResourceNode], config: &OptimizerConfig) -> SearchCo
943943 weights_arr,
944944 epsilons_arr,
945945 res_to_idx,
946- waterwell_idx ,
946+ waterwell_nodes ,
947947 land_mask,
948948 }
949949}
@@ -981,7 +981,7 @@ fn grid_search_refine(
981981 & ctx. weights_arr ,
982982 & ctx. epsilons_arr ,
983983 & ctx. res_to_idx ,
984- ctx. waterwell_idx ,
984+ & ctx. waterwell_nodes ,
985985 & ctx. land_mask ,
986986 )
987987 } )
@@ -1068,7 +1068,7 @@ fn grid_search_refine(
10681068 & ctx. weights_arr ,
10691069 & ctx. epsilons_arr ,
10701070 & ctx. res_to_idx ,
1071- ctx. waterwell_idx ,
1071+ & ctx. waterwell_nodes ,
10721072 & ctx. land_mask ,
10731073 )
10741074 } )
@@ -1187,7 +1187,7 @@ fn optimize_fast(ctx: &SearchContext, config: &OptimizerConfig) -> Vec<Optimizat
11871187 & ctx. weights_arr ,
11881188 & ctx. epsilons_arr ,
11891189 & ctx. res_to_idx ,
1190- ctx. waterwell_idx ,
1190+ & ctx. waterwell_nodes ,
11911191 & ctx. land_mask ,
11921192 )
11931193 } )
@@ -1211,6 +1211,28 @@ mod tests {
12111211 use super :: * ;
12121212 use crate :: models:: { GamePhase , OptimizerConfig } ;
12131213
1214+ #[ test]
1215+ fn test_water_distance_inside_static_water_body ( ) {
1216+ let distance = distance_to_nearest_water ( 140000.0 , 230000.0 , & [ ] ) ;
1217+
1218+ assert ! ( distance. abs( ) < f64 :: EPSILON ) ;
1219+ }
1220+
1221+ #[ test]
1222+ fn test_water_distance_prefers_nearby_waterwell ( ) {
1223+ let distance = distance_to_nearest_water ( 0.0 , 0.0 , & [ ( 300.0 , 400.0 ) ] ) ;
1224+
1225+ assert ! ( ( distance - 5.0 ) . abs( ) < f64 :: EPSILON ) ;
1226+ }
1227+
1228+ #[ test]
1229+ fn test_water_distance_without_waterwells_uses_static_water ( ) {
1230+ let distance = distance_to_nearest_water ( 0.0 , 0.0 , & [ ] ) ;
1231+ let expected_distance = ( 30_000.0_f64 . powi ( 2 ) + 5_000.0_f64 . powi ( 2 ) ) . sqrt ( ) / 100.0 ;
1232+
1233+ assert ! ( ( distance - expected_distance) . abs( ) < f64 :: EPSILON ) ;
1234+ }
1235+
12141236 #[ test]
12151237 fn test_ignore_spawns ( ) {
12161238 let nodes = crate :: data_loader:: load_default_nodes ( ) ;
@@ -1237,7 +1259,7 @@ mod tests {
12371259 & ctx. weights_arr ,
12381260 & ctx. epsilons_arr ,
12391261 & ctx. res_to_idx ,
1240- ctx. waterwell_idx ,
1262+ & ctx. waterwell_nodes ,
12411263 & ctx. land_mask ,
12421264 ) ;
12431265 let ignored_score = calculate_utility (
@@ -1250,7 +1272,7 @@ mod tests {
12501272 & ctx. weights_arr ,
12511273 & ctx. epsilons_arr ,
12521274 & ctx. res_to_idx ,
1253- ctx. waterwell_idx ,
1275+ & ctx. waterwell_nodes ,
12541276 & ctx. land_mask ,
12551277 ) ;
12561278
0 commit comments