Enemy AI Enhancement


Smarter biter AI with adaptive resistance, breach reinforcement, scheduled raids, wall regen, and overhaul-mod compatibility.

Overhaul
11 hours ago
2.0
929
Combat

Changelog

Version: 10.5.7
Date: 2026-06-04
  Bugfixes:
    - Fixed a load crash ("Reached id limit for entity (max 65535)") on heavily-
      modded enemy stacks. Adaptive Resistance generates a hidden variant
      prototype for every enemy unit/turret x damage type x resistance level;
      with many enemy mods installed (Rampant, bobenemies, ...) the level
      dimension multiplied the total past Factorio's hard 65535 entity-prototype
      ceiling, crashing the game on load. The generated set is now the sparse,
      cap-preserving {1,3,5,7,10} (10/30/50/70/100%) instead of all ten levels,
      roughly halving the variant-prototype count.
  Balancing:
    - Each difficulty preset's resistance cap is unchanged for neutral enemies
      (Easy 30% / Normal 50% / Hard 70% / Custom 100%): those cap levels are
      still generated. Intermediate adaptation levels step down to the nearest
      generated level at spawn, so resistance is slightly coarser between caps
      but still reaches the same ceilings.
  Notes:
    - No save schema change. In existing saves, any variant biters sitting at a
      now-ungenerated level are dropped by the engine on load (cosmetic); the
      per-base adaptive-damage ledger is unaffected, so adaptation continues and
      the next spawns use the new set.
  Developer / Codebase:
    - Extracted the variant level math into data/resist-levels.lua (pure,
      unit-tested in tests/test_resist_levels.lua); data/resistance-variants.lua
      now only does the data.raw iteration and the deepcopy-clone.
Version: 10.5.6
Date: 2026-06-03
  Features:
    - Added "Biter Expansion: Never Easier Than Map Settings" (Map setting, default on).
      Dynamic Biter Expansion may now scale HARDER than your map's configured expansion
      settings but never EASIER. Previously, at low evolution the intensity presets
      lengthened expansion cooldowns and shrank settler group sizes below the values you
      configured at map generation, softening early-game expansion. The floor caps
      cooldowns at your map settings and floors group sizes and expansion distance at
      them, so scaling can still ramp up over time but never drop below your baseline.
  Notes:
    - Default on: existing saves at low evolution will see expansion cooldowns and group
      sizes return to at least their map-configured values. Disable the new setting to keep
      the previous softer-at-low-evolution behavior. No save schema change.
Version: 10.5.5
Date: 2026-06-03
  Bugfixes:
    - Dynamic Biter Expansion now scales to the highest evolution factor across
      every enemy force present on each surface, matching how Research Pressure
      already reads evolution. With overhaul mods that register their own enemy
      forces -- whose evolution can exceed the vanilla enemy force -- expansion
      cooldown, distance, and group size now respond to the strongest force
      instead of only the vanilla one. Vanilla games are unaffected.
    - The get_resist_factor diagnostic remote now clamps to the active
      difficulty's resistance cap, so it reports the resistance level variant
      units are actually given rather than a deeper level than any spawn
      produces. Gameplay is unchanged; only the diagnostic value is corrected.
  Developer / Codebase:
    - Removed dead code: the deprecated Research Pressure on_path_result no-op,
      an unused enemy-forces alias, a no-op target-registry field assignment,
      and the never-written numeric-TTL branch of the raid unreachable-target
      mark (the permanent form is the only one written; legacy saves still read
      the old numeric form).
    - Aligned the duplicated squad-surface-index helper in research_pressure
      with the canonical lifecycle copy, dropping an unreachable branch.
  Notes:
    - No save schema change; existing saves load and behave identically. Adaptive
      Resistance, raid pacing, target selection, and pathing are unchanged.
Version: 10.5.4
Date: 2026-06-02
  Bugfixes:
    - Status panel positions near the top-left edge are now clamped on-screen instead of being
      discarded and re-centered.
  Changes:
    - Enabled and disabled status rows now use green and red value text for easier scanning.
  Info:
    - Removed temporary status-panel diagnostic chat output.
    - No new migration; existing saves load and behave identically.
Version: 10.5.3
Date: 2026-06-02
  Bugfixes:
    - Status panel now reliably reopens at the position you left it. Drag it by the title bar to
      reposition.
  Changes:
    - Status panel now also shows Wall Regeneration, Wall Block Projectile, and Biter Expansion
      Intensity.
  Info:
    - No new migration; existing saves load and behave identically.
Version: 10.5.2
Date: 2026-06-02
  Changes:
    - The Smart Enemies status panel now remembers its on-screen position per player and reopens
      where you left it instead of re-centering each time. Drag it by the title bar to reposition.
  Info:
    - No new migration; existing saves load and behave identically.
Version: 10.5.1
Date: 2026-06-02
  Bugfixes:
    - Status GUI now rebuilds correctly after mod configuration or settings changes, preventing
      stale or unresponsive panels after updating the mod.
    - Clearing a surface (via scenarios or other mods) now cleans up Smart Enemy AI state for that
      surface instead of leaving stale spawner, squad, and raid data behind.
    - Smart Enemy AI surface logic (expansion scaling, spawner scanning, research pressure, variant
      cleanup) now skips space platform surfaces in Space Age.
  Changes:
    - Quieted verbose raid candidate-selection logging unless raid diagnostics are enabled.
  Info:
    - No save schema change; existing saves load and behave identically.
Version: 10.5.0
Date: 2026-06-01
  Features:
    - Added Dynamic Biter Expansion. Biter expansion cooldown, distance, and group size now
      scale dynamically based on evolution factor (max evolution factor across all active surfaces
      for Space Age compatibility) and chosen intensity preset (Off, Low, Normal, High, Custom).
    - Unlocked Custom expansion settings sliders: Custom: Expansion Cooldown, Custom: Expansion Group Size,
      and Custom: Expansion Distance multipliers (range 0.1x to 5.0x).
    - Added Technological Frenzy: completed research doubles the biter expansion rate for 5 minutes,
      alerting players via chat.
    - Added radar scout alerts: map tags (skull icons) and chat alerts with clickable GPS coordinates
      warn players when biter expansion groups finished gathering and head toward visible chunks.
  Developer / Codebase:
    - Implemented lib/expansion.lua, tests/test_expansion.lua, and updated settings.lua/control.lua.
Version: 10.4.0
Date: 2026-06-01
  Balancing:
    - Reworked Adaptive Resistance scaling from a linear function to a customized
      exponential curve. Resistance levels (10% to 100%) now require damage
      doubling at each step, beginning at a baseline threshold of 10,000 damage
      (Level 1) up to 5,120,000 damage (Level 10). This prevents biters from
      maxing out their resistances too quickly, while preserving the difficulty preset caps.
  Developer / Codebase:
    - Updated RESIST_NORMALIZE to 10000 to act as the base exponential threshold.
    - Updated level_for in spawn_variant.lua and resist_factor in state.lua to
      use the exponential logarithmic formula.
    - Added and aligned unit tests for specific exponential thresholds.
Version: 10.3.0
Date: 2026-05-30
  Features:
    - Adaptive Resistance now applies on every surface that has enemy
      spawners, not only Nauvis. With the Space Age expansion, Gleba
      wrigglers build resistance to the damage types they repeatedly
      take, the same way Nauvis biters do. Resistance stays damage-
      driven and fades through population turnover, and each spawner's
      owned-unit cap is respected on every surface.
  Bugfixes:
    - The squad maze-detour guard now measures the full path,
      including the first segment from the spawn point. Excessively
      winding wall routes are correctly rejected so the squad falls
      back to a direct assault instead of walking the maze.
    - The status panel now opens centered on screen.
  Optimizations:
    - On long-running saves the unreachable-target and raid
      area-history tables are now pruned instead of growing without
      bound, keeping save size and per-tick iteration stable.
  Developer / Codebase:
    - Adaptive Resistance now applies on every surface with enemy
      spawners; the disable-unwind sweep visits all surfaces so no
      variant units are orphaned off Nauvis.
    - De-duplicated the shared position-copy and squared-distance
      helpers into lib/util, plus the raid-interval formula and the
      resistance normalization constant into shared modules.
    - Removed dead code: the unused one-shot pathfinder feature, two
      deprecated Research Pressure path-callback stubs, and several
      unused exports and locals.
    - Split the large advance_spawn_job routine into focused helpers
      (preparation, holding re-stamp, spawn batch, dispatch) with no
      behavior change.
  Notes:
    - No save schema change. On Nauvis, Adaptive Resistance behaves
      exactly as before. On Gleba, only spawner-hatched wrigglers build
      resistance; strafers and stompers are unaffected. The detour-guard
      fix can make squads on very winding routes choose a direct assault
      more often.
Version: 10.2.0
Date: 2026-05-25
  Balancing:
    - Reworked Research Pressure from "wake N nest chunks" into a
      separate per-surface phase-scaled pressure budget. Tech lab-cost still influences the
      event, but the current game phase now clamps target chunks,
      units per chunk, per-surface total units, and pollution threshold. Very
      early research is capped to small, pollution-relevant attacks;
      mid-game pressure ramps gradually; late-game research can still
      mobilize large bounded waves.
    - Added a conservative modded-enemy raid size safety factor.
      Smart Enemy still uses each selected spawner's natural
      result_units composition; it does not score enemy health,
      damage, or resistances. When the selected raid composition can
      currently produce non-vanilla enemy units, scheduled raid size is
      scaled by Raid Intensity: Low = 50%, Normal = 75%, High = 100%.
      Custom intensity remains fully user-controlled and receives no
      automatic size reduction.
    - Vanilla raid balance is unchanged. Vanilla biters and spitters,
      including Smart Enemy adaptive-resistance variants whose base
      unit is vanilla, still count as vanilla for this safety factor.
  Developer / Codebase:
    - Research Pressure diagnostics and chat warnings now track
      mobilized unit counts in addition to chunk counts.
    - Raid spawn diagnostics now expose whether the active spawn job
      detected a modded enemy composition, the applied size multiplier,
      and the pre-scaling raid size.
  Notes:
    - No save schema change. Raid target selection, spawner selection,
      pathing, and Adaptive Resistance mechanics are unchanged. This
      release changes Research Pressure attack sizing and scheduled
      raid size when the selected spawner composition includes active
      non-vanilla units.
