Damian Sirbu, xlibs and AlifePlus developer · 2026-04-05 · Subject: APS by forTheMonolith
"AlifePlus SISKI Synergy" (APS) v2.7.0 appeared on ModDB on April 4, 2026. 28 scripts, 6,112 lines of Lua. It depends on xlibs, AlifePlus, and SISKI 0.9.5 (which is under a DMCA takedown). Uploaded by forTheMonolith — whether this is the same person as qkff99 (SISKI author) is unknown, but the patterns are the same: AI-generated code, deep access to AlifePlus internals, no development history, no license file.
| Finding | Evidence | Section |
|---|---|---|
| AI-generated code | 67 pcall wraps, hallucinated items, empty loops, mechanical uniformity in 28/28 files | ↓ |
| Internal conventions copied | Naming, handler structure, config keys, registration loops — all from conventions.md | ↓ |
| 70+ undocumented API calls | Tracing, state manager, chase system, pipeline internals — vs ~8 documented | ↓ |
| Design language copied | "YANS" term and ModDB description structure reproduced from AlifePlus readme | ↓ |
| Fatal crash on first test | tostring(vector) at line 182 — crashes AP's pipeline for all users | ↓ |
| Depends on DMCA'd code | Requires SISKI 0.9.5, which is under active DMCA takedown | ↓ |
| License requirements broken | No LICENSE file, 15+ internal functions accessed, source fed to code generator | ↓ |
| Field | Value |
|---|---|
| Author | forTheMonolith (ModDB) |
| Uploaded | April 4, 2026 |
| Size | 73.51 KB (28 scripts + 2 XML) |
| Lines | 6,112 |
| License on ModDB | "Proprietary" |
| License file in mod | None |
| Git repository | None (zip upload) |
| Dependencies | xlibs 1.2.3+, AlifePlus 1.2.2+, SISKI 0.9.5+ |
| Credits in ModDB description | "AlifePlus and xlibs by Damian Sirbu, SISKI by qkff99" |
| Credits file in mod | None |
AlifePlus has internal conventions for how causes and consequences are structured. These are documented in architecture.md and conventions.md -- development documents, not a public API specification. APS follows these conventions exactly:
In plain English: AlifePlus has a naming system for its files, events, and settings — not published anywhere as a standard. APS uses the exact same scheme, just swapping in different words. The structure, separators, prefixes, and suffixes are character-for-character identical.
| What | AlifePlus pattern | APS (identical pattern) |
|---|---|---|
| Cause file | ap_cause_stash.script | aps_cause_wealthy.script |
| Consequence file | ap_consequence_stash_ambush.script | aps_consequence_wealthy_ambush.script |
| Cause event name | "cause:stash" | "cause:wealthy" |
| Consequence event name | "consequence:stash_ambush" | "consequence:wealthy_ambush" |
| MCM enabled key | cause_stash_enabled | cause_wealthy_enabled |
| MCM chance key | consequence_stash_ambush_chance | consequence_wealthy_rob_chance |
| MCM PDA key | consequence_stash_ambush_pda_chance | consequence_wealthy_rob_pda_chance |
| PDA message key | "text_pda_consequence_stash_ambush" | "text_pda_consequence_wealthy_rob" |
| PDA lost message | "text_pda_consequence_squadkill_revenge_lost" | "text_pda_consequence_wealthy_rob_lost" |
| PDA found message | "text_pda_consequence_squadkill_revenge_found" | "text_pda_consequence_wealthy_rob_found" |
| Chase handler key | "revenge_chase" | "wealthy_rob_chase" |
| MCM config prefix | xmcm.create_config("ap", ...) | xmcm.create_config("aps", ...) |
| Save data key | m_data.ap_tension | m_data.aps_tension |
Every row follows the same template with only the bold word substituted. This pattern is documented in AlifePlus's internal conventions.md -- a development document, not a public specification.
In plain English: Every AlifePlus consequence handler follows the same structure: extract the trace, wrap in observe(), check gates in a fixed order, find targets, move squads, send PDA, return a result code. APS handlers follow the exact same structure in the exact same order. Below is real code from both — the only difference is the consequence name.
local function _handler(event_data)
local trace = event_data._trace
return ap_debug.observe(trace,
CONSEQUENCE.STASH_AMBUSH, function()
-- GATE 1: MCM enabled?
if not cfg.consequence_stash_ambush_enabled
then return { code = RESULT.DISABLED_NEXT }
end
-- GATE 2: chance roll
if not xmath.chance(cfg
.consequence_stash_ambush_chance)
then return { code = RESULT.CHANCE_NEXT }
end
-- GATE 3: data validation
if not position then
return { code = RESULT.RULES_NEXT }
end
-- FIND: squads near position
local ambushers =
ap_utils.find_squads_observed(trace,
position, opts)
-- MOVE: script squads to smart
ap_debug.observe(trace,
ACTION.MOVE_SQUAD, function()
for _, sq in ipairs(ambushers) do
ap_alife_tracker.script_squad(
sq, smart, { rush = cfg
.consequence_stash_ambush_rush })
end end)
-- PDA: send message
ap_debug.observe(trace,
ACTION.SEND_PDA, function()
ap_utils.send_pda(cfg
.consequence_stash_ambush_pda_chance,
"text_pda_consequence_stash_ambush",
{ location = location })
end)
-- RESULT
return ap_debug.result_squads(moved, {
code = RESULT.OK_STOP,
dst_id = smart.id,
})
end)
end
local function _handler(event_data)
local trace = event_data._trace
return ap_debug.observe(trace,
CONSEQUENCE.WEALTHY_AMBUSH, function()
-- GATE 1: MCM enabled?
if not cfg.consequence_wealthy_ambush_enabled
then return { code = RESULT.DISABLED_NEXT }
end
-- GATE 2: chance roll
if not xmath.chance(
aps_difficulty.adjust_chance(cfg
.consequence_wealthy_ambush_chance,
"ambush"))
then return { code = RESULT.CHANCE_NEXT }
end
-- GATE 3: data validation
if not position then
return { code = RESULT.RULES_NEXT }
end
-- FIND: squads near position
local ambushers =
ap_utils.find_squads_observed(trace,
ambush_smart.position, opts)
-- MOVE: script squads to smart
ap_debug.observe(trace,
ACTION.MOVE_SQUAD, function()
for _, ambusher in ipairs(ambushers) do
ap_alife_tracker.script_squad(
ambusher, ambush_smart, { rush = cfg
.consequence_wealthy_ambush_rush })
end end)
-- PDA: send message
ap_debug.observe(trace,
ACTION.SEND_PDA, function()
ap_utils.send_pda(cfg
.consequence_wealthy_ambush_pda_chance,
"text_pda_consequence_wealthy_ambush",
{ location = location })
end)
-- RESULT
return ap_debug.result_squads(moved, {
code = RESULT.OK_NEXT,
dst_id = ambush_smart.id,
})
end)
end
Line by line: same function signature, same trace extraction, same observe() wrapper, same gate order (enabled -> chance -> validation), same find_squads_observed(), same nested observe(ACTION.MOVE_SQUAD), same script_squad() call with same rush config key, same nested observe(ACTION.SEND_PDA), same send_pda() call with same key pattern, same result_squads() return. The only differences are the consequence name and the addition of aps_difficulty.adjust_chance().
function on_game_start()
local radiant = ap_const.RADIANT_CALLBACKS
for i = 1, #radiant do
ap_producer_reactive.register(
CAUSE.STASH,
{ callback = radiant[i] },
_on_smart
)
end
ap_debug.info("[CAUSE.STASH] Initialized")
end
function on_game_start()
cfg = aps_mcm.cfg
for i = 1, #ap_const.RADIANT_CALLBACKS do
ap_producer_reactive.register(
CAUSE.WEALTHY,
{ callback =
ap_const.RADIANT_CALLBACKS[i],
priority = 80 },
_on_radiant
)
end
_aps_log.info("[CAUSE.WEALTHY] Initialized")
end
ap_consumer.register(
CONSEQUENCE.STASH_AMBUSH, {
event = CAUSE.STASH,
priority = 20,
condition = function()
return cfg
.consequence_stash_ambush_enabled
end,
},
_handler
)
ap_consumer.register(
CONSEQUENCE.WEALTHY_AMBUSH, {
event = CAUSE.WEALTHY,
priority = 20,
condition = function()
return cfg
.consequence_wealthy_ambush_enabled
end,
},
_handler
)
Same function. Same argument structure. Same field names. Same priority value (20). Same condition pattern. Only the cause/consequence names differ.
local chase_opts = {
handler_key = "revenge_chase",
log_prefix = "CONSEQUENCE.SQUADKILL_REVENGE",
max_chases_key= "consequence_squadkill_revenge_max_chases",
pda_chance_key= "consequence_squadkill_revenge_pda_chance",
pda_start = "text_pda_consequence_squadkill_revenge",
pda_lost = "text_pda_consequence_squadkill_revenge_lost",
pda_found = "text_pda_consequence_squadkill_revenge_found",
rush_key = "consequence_squadkill_revenge_rush",
}
-- on_game_start:
chase_opts.cfg = cfg
ap_utils.chase_register(chase_opts)
local chase_opts = {
handler_key = "wealthy_rob_chase",
log_prefix = "CONSEQUENCE.WEALTHY_ROB",
max_chases_key= "consequence_wealthy_rob_max_chases",
pda_chance_key= "consequence_wealthy_rob_pda_chance",
pda_start = "text_pda_consequence_wealthy_rob",
pda_lost = "text_pda_consequence_wealthy_rob_lost",
pda_found = "text_pda_consequence_wealthy_rob_found",
rush_key = "consequence_wealthy_rob_rush",
}
-- on_game_start:
chase_opts.cfg = cfg
ap_utils.chase_register(chase_opts)
9 identical table keys. Same registration call. Same .cfg = cfg assignment. The values follow the naming convention from AlifePlus's conventions.md.
cfg, get_config, load_config =
xmcm.create_config("ap", defaults, path_builder)
function on_game_start()
load_config()
RegisterScriptCallback(
"on_option_change", load_config)
end
cfg, get_config, load_config =
xmcm.create_config("aps", defaults, path_builder)
function on_game_start()
load_config()
RegisterScriptCallback(
"on_option_change", load_config)
end
The only difference is "ap" vs "aps" -- one character. Everything else is identical: same variable names, same function call, same callback registration, same lifecycle hook.
In plain English: AlifePlus has two layers: a public guide that shows mod authors what functions to call, and internal development documents that describe how AlifePlus itself is built — naming rules, file organization, handler structure, config key formats. APS bypassed the public API and used the internal documents directly, reproducing the internal conventions character-for-character with only the names changed.
The usage guide documents these integration paths:
| Function | Level | Purpose |
|---|---|---|
xbus.subscribe() | Level 1 | Listen to cause events |
ap_producer_reactive.register() | Level 2 | Register cause predicates |
ap_consumer.register() | Level 2 | Register consequence handlers |
ap_const.RADIANT_CALLBACKS | Level 2 | Callback array for registration loops |
ap_const.RESULT.* | Level 2 | Handler return codes |
APS uses these calls correctly. This is not the issue.
These functions do not appear in the guide. They can only be discovered by reading AlifePlus source code. ~8 documented API calls vs ~70+ undocumented internal calls.
Tracing system (33+ calls) — AP's internal hierarchical trace/debug pipeline:
| Function | Calls | In public API? |
|---|---|---|
ap_debug.observe() — wrap logic in trace context | 10+ | No |
ap_debug.trace() / result_squads() — create traces, build results | 8 | No |
ap_debug.debug() / warn() — internal logging | 15+ | No |
State manager (15 calls) — AP's internal squad tracking and elite system:
| Function | Calls | In public API? |
|---|---|---|
ap_alife_tracker.script_squad() — script squads to destinations | 6 | No |
ap_alife_tracker.script_actor_target() — lock squads onto player | 2 | No |
ap_alife_tracker.register_arrival_handler() — arrival callbacks | 1 | No |
ap_alife_tracker.get_elite_level() / is_elite() — elite queries | 6 | No |
Internal helpers and data (22+ calls) — chase system, search wrappers, pipeline internals:
| Function | Calls | In public API? |
|---|---|---|
ap_utils.chase_start() / chase_register() — chase mechanics | 2 | No |
ap_utils.find_squads_observed() / find_smart_observed() — traced search | 6 | No |
ap_utils.event_publish() / increment_cause_counter() — bypass cause flow | 4 | No |
ap_utils.send_pda() / get_location_description() — PDA formatting | 7 | No |
ap_const.RADIUS_CLOSE_MAX / DISTANT_MAX — distance thresholds | 5 | No |
ap_data.community_stalker / community_predator — faction filters | 6 | No |
Several modules are accessed via rawget(_G, ...) — bypassing module resolution to reach into the global table directly.
APS uses 14 of 16 xlibs modules. Every AlifePlus and xlibs function call is correct — right module, right name, right arguments, right field names. No framework call is hallucinated. This strongly suggests the complete source of both codebases was provided to the code generation tool. (Game item sections and engine APIs, which were not in the provided source, are hallucinated — see above.)
AlifePlus's readme.txt opens with: "You are not special." Its architecture.md defines it as: "YANS (You Are Not Special). Same rules for player and NPCs." This term appears only in AlifePlus documentation.
APS reproduces it in three places:
| Where | APS text |
|---|---|
| ModDB heading | "CORE PHILOSOPHY: You Are Not Special" |
| ModDB body | "You are not special — same rules apply to everyone" |
Code comment (aps_cause_wealthy.script:32) | "YANS: same evaluation logic for player and NPCs" |
The ModDB description itself follows AlifePlus's readme structure section by section:
| Section pattern | AlifePlus readme | APS ModDB description |
|---|---|---|
| Opening philosophy | "You are not special" | "CORE PHILOSOPHY: You Are Not Special" |
| Zone runs itself | "the Zone runs on its own rules, and the actor is just another entity inside it" | "The Zone has its own life, its own conflicts, its own economy. You just happen to be in it." |
| Step-by-step walkthrough | "Example scenario (systemic interaction): A stalker reaches a location..." | "How it works step by step: Your wealth is scored every 5 game-minutes..." |
| Realism section | "NPCs consume real items from their inventory on arrival" | "What makes it realistic: Each squad runs through a personality-driven risk evaluation" |
| PDA notifications | PDA map markers as feature | "PDA notifications:" with identical formatting |
| FAQ structure | "Does it conflict...", "Does it work with GAMMA?", "Does it work mid-save?" | Same questions, same order, same answers |
| MO2 install | "Install (MO2): 1. Install xlibs 2. Install AlifePlus" | Same steps, same format, adds priority numbers |
| Dual language | English only | English + Russian (semantically identical, machine-translated) |
The APS ModDB description is approximately 5,500 words in English + 2,000 words in Russian. The AlifePlus readme was provided to the code generation tool alongside the source code, and the AI reproduced the same organizational template for a different mod.
The first user to test the mod reported a fatal engine crash:
FATAL ERROR
aps_consequence_wealthy_rob.script(182) : _run_handler
LUA error: No such operator defined
The crash occurs at line 182, where tostring(event_data.position) is called on an X-Ray engine vector userdata. X-Ray vectors do not define __tostring. This is a known engine limitation that any STALKER modder encounters early.
The crash propagates through AP's internal pipeline: ap_consumer._process -> xbus.publish -> ap_producer_reactive. Because APS injects directly into AP's dispatch chain via ap_producer_reactive.register() and ap_consumer.register(), a crash in APS code crashes AlifePlus itself for any user who has both installed.
The "v2.7.0" version number in the mod name does not correspond to any observable development history. No git repository exists.
APS requires SISKI 0.9.5+ and accesses its internals 32 times via _G.siski.* (bus, memory, board_index, mutants, loot, fast_trading, ap_bridge, tasks). SISKI 0.9.5 is the version that is the subject of an active DMCA takedown for containing architecture copied from AlifePlus.
The dependency chain:
AlifePlus (original) --> SISKI 0.9.5 (under DMCA) --> APS v2.7.0 (this mod)
xlibs (original) --> SISKI 0.9.5 (under DMCA) --> APS v2.7.0 (this mod)
Beyond the framework and API usage documented above, APS's feature list is assembled from ideas found in existing Anomaly mods and in AlifePlus itself. The code implementations differ, but the gameplay concepts are taken directly from these sources without acknowledgment.
| APS feature | Source | Original author | What was taken |
|---|---|---|---|
| Cross-level robbery: outlaws hear about wealthy targets and send squads to rob them | Mugging Squads | Demonized | The core concept: hostile NPCs approach the player specifically to rob them based on gear/wealth. Demonized's version includes dialogue, warnings, and player choices. APS strips this to silent combat but the gameplay idea (NPCs target you for your gear) is the same. |
| Faction tension system: hostility accumulates from combat, triggers retaliatory strikes | Dynamic Faction Relations Customizer | dEmergence | The concept of tracking per-faction hostility that changes dynamically based on combat events. DFRC modifies engine faction relations directly; APS tracks a parallel "tension" score. Different system, same idea. |
| Boss/elite NPC emergence: squads earn status through survival and kills, get better gear and rank | AlifePlus itself | Damian Sirbu | AlifePlus has an elite system (ap_alife_tracker elite tracking, ap_consequence_elite_promote) and formerly had a legend system. APS's 3-tier boss system (Lieutenant/Captain/Warlord with gear injection, rank boost, PDA markers) is a direct reimplementation of AP's elite promotion concept with added RPG chrome (nickname pools, tiered gear tables). The AI was given AP's source and generated a "fancier" version of the existing feature. |
| Bounty system: factions place bounties on each other, hunter squads pursue targets | Bounty Squads Extended (concept) + AlifePlus elitekill_targeted |
Vintar0 / Damian Sirbu | The bounty concept exists in multiple Anomaly mods. APS's specific implementation (tension threshold triggers bounty placement, hunter squads dispatched via personality evaluation) mirrors AP's existing elitekill_targeted consequence pattern with a tension-driven trigger added. |
| Weather-driven behavior: emissions suppress robbery, night increases it | Dynamic NOCTURNAL Mutants | bmblbx176 | The concept of time-of-day affecting NPC behavior (night = more dangerous). APS uses the same night hours (22:00-05:00) for robbery bonuses that Nocturnal Mutants uses for spawn waves. |
| Population safeguard: prevent maps from becoming empty | Zone Customization Project (ZCP) | dph-hcl | ZCP's population factors control squad density per level. APS's population safeguard monitors the same metric and blocks outbound scripting when counts drop too low. Different mechanism, same problem being solved. |
Individually, none of these similarities would be notable — many mods solve similar problems. But the pattern is that APS's entire feature list was assembled by systematically cataloguing what already exists in the Anomaly modding ecosystem and asking an AI to generate equivalents. Nearly every feature in APS traces to an existing Anomaly mod or to AlifePlus itself. The remainder are generic RPG templates — personality traits, boss tiers, nickname pools — with no STALKER-specific grounding.
Both AlifePlus and xlibs use the PolyForm Perimeter License. The usage guide explains what is permitted.
| Requirement | Status | Detail |
|---|---|---|
| Visible credit for AlifePlus | Partial | Present in ModDB description. Not in the mod package (no LICENSE, README, or credits file). |
| Visible credit for xlibs | Partial | Same -- ModDB only. |
| No reverse engineering internals | Broken | Uses 15+ non-public internal functions. AP's internal tracing, chase system, and state manager are called directly rather than through the public API surface. |
| No automated extraction | Broken | Complete source was provided to a code generation tool. The resulting code reproduces internal conventions character-for-character. |
ap_debug.observe, ap_alife_tracker.script_squad, ap_utils.chase_start) with their public equivalents (xsquad.control_squad, xsquad.target_actor, xbus subscriptions). Add a LICENSE file to the mod. If deeper integration is needed, coordinate with the author to establish proper APIs.