AlifePlus SISKI Synergy (APS) v2.7.0: Technical Analysis

Damian Sirbu, xlibs and AlifePlus developer · 2026-04-05 · Subject: APS by forTheMonolith

ModDB: AlifePlus SISKI Synergy v2.7.0

Overview

"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.

FindingEvidenceSection
AI-generated code67 pcall wraps, hallucinated items, empty loops, mechanical uniformity in 28/28 files
Internal conventions copiedNaming, handler structure, config keys, registration loops — all from conventions.md
70+ undocumented API callsTracing, 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 testtostring(vector) at line 182 — crashes AP's pipeline for all users
Depends on DMCA'd codeRequires SISKI 0.9.5, which is under active DMCA takedown
License requirements brokenNo LICENSE file, 15+ internal functions accessed, source fed to code generator

The Mod

FieldValue
AuthorforTheMonolith (ModDB)
UploadedApril 4, 2026
Size73.51 KB (28 scripts + 2 XML)
Lines6,112
License on ModDB"Proprietary"
License file in modNone
Git repositoryNone (zip upload)
Dependenciesxlibs 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 modNone

Authorship Indicators

The mod's code exhibits numerous characteristics consistent with AI-generated output. The evidence falls into several categories.

Author's own statement

"The more logs file I can get what the more behaviour I can learn and train into the mod."

This describes an iterative process: code generation tool produces output, users test and report crashes, crash logs are fed back to generate fixes. The first real user to install the mod got a fatal engine crash (below), confirming zero pre-upload testing.

Mechanical uniformity

Every file follows the exact same template, without any of the natural variation that appears in hand-written code:

PatternCountNote
-- ===...=== section dividers28/28 filesIdentical formatting, identical spacing, identical separator length
Footer: if printf then printf('[APS] X loaded') end28/28 filesEvery file ends with this exact line. printf is always defined in Anomaly.
Header doc comment block (---)28/28 filesEvery file opens with a multi-line doc comment explaining what it does
Comments + annotations842 + 113842 comment lines + 113 @param/@return annotations in 6,112 total lines (13.8% comments, 1.8% annotations). Human STALKER mods have near-zero annotations.

Defensive coding and pcall misuse

67 pcall wraps in 6,112 lines. Wrapping calls in pcall hides the problem instead of fixing it. Two categories:

What's wrappedExists?Can pcall catch it?What actually happens
:commander_id()Yes (C++/luabind)NoEngine terminates on fault. pcall does nothing.
:distance_to()Yes (C++/luabind)NoEngine terminates on fault. pcall does nothing.
alife_create_itemYes (C++/luabind)NoEngine terminates on fault. pcall does nothing.
ap_alife_tracker.script_squadYes (AP internal)Yes, but hides real bugsSilently swallows errors in AP's state manager.
alife_create_item("outfit_exo_1")No — hallucinatedItem doesn't exist. pcall hides the failure.
alife_create_item("wpn_sig550")No — hallucinatedItem doesn't exist. pcall hides the failure.
alife_create_item("wpn_fn2000")No — hallucinatedItem doesn't exist. pcall hides the failure.
alife_create_item("wpn_gauss")Not spawnable (dialog only)Fails silently or crashes.
alife_create_item("wpn_pkm")Not spawnable (dialog only)Fails silently or crashes.
+ 14 more C++/luabind callsYesNoSame pattern — false safety.
+ ~30 Lua-side callsYesYes, but unnecessaryGuards on code that cannot fail.

The generator doesn't know the X-Ray runtime boundary: pcall cannot catch C++/luabind faults because the engine terminates the process before Lua sees the error. And for hallucinated items, pcall just hides the fact that the feature doesn't work. For comparison, SISKI has 488 pcalls at 1/59 lines.

Other indicators: 74 tostring() wraps including one on an X-Ray vector that caused the fatal crash, 125 nil-guards on values guaranteed by the code's own logic, 170 log statements (1 per 36 lines vs AlifePlus's 1 per 120), and two empty for loops that iterate over results but execute no code (vestigial from an earlier generation pass).

RPG template systems

Several gameplay systems follow textbook AI RPG design patterns with no STALKER-specific grounding:

  • 4-trait personality system (aggression, greed, caution, loyalty) with faction bias tables and random variation -- a standard AI fantasy RPG pattern
  • 3-tier boss system (Lieutenant/Captain/Warlord) with nickname pools ("Razor", "Butcher", "Ghost", "Shadow"), gear injection, rank boost -- classic AI game design template
  • Wealth scoring formula (outfit_tier * weight + artefact_count * weight + money / divisor) -- textbook numeric scoring that looks reasonable but has never been tested against actual game balance
  • Passive tension generation between 30 hardcoded faction pairs -- the AI generated a complete faction conflict matrix without understanding which factions actually interact in-game

Fabricated version number

"v2.7.0" implies a long development history with 2 major versions and 7 minor releases. No git repository exists. No prior versions exist on ModDB. The first user to install the mod got a fatal crash on basic gameplay. No version with this code could have been tested or iterated on.

ModDB readme

The ModDB description is approximately 5,500 words in English + 2,000 words in Russian. The section structure mirrors AlifePlus's own ModDB description: "How it works step by step:", numbered bullets, "What makes it realistic:", "PDA notifications:", "What you experience:". The AlifePlus readme was provided to the code generation tool alongside the source, and the AI reproduced the same organizational template for a different mod. Both language versions are semantically identical -- consistent with generation from a single source followed by machine translation.