Version: 10.1.2
Date: 2026-05-24
  Bugfixes / Polish:
    - Fixed wall-regeneration destroy tracking. The
      on_object_destroyed handler now keys cleanup by the
      object-destroy registration number (the id
      register_on_object_destroyed returns and the regen
      registry stores/clears) instead of the entity useful_id,
      which for entities is the unit_number -- a different id
      space. Destroyed queued walls now clean up promptly
      instead of waiting for the periodic validity sweep, and a
      rare registration-number / unit-number id collision can no
      longer dequeue an unrelated wall (which would have
      silently stopped it healing until next damaged).
  Notes:
    - No save schema change.
Version: 10.1.1
Date: 2026-05-18
  Bugfixes / Polish:
    - Fixed a severe nest-fight UPS drop caused by Adaptive
      Resistance. When replacing a natural spawner biter with a
      resistance variant, the mod destroyed the vanilla unit and
      created the variant with create_entity -- which has no
      spawner-owner parameter, so the variant was not counted
      against the spawner's owned-unit cap
      (max_count_of_owned_units). The freed slot was refilled
      and replaced again: an overproduction loop that flooded
      biters during nest fights and dragged UPS down.
    - Adaptive Resistance still applies to natural spawner
      output, but is now cap-safe through shadow-ownership
      accounting that enforces a hard invariant: a spawner's
      owned vanilla units plus the resistance variants the mod
      created for it never exceed its owned-unit cap. The mod
      tracks its variant count per spawner; on each spawn, if
      owned units plus that shadow count would exceed the cap
      the just-spawned vanilla unit is culled (destroyed, no
      variant) so the population cannot grow past the cap.
      Otherwise the variant is swapped in, taking the slot the
      original vacated. Variant deaths decrement the shadow
      count via on_object_destroyed.
    - Raid-spawn Adaptive Resistance is unchanged: scheduled
      raids own a fixed population budget and apply variants
      directly in the raid spawn pipeline.
  Developer / Codebase:
    - Added read-only diagnostic telemetry for investigating
      nest-fight UPS dips, surfaced through the diag_dump
      remote: live pending path-request depth
      (path_pending / path_oneshot_pending), path-request
      issuance counters (path_stats), a per-broadcast breakdown
      of breach recipients (tracked squads vs loose biters), and
      shadow-ownership counters (adaptive_natural: variants
      created, over-cap spawns culled, live/peak shadow totals).
      All counters are O(1) on hot paths and never gate
      behavior.
  Notes:
    - No save schema change; existing saves load and behave
      identically (shadow-ownership storage is lazy-initialized;
      variant units from older saves are intentionally left
      untracked rather than retrofitted on a hot path). Existing
      resistance-variant units are still converted back to base
      prototypes on configuration change if Adaptive Resistance
      is disabled (unchanged unwind behavior). No changes to
      raid target order, Research Pressure, squad pathing, or
      the spawner registry beyond shadow-count cleanup hooks on
      spawner removal.
Version: 10.1.0
Date: 2026-05-18
  Developer / Codebase:
    - Raid scheduler module split. The previously single
      ~3900-line lib/raids/scheduler.lua has been broken into
      focused sub-modules so the raid pipeline is easier to
      read and modify in isolation:
        * lib/raids/target_registry.lua: persistent target
          record, dirty-chunk queue, full-surface
          compatibility scan, unreachable-target TTL.
        * lib/raids/target_selection.lua: target clustering,
          per-kind queues, deck rotation, multi-phase scan.
        * lib/raids/candidate_selection.lua: candidate
          spawner discovery, classification, candidate-build
          job, async path validation, clustered-fallback
          handling.
        * lib/raids/spawn_pipeline.lua: SELECTED to SPAWNING
          to HOLDING to DISPATCHED state machine, raid
          alerts, active-raid tracking, evolution and raid-
          size formula, unit composition.
        * lib/raids/diagnostics.lua: per-job snapshots,
          path-blocker analyser, chart-tag classifier;
          backs the six raids.diag_* remote functions.
        * lib/raids/scheduler.lua: now a thin facade owning
          per-tick orchestration and re-exporting the public
          raids.<fn> API consumed by control.lua.
      Module dependency order is one-way:
        target_registry -> target_selection ->
        candidate_selection -> spawn_pipeline ->
        diagnostics -> scheduler facade.
      Static validator extended to track the new module
      layout and to block any of the moved helpers from
      regrowing in scheduler.lua.
  Notes:
    - No gameplay tuning. No new settings. No save schema
      change; existing saves load and behave identically.
      Tick order in the raid pipeline is preserved exactly,
      and the public raids.<fn> API used by control.lua is
      unchanged.
Version: 10.0.0
Date: 2026-05-17
  Major Features:
    - Architectural rework. Major-version bump signals an
      under-the-hood overhaul rather than a feature drop. No
      gameplay changes; existing saves migrate automatically and
      should behave identically.
    - Persistent spawner registry. The full-surface unit-spawner
      scans that ran on every research event, raid target lookup,
      and squad retreat have been replaced with a chunk-indexed
      cache maintained incrementally via build / death / chunk-
      generated events. On megabases this removes the dominant
      non-amortized UPS cost of a research event; smaller bases
      see a smaller but still measurable improvement. Multi-
      surface aware.
    - Lazy upgrade-save bootstrap. Upgrade saves populate the
      registry over scheduler slices per surface rather than
      blocking on a one-shot scan. Per-surface readiness means a
      long-running bootstrap on one surface no longer forces
      consumers on already-bootstrapped surfaces onto a fallback
      path.
    - New diagnostic remote: diag_spawner_registry returns the
      registry's per-surface chunk count, total spawner count,
      and bootstrap state for troubleshooting.
  Bugfixes / Polish:
    - Adaptive Resistance: Custom: Resistance Cap now exposes the
      actual implemented range, 0.0 to 1.0. 1.0 maps to the
      current 30% prototype ceiling, 0.0 disables adaptive
      resistance entirely for Custom difficulty. The Difficulty
      Preset description for Hard now reads 30% (matching the
      real cap) instead of the earlier ~40% claim.
    - Projectile-block data-stage validation no longer errors on
      enemy packs whose units are melee-only. Packs without
      projectile or stream attacks are now treated as a valid
      no-op rather than a missing-patch failure.
    - Spawner registry random sampling uses the persisted
      deterministic mod RNG, so the same save reproduces the
      same Research Pressure samples instead of drifting with
      Factorio's shared math.random state.
    - Squad retreat: nearest-spawner lookup now uses the bounded
      spawner-radius registry query after the registry has
      bootstrapped for that surface, replacing a full chunk walk.
    - Research Pressure: a research event that lands while a
      surface's spawner registry is still bootstrapping now
      defers attacks on that surface for that one event instead
      of running a full-surface fallback scan; the next research
      event after the surface finishes bootstrapping picks up
      normally. The per-slice performance counter no longer
      reports work for deferred, peaceful, or no-eligible-chunks
      surfaces.
    - Raid Alerts setting text now matches behavior: "No alerts"
      also hides the first-raid countdown banner. (Banner gating
      itself has not changed; only the description was wrong.)
    - Status panel was simplified. The Raid status, Shed level,
      and Scheduler load rows were removed. They were internal-
      leaning diagnostics; the underlying data remains available
      to mod authors via the diag remote interface.
    - Diagnostic remote diag_tag_classified_targets no longer
      crashes after the raid-scheduler path-diagnostics refactor.
  Developer / Codebase:
    - New static gotcha validator (tools/validate-static.ps1)
      catches several classes of Factorio API landmines this mod
      has historically tripped over (functions in storage,
      unit_group field access, on_load storage mutation,
      unconditional schema bump, settings/locale typos) plus a
      no-spawner-find-bypass check that prevents new full-surface
      spawner scans from sneaking back in. Suppression
      annotations on legitimate exceptions support multi-line
      comment blocks above the offending line.
    - Storage schema bumped to v13. Migration 12 flags upgrade
      saves as needing spawner-registry bootstrap.
  Notes:
    - The planned raid-scheduler module split (~4200-line file
      broken into ~5 files) has been deferred to 10.1.0 to avoid
      shipping a pure-refactor in the same release as the
      registry change. Behavior is unchanged either way.
Version: 9.5.6
Date: 2026-05-17
  Bugfixes:
    - Scheduled raid target selection now honors the configured
      target-kind deck order before falling back to other target
      kinds. A `front` deck slot, for example, now picks a valid
      front target even if that front area is recent; it only falls
      through to `core` or `outpost` when no usable front target
      exists. This restores the intended
      core -> front -> outpost -> core -> outpost -> front rotation
      for bases with mixed core, wall, and outpost targets.
Version: 9.5.5
Date: 2026-05-17
  Bugfixes:
    - The "First Raid Incoming" countdown banner now respects the
      Show Status Panel and Raid Alerts settings. Previously the
      banner ignored both and would display whenever Raid
      Intensity was anything except Off, even on test maps where
      the player had deliberately silenced the mod's UI.
      Disabling Show Status Panel OR setting Raid Alerts to "No
      alerts" now also hides the banner; both on (the default)
      keeps the existing countdown behaviour.
Version: 9.5.4
Date: 2026-05-17
  Bugfixes:
    - Research Pressure no longer uses an async path-feasibility
      pre-check; it now mobilizes biter groups directly and lets
      the engine's pollution-attack AI handle reachability. The
      pre-check was stricter than vanilla pollution-attack
      pathing -- on water-fragmented maps with biter nests on
      separate islands from the player base, 100% of paths
      rejected as no_path even though vanilla biters from those
      same nests would happily mobilize (and then disperse
      harmlessly at the shoreline). Reachable nests now fire
      attacks normally; unreachable nests' biters form groups,
      walk a bit, and disperse the same way vanilla pollution
      attacks behave. Net effect on island-heavy bases: Research
      Pressure actually works.
    - Removed the path-rejection sub-counters (no_path,
      needs_destroy, detour) from research_pressure_stats since
      paths are no longer attempted. The base counters
      (total_chunks_attempted, total_chunks_attacks_mobilized,
      total_chunks_no_free_biters) remain.
Version: 9.5.3
Date: 2026-05-17
  Bugfixes:
    - Research Pressure now mobilizes its target chunk count even
      when some path requests get rejected. Previously the cap N
      was an "attempts" cap: if 8 of 20 paths rejected, you got
      12 attacks instead of 20. The cap N is now a "success
      target": rejected paths pull a replacement from the
      shuffled reserve pool of eligible chunks on that surface
      until N successes accrue, the reserve is exhausted, or a
      hard oversample cap of N * 3 attempts is hit. The
      total_chunks_attempted counter in diag now reflects the
      actual attempt count (including replacements) rather than
      a fixed initial sample.
