11// FICSIT Starting Position Optimizer
22// Web Dashboard Logic
33
4+ import { hasOptimizationObjective , nonZeroWeights , parseNodes , RESOURCES } from "./mapContracts.js" ;
5+
46const LAND_MASK_SECTORS = 128 ;
57const LAND_MASK_BUFFER_CM = 22000 ;
68const MAP_PIXEL_TO_CM = 1 / 0.0013653321 ;
@@ -19,41 +21,6 @@ const PRESET_SIGMAS = {
1921 collectibles : 1000 ,
2022} ;
2123
22- // Resource Registry and Stylings
23- const RESOURCES = [
24- // Core Resources
25- { id : "iron" , name : "Iron Ore" , color : "#4682B4" , category : "core" } ,
26- { id : "copper" , name : "Copper Ore" , color : "#B87333" , category : "core" } ,
27- { id : "limestone" , name : "Limestone" , color : "#E5E5C0" , category : "core" } ,
28- { id : "coal" , name : "Coal" , color : "#555555" , category : "core" } ,
29- { id : "water" , name : "Water (Static)" , color : "#00BFFF" , category : "core" } ,
30- { id : "oil" , name : "Crude Oil" , color : "#2E2E2E" , category : "core" } ,
31- { id : "sulfur" , name : "Sulfur" , color : "#DAA520" , category : "core" } ,
32- { id : "quartz" , name : "Raw Quartz" , color : "#FFD1DC" , category : "core" } ,
33- { id : "caterium" , name : "Caterium Ore" , color : "#FFD700" , category : "core" } ,
34- { id : "bauxite" , name : "Bauxite" , color : "#CD5C5C" , category : "core" } ,
35- { id : "uranium" , name : "Uranium" , color : "#7FFF00" , category : "core" } ,
36- { id : "sam" , name : "SAM Ore" , color : "#9370DB" , category : "core" } ,
37- { id : "nitrogenwell" , name : "Nitrogen Gas Well" , color : "#48D1CC" , category : "core" } ,
38- { id : "waterwell" , name : "Water Well" , color : "#1E90FF" , category : "core" } ,
39- { id : "geyser" , name : "Geyser" , color : "#A9A9A9" , category : "core" } ,
40-
41- // Collectibles / Research
42- { id : "blueslug" , name : "Blue Slug" , color : "#00FFFF" , category : "collectible" } ,
43- { id : "yellowslug" , name : "Yellow Slug" , color : "#FFFF00" , category : "collectible" } ,
44- { id : "purpleslug" , name : "Purple Slug" , color : "#FF00FF" , category : "collectible" } ,
45- { id : "mercer" , name : "Mercer Sphere" , color : "#FF7F50" , category : "collectible" } ,
46- { id : "somersloop" , name : "Somersloop" , color : "#D1C4E9" , category : "collectible" } ,
47- { id : "harddrive" , name : "Hard Drive" , color : "#CD7F32" , category : "collectible" } ,
48- { id : "paleberry" , name : "Paleberry" , color : "#DB7093" , category : "collectible" } ,
49- { id : "berylnut" , name : "Berylnut" , color : "#D2B48C" , category : "collectible" } ,
50- { id : "baconagaric" , name : "Bacon Agaric" , color : "#BC8F8F" , category : "collectible" } ,
51-
52- // Threats
53- { id : "sporeflower" , name : "Spore Flower" , color : "#8B008B" , category : "threat" } ,
54- { id : "gaspillar" , name : "Gas Pillar" , color : "#2E8B57" , category : "threat" } ,
55- ] ;
56-
5724// App State
5825const state = {
5926 rawNodes : [ ] ,
@@ -158,156 +125,6 @@ function computeBuildableLandPolygon(nodes) {
158125 } ) ;
159126}
160127
161- // Parse Complete Map Data from Raw JSON format
162- function parseNodes ( data ) {
163- const nodes = [ ] ;
164- const waterwellIndices = [ ] ;
165-
166- if ( data && data . options ) {
167- data . options . forEach ( ( category ) => {
168- if ( category . options ) {
169- category . options . forEach ( ( subcat ) => {
170- if ( subcat . options ) {
171- subcat . options . forEach ( ( item ) => {
172- let resType = "" ;
173- const id = item . layerId ;
174- if ( id . startsWith ( "limestone" ) ) resType = "limestone" ;
175- else if ( id . startsWith ( "iron" ) ) resType = "iron" ;
176- else if ( id . startsWith ( "copper" ) ) resType = "copper" ;
177- else if ( id . startsWith ( "caterium" ) ) resType = "caterium" ;
178- else if ( id . startsWith ( "coal" ) ) resType = "coal" ;
179- else if ( id . startsWith ( "oilWell" ) || id . startsWith ( "oil" ) ) resType = "oil" ;
180- else if ( id . startsWith ( "sulfur" ) ) resType = "sulfur" ;
181- else if ( id . startsWith ( "bauxite" ) ) resType = "bauxite" ;
182- else if ( id . startsWith ( "quartz" ) ) resType = "quartz" ;
183- else if ( id . startsWith ( "uranium" ) ) resType = "uranium" ;
184- else if ( id . startsWith ( "sam" ) ) resType = "sam" ;
185- else if ( id . startsWith ( "nitrogen" ) ) resType = "nitrogenwell" ;
186- else if ( id . startsWith ( "water" ) ) resType = "waterwell" ;
187- else if ( id . startsWith ( "geyser" ) ) resType = "geyser" ;
188- else if ( id === "greenSlugs" ) resType = "blueslug" ;
189- else if ( id === "yellowSlugs" ) resType = "yellowslug" ;
190- else if ( id === "purpleSlugs" ) resType = "purpleslug" ;
191- else if ( id === "mercerSpheres" ) resType = "mercer" ;
192- else if ( id === "somersloops" ) resType = "somersloop" ;
193- else if ( id === "hardDrives" ) resType = "harddrive" ;
194- else if ( id === "paleBerry" ) resType = "paleberry" ;
195- else if ( id === "berylNut" ) resType = "berylnut" ;
196- else if ( id === "baconAgaric" ) resType = "baconagaric" ;
197- else if ( id === "sporeFlowers" ) resType = "sporeflower" ;
198- else if ( id === "pillars" ) resType = "gaspillar" ;
199-
200- if ( ! resType ) return ;
201-
202- if ( item . markers ) {
203- const addMarker = ( marker ) => {
204- let rawType = marker . type || resType ;
205- let mType = resType ;
206-
207- switch ( rawType ) {
208- case "Desc_Stone_C" :
209- mType = "limestone" ;
210- break ;
211- case "Desc_OreIron_C" :
212- mType = "iron" ;
213- break ;
214- case "Desc_OreCopper_C" :
215- mType = "copper" ;
216- break ;
217- case "Desc_OreGold_C" :
218- mType = "caterium" ;
219- break ;
220- case "Desc_Coal_C" :
221- mType = "coal" ;
222- break ;
223- case "Desc_LiquidOil_C" :
224- mType = "oil" ;
225- break ;
226- case "Desc_Sulfur_C" :
227- mType = "sulfur" ;
228- break ;
229- case "Desc_OreBauxite_C" :
230- mType = "bauxite" ;
231- break ;
232- case "Desc_RawQuartz_C" :
233- mType = "quartz" ;
234- break ;
235- case "Desc_OreUranium_C" :
236- mType = "uranium" ;
237- break ;
238- case "Desc_SAM_C" :
239- mType = "sam" ;
240- break ;
241- case "Desc_NitrogenGas_C" :
242- mType = "nitrogenwell" ;
243- break ;
244- case "Desc_Water_C" :
245- mType = "waterwell" ;
246- break ;
247- default :
248- if ( marker . pathName && marker . pathName . includes ( "BP_ResourceNodeGeyser" ) ) {
249- mType = "geyser" ;
250- } else {
251- mType = resType ;
252- }
253- break ;
254- }
255-
256- if ( mType . startsWith ( "water" ) ) mType = "waterwell" ;
257- if ( mType . startsWith ( "nitrogen" ) ) mType = "nitrogenwell" ;
258-
259- let purityStr = marker . purity || item . purity || "normal" ;
260- let purityMultiplier = 1.0 ;
261- if ( purityStr . includes ( "pure" ) && ! purityStr . includes ( "impure" ) ) {
262- purityMultiplier = 2.0 ;
263- } else if ( purityStr . includes ( "impure" ) || purityStr . includes ( "inpure" ) ) {
264- purityMultiplier = 0.5 ;
265- }
266-
267- let obstructed = ! ! marker . obstructed ;
268- if ( ! obstructed && mType === "caterium" ) {
269- const isStartingCaterium =
270- Math . abs ( marker . x - - 220000.0 ) < 50000.0 &&
271- Math . abs ( marker . y - - 150000.0 ) < 50000.0 ;
272- if ( ! isStartingCaterium ) obstructed = true ;
273- }
274-
275- const node = {
276- resource_type : mType ,
277- x : parseFloat ( marker . x ) ,
278- y : parseFloat ( marker . y ) ,
279- z : parseFloat ( marker . z || 0 ) ,
280- purityMultiplier,
281- purity : purityStr ,
282- obstructed,
283- } ;
284-
285- nodes . push ( node ) ;
286-
287- if ( mType === "waterwell" ) {
288- waterwellIndices . push ( nodes . length - 1 ) ;
289- }
290- } ;
291-
292- if ( Array . isArray ( item . markers ) ) {
293- item . markers . forEach ( addMarker ) ;
294- } else if ( typeof item . markers === "object" && item . markers !== null ) {
295- Object . keys ( item . markers ) . forEach ( ( k ) => {
296- addMarker ( item . markers [ k ] ) ;
297- } ) ;
298- }
299- }
300- } ) ;
301- }
302- } ) ;
303- }
304- } ) ;
305- }
306-
307- nodes . waterwellIndices = waterwellIndices ;
308- return nodes ;
309- }
310-
311128// Distance to nearest water body helper.
312129// NOTE: We only use static water body rectangles and water wells.
313130// The old "coast edge" checks (x < -250000 → dist=0) were REMOVED because the map's
@@ -326,6 +143,14 @@ async function runGlobalOptimization() {
326143 els . mapLoading . classList . add ( "active" ) ;
327144
328145 try {
146+ const weights = nonZeroWeights ( config . weights ) ;
147+ if ( ! hasOptimizationObjective ( config . weights ) ) {
148+ els . mapLoading . innerHTML = `<span style="color: #ff3333; font-weight: bold; font-size: 1.1rem; margin-bottom: 12px;">OPTIMIZATION FAILED: Select at least one weighted resource.</span>
149+ <span style="font-size: 0.8rem; color: var(--color-text-muted);">Enable a resource slider or apply a phase preset, then try again.</span>` ;
150+ els . mapLoading . classList . add ( "active" ) ;
151+ return ;
152+ }
153+
329154 // Build the request body
330155 const reqBody = {
331156 utility_func : config . utilityFunc ,
@@ -335,7 +160,7 @@ async function runGlobalOptimization() {
335160 game_phase : config . gamePhase ,
336161 sigma : config . sigma ,
337162 ignore_spawns : config . ignoreSpawns ,
338- weights : Object . fromEntries ( Object . entries ( config . weights ) . filter ( ( [ _ , v ] ) => v !== 0 ) ) ,
163+ weights,
339164 } ;
340165
341166 const apiRes = await fetch ( "/api/optimize" , {
0 commit comments