Framework Pattern Matching

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:

4a. Naming conventions -- every layer matches

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.

WhatAlifePlus patternAPS (identical pattern)
Cause fileap_cause_stash.scriptaps_cause_wealthy.script
Consequence fileap_consequence_stash_ambush.scriptaps_consequence_wealthy_ambush.script
Cause event name"cause:stash""cause:wealthy"
Consequence event name"consequence:stash_ambush""consequence:wealthy_ambush"
MCM enabled keycause_stash_enabledcause_wealthy_enabled
MCM chance keyconsequence_stash_ambush_chanceconsequence_wealthy_rob_chance
MCM PDA keyconsequence_stash_ambush_pda_chanceconsequence_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 prefixxmcm.create_config("ap", ...)xmcm.create_config("aps", ...)
Save data keym_data.ap_tensionm_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.

4b. Handler structure -- the skeleton is identical

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.

AlifePlus: ap_consequence_stash_ambush.script
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
APS: aps_consequence_wealthy_ambush.script
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().

4c. Cause registration -- identical loop

AlifePlus: ap_cause_stash.script :on_game_start
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
APS: aps_cause_wealthy.script :248-280
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

4d. Consequence registration -- identical call

AlifePlus: ap_consequence_stash_ambush.script
ap_consumer.register(
    CONSEQUENCE.STASH_AMBUSH, {
        event = CAUSE.STASH,
        priority = 20,
        condition = function()
            return cfg
              .consequence_stash_ambush_enabled
        end,
    },
    _handler
)
APS: aps_consequence_wealthy_ambush.script :92
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.

4e. Chase configuration -- identical table keys

AlifePlus: ap_consequence_squadkill_revenge
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)
APS: aps_consequence_wealthy_rob.script :28-36
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.

4f. MCM config creation -- one character different

AlifePlus: ap_mcm.script :508
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
APS: aps_mcm.script :74-79
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.

Framework API Usage

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.

5a. Documented API (Level 1 and 2 — these are fine to use)

The usage guide documents these integration paths:

FunctionLevelPurpose
xbus.subscribe()Level 1Listen to cause events
ap_producer_reactive.register()Level 2Register cause predicates
ap_consumer.register()Level 2Register consequence handlers
ap_const.RADIANT_CALLBACKSLevel 2Callback array for registration loops
ap_const.RESULT.*Level 2Handler return codes

APS uses these calls correctly. This is not the issue.

5b. Undocumented internals (Level 3 — not part of any public API)

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:

FunctionCallsIn public API?
ap_debug.observe() — wrap logic in trace context10+No
ap_debug.trace() / result_squads() — create traces, build results8No
ap_debug.debug() / warn() — internal logging15+No

State manager (15 calls) — AP's internal squad tracking and elite system:

FunctionCallsIn public API?
ap_alife_tracker.script_squad() — script squads to destinations6No
ap_alife_tracker.script_actor_target() — lock squads onto player2No
ap_alife_tracker.register_arrival_handler() — arrival callbacks1No
ap_alife_tracker.get_elite_level() / is_elite() — elite queries6No

Internal helpers and data (22+ calls) — chase system, search wrappers, pipeline internals:

FunctionCallsIn public API?
ap_utils.chase_start() / chase_register() — chase mechanics2No
ap_utils.find_squads_observed() / find_smart_observed() — traced search6No
ap_utils.event_publish() / increment_cause_counter() — bypass cause flow4No
ap_utils.send_pda() / get_location_description() — PDA formatting7No
ap_const.RADIUS_CLOSE_MAX / DISTANT_MAX — distance thresholds5No
ap_data.community_stalker / community_predator — faction filters6No

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.)

How to do this properly: The guide documents three integration levels. Level 1 (xbus subscriptions) and Level 2 (cause/consequence registration) cover most use cases without touching internals. APS uses Level 2 registration correctly but also reaches directly into Level 3 internals (tracker, chase system, tracing pipeline). These APIs are not stable and will change.

Design Language and ModDB Description

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:

WhereAPS 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 patternAlifePlus readmeAPS 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 notificationsPDA 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 languageEnglish onlyEnglish + 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.

Runtime Behavior

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.

SISKI Dependency

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)

Feature Set Cloning

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 featureSourceOriginal authorWhat 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.

On ideas vs code: Gameplay ideas are not copyrightable, and multiple mods can implement similar features. But there is a difference between independently arriving at the same idea and feeding existing mods' source code into an AI to generate equivalents. The original authors — Demonized, Vintar0, xcvb, dEmergence, bmblbx176, dph-hcl — spent real time solving these problems. None are credited in APS.

License

Both AlifePlus and xlibs use the PolyForm Perimeter License. The usage guide explains what is permitted.

RequirementStatusDetail
Visible credit for AlifePlusPartialPresent in ModDB description. Not in the mod package (no LICENSE, README, or credits file).
Visible credit for xlibsPartialSame -- ModDB only.
No reverse engineering internalsBrokenUses 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 extractionBrokenComplete source was provided to a code generation tool. The resulting code reproduces internal conventions character-for-character.
How to make this compliant: The features APS implements could work within the documented integration path. Replace internal API calls (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.