Version: 9.5.2
Date: 2026-05-17
  Bugfixes:
    - Research Pressure was rejecting 100% of path requests on walled
      bases, resulting in zero biter attacks despite the chain
      otherwise working end-to-end. The pathing module's oneshot path
      filter inherited the squad-path 'needs_destroy_to_reach'
      rejection, which is correct for squad attacks (biters fall back
      to attack_area against walls) but wrong for Research Pressure
      (biters should mobilize and attack the walls -- that's the
      intended behavior). Added a skip_destroy_filter flag to
      pathing.request_oneshot_path; Research Pressure now sets it to
      true. The detour-ratio rejection still applies to keep
      genuinely unreachable nests from firing useless waves.
Version: 9.5.1
Date: 2026-05-17
  Bugfixes:
    - Research Pressure no longer stores Lua function closures in
      storage, which Factorio refuses to serialize. The pathing
      module's oneshot path API now stores only serializable
      request metadata; result dispatch goes through a module-
      level listener registered at script load (re-registered
      automatically on save/load, never serialized). Affected any
      attempt to save the game while a research-pressure path
      request was pending; symptom was a save-failure error
      referencing storage.path_q.oneshot_pending.
    - Research Pressure no longer mutates storage from on_load
      (Factorio's documented constraint). The post-load path-
      request reissue now happens on the first tick_slice after
      load via a module-local flag, mirroring the existing
      lib/scheduler.lua needs_post_load_cleanup pattern.
    - Research Pressure groups are now tracked in
      storage.research_pressure_active_groups and their members
      are excluded from future free-biter recruitment. Previously
      a second research event firing soon after the first could
      re-recruit biters still in the first event's group;
      fragmenting that group or behaving unpredictably depending
      on Factorio's add_member-to-existing-group semantics.
      Invalid group references are lazy-pruned on each
      recruitment build.
Version: 9.5.0
Date: 2026-05-17
  Features:
    - Research Pressure: a new pressure system that triggers biter
      attacks whenever you finish a research, scaled by the tech's
      lab-cost. The feature does NOT spawn new biters -- it
      mobilizes existing nest populations into autonomous attack
      groups (Scary-Nights-style), then lets the engine's pollution-
      attack AI route them. Each research event picks N nest-bearing
      chunks within pollution range of your assets and issues async
      path-feasibility checks (same detour rules as scheduled raids,
      so unreachable chunks are silently dropped). N = floor(sqrt(
      lab_seconds / DAMP)) clamped to [1, CAP], with intensity tier
      multiplying the final count. Defaults: DAMP=25, CAP=20.
    - New setting Research Pressure (Off / Low / Normal / High /
      Custom, default Normal). Custom unlocks three sliders for
      chunks magnitude, curve-damping, and endgame cap.
    - Multi-surface aware: a single research event triggers
      independently on every surface with enemy unit-spawners.
      Works on Nauvis biters, Gleba pentapods, and modded-planet
      nests (any surface with type="unit-spawner" entities). Surfaces
      without unit-spawners (Fulgora, Aquilo, space platforms) no-op
      silently. Peaceful-mode surfaces are skipped.
    - Existing saves upgrading from <9.5.0 are grandfathered: the
      feature is disabled in those saves until the player explicitly
      opts in via /smart-enemy-ai-research-pressure-opt-in or the
      status panel button. New saves get the feature active by
      default per the Normal setting. A one-time chat banner fires
      on first load of a grandfathered save explaining the option.
    - Status panel gets one new row showing the current mode and
      (if grandfathered) the opt-in button.
    - Alerts piggyback on the existing Raid Alerts setting -- no
      separate alert toggle for research pressure.
Version: 9.4.3
Date: 2026-05-16
  Bugfixes:
    - Removed stale "more raids" / "raids stay rare" / "raids
      will be frequent" claims from the difficulty-change chat
      warning (fires when you switch to Hard mid-game) and from
      the first-launch welcome messages for Easy and Hard
      difficulty. Since 9.3.0 the Difficulty Preset only
      controls biter-behaviour parameters (resistance cap,
      retreat threshold, pathfinding detour, wall regen
      cooldown, breach radius); raid frequency and size are
      owned exclusively by the separate Raid Intensity setting.
      The chat messages were carrying pre-9.3.0 wording that
      misled players into thinking changing the difficulty would
      change raid pacing. Each updated message now explicitly
      points at Raid Intensity for raid-related concerns so the
      layer separation is clear in-game, not just in the setting
      descriptions.
Version: 9.4.2
Date: 2026-05-16
  Bugfixes:
    - Fixed a hard crash on on_configuration_changed (mod update
      or first load after a save was created in an older
      version) when the Biter Build Adaptive Resistance startup
      toggle is disabled. spawn_variant.unwind_variants was
      lazily calling require("lib.state") inside the function
      body, which Factorio's runtime rejects with "Require can't
      be used outside of control.lua parsing." Regression
      introduced in 9.3.1 with the squad-sweep follow-up to the
      resistance evaluation. The require has been hoisted to
      module scope where it belongs; state.lua does not
      transitively require spawn_variant, so there was never a
      circular-dependency reason to defer the load. Players
      running with adaptive resistance enabled were not
      affected; the unwind path only fires when the toggle is
      off.
Version: 9.4.1
Date: 2026-05-16
  Changes:
    - Renamed the "Custom: Wall Regen Speed" slider to "Custom:
      Wall Regen Cooldown" (internal setting key
      smart-enemy-ai-custom-regen-speed ->
      smart-enemy-ai-custom-regen-cooldown). The math is
      unchanged; the new label matches the multiplier direction
      (cooldown=2.0 means twice as long, slower regen) and the
      naming convention established by Custom: Raid Cooldown in
      9.3.0. If you installed 9.4.0, selected Difficulty Preset
      = Custom, and had moved this slider away from 1.0, the
      stored value will not migrate to the renamed setting --
      please re-enter your preferred multiplier under Mod
      Settings -> Smart Enemies -> Custom: Wall Regen Cooldown.
      The other four Custom: difficulty sliders (Resistance Cap,
      Retreat Threshold, Pathfinding Detour, Breach Radius) keep
      their existing keys and are unaffected.
Version: 9.4.0
Date: 2026-05-16
  Features:
    - Difficulty Preset now has a Custom value that mirrors the
      Custom Raid Intensity flow added in 9.3.0. Selecting
      "Custom" unlocks five new runtime-global multipliers, each
      a fully manual override on a single biter-behaviour
      parameter: Custom: Resistance Cap (adaptive resistance
      ceiling), Custom: Retreat Threshold (squad-retreat health
      fraction), Custom: Pathfinding Detour (how far biters
      detour around walls), Custom: Wall Regen Speed (drives
      both the damage-free window and the time to fully recover
      because the built-in presets always scale them together --
      one knob, one mental concept), and Custom: Breach Radius
      (breach reinforcement ping radius). Each multiplier
      defaults to 1.0 (Normal-equivalent baseline) and clamps to
      [0.1, 5.0]. Raid pacing remains owned by Raid Intensity --
      the Custom difficulty branch deliberately does not touch
      RAID_FIRST_INTERVAL, RAID_INTERVAL_BASE, or
      RAID_SIZE_EVO_FACTOR.
Version: 9.3.3
Date: 2026-05-15
  Bugfixes:
    - Raid biters can no longer spawn on the wrong side of a river
      or lake when the chosen spawner sits near water. The raid
      pipeline previously relied on find_non_colliding_position
      alone to pick scatter positions, which only checks tile
      placeability -- it has no notion of connectivity, so when
      the scatter radius reached across water FNC would happily
      return a perfectly placeable grass tile on the far bank.
      The resulting biters formed an orphan group that could
      never reach the player base. The spawn loop now walks a
      straight line from the spawner anchor to each candidate
      position and rejects samples that cross a water tile,
      letting the existing spawn_attempts retry machinery pick a
      fresh jitter on the correct side. Diagnostic counter
      water_rejections is logged on the HOLDING transition for
      triage of water-bound spawners.
Version: 9.3.2
Date: 2026-05-15
  Optimizations:
    - Adaptive resistance damage accumulator (state.resist_record)
      now takes a same-tick fast path when multiple damage events
      against the same base prototype + damage type resolve on the
      same tick. The decay math was already a no-op for same-tick
      events (decay_value's dt <= 0 early return returned rec.value
      unchanged), so the visible math is identical -- this just
      skips the decay_value function call plus the redundant
      rec.last_tick write. Small per-event win that compounds in
      turret-cluster combat where dozens of damage events land on
      the same tick.
Version: 9.3.1
Date: 2026-05-15
  Bugfixes:
    - The adaptive-resistance decay slice now writes the
      decayed value back to storage for entries that survive
      pruning, instead of leaving the pre-decay accumulation
      sitting in storage.resist forever between damage events.
      All consumers (variant picker, resist_factor accessor,
      diag_dump) already recomputed decay on-read, so the
      effective value was always correct; storage just no longer
      lies under direct inspection.
    - When adaptive resistance is toggled off mid-game,
      unwind_variants now drops any tracked squad whose members
      included a converted variant biter. Previously the squad
      kept a stale _baseline count relative to the post-unwind
      group, which could trigger a spurious retreat command for
      one retreat-window. Survivors of dropped squads become
      freelance biters under the engine's pollution-attack AI.
Version: 9.3.0
Date: 2026-05-15
  Changes:
    - Raid Intensity gains a fifth value, "Custom". Picking
      Custom unlocks two dedicated Map settings: Custom: Raid
      Cooldown (multiplier on the inter-raid wait time and the
      first-raid grace, range 0.1x to 5.0x; lower = faster
      raids, higher = slower) and Custom: Raid Size (multiplier
      on max biters per raid at evolution 1.0, range 0.1x to
      5.0x). 1.0 = current Normal-intensity values.
    - Status panel now renders the Difficulty Preset and Raid
      Intensity names via the existing locale labels (so
      "Custom" instead of "custom", "Hard" instead of "hard",
      etc.).
    - Difficulty Preset (Easy / Normal / Hard) no longer affects
      raid frequency, first-raid grace, or raid size. All raid
      pacing is now controlled solely by Raid Intensity.
      Difficulty Preset still scales resistance cap, retreat
      threshold, regen window/full ticks, pathing detour, and
      breach radius.
    - New "Raid Alerts" Map setting controls the per-raid
      heads-up. "All alerts" (default) is the current behaviour:
      a red skull pinned to the map at the target chunk plus a
      "Raid incoming!" chat warning. "Map icon only" keeps the
      skull but suppresses the chat warning. "No alerts"
      silences both; raids still fire, you just won't be told.
      The first-raid countdown banner is independent and not
      gated by this setting.
  Breaking change:
    - Easy players coming from 9.2.0 or earlier: raids used to
      fire ~2x slower and 0.7x smaller because Easy difficulty
      multiplied them down. With 9.3.0 those multipliers are
      gone -- raids now follow Raid Intensity (default Normal).
      To restore the previous Easy pacing, set Raid Intensity to
      Low (or to Custom with cooldown=2.0, size=0.7).
    - Hard players: symmetric. Raids used to fire ~2x faster and
      1.3x larger on Hard. With 9.3.0 they follow Raid Intensity.
      To restore the previous Hard pacing, set Raid Intensity to
      High (or to Custom with cooldown=0.5, size=1.3).
