Smarter biter AI with adaptive resistance, breach reinforcement, scheduled raids, wall regen, and overhaul-mod compatibility.
Large total conversion mods.
Version: 8.5.6
Date: 2026-05-11
Bugfixes:
- 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).
Changes:
- Breach ping radius doubled (Normal 64 → 128 tiles, Easy
96, Hard 192). The original 64-tile radius covered small
bases; on large wall networks the redirected biters were
often not in range of the breach they should reinforce.
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 between primary and secondary,
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). pick_target_chunk 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 — 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.