11// FICSIT Starting Position Optimizer
22// Web Dashboard Logic
33
4- import { hasOptimizationObjective , nonZeroWeights , RESOURCES } from "./mapContracts.js" ;
4+ import {
5+ countObstructedNodes ,
6+ formatDiversity ,
7+ formatRuggedness ,
8+ formatYield ,
9+ hasOptimizationObjective ,
10+ nearbyWeightedNodes ,
11+ nodeInspectionKey ,
12+ nonZeroWeights ,
13+ RESOURCES ,
14+ topResourceYields ,
15+ } from "./mapContracts.js" ;
516
617const LAND_MASK_SECTORS = 128 ;
718const LAND_MASK_BUFFER_CM = 22000 ;
@@ -61,6 +72,14 @@ function gameToPixel(gx, gy) {
6172 return { x : px , y : py } ;
6273}
6374
75+ function formatContribution ( value ) {
76+ const numericValue = Number ( value ) ;
77+ if ( ! Number . isFinite ( numericValue ) ) return "0.00" ;
78+
79+ const formatted = formatYield ( numericValue ) ;
80+ return numericValue > 0 ? `+${ formatted } ` : formatted ;
81+ }
82+
6483function computeBuildableLandPolygon ( nodes ) {
6584 if ( ! nodes . length ) return [ ] ;
6685
@@ -194,6 +213,17 @@ function renderMapOverlay() {
194213 const activeKeys = Object . keys ( state . config . weights ) . filter (
195214 ( k ) => Math . abs ( state . config . weights [ k ] ) > 0 ,
196215 ) ;
216+ const selectedResult = state . results [ state . selectedResultIdx ] ;
217+ const inspectionRows = selectedResult
218+ ? nearbyWeightedNodes (
219+ state . rawNodes ,
220+ selectedResult ,
221+ state . config . weights ,
222+ state . config . sigma ,
223+ Number . POSITIVE_INFINITY ,
224+ )
225+ : [ ] ;
226+ const inspectedNodeKeys = new Set ( inspectionRows . map ( ( row ) => row . key ) ) ;
197227
198228 // 1. Draw Resource Nodes (if layer visible)
199229 if ( state . visibleLayers . nodes && state . rawNodes . length > 0 ) {
@@ -213,7 +243,15 @@ function renderMapOverlay() {
213243 circle . setAttribute ( "fill" , res . color ) ;
214244 circle . setAttribute ( "stroke" , "#06090e" ) ;
215245 circle . setAttribute ( "stroke-width" , "0.5" ) ;
216- circle . setAttribute ( "class" , "node-marker" ) ;
246+ const classes = [ "node-marker" ] ;
247+ if ( selectedResult ) {
248+ if ( inspectedNodeKeys . has ( nodeInspectionKey ( node ) ) ) {
249+ classes . push ( "inspected-node" ) ;
250+ } else {
251+ classes . push ( "dimmed-node" ) ;
252+ }
253+ }
254+ circle . setAttribute ( "class" , classes . join ( " " ) ) ;
217255
218256 // Node description tooltip content
219257 const purityStr =
@@ -267,6 +305,17 @@ function renderMapOverlay() {
267305 svg . appendChild ( borderPoly ) ;
268306 }
269307
308+ if ( selectedResult ) {
309+ const pix = gameToPixel ( selectedResult . x , selectedResult . y ) ;
310+ const radiusCircle = document . createElementNS ( "http://www.w3.org/2000/svg" , "circle" ) ;
311+ radiusCircle . setAttribute ( "cx" , pix . x ) ;
312+ radiusCircle . setAttribute ( "cy" , pix . y ) ;
313+ radiusCircle . setAttribute ( "r" , state . config . sigma * 0.13653321 ) ;
314+ radiusCircle . setAttribute ( "class" , "inspection-radius" ) ;
315+ radiusCircle . style . pointerEvents = "none" ;
316+ svg . appendChild ( radiusCircle ) ;
317+ }
318+
270319 // 2. Draw Start Spawn Pod locations (S) and starting area circles
271320 const ignoreSpawns = state . config . ignoreSpawns ;
272321
@@ -449,6 +498,10 @@ function renderResultsPanel() {
449498 nodeItems . push ( `${ res . local_nodes [ label ] } x ${ label } ` ) ;
450499 }
451500 const nodesSummary = nodeItems . slice ( 0 , 4 ) . join ( ", " ) + ( nodeItems . length > 4 ? "..." : "" ) ;
501+ const yields = topResourceYields ( res . resource_yields || { } , 4 ) ;
502+ const ruggedness = Number . isFinite ( res . terrain_ruggedness ) ? res . terrain_ruggedness : 0 ;
503+ const diversity = Number . isFinite ( res . diversity_score ) ? res . diversity_score : 0 ;
504+ const obstructedCount = countObstructedNodes ( res . obstructed_nodes ) ;
452505
453506 card . innerHTML = `
454507 <div class="result-card-title">
@@ -461,8 +514,126 @@ function renderResultsPanel() {
461514 <div class="result-card-details">
462515 <strong>Nodes in range (${ state . config . sigma } m):</strong> ${ nodesSummary || "None" }
463516 </div>
517+ <div class="result-metrics">
518+ <div class="result-metric">
519+ <span>Terrain</span>
520+ <strong>${ formatRuggedness ( ruggedness ) } </strong>
521+ </div>
522+ <div class="result-metric">
523+ <span>Diversity</span>
524+ <strong>${ formatDiversity ( diversity ) } </strong>
525+ </div>
526+ </div>
464527 ` ;
465528
529+ const yieldStrip = document . createElement ( "div" ) ;
530+ yieldStrip . className = "yield-strip" ;
531+ const yieldLabel = document . createElement ( "span" ) ;
532+ yieldLabel . className = "yield-label" ;
533+ yieldLabel . textContent = "Top yields" ;
534+ yieldStrip . appendChild ( yieldLabel ) ;
535+
536+ if ( yields . length > 0 ) {
537+ yields . forEach ( ( resourceYield ) => {
538+ const pill = document . createElement ( "span" ) ;
539+ pill . className = "yield-pill" ;
540+
541+ const dot = document . createElement ( "span" ) ;
542+ dot . className = "yield-dot" ;
543+ dot . style . backgroundColor = resourceYield . color ;
544+ pill . appendChild ( dot ) ;
545+
546+ const name = document . createElement ( "span" ) ;
547+ name . textContent = resourceYield . name ;
548+ pill . appendChild ( name ) ;
549+
550+ const value = document . createElement ( "strong" ) ;
551+ value . textContent = formatYield ( resourceYield . value ) ;
552+ pill . appendChild ( value ) ;
553+
554+ yieldStrip . appendChild ( pill ) ;
555+ } ) ;
556+ } else {
557+ const empty = document . createElement ( "span" ) ;
558+ empty . className = "yield-empty" ;
559+ empty . textContent = "None" ;
560+ yieldStrip . appendChild ( empty ) ;
561+ }
562+
563+ card . appendChild ( yieldStrip ) ;
564+
565+ if ( isSelected ) {
566+ const inspectionRows = nearbyWeightedNodes (
567+ state . rawNodes ,
568+ res ,
569+ state . config . weights ,
570+ state . config . sigma ,
571+ 8 ,
572+ ) ;
573+ const inspectionList = document . createElement ( "div" ) ;
574+ inspectionList . className = "inspection-list" ;
575+
576+ const inspectionLabel = document . createElement ( "span" ) ;
577+ inspectionLabel . className = "yield-label" ;
578+ inspectionLabel . textContent = "Nearby contributors" ;
579+ inspectionList . appendChild ( inspectionLabel ) ;
580+
581+ if ( inspectionRows . length > 0 ) {
582+ inspectionRows . forEach ( ( row ) => {
583+ const item = document . createElement ( "div" ) ;
584+ item . className = "inspection-row" ;
585+
586+ const dot = document . createElement ( "span" ) ;
587+ dot . className = "yield-dot" ;
588+ dot . style . backgroundColor = row . color ;
589+ item . appendChild ( dot ) ;
590+
591+ const body = document . createElement ( "div" ) ;
592+ body . className = "inspection-row-body" ;
593+
594+ const title = document . createElement ( "span" ) ;
595+ title . className = "inspection-row-title" ;
596+ title . textContent = row . name ;
597+ body . appendChild ( title ) ;
598+
599+ const meta = document . createElement ( "span" ) ;
600+ meta . className = "inspection-row-meta" ;
601+ meta . textContent = `${ row . purity } - ${ Math . round ( row . distance ) } m` ;
602+ body . appendChild ( meta ) ;
603+
604+ item . appendChild ( body ) ;
605+
606+ const contribution = document . createElement ( "strong" ) ;
607+ contribution . className = "inspection-contribution" ;
608+ contribution . textContent = formatContribution ( row . contribution ) ;
609+ item . appendChild ( contribution ) ;
610+
611+ if ( row . obstructed ) {
612+ const badge = document . createElement ( "span" ) ;
613+ badge . className = "inspection-badge" ;
614+ badge . textContent = "Obstructed" ;
615+ item . appendChild ( badge ) ;
616+ }
617+
618+ inspectionList . appendChild ( item ) ;
619+ } ) ;
620+ } else {
621+ const empty = document . createElement ( "span" ) ;
622+ empty . className = "yield-empty" ;
623+ empty . textContent = "None" ;
624+ inspectionList . appendChild ( empty ) ;
625+ }
626+
627+ card . appendChild ( inspectionList ) ;
628+ }
629+
630+ if ( obstructedCount > 0 ) {
631+ const obstructedNote = document . createElement ( "div" ) ;
632+ obstructedNote . className = "obstructed-note" ;
633+ obstructedNote . textContent = `${ obstructedCount } obstructed node${ obstructedCount === 1 ? "" : "s" } excluded` ;
634+ card . appendChild ( obstructedNote ) ;
635+ }
636+
466637 card . addEventListener ( "click" , ( ) => {
467638 selectResult ( idx ) ;
468639 } ) ;
0 commit comments