Version: 9.2.0
Date: 2026-05-15
  Changes:
    - New in-game status panel summarises Advanced AI state,
      current Raid Intensity, difficulty preset, time until the
      next scheduled raid, active raid count, scheduler shed
      level, and scheduler load. Toggle it with the Smart Enemies
      Status shortcut, a keybinding under Controls -> Mods, or
      the /smart-enemy-ai-status command.
    - New runtime setting "Raid Intensity" with Off / Low /
      Normal / High. Scales scheduled mega-raid frequency and
      size independently of the Difficulty Preset. Off stops new
      scheduled raids without affecting defensive features or
      other smart enemy behaviour.
    - First-raid heads-up: a bold red "First Raid Incoming"
      banner with a live MM:SS countdown pins to the top of
      every player's screen for the entire first-raid countdown
      (Easy 90 min, Normal 60 min, Hard 30 min) and disappears
      the moment that raid fires. Replaces the previous
      one-shot chat message and is independent of the Show
      Status Panel setting.
    - New runtime setting "Show Status Panel" (default on) lets
      the host silence the status GUI from Map settings. When
      off, the shortcut, keybind, command, and any open panel
      are inert. The shortcut bar entry stays visible because
      Factorio cannot hide shortcut prototypes at runtime.
    - Status panel now refreshes once per second while open
      (cost is one player walk + GUI lookup when no panel is
      visible, then a full context rebuild when at least one
      panel is open). Scheduler load %, next-raid countdown,
      and raid status update live without closing and reopening.
    - Status panel "Active raids" row is replaced with a
      player-readable "Raid status" line: Calm, Building up,
      Attacking, or Off. Only the spawn job counts as
      Building up (biters actually appearing at spawners);
      the scheduler's target selection runs during the
      inter-raid cooldown and stays on the Calm side, so the
      panel no longer flips to Building up the instant a wave
      ends.
Version: 9.1.3
Date: 2026-05-14
  Changes:
    - The retreat-hysteresis lock (squad._fleeing_until_tick) has been
      removed. Retreat was already a one-shot squad dissolution in
      practice; the lock field was checked but never written. Code and
      comments now match the actual behavior. The RETREAT_HYSTERESIS_TICKS
      constant is gone; the diag_squad_state / diag_nearby_squads remotes
      no longer expose a fleeing_until field.
    - Shared data-stage enemy-prototype heuristic at
      data/enemy-heuristic.lua, used by prototype-scan, resistance-variants,
      and validate. Previously each file carried its own copy; the
      validator's copy was strictly weaker than the scanner's, which let
      the "zero patches but enemies exist" guard pass silently on modded
      enemy packs that used non-vanilla subgroup names like "enemy-units".
  Bugfixes:
    - The data-stage validator now scans both data.raw.unit and
      data.raw.turret when checking that "zero patches means zero
      enemies." A turret-only or worm-only enemy pack would otherwise
      slip past the guard if no scan patches were produced.
    - on_configuration_changed cleanup of stale regen entries now
      routes through state.regen_dequeue so the matching destroy-map
      entry is cleared too (previously the destroy-map entry could
      orphan until the wall was eventually destroyed).
    - diag_dump now reports the actual mod version from
      script.active_mods instead of a hardcoded string that lagged behind
      info.json across releases.
    - storage._next_squad_id is now explicitly initialized in on_init,
      preventing a theoretical squad-id collision on upgrades where the
      field happened to be missing while squads were live.
    - on_configuration_changed now sweeps invalid entries out of
      storage.ignored_groups so scenarios that call the ignore_group
      remote while offensive AI is disabled don't accumulate destroyed-
      group references.
    - lib/chunks.lua no longer carries a partial duplicate of the raid
      target_registry initializer, which had diverged from the canonical
      shape in lib/state.lua over the last two schema bumps.
    - Removed dead compat.is_player_defense (a wall/gate-only stub
      shadowed by the wall/gate/turret-aware copy in lib/ai/squads.lua).
    - ARCHITECTURE.md now correctly reports the current storage schema
      version as 11 (previously 10, stale since 9.1.2).
    - Comments around RESIST_DECAY_TAU now call tau the exponential decay
      time constant instead of the "half-life" (half-life is tau * ln 2,
      ~20.8 minutes for the shipped 30-minute tau).
Version: 9.1.2
Date: 2026-05-14
  Changes:
    - Raid target selection now uses deterministic clustered queues by
      target kind instead of score-based picking. Raids rotate through
      core, front, and outpost target queues more predictably and store
      the selected target entity position instead of falling back to
      chunk centers.
    - Existing saves now backfill raid target chunks through the scheduler
      in small slices instead of doing a startup/configuration-change
      player-asset scan.
    - Added remote diagnostics `diag_tag_classified_targets()` and
      `diag_clear_classification_tags()` for visual raid target
      classification review.
  Bugfixes:
    - Unreachable raid targets are now marked until the target chunk is
      dirtied and reclassified, preventing repeated path-selection thrash
      on the same blocked target.
    - Raid target classification no longer treats power-only chunks or
      radar-only core chunks as raid-worthy targets, and outposts near
      core assets are classified against the nearby core target.
    - Raid candidate draining now uses cursor indexes and a bounded drain
      slice instead of shifting candidate arrays.
    - Damaged walls and gates recreated by warp/build/revive/clone flows
      now enter wall regeneration even when no fresh damage event fires.
Version: 9.1.1
Date: 2026-05-13
  Changes:
    - Added remote diagnostic `diag_raid_path_blockers()` for raid
      path `no_path` triage. It records the last failed candidate's
      actual path start and reports bounded heuristic blockers without
      changing raid selection behavior.
    - Removed the deprecated raid breach-pressure subsystem. Final
      dispatched raid commands now use `attack_area` with
      `distraction=by_damage` so groups rely on Factorio's normal
      aggressive hit response.
  Bugfixes:
    - Mixed raids now use every nearby same-force unit-spawner with a
      unique prototype name as a composition source, round-robin units
      across live source curves, and still spawn every group at the
      validated primary origin without increasing total raid size.
    - Broadened modded enemy subgroup detection for adaptive resistance
      variants and projectile blocking. Custom enemy stacks whose subgroup
      names contain enemy, biter, or spitter now participate.
    - Raid fallback spawning now skips safely when vanilla biter prototypes
      are missing instead of trying to spawn a nil or absent unit prototype.
    - Adaptive-resistance variant unwind now scans Nauvis only instead of
      walking every surface.
    - Raid path validation now guards missing stone-wall and small-biter
      helper prototypes before using them for fallback start-position probes.
    - Raid spawning now rotates planned groups on failed placement attempts
      instead of retrying one blocked slot until the attempt cap.
Version: 9.1.0
Date: 2026-05-13
  Bugfixes:
    - Spitter-spawner groups in multi-spawner raids now actually
      reach the target wall. 9.0.0's "assault lanes" feature
      assigned each raid group a different per-group lane_destination
      (target_position plus a perpendicular offset up to plus/minus 72
      tiles for 7 groups). For multi-spawner raids the spitter groups
      (lanes 5-7) got lane destinations perpendicular to the spawner
      ->target axis that often pointed off the wall or into killbox
      territory; fragile spitter units died before reaching their
      assigned lane, producing the visible "biters at the wall but no
      spitters" symptom. Restored the 8.5.x dispatch behavior:
      every live group walks the same cached_path (forward-pruned
      per group) and attacks the same shared target_position, no
      lane offset. Biters and spitters intermingle and converge as
      one wave. Breach assault-control still spreads groups around
      the breach position when a wall falls (WP5 behavior preserved
      there); only the initial dispatch is back to 8.5.x semantics.
  Bugfixes:
    - find_active_raid_group_for_unit no longer scans every member of
      every group of every active raid on every on_entity_died for a
      player structure during a raid. It now does an O(1) lookup via
      the storage.squad_members reverse index added in 9.0.0 (about
      1500 iterations worst-case became about 21).
    - find_active_raid_near_position uses Factorio's spatial index
      (find_entities_filtered with position+radius) for the bounded
      candidate scan instead of iterating every raid x group x member
      with a per-member distance check.
  Changes:
    - Continuous assault pressure during breach-pressure mode. While a
      raid is assaulting, each live group's attack_area is re-stamped
      every phase-2 tick toward the nearest player-side structure
      within ASSAULT_PRESSURE_REPEAT_RADIUS (96 tiles). Previously the
      engine would let the previous attack_area complete (target
      destroyed -> no eligible target in radius), idle the group
      briefly, and rely on vanilla biter AI to find the next target
      -- that idle gap is the visible 0.1 s pause-then-retarget cycle
      players observed after every wall/turret kill. The re-stamp is
      gated on the breach shed level so behavior degrades cleanly
      under UPS pressure. Vanilla biter and pollution-attack squads
      (not in active_raids) are unaffected.
