Skip to content

States Module - Diplomacy Missing Statistics (NaN Cascade) #1449

Description

@MrHaribo

States Module - Diplomacy Missing Statistics (NaN Cascade)

Summary

In the states module, the generateDiplomacy() function attempts to access the state area metric. However, area is only calculated later in the pipeline via the collectStatistics() method. This results in area being undefined during diplomacy generation, causing all area-based logic to silently fail with NaN comparisons.

Problem

In JavaScript, trying to perform math on undefined variables results in NaN (Not a Number). Any logical comparison against NaN (e.g., NaN > 5, NaN < NaN, NaN == NaN) strictly evaluates to false.

The state generation pipeline executes in this order:

// Inside States.generate()
pack.states = this.createStates(); // State objects are created, but 'area' is not initialized
this.expandStates();
this.normalize();
this.getPoles();
this.findNeighbors();
this.assignColors();
this.generateCampaigns();
this.generateDiplomacy();          // Math is performed using state.area here!

However, collectStatistics(), which actually calculates and assigns the area value, is not called until much later in the global generation sequence:

// Global map generation pipeline
Burgs.generate();
States.generate();
Routes.generate();
Religions.generate();

Burgs.specify();
States.collectStatistics(); // <--- 'area' is finally assigned here
States.defineStateForms();

Because collectStatistics() runs after generateDiplomacy(), state.area is undefined. This triggers a catastrophic cascade of NaN values:

d3.mean(valid.map(s => s.area!)) // evaluates to NaN
ap = states[attacker].area! * states[attacker].expansionism // evaluates to NaN
dp += states[d].area! * states[d].expansionism // evaluates to NaN

The Consequence: Massive chunks of the generateDiplomacy algorithm are essentially dead code.

  • Defenders will never break defense pacts out of fear (ap / dp > 2 is always false).

  • Attackers will never abort an attack because the defender is too strong (ap < dp * gauss(...) is always false).

  • Vassalage assignments based on size disparity (area / area > 2) will never trigger.

Code

modules\states-generator.ts - line 364

const areaMean: number = mean(valid.map(s => s.area!)) as number; // areaMean becomes NaN
    
    // ... later in the function ...
    
    if (
      neib &&
      P(0.8) &&
      states[f].area! > areaMean &&            // NaN > NaN is false
      states[t].area! < areaMean &&            // NaN < NaN is false
      states[f].area! / states[t].area! > 2    // NaN > 2 is false
    )
      status = "Vassal"; // Vassalage is never assigned based on area

Suggestion

Ensure that state.area is calculated prior to running diplomacy resolution.

The easiest fix is to move this.collectStatistics() inside the States.generate() method, placing it immediately before this.generateDiplomacy().

generate() {
    TIME && console.time("generateStates");
    pack.states = this.createStates();
    this.expandStates();
    this.normalize();
    this.getPoles();
    this.findNeighbors();
    this.assignColors();
    this.generateCampaigns();
    this.collectStatistics();  // <--- Move here
    this.generateDiplomacy();

    TIME && console.timeEnd("generateStates");
  }

However, if moving collectStatistics inside generateStates causes downstream side effects in the global pipeline, or if it needs data that is not yet calculated, a lightweight area-calculation loop should be added to the top of generateDiplomacy() instead

Metadata

Metadata

Assignees

Labels

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions