Smarter biter AI with adaptive resistance, breach reinforcement, scheduled raids, wall regen, and overhaul-mod compatibility.
Large total conversion mods.
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.