Version: 9.0.0
Date: 2026-05-13
  Features:
    - Added WP6 raid target diversity. Raids now maintain a
      low-budget target registry that classifies tracked player chunks
      as core, front, or outpost targets, rotates through those kinds,
      and avoids recently targeted 256-tile areas when alternatives
      exist.
    - Added `diag_raid_context()` for diagnostic-only WP4 raid
      triage. It reports current/effective raid timing and size,
      key raid constants, target/candidate/spawn job summaries,
      tracked chunk counts, unreachable-target cache counts, and
      active raid group/member counts without changing raid behavior.
  Bugfixes:
    - Third-party invasion/event mods that spawn enemy-force unit
      groups with scripted commands (Big Monsters Mod's humies and
      zillas, RPG-style raid mods) are no longer hijacked by Smart
      Enemy's on_unit_group_finished_gathering handler. The handler
      previously claimed any group whose force was in enemy_forces,
      wrapped it as a Squad, and overwrote its compound command with
      attack_area via the path callback — leaving units idle if no
      hostiles were within 16 tiles. Two cooperation paths: explicit
      opt-out via `remote.call("smart-enemy-ai", "ignore_group", group)`,
      and auto-detection of
      `ai_settings.allow_destroy_when_commands_fail = false` on any
      group member (vanilla biters leave this at the engine default).
      Breach-ping redirects respect the same auto-detect signal so a
      turret death inside the breach radius can't yank externally
      managed loose units out of their scripted commands.
    - Mega-raids now scan registered custom enemy forces as raid
      sources instead of only `game.forces["enemy"]`. Candidate
      selection, path validation, spawning, and raid diagnostics
      preserve the selected spawner force.
    - Raid unit sampling now accepts Factorio's array-shaped
      result_units entries, so spitter-spawner raid groups no longer
      fall back to vanilla biter tiers.
    - Two-spawner raids now create each planned raid group lazily
      when its first member is ready. Empty scripted groups could
      dissolve before later spawn batches reached them, collapsing
      secondary spawner slots back onto the primary group and producing
      biter-only waves from biter+spitter raids.
    - Defense-destruction retreat suppression now uses a persistent
      squad member index to find the squad that destroyed a wall, gate,
      or turret without scanning every tracked squad.
    - Configuration changes no longer seed raid chunk tracking by
      iterating every generated Nauvis chunk. Existing saves with no
      tracked raid chunks now get a bounded bootstrap from player base
      assets, and saves that already have tracked chunks skip the work.
      Raid candidate classification also stays on the sliced
      candidate_build_job path when a raid is due, so late preparation
      delays the raid instead of doing one one-shot classifier pass.
      Raid target registry maintenance and selection are sliced too,
      so large saves no longer classify or select every tracked base
      chunk in one phase-2 tick.
    - Raid target area cooldown now treats a previously selected 8x8
      chunk area as recent until both the 30-minute tick cooldown and
      one full target-kind deck cycle have elapsed. If every otherwise
      valid target is still recent, the fallback still allows a raid.
    - UPS stutter spikes between raids on large biter maps are
      eliminated. The v8.4.0 raid pipeline replaced the v8.3-era
      bounded spawner scan (find_entities_filtered with
      position+radius+limit, gated to fire once per raid_interval)
      with an unbounded surface-wide find_entities_filtered that
      returned every enemy spawner on Nauvis. The v8.5.2 isolation
      classifier added a per-spawner count_entities_filtered on top
      of that. Together those run every phase-2 tick (~twice per
      second) for the duration of the multi-minute cooldown after
      each raid, because the cooldown gate moved from the top of
      tick_slice down into try_promote_to_spawning. On dense biter
      networks with thousands of generated chunks the per-tick cost
      blew the scheduler's 2 ms budget for a single tick at a time,
      producing visible stutter (the rolling 60-tick shed-window
      average never tripped because each spike was isolated). The
      surface-wide scan is now bounded with position+radius — the
      engine spatial index does the prefilter in C instead of
      handing the full list to Lua — and the resulting candidate
      list is cached per target chunk with a 10-second TTL so it
      isn't rebuilt twice per second across the cooldown. Same
      candidates are still found and evaluated; the spawner-valid
      gate in try_promote_to_spawning still re-checks each
      selected spawner before SPAWNING begins.
    - Retreat now fires correctly on large maps where the biter
      nest is far from the player base. RETREAT_WINDOW_TICKS was
      30 seconds (1800 ticks) measured from when the group
      finished gathering — shorter than a typical nest-to-base
      march on a large map, so the window expired during the
      march and retreat was never assessed even at 50%+ losses.
      Window raised to 5 minutes (18000 ticks).
    - Normal squads now retreat to a real destination on large maps
      even when combat happens more than 256 tiles from the nest. The
      spawner search uses a 1024-tile radius, picks the nearest
      same-force spawner explicitly, and falls back to the squad's
      stored gather/home position instead of its current combat
      position.
    - Retreat now calls `start_moving()` immediately after assigning the
      flee command. Logs showed valid retreat destinations but no visible
      movement; the command was being set without explicitly waking the
      already-engaged group.
  Changes:
    - Breach-ping redirects now use hit-response-style focus
      (`distraction=by_damage`) and explicitly wake tracked groups after
      assigning the breach command. This does not change unit movement
      speed.
    - Raid alerts now schedule at SPAWNING start under a raid-scoped
      alert key instead of waiting for HOLDING and keying off the first
      live squad. `diag_spawn_job()` reports `alert_tick` and
      `dispatch_not_before_tick`; HOLDING sets `dispatch_tick` to the
      later of current tick and that warning floor. Small raids still
      provide the warning window, while raids whose spawn phase already
      consumed the floor can dispatch immediately after assembly.
    - Raid dispatch now assigns each live raid group a deterministic
      assault lane and broad `attack_area` near the selected target,
      reducing same-tile crowding and turret-by-turret command jitter.
      On first raid-caused player/non-enemy structure kill, those same
      lane offsets are applied to the breach position and groups stay
      under scripted assault control for a bounded 30-second window.
    - Raid skull chart tags now stay on the map for 150 seconds after
      the alert fires, up from 60 seconds.
    - Breach ping radius increased again (Normal 192 tiles, Easy
      144, Hard 288). The earlier 128-tile Normal radius covered small
      bases; on large wall networks the redirected biters were
      often not in range of the breach they should reinforce.
    - Raid waves now use persistent breach assault control. The first
      raid-caused player/non-enemy structure kill orders all live raid
      groups into local, lane-offset breach pressure. Later structure
      kills leave group ownership intact and stamp contact diagnostics;
      the assault-control timeout cleans up leftovers. The
      spawn/hold/dispatch pipeline still delivers the large wave, while
      post-breach control stays bounded to group-scale work and does not
      issue hundreds of per-unit commands in one tick.
      If projectile attribution hides the biter as `event.cause`, the
      attribution falls back to a nearby active raid unit check around the
      destroyed entity.
    - `diag_squad_state` now reports retreat baseline and current
      health ratio, and natural retreat triggers write one structured
      log line for verification.
    - `diag_squad_state` also reports squad home position plus the last
      retreat source and destination positions.
    - Flanking has been removed. Scripted split groups conflicted with
      Factorio group AI and obscured retreat behavior, so attack groups
      now stay as one squad.
Version: 8.5.5
Date: 2026-05-11
  Bugfixes:
    - Mod no longer fails to load alongside biter/enemy mods
      whose attack chain references a projectile prototype that
      is also fired by a player ammo (e.g. uranium-shotgun-pellet
      from Combat Tweaks and Additions, when an enemy mod also
      uses that projectile name for a biter attack). The data-
      stage projectile-block patcher used to mutate the
      projectile prototype in place when its substring-based
      heuristic failed to recognize it as a player projectile,
      and a hard validate.lua check refused to load the mod
      stack at all. The patcher now always clones the
      projectile into <original>-smart-enemy-ai-blockable and
      rewires only the enemy's attack delivery to the clone;
      originals are never touched. The validate check is
      removed (unreachable under the new rule). Player turret
      firing the original projectile is unaffected.
  Minor Features:
    - Breach pings (the redirect biters get when a player
      turret dies inside an active attack) are now rate-
      limited to one broadcast per 60 seconds globally.
      Heavy raids kill many turrets in succession; without
      the cap, every death rebroadcast a redirect and
      nearby biters snap-converged on each successive
      turret, looking like permanent retargeting jitter.
      One ping per minute communicates "the perimeter is
      under sustained pressure" without the constant
      churn. Per-squad cooldown (30 s) is unchanged. The
      force_breach_ping debug remote bypasses the gate
      for instant manual testing but still stamps the
      cooldown clock.
  Internal:
    - Removed the maintainer-only "Debug: Command Trace"
      runtime setting, the debug_trace_squad remote, the
      lib/debug/trace.lua module, and every trace call
      site across the squad/raid/breach/path subsystems.
      The trace channel served its purpose during the
      jitter investigation that produced the holding-
      restamp and stale-waypoint-prune fixes; it has no
      runtime use for players. set_command call paths are
      simpler and faster as a result.
  Bugfixes:
    - Mod no longer fails to load alongside enemy mods
      (e.g. Fulgoran Enemies) whose biter/spitter variants
      reuse a vanilla player projectile prototype in their
      attack chain. Previously the data-stage scan mutated
      the shared projectile prototype in place to add the
      projectile-block layer, which would have broken the
      player's shotgun/turret firing the same projectile;
      the load-time validate caught the invariant violation
      and aborted load. The scan now detects when an enemy
      attack delivery references a projectile that is also
      reachable from player ammo and clones the prototype
      under a mod-namespaced name, rewiring only the enemy
      delivery to the clone. The original prototype stays
      clean for player weapons and the cloned copy carries
      the block layer so the modded enemy's attack still
      collides with walls/gates as intended.
Version: 8.5.2
Date: 2026-05-10
  Major Features:
    - Raid candidate selection is now pollution-aware. Each
      candidate spawner's chunk pollution is sampled at
      selection time, and the candidate list is sorted
      pollution-ascending (distance-ascending tiebreaker).
      Spawners in low-pollution chunks are preferred because
      the engine's pollution-attack AI is not actively
      coordinating attack waves from them, so members of
      our raid groups don't get siphoned into competing
      vanilla attack groups during the 30-second warning
      window. In-game verification on a dense Space-Age save
      where hold_loss had been 20-60% of assembled members:
      hold_loss=0 across the test sample once pollution-
      aware selection landed.
    - Raids now spawn from two different unit-spawner
      prototypes when a candidate of a different type is
      available within 96 tiles of the primary spawner. The
      group split is 50/50 across the two selected spawners,
      so vanilla biter-spawner + spitter-spawner pairs
      produce mixed melee/ranged waves automatically. The
      comparison is by entity.name, so modded enemy stacks
      with additional spawner prototypes get the same
      mixed-composition behavior without prototype-name
      hardcoding. Falls back to single-spawner raids when
      no different-type spawner is nearby.
  Minor Features:
    - Spawner-isolation filter during candidate selection.
      Candidates with more than 4 spawners within a 16-tile
      radius are deprioritized and used only as a fallback
      when no isolated candidates exist. Dense biter
      networks were causing the engine's coordination logic
      to compete for biter membership during HOLDING;
      isolated candidates avoid that competition.
    - Unreachable-target cache. When candidate selection
      exhausts every spawner without finding a valid path
      to the target chunk, the chunk is marked unreachable
      for 7200 ticks (2 minutes). Target selection skips
      marked chunks during that window so the selection
      pipeline doesn't thrash for ~6 minutes re-trying the
      same impossible target every phase-2 tick.
    - HOLDING and DISPATCHED log lines now break the loss
      accounting into add_member_failures (engine refused
      the add), drift_loss (added but disappeared before
      HOLDING), and hold_loss (left during the warning
      window). Selection accept-log includes pollution and
      distance for post-hoc correlation analysis.
    - Path obstacles now only veto a candidate when the
      destroy-set includes a cliff. Trees, rocks, neutral
      entities, and enemy spawners are no longer reasons to
      reject a candidate — biters chew through them. Water
      remains impassable via the pathfinder's collision
      mask (not via the destroy-set check).
  Bugfixes:
    - scatter_radius is now clamped at
      max_group_radius - 4 (default cap: 26 tiles). Without
      the clamp, raids of ~900+ biters scatter members at
      sqrt(total) >= 30 tiles from the spawner, outside the
      group's coordination radius; the engine accepts the
      add_member call but releases the unit on the next
      tick, and the engine's pollution-attack AI scoops it
      into a vanilla attack group. Clamping eliminates this
      bleed (drift_loss=0 on raids that previously lost
      hundreds of biters during SPAWNING).
    - add_member failures are now detected via the member-
      count delta and the failed biter is destroyed rather
      than left loose, so engine pollution-attack groups
      can't form around abandoned spawn-residue. A new
      bail-out cap promotes the spawn_job to HOLDING when
      add_member failures hit half the target, preventing
      multi-minute spin loops on adversarial saves.
    - Two-spawner raids correctly survive when one spawner
      dies mid-spawn — the spawn loop continues sampling
      from the survivor's result_units curve and using its
      position as the jitter origin.
  Changes:
    - RAID_SPAWNER_MAX_DISTANCE raised from 512 to 1024
      tiles. Wider candidate pool gives pollution-aware
      selection room to reach low-pollution spawners that
      are further from the player base on real maps where
      water and cliffs partition the landmass.
    - Dispatch waypoints subsampled to a minimum 24-tile
      spacing. The pathfinder's raw output had ~50+ way-
      points for a typical raid path, each producing a
      brief sub-command transition pause during the march
      (visible as 2-second stop-go-stop micro-stutter).
      Subsampling collapses transition count to ~5-10
      while preserving turn points and final attack
      command, so the wave moves smoothly and only
      reorients at meaningful path corners.
    - Selection candidate records now carry pollution and
      distance fields used by the new sort comparator and
      the accept log line.
Version: 8.5.0
Date: 2026-05-10
  Bugfixes:
    - Large raids no longer arrive as a strung-out line of
      loose biters with no warning. The pipeline now splits
      raids of more than 150 biters across multiple unit
      groups (one group per ~150 biters, round-robin
      distributed). Previously a single oversized scripted
      group was siphoned apart by the engine's pollution-
      attack AI mid-HOLDING — biters were reassigned into
      a vanilla attack group and walked to the player's
      base under engine AI, defeating both the 30-second
      warning window and the cached path. With the split,
      each group stays well below the engine's
      max_unit_group_size cap and the wave dispatches
      coherently after the full warning window.
Version: 8.4.1
Date: 2026-05-10
  Minor Features:
    - Raid chart-tag skull is now bright red (was yellow), so
      the alert reads more clearly at low zoom.
    - Raid chart tags now auto-clean after 60 seconds (raised to
      150 seconds in a later release) — the
      mod parks each tag in storage.raids.expiring_tags on
      creation and the alerts tick slice destroys expired
      entries. Previously the tag persisted until the player
      removed it manually.
Version: 8.4.0
Date: 2026-05-10
  Major Features:
    - Mega-raid pipeline rebuilt around a single out-of-sight
      enemy spawner. The scheduler picks one spawner within
      pollution range of the most-threatened chunk where no
      player vision currently reaches, validates a walkable path
      (player-force walls/buildings ok; trees, rocks, and enemy
      spawners blocking the route veto the candidate), then
      spawns max(round(evolution * factor), 30) biters at that
      spawner across multiple ticks into a single cohesive group.
      Sample distribution matches the spawner's own result_units
      curve at the current evolution, so unit composition feels
      native to that nest. Factor is 700 / 1000 / 1300 per
      Easy / Normal / Hard preset. Hard-serial: one raid in
      flight at a time. Replaces the previous all-spawners-fire-
      simultaneously cluster dispatch.
    - Raid warning is strictly serialized: spawn silently → fire
      the alert when the wave is fully assembled at the spawner →
      hold for 30 seconds → start moving. The player gets a
      consistent 30-second warning window regardless of raid
      size, rather than a window that overlapped with spawn-up
      and varied with evolution.
    - Raid chart-tag icon is a yellow-tinted skull (custom
      virtual signal). "Raid incoming" chat warning uses bold
      rich text, and the skull's chart-tag label is bold too —
      easier to spot when scanning the map at low zoom.
  Bugfixes:
    - Small biter groups no longer jitter when retreating. The
      hysteresis lock kept assess() returning true (correct), but
      refresh_command was issuing a fresh flee command every
      slice during the lock, and the destination could flip
      between iterations of the spawner search, causing visible
      back-and-forth. Command issuance is now gated behind the
      same lock check that gates the lock setting: one flee
      command per fresh retreat, no re-issuance until the lock
      expires.
    - Alerts no longer fire for raid waves whose biters all died
      before arrival. The alert pipeline checks the group's live-
      member count at firing time and silently drops phantom
      entries.
    - UPS-budget shedding actually engages now. The profiler
      reading was treated as microseconds when it was already in
      seconds, so the rolling average came in ~10^6 too small and
      the budget threshold never tripped. The mod now correctly
      converts seconds to microseconds before averaging.
    - Wall regen and projectile blocking are guaranteed never
      shed. Wall regen had been listed as the level-5 shed
      target, contradicting its "never shed" contract; that entry
      is gone and the regen tick slice runs unconditionally.
    - First-raid grace period now scales with difficulty (Easy
      90 min, Normal 60 min, Hard 30 min). Previously the first
      raid on any preset fired at 5 minutes regardless.
    - Pathing requests rejected with try_again_later now fall
      back to attack_area like the other failure branches, so
      squads no longer stall after pathfinder overload.
    - Path cache hits clear any in-flight request id, so a late
      callback for an abandoned earlier request can no longer
      overwrite the freshly-installed cached waypoints.
    - Held raid waves wiped during the warning window (e.g. by
      artillery) abandon cleanly instead of issuing set_command
      on an invalid group at dispatch.
    - Raid spawn counter tracks actual placements rather than
      attempts, with a 3x attempt cap so locked terrain (water,
      cliffs, packed entities) cannot pin the spawn pipeline
      forever.
    - Wall regen no longer leaks destroy-tracking entries.
      Repeated damage/heal cycles previously stranded one
      destroy_map entry per cycle until the wall was destroyed;
      the dequeue path now clears the entry alongside the regen
      record.
    - Adaptive resistance variant swaps and unwind-on-disable
      now run find_non_colliding_position before destroying the
      original entity, restoring the v7.2.3-class safety against
      losing biters when a neighbor briefly occupies the tile.
    - Stream-to-projectile conversion handles both single-action
      and array-action ammo definitions, so modded units that
      use array form get projectile blocking too.
    - Resistance decay slice no longer relies on next() against
      a key just deleted from the iterated table; empty protos
      are collected during traversal and removed after the slice
      finishes.
    - Offensive AI is fully disabled on non-Nauvis surfaces:
      group-gather and squad-refresh now check the surface index
      and skip non-Nauvis squads (with proper pending-request
      cleanup). Defensive features (wall regen, projectile
      blocking) remain surface-agnostic.
    - Surface-deletion squad cleanup uses the Squad method
      directly instead of a metatable-truthy guard that always
      passed, eliminating the misleading dead branch.
  Minor Features:
    - New diagnostic remotes diag_selection_job() and
      diag_spawn_job() expose the in-flight raid pipeline state
      (selection candidates, spawn progress, hold/dispatch phase)
      for triage. diag_dump() summary extended with selection /
      spawn progress fields.
  Changes:
    - Storage schema bumped from v2 to v3 with a no-op migration
      that ensures storage.raids.selection_job and spawn_job
      substructures exist on existing saves. Pre-8.4 saves load
      and run normally.
    - Old cluster-dispatch logic in lib/raids/scheduler.lua
      replaced; size_per_spawner constants removed. Dead
      compute_eta and on_path_finished_for_raid hooks removed
      now that the raid pipeline owns its own path lifecycle.
Version: 8.3.0
Date: 2026-05-08
  Bugfixes:
    - Breach reinforcement: surface.find_units expected a single
      ForceID, not an array. v8.2.0 passed an array which crashed
      every force_breach_ping call and every real turret-death event.
      Replaced with surface.find_entities_filtered{type="unit", ...}
      to match the established codebase pattern.
    - Breach reinforcement: LuaEntity.unit_group access throws in
      Factorio 2.0 (the property was reorganized). Replaced with a
      tracked-members set built from storage.squads — narrower
      semantics but no throwing field access.
  Major Features:
    - Test suite reworked for release verification. Eight new
      scenarios cover previously-untested features: T7 (retreat
      hysteresis, revived from v6 deletion), T13 (flanking),
      T14 (breach cooldown), T15 (gate regen), T16 (raid alerts),
      T17 (difficulty preset scaling), T18 (master toggle off),
      T19 (player gun-turret data-stage patch safety). Existing
      T5/T6/T10/T11 tightened: event-driven path-callback waits,
      narrower outcome accept set, raise_built on chunk-threat
      walls, directional evolution check via the spawner's own
      result_units curve.
    - New docs/release-check.md ties scenarios + manual checks
      (UPS shed, multiplayer determinism, welcome message, preset
      hot-reload) into a step-by-step pre-portal-upload checklist.
  Minor Features:
    - New diagnostic remotes diag_squad_kind(squad_id) and
      diag_pending_alerts(). Used by the new scenarios; safe for
      manual triage.
    - diag_squad_state(squad_id) extended with alive_count.
  Changes:
    - ARCHITECTURE.md pitfall list: added entries for the
      LuaEntity.unit_group Factorio-2.0-throw and the
      on_unit_group_finished_gathering duplicate-Squad pattern.
Version: 8.2.0
Date: 2026-05-08
  Major Features:
    - Breach reinforcement: when a player turret dies during an attack,
      nearby enemy squads and loose biters are pulled toward the breach
      via a position "ping." Radius scales with the difficulty preset
      (Easy 48 tiles, Normal 64, Hard 96). Per-recipient 30-second
      cooldown prevents ping-pong when several turrets fall in quick
      succession. Disabled with the rest of the offensive AI when the
      Enemy Advanced AI master toggle is OFF; sheds at the same load
      level as flanking when the UPS budget tightens.
  Minor Features:
    - New diagnostic remote `diag_squad_state(squad_id)` returns
      `{cooldown_until, fleeing_until, last_path_outcome}` for triage
      of squad behavior.
    - `diag_dump` includes a `breach` block with `pings_fired` and
      `recipients_total` counters.
    - Debug remote `force_breach_ping(position [, surface_index])`
      fires a ping without needing a turret to die — used by the new
      T12 scenario and available for player-side console debugging.
  Changes:
    - Storage schema bumped from v1 to v2 with a no-op migration that
      ensures `storage.coord` exists. No save data is rewritten;
      existing saves load and run normally.
Version: 8.1.1
Date: 2026-05-07
  Changes:
    - Welcome messages rewritten for distinct per-preset tone and
      player-facing language. Each difficulty now reads in its own
      voice: Easy is welcoming, Normal is matter-of-fact, Hard is a
      warning. Replaced engineer-speak ("Path-around-walls AI, squad
      retreat, adaptive resistance") with player-language ("biters
      now path around your walls when they can, retreat when bloodied,
      and grow resistant to weapons you over-rely on"). Dropped the
      diag_dump command from every welcome — it lives in the README
      for the small fraction of players who need it.
    - Hard-difficulty mid-game warning rewritten to match the new
      welcome tone.
    - Settings path arrows upgraded from `>` to `→` for visual
      polish, matching the README and mod portal copy.
Version: 8.1.0
Date: 2026-05-07
  Major Features:
    - New "Difficulty Preset" runtime-global setting (Easy / Normal / Hard).
      Easy: raids half as frequent, 15% resistance cap, level-1 variant
      restriction, fast wall regen (5s window). Normal: defaults
      unchanged from prior versions. Hard: frequent raids, 40% cap,
      slower regen (20s window), aggressive flee threshold.
      Players can change the preset mid-save without restarting; effective
      values are computed on every read so the change takes effect at
      the next subsystem tick. Multipliers live in the new
      lib/difficulty.lua module.
    - One-time in-game welcome message on first mod load. Briefly
      describes which features are active and points at the settings
      panel. Content adapts to the active difficulty preset and the
      umbrella toggle state. Fires once via storage._welcomed flag;
      subsequent loads are silent.
    - New "Enemy Advanced AI" master startup toggle. When OFF, all
      offensive enemy-AI features (smarter pathing, squad retreat,
      flanking, target priority, mega-raids, adaptive resistance) are
      disabled in one place. Defensive features (wall regeneration,
      projectile blocking) continue to work. Use this for a
      "vanilla biters + wall QoL" experience or to stack with another
      AI mod that handles enemy AI. With the umbrella OFF, the
      offensive event handlers are not registered at all and the
      on_entity_damaged filter drops the unit-type subscription —
      zero per-event dispatch cost on megabases.
    - Mega-raid unit selection now uses a three-strategy fallback chain
      that supports modded enemies without registration:
      (1) weighted pick from the spawner's result_units curve at the
      current evolution; (2) evolution-indexed pick from result_units
      treated as tier-ordered when weights are unreadable or all zero;
      (3) vanilla biter chain only as a last resort. Strategy 2 uses
      Factorio's standard biter evolution thresholds (0.2 / 0.5 / 0.9)
      so the "feel" of evolution progression matches vanilla regardless
      of how many tiers the spawner declares.
    - Adaptive resistance variants now respect parent vulnerability.
      Overhaul mods (Pyanodons, Space-Age-extras, etc.) that declare
      biters as vulnerable to a damage type (negative percent — e.g.
      cold spitter at fire = -100%) get correctly-adapted variants:
      the generator clamps the parent's resistance at 0 before adding
      the adaptation level, so a fire-adapted cold spitter spawns
      fire-RESISTANT (matching the design intent) rather than just
      slightly-less-fire-vulnerable.
    - Dynamic damage-type discovery. Variant prototypes are now
      generated for every damage type registered by the mod stack
      (laser, electric, poison, cold, custom Bob's/Pyanodons types,
      etc.) instead of the fixed vanilla four. Modded damage types
      participate in adaptive resistance with no registration.
  Bugfixes:
    - Save/load no longer crashes with "Detected modifications to the
      'storage' table" CRC mismatch. on_load now snapshots stale
      path-request IDs into a module-local; the first scheduler tick
      after load consumes the snapshot and removes only those specific
      IDs, preserving any new requests submitted between load and that
      tick.
    - Mega-raid biters no longer spawn stuck inside their own spawner.
      The previous loop jittered each unit's spawn position by ±2 tiles,
      but spawners occupy a ~3x3 footprint, so create_entity placed
      biters at colliding positions — friendly-collision (same enemy
      force) prevented physical pushout, leaving the entire raid wedged
      inside its own spawner cluster, unable to move or be targeted.
      Spawn positions now jitter ±4 tiles (clearing the footprint) and
      snap to a non-colliding tile via surface.find_non_colliding_position.
    - Mega-raids and flank-split sub-squads now reliably advance toward
      their target. Scripted unit groups created via
      surface.create_unit_group sit in "gathering" state and ignore
      set_command alone; group.start_moving() is now called after
      pathing.request for every freshly-created scripted group.
    - Raid-incoming chart-tag alert now fires for every raid, not just
      those whose path was accepted by the pathfinder. Rejected paths
      (detour, destroy-required, queue saturation) used to silently
      drop the alert; the handler now falls back to a straight-line
      ETA from the squad's average position to the target so every
      raid produces a chart tag and chat message.
    - Raid unit selection no longer crashes on modded spawners whose
      result_units curves use the Factorio 2.0 runtime field name
      (.weight) instead of the data-stage name (.spawn_weight). The
      interpolator now reads through tolerant accessors that try every
      known shape (.spawn_weight / .weight / pt[2]) and fall back to 0
      if all fail.
    - Raid alerts no longer crash with "LuaEntityPrototype doesn't
      contain key movement_speed" when a raid contains a non-Unit
      member or a unit subtype that omits movement_speed. The speed
      read is now wrapped in pcall and gated on prototype.type ==
      "unit" — non-units contribute nothing to the slowest calculation,
      ETA falls through to the default if every member fails to expose
      a speed.
    - pick_unit_for_spawner's result_units access is similarly
      pcall-guarded against modded spawner prototypes that don't
      expose the typed field.
    - Adaptive resistance variant generation no longer aborts mod load
      when an overhaul mod declares biter prototypes with negative
      resistance percent (vulnerability). The variant generator clamps
      the parent at 0 before adding adaptation; the validate-variants
      pass relaxes the lower bound check (only enforces the 30% upper
      cap) as defense in depth.
    - validate-variants no longer rejects modded damage types whose
      names contain hyphens (e.g. Bob's "bob-pierce"). The parse pattern
      `<base>-smart-resist-<dmg>-<level>` was already unambiguous via
      the end-anchored level digit run; the over-cautious explicit
      hyphen-rejection check has been removed. Modded enemy stacks
      with hyphenated damage type names load cleanly.
    - on_built_entity / on_robot_built_entity / script_raised_built no
      longer count enemy-force walls, turrets, or buildings as
      player-side threat. The previous filter checked only
      type == unit, so PvE-with-enemy-base scenarios could see raids
      targeted at enemy infrastructure.
    - flank.maybe_split now snapshots the unit-group members table
      before calling add_member. add_member re-parents the unit and
      mutates the source list; iterating the live list could skip
      members and produce uneven splits.
    - Chunk eviction and raid alerts now resolve the Nauvis surface
      through compat.nauvis_index() instead of a hardcoded surface
      index of 1. Scenarios that reorder surfaces no longer silently
      target the wrong surface.
    - Welcome message uses game.print (visible chat) instead of log
      (factorio-current.log only) so players actually see the
      orientation message. Earlier draft made the message invisible
      to the player and defeated the whole point of the C2 component.
    - Squad:alive_count() now checks per-member validity the same way
      avg_position() does, preventing inflated health ratios from
      skewing retreat decisions when group members go invalid.
    - compat.nauvis_index() returns nil instead of 1 when Nauvis is
      absent from the surface table; call sites are guarded against
      nil so non-Nauvis-only setups don't accidentally target surface 1.
    - on_entity_spawned variant swap now checks the create_entity
      return value; falls back to the base prototype if variant
      creation fails instead of silently losing the unit.
    - storage.raids initialization uses per-field defaults so partial
      tables loaded from old saves cannot leave pending_alerts nil and
      crash the alert tick_slice.
    - diag_resist_cap remote now returns the difficulty-scaled
      effective cap value rather than the constants.lua base.
  Changes:
    - RESIST_CAP difficulty multiplier limits variant level selection:
      Easy restricts pick_variant_for to level 1 (10% resist max),
      Normal/Hard allow up to level 3 (30%).
    - Strategy 2 (index-based fallback) and Strategy 3 (vanilla
      hardcoded last resort) for raid unit selection share a single
      threshold function (evolution_to_tier_index). Future tuning of
      evolution breakpoints applies uniformly to both paths.
    - Storage now carries a schema version (storage._schema_version
      = 1) to support future save migrations. Existing saves are
      treated as version 1 on first load. No migrations exist yet.
    - The Nauvis-only design assumption is now codified via a
      compat.nauvis_index() helper instead of scattered
      surface-index/name compares.
    - Squad metatable now carries a _kind field
      ("vanilla" / "raid" / "scripted") so consumers know which groups
      need explicit start_moving() handling.
    - Squad:avg_position now consistently returns Factorio's standard
      {x=, y=} table form (matching LuaEntity.position). Previous
      mixed-shape behavior forced consumers to handle both shapes via
      dual-form readers.
    - Path cache freshness window (was hardcoded 600 ticks) hoisted
      to lib/constants.lua as PATH_CACHE_TTL_TICKS.
    - Removed leftover [DBG] log spam from the raid-alert path.
  Compatibility:
    - Auto-detects modded enemies via subgroup == "enemies" AND via
      spawner result_units for raid composition.
    - Plays nicely with Armored Biters and other resistance-adding
      mods (clamped 30% cap on variant resistance).
    - Tested-working with Armored Biters, Pyanodons, Space Age +
      Space-Age-extras, Rampant.
    - Mods adding custom damage types (laser turrets, elemental
      overhauls, etc.) get full adaptive resistance support with no
      registration. Hyphenated damage type names are supported.
    - Storage shape unchanged from v7.0.1 except for the additive
      storage._welcomed boolean flag. No schema bump. v7.0.1 saves
      load directly into v8.0.0; the welcome message fires once on
      the first scheduler tick after upgrade.
    - Multiplayer-safe: deterministic across peers, no on_load storage
      mutation, runtime-global settings sync enforced by Factorio,
      difficulty effective values computed on every read (no stale
      cached state).
  Info:
    - README.md and ARCHITECTURE.md fully refreshed for v8.0.
      ARCHITECTURE pitfalls section documents the post-v7.0 hard-won
      patterns: surface.find_non_colliding_position before create_entity
      for enemy units; pcall around LuaEntityPrototype type-specific
      field access. Several known-and-deferred limitations are also
      explicitly documented (chunks PvP-blind raid targeting, variant
      level fallback for high-resistance parents, bounded
      pending_alerts accumulation, force_squad_flee health snapshot).
    - Test scenarios updated. T11 (raid unit selection) loosened to
      assert evolution-responsiveness (low ≠ high, both valid strings)
      instead of exact vanilla unit names — modded environments where
      biter-spawner.result_units now includes spitters or other tiered
      enemies pass cleanly.
    - New welcome message and difficulty-hard-warning locale strings.
Version: 7.0.1
Date: 2026-05-06
  Bugfixes:
    - Save/load no longer crashes with "Detected modifications to the
      'storage' table" CRC mismatch. The v7.0.0 (and earlier) on_load
      handler cleared storage.path_q.pending in place, which Factorio
      forbids — on_load must be strictly read-only or it breaks
      multiplayer determinism and trips the engine's CRC check. The
      handler now snapshots the IDs of in-flight path requests (which
      the engine abandons across save boundaries) and the first
      scheduler tick after load deletes only those specific IDs —
      preserving any new requests submitted between load and that
      tick. Test scenarios that submit a path request from on_init
      (T5/T6/T8) run to completion as expected.
Version: 7.0.0
Date: 2026-05-03
  Bugfixes:
    - Mega-raids now spawn modded biter prototypes by reading the chosen
      spawner's result_units list, instead of hardcoding vanilla biter
      names. Mods like Rampant, Krastorio2, and Armored Biters get
      automatic raid support with no registration required. Falls back
      to the previous vanilla-biter switch if a spawner has no usable
      result_units.
  Changes:
    - Storage now carries a schema version (storage._schema_version = 1)
      to support future save migrations. Existing saves are treated as
      version 1 on first load. No migrations exist yet.
    - The Nauvis-only design assumption is now codified via a compat.
      nauvis_index() helper instead of scattered surface-index/name
      compares. Behavior unchanged.
  Info:
    - README "Known limitations" section removed; the items it listed are
      no longer reproducible in v7.0.
    - Raid-interval comment in lib/raids/scheduler.lua now states the
      exact 0.34 multiplier instead of the loose "/3" approximation.
    - New data-stage validation pass for resistance variants. Loud
      failure at load time if variant generation desyncs from the
      naming contract, instead of silent breakage.
    - Comment added to lib/scheduler.lua's profiler-parsing line
      explaining why the serpent.line regex is the correct pattern in
      Factorio 2.0 (LuaProfiler exposes no numeric accessor).
    - DO-NOT-RENAME warning added near the existing
      script.register_metatable call in lib/state.lua to protect save
      compatibility.
    - diag_dump's hardcoded version string updated to 7.0.0 (was stuck
      at 6.2.0; pre-existing drift).
    - New test scenario T11 (11_raid_unit_selection) verifies the new
      raid unit selection at low and high evolution.
  Compatibility:
    - Nauvis-only by design. Space Age planets (Vulcanus, Gleba, Aquilo)
      are explicitly out of scope.
    - Modded enemy prototypes auto-detected via subgroup == "enemies"
      (unchanged) AND now via spawner result_units for raid composition.
    - Plays nicely with Armored Biters and other resistance-adding mods
      (introduced in v6.2.1; restated for v7.0 visibility).
Version: 6.2.1
Date: 2026-05-02
  Balancing:
    - Adaptive resistance variants now respect the 30% cap when the
      parent enemy already carries a base resistance from another mod
      (e.g. Armored Biters). For each damage type the merged percent
      is clamped at 30, and once the cap is reached no further variant
      levels are generated. Damage types where the parent is already
      at or above 30% produce no variants at all; other damage types
      are unaffected. Prevents super-tanky enemies when stacking with
      resistance-adding mods.
Version: 6.2.0
Date: 2026-05-01
  Info:
    - New diagnostic remote `diag_dump` returns a comprehensive state
      snapshot suitable for bug reports. Users invoke
      /c game.print(serpent.line(remote.call("smart-enemy-ai", "diag_dump")))
      and paste the result.
    - New ARCHITECTURE.md at the mod root documents subsystems,
      scheduler phases, storage shape, lifecycle, and common pitfalls.
      Aimed at future maintainers and contributors.
    - New T10 smoke integration test scenario exercises every subsystem
      simultaneously for 60 seconds and asserts no crashes or runaway
      state growth.
    - Inline header comments added to lib/spawn_variant.lua,
      lib/state.lua, lib/ai/retreat.lua, and lib/ai/pathing.lua to
      explain non-obvious invariants.
    - Path outcome counters now tracked under storage.path_q.outcome_counts
      so diag_dump can report distribution (accepted, rejected_detour,
      rejected_destroy_required, fallback_*).
Version: 6.0.1
Date: 2026-05-01
  Bugfixes:
    - Disabling the "Biter Build Adaptive Resistance" startup toggle now
      converts existing variant biters back to base prototypes during the
      configuration-change handler, instead of leaving them as orphaned
      entities that the engine would silently drop on save load.
    - Slice iterator cursors (regen, squads, resistance ledger) now
      validate their saved key against the target table before calling
      `next()`, defending against a Lua 5.2 edge case where a stored
      cursor could refer to a key deleted between slices.
    - Resistance ledger prune threshold lowered from 1.0 to 0.01 so
      single small-damage events (e.g. low-tier turret hits) survive
      long enough to influence variant selection at the next biter
      spawn. Storage growth is negligible — entries below 0.01
      contribute less than 0.000003 to the resist factor anyway, and
      natural decay (tau ~30 minutes) eventually evicts them.
  Info:
    - Removing the mod entirely from a save remains under Factorio's
      standard handling: orphaned variant biters are dropped by the
      engine, the save loads cleanly, the world continues. New biters
      from spawners are unaffected.
Version: 6.0.0
Date: 2026-05-01
  Changes:
    - Pathfinding now rejects routes more than twice as long as the
      straight-line distance to the target. Previously biters could
      slip through any maze of walls indefinitely without taking
      damage; they will now attack walls along the direct path
      instead. The threshold is adjustable via the new "Max
      pathfinding detour ratio" runtime setting.
    - Retreating squads now stay in flee mode for at least 10
      seconds once retreat is triggered. Previously the flee command
      could flicker on and off as members died, producing visibly
      hesitant movement. Configurable via the new "Retreat
      hysteresis" runtime setting.
    - Biters reaching their attack target now prioritize turrets,
      power infrastructure, and machinery over walls, with a final
      area-sweep to clean up remaining targets. Previously the
      generic attack-area command often ended up targeting the
      nearest wall regardless of what else was present.
  Major Features:
    - Adaptive resistance reworked from runtime heal-back to engine-
      applied prototype variants. When a biter spawns and your
      damage history shows accumulated damage of a type, the engine
      spawns a more resistant variant with that resistance baked in.
      No more visible "biter takes damage then heals back". The
      resistance fades through population turnover as new biters
      spawn under updated ledger state.
  Compatibility:
    - Mod loads cleanly with Space Age installed. AI and raid
      behaviors are Nauvis-only; defensive features (wall regen,
      projectile blocking) work surface-agnostically.
  Info:
    - README expanded with a Diagnostics section documenting all
      remote-interface helpers.
    - Test scenarios entirely replaced. Old flaky scenarios removed;
      seven new focused scenarios exercise the behavioral changes
      deterministically. Retreat hysteresis is verified by code
      review rather than scenario playback (the lock arithmetic is
      trivial and Factorio's scripted unit-group lifecycle does
      not cooperate with synthetic squads).
Version: 5.0.7
Date: 2026-05-01
  Balancing:
    - Default Adaptive Resistance Cap restored to 30%. The temporary
      reduction to 15% was needed only to mask the killing-blow bug
      fixed in 5.0.4; with that fix in place, 30% is the intended
      design value.
Version: 5.0.6
Date: 2026-05-01
  Bugfixes:
    - Reduced pathfinding bounding box so biters can navigate single-tile
      gaps in player walls. Previously the pathfinder rejected paths
      through narrow gaps, and biters fell back to attacking the closest
      wall instead of using the open route.
  Graphics:
    - Added mod portal thumbnail.
Version: 5.0.5
Date: 2026-05-01
  Info:
    - Updated mod author metadata.
Version: 5.0.4
Date: 2026-05-01
  Bugfixes:
    - Enemy unit groups created via the mod's remote interface now
      correctly begin moving after their pathfinding command is set.
      Previously such groups would receive a path but stand still until
      another command displaced them.
Version: 5.0.3
Date: 2026-05-01
  Info:
    - Added internal diagnostic logging for the raid-alert flow. No
      player-facing change.
Version: 5.0.2
Date: 2026-05-01
  Major Features:
    - Complete rewrite for Factorio 2.0.
    - Smarter enemy AI. Biter groups path around walls when an open
      route exists, retreat to the nearest spawner when their squad
      takes heavy losses, and split into multiple wings on large
      attacks.
    - Adaptive resistance. Enemies accumulate temporary resistance to
      recently-used damage types, with exponential decay and a
      configurable cap. Encourages mixed-damage strategies; resistance
      fades when you switch weapons.
    - Scaling raids. In addition to vanilla pollution attacks, scheduled
      mega-raids spawn periodically. A subtle alert appears on the map
      roughly thirty seconds before arrival. Raid size and frequency
      scale with evolution.
    - Wall and gate regeneration. Any wall or gate that has taken no
      damage for ten seconds will gradually regenerate to full HP over
      the following ten seconds. Any new damage during regeneration
      pauses and resets the timer.
    - Projectile blocking. Spitter and worm acid streams are converted
      into physical projectiles that collide with walls and gates
      instead of arcing over them. Player turret projectiles are
      unaffected.
    - UPS-budget shedding. The mod measures its own per-tick cost and
      automatically disables features in priority order when over
      budget. Features re-enable stepwise once load drops.
  Compatibility:
    - Auto-detects modded enemies that follow Factorio's standard
      "enemies" subgroup convention. Prototypes that do not can be
      added through the mod settings.
    - Mods that create custom enemy forces can register them via
      remote.call("smart-enemy-ai", "register_enemy_force", "name").
    - Multiplayer-safe. All randomness is seeded; behaviour is
      deterministic and replay-stable.
  Settings:
    - Fifteen tunable settings cover the UPS budget, regen window and
      duration, resistance cap and decay rate, raid interval and
      warning lead time, retreat threshold and assessment window,
      shed-ladder hysteresis, and additional enemy-prototype/force
      compatibility lists.