From 64f5e780b216d766bea9515f769f10d4749ea444 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E8=88=AA=E5=91=B3=E9=BA=BB=E9=85=B1?=
<93972760+TaranDahl@users.noreply.github.com>
Date: Tue, 10 Mar 2026 23:50:32 +0800
Subject: [PATCH 1/5] core
core
core
Update AttachEffect.cpp
update
Update README.md
Update docs/Whats-New.md
update
Potential fix for pull request finding
Update Macro.h
Create .po entries
udpate
update
Update Phobos.vcxproj
Co-Authored-By: Noble Fish <89088785+DeathFishAtEase@users.noreply.github.com>
Co-Authored-By: Copilot <175728472+Copilot@users.noreply.github.com>
Co-Authored-By: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
---
.gitignore | 2 +
CREDITS.md | 1 +
Phobos.vcxproj | 8 ++
README.md | 4 +
docs/Whats-New.md | 1 +
docs/locale/zh_CN/LC_MESSAGES/CREDITS.po | 3 +
docs/locale/zh_CN/LC_MESSAGES/Whats-New.po | 5 +
docs/locale/zh_CN/LC_MESSAGES/index.po | 11 +++
src/Interop/AttachEffect.cpp | 109 +++++++++++++++++++++
src/Interop/AttachEffect.h | 58 +++++++++++
src/Interop/BulletExt.cpp | 16 +++
src/Interop/BulletExt.h | 7 ++
src/Interop/EventExt.cpp | 10 ++
src/Interop/EventExt.h | 6 ++
src/Interop/TechnoExt.cpp | 7 ++
src/Interop/TechnoExt.h | 26 +++++
src/Utilities/Macro.h | 7 ++
17 files changed, 281 insertions(+)
create mode 100644 src/Interop/AttachEffect.cpp
create mode 100644 src/Interop/AttachEffect.h
create mode 100644 src/Interop/BulletExt.cpp
create mode 100644 src/Interop/BulletExt.h
create mode 100644 src/Interop/EventExt.cpp
create mode 100644 src/Interop/EventExt.h
create mode 100644 src/Interop/TechnoExt.cpp
create mode 100644 src/Interop/TechnoExt.h
diff --git a/.gitignore b/.gitignore
index c7875aa55b..874d93ae3c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -27,3 +27,5 @@ out
# Python virtual environment for docs
.venv/
+
+debug.log
diff --git a/CREDITS.md b/CREDITS.md
index 99727ed0e0..3a86547c6f 100644
--- a/CREDITS.md
+++ b/CREDITS.md
@@ -737,6 +737,7 @@ This page lists all the individual contributions to the project by their author.
- Miners back to work when ore regenerated
- Allow disable an over-optimization in targeting
- Extra threat
+ - Export interface for external call
- **solar-III (凤九歌)**
- Target scanning delay customization (documentation)
- Skip target scanning function calling for unarmed technos (documentation)
diff --git a/Phobos.vcxproj b/Phobos.vcxproj
index d45a6ac91f..2adaf9e2fa 100644
--- a/Phobos.vcxproj
+++ b/Phobos.vcxproj
@@ -30,6 +30,10 @@
+
+
+
+
@@ -221,6 +225,10 @@
+
+
+
+
diff --git a/README.md b/README.md
index 52c77fe94d..e99a76ab2c 100644
--- a/README.md
+++ b/README.md
@@ -95,6 +95,10 @@ Credits
This project was founded by [@Belonit](https://github.com/Belonit) (Gluk-v48) and [@Metadorius](https://github.com/Metadorius) (Kerbiter) in 2020, with the first public stable release in 2021. Since then it has grown into a large community project with many contributors and maintainers.
+### Interoperability
+
+Phobos has opened the external interfaces of some key components. If you are also developing your own engine extension and wish to use Phobos at the same time, please check out [Interop](https://github.com/Phobos-developers/Phobos/tree/develop/src/Interop).
+
### Maintenance crew
Maintenance crew consists of experienced Phobos contributors who are recognized and given the permission to maintain and shape the project to the extent of their permissions.
diff --git a/docs/Whats-New.md b/docs/Whats-New.md
index 23bf39f7e2..9a1358049e 100644
--- a/docs/Whats-New.md
+++ b/docs/Whats-New.md
@@ -691,6 +691,7 @@ Fixes / interactions with other extensions:
- Fixed the initial direction of building placed by Ares's UnitDelivery superweapon (by NetsuNegi)
- [Customize whether transport can kept or kill passengers when driver has been killed](New-or-Enhanced-Logics.md#customize-whether-transport-can-kept-or-kill-passengers-when-driver-has-been-killed) (by NetsuNegi)
- Fixed a bug where passengers created by the InitialPayload logic or TeamType with `Full=true` would fail to fire when the transport unit with `OpenTopped=yes` moved to an area that the passengers' `MovementZone` cannot move into (by NetsuNegi)
+- [Export interface for external call](index.md#interoperability) (by TaranDahl)
```
diff --git a/docs/locale/zh_CN/LC_MESSAGES/CREDITS.po b/docs/locale/zh_CN/LC_MESSAGES/CREDITS.po
index 11ac79c191..854aae154e 100644
--- a/docs/locale/zh_CN/LC_MESSAGES/CREDITS.po
+++ b/docs/locale/zh_CN/LC_MESSAGES/CREDITS.po
@@ -2449,6 +2449,9 @@ msgstr "修复了非超时空矿车在矿区无矿后即便矿石已重新生成
msgid "Miners back to work when ore regenerated"
msgstr "矿车在矿石再生后返回工作"
+msgid "Export interface for external call"
+msgstr "用于外部调用的导出接口"
+
msgid "**solar-III (凤九歌)**"
msgstr "**solar-III(凤九歌)**"
diff --git a/docs/locale/zh_CN/LC_MESSAGES/Whats-New.po b/docs/locale/zh_CN/LC_MESSAGES/Whats-New.po
index 5a38a291ca..bc96b13e28 100644
--- a/docs/locale/zh_CN/LC_MESSAGES/Whats-New.po
+++ b/docs/locale/zh_CN/LC_MESSAGES/Whats-New.po
@@ -2450,6 +2450,11 @@ msgstr ""
"修复了 `OpenTopped=yes` 的运输工具移动到乘客 `MovementZone` 所不支持的区域时由初始载员逻辑或勾选了 "
"`预装载小队` 的作战小队创建的乘客无法开火的 Bug(by NetsuNegi)"
+msgid ""
+"[Export interface for external call](index.md#interoperability) (by "
+"TaranDahl)"
+msgstr "用于外部调用的导出接口(by TaranDahl)"
+
msgid "0.4.0.3"
msgstr "0.4.0.3"
diff --git a/docs/locale/zh_CN/LC_MESSAGES/index.po b/docs/locale/zh_CN/LC_MESSAGES/index.po
index a158b9f6f9..6b2bd6d582 100644
--- a/docs/locale/zh_CN/LC_MESSAGES/index.po
+++ b/docs/locale/zh_CN/LC_MESSAGES/index.po
@@ -384,6 +384,17 @@ msgstr ""
"[@Metadorius](https://github.com/Metadorius)(Kerbiter)于 2020 年创立,2021 "
"年首次发布稳定版。此后它已发展成为一个由众多贡献者和维护者组成的大型社区项目。"
+msgid "Interoperability"
+msgstr "互操作性"
+
+msgid ""
+"Phobos has opened the external interfaces of some key components. If you "
+"are also developing your own engine extension and wish to use Phobos at "
+"the same time, please check out [Interop](https://github.com/Phobos-"
+"developers/Phobos/tree/develop/src/Interop)."
+msgstr "Phobos为一些关键组件打开了外部接口。如果你也在开发自己的引擎扩展并且希望同时使用Phobos,"
+"请查阅[Interop](https://github.com/Phobos-developers/Phobos/tree/develop/src/Interop)。"
+
msgid "Maintenance crew"
msgstr "维护团队"
diff --git a/src/Interop/AttachEffect.cpp b/src/Interop/AttachEffect.cpp
new file mode 100644
index 0000000000..681a1a96d7
--- /dev/null
+++ b/src/Interop/AttachEffect.cpp
@@ -0,0 +1,109 @@
+
+#include "AttachEffect.h"
+#include "New/Entity/AttachEffectClass.h"
+#include "New/Type/AttachEffectTypeClass.h"
+
+DEFINE_EXPORT(int, AE_Attach,
+ TechnoClass* pTarget,
+ HouseClass* pInvokerHouse,
+ TechnoClass* pInvoker,
+ AbstractClass* pSource,
+ const char** effectTypeNames,
+ int typeCount,
+ int durationOverride,
+ int delay,
+ int initialDelay,
+ int recreationDelay
+)
+{
+ if (!pTarget || !effectTypeNames || typeCount <= 0)
+ return 0;
+
+ AEAttachInfoTypeClass attachInfo;
+
+ for (int i = 0; i < typeCount; i++)
+ {
+ if (effectTypeNames[i])
+ {
+ if (auto pType = AttachEffectTypeClass::Find(effectTypeNames[i]))
+ attachInfo.AttachTypes.push_back(pType);
+ }
+ }
+
+ if (attachInfo.AttachTypes.empty())
+ return 0;
+
+ if (durationOverride != 0)
+ attachInfo.DurationOverrides.push_back(durationOverride);
+
+ if (delay >= 0)
+ attachInfo.Delays.push_back(delay);
+
+ if (initialDelay >= 0)
+ attachInfo.InitialDelays.push_back(initialDelay);
+
+ if (recreationDelay >= -1)
+ attachInfo.RecreationDelays.push_back(recreationDelay);
+
+ return AttachEffectClass::Attach(pTarget, pInvokerHouse, pInvoker, pSource, attachInfo);
+}
+
+DEFINE_EXPORT(int, AE_Detach,
+ TechnoClass* pTarget,
+ const char** effectTypeNames,
+ int typeCount
+)
+{
+ if (!pTarget || !effectTypeNames || typeCount <= 0)
+ return 0;
+
+ AEAttachInfoTypeClass detachInfo;
+
+ for (int i = 0; i < typeCount; i++)
+ {
+ if (effectTypeNames[i])
+ {
+ if (auto pType = AttachEffectTypeClass::Find(effectTypeNames[i]))
+ detachInfo.RemoveTypes.push_back(pType);
+ }
+ }
+
+ if (detachInfo.RemoveTypes.empty())
+ return 0;
+
+ return AttachEffectClass::Detach(pTarget, detachInfo);
+}
+
+DEFINE_EXPORT(int, AE_DetachByGroups,
+ TechnoClass* pTarget,
+ const char** groupNames,
+ int groupCount
+)
+{
+ if (!pTarget || !groupNames || groupCount <= 0)
+ return 0;
+
+ AEAttachInfoTypeClass detachInfo;
+
+ for (int i = 0; i < groupCount; i++)
+ {
+ if (groupNames[i])
+ detachInfo.RemoveGroups.push_back(groupNames[i]);
+ }
+
+ if (detachInfo.RemoveGroups.empty())
+ return 0;
+
+ return AttachEffectClass::DetachByGroups(pTarget, detachInfo);
+}
+
+DEFINE_EXPORT(void, AE_TransferEffects,
+ TechnoClass* pSource,
+ TechnoClass* pTarget
+)
+{
+ if (!pSource || !pTarget)
+ return;
+
+ AttachEffectClass::TransferAttachedEffects(pSource, pTarget);
+}
diff --git a/src/Interop/AttachEffect.h b/src/Interop/AttachEffect.h
new file mode 100644
index 0000000000..40c40a84f2
--- /dev/null
+++ b/src/Interop/AttachEffect.h
@@ -0,0 +1,58 @@
+#pragma once
+
+#include
+#include
+#include
+// Interop exports for AttachEffect operations.
+// C# P/Invoke examples (simplified):
+// ```csharp
+// [DllImport("Phobos.dll", CallingConvention = CallingConvention.StdCall, EntryPoint = "AE_Attach")]
+// public static extern int AE_Attach(IntPtr pTarget, IntPtr pInvokerHouse, IntPtr pInvoker, IntPtr pSource, IntPtr effectTypeNames, int typeCount, int durationOverride, int delay, int initialDelay, int recreationDelay);
+//
+// [DllImport("Phobos.dll", CallingConvention = CallingConvention.StdCall, EntryPoint = "AE_Detach")]
+// public static extern int AE_Detach(IntPtr pTarget, IntPtr effectTypeNames, int typeCount);
+// ```
+
+#include "Utilities/Macro.h"
+
+///
+/// Attaches AttachEffect instances to a target unit.
+///
+DEFINE_EXPORT(int, AE_Attach,
+ TechnoClass* pTarget,
+ HouseClass* pInvokerHouse,
+ TechnoClass* pInvoker,
+ AbstractClass* pSource,
+ const char** effectTypeNames,
+ int typeCount,
+ int durationOverride,
+ int delay,
+ int initialDelay,
+ int recreationDelay
+);
+
+///
+/// Removes AttachEffect instances matching given types from a unit.
+///
+DEFINE_EXPORT(int, AE_Detach,
+ TechnoClass* pTarget,
+ const char** effectTypeNames,
+ int typeCount
+);
+
+///
+/// Removes AttachEffect instances matching given groups from a unit.
+///
+DEFINE_EXPORT(int, AE_DetachByGroups,
+ TechnoClass* pTarget,
+ const char** groupNames,
+ int groupCount
+);
+
+///
+/// Transfers AttachEffect instances from one unit to another.
+///
+DEFINE_EXPORT(void, AE_TransferEffects,
+ TechnoClass* pSource,
+ TechnoClass* pTarget
+);
diff --git a/src/Interop/BulletExt.cpp b/src/Interop/BulletExt.cpp
new file mode 100644
index 0000000000..c73d6dac9a
--- /dev/null
+++ b/src/Interop/BulletExt.cpp
@@ -0,0 +1,16 @@
+
+#include "BulletExt.h"
+
+DEFINE_EXPORT(bool, Bullet_SetFirerOwner, BulletClass* pBullet, HouseClass* pHouse)
+{
+ if (!pBullet)
+ return false;
+
+ const auto pBulletExt = BulletExt::ExtMap.TryFind(pBullet);
+
+ if (!pBulletExt)
+ return false;
+
+ pBulletExt->FirerHouse = pHouse;
+ return true;
+}
diff --git a/src/Interop/BulletExt.h b/src/Interop/BulletExt.h
new file mode 100644
index 0000000000..3fe4a0130e
--- /dev/null
+++ b/src/Interop/BulletExt.h
@@ -0,0 +1,7 @@
+#pragma once
+
+#include "Utilities/Macro.h"
+#include
+#include
+
+DEFINE_EXPORT(bool, Bullet_SetFirerOwner, BulletClass* pBullet, HouseClass* pHouse);
diff --git a/src/Interop/EventExt.cpp b/src/Interop/EventExt.cpp
new file mode 100644
index 0000000000..61576116d9
--- /dev/null
+++ b/src/Interop/EventExt.cpp
@@ -0,0 +1,10 @@
+#include "EventExt.h"
+
+DEFINE_EXPORT(bool, EventExt_AddEvent, EventExt* pEventExt)
+{
+ if (pEventExt)
+ {
+ return pEventExt->AddEvent();
+ }
+ return false;
+}
diff --git a/src/Interop/EventExt.h b/src/Interop/EventExt.h
new file mode 100644
index 0000000000..d4c91c519c
--- /dev/null
+++ b/src/Interop/EventExt.h
@@ -0,0 +1,6 @@
+#pragma once
+
+#include "Utilities/Macro.h"
+#include
+
+DEFINE_EXPORT(bool, EventExt_AddEvent, EventExt* pEventExt);
diff --git a/src/Interop/TechnoExt.cpp b/src/Interop/TechnoExt.cpp
new file mode 100644
index 0000000000..643c081629
--- /dev/null
+++ b/src/Interop/TechnoExt.cpp
@@ -0,0 +1,7 @@
+#include "TechnoExt.h"
+#include
+
+DEFINE_EXPORT(bool, ConvertToType_Phobos, FootClass* pThis, TechnoTypeClass* toType)
+{
+ return TechnoExt::ConvertToType(pThis, toType);
+}
diff --git a/src/Interop/TechnoExt.h b/src/Interop/TechnoExt.h
new file mode 100644
index 0000000000..c956c0551d
--- /dev/null
+++ b/src/Interop/TechnoExt.h
@@ -0,0 +1,26 @@
+#pragma once
+
+
+// Interop exports for external engine extensions.
+// C# P/Invoke example:
+// ```csharp
+// [DllImport("Phobos.dll", CallingConvention = CallingConvention.StdCall, EntryPoint = "ConvertToType_Phobos")]
+// [return: MarshalAs(UnmanagedType.I1)]
+// public static extern bool ConvertToType_Phobos(IntPtr pThis, IntPtr toType);
+// ```
+// Use `IntPtr` or `unsafe` pointers in managed code. The function below exposes
+// the same signature as used internally (FootClass*, TechnoTypeClass*).
+
+#include "Utilities/Macro.h"
+
+#include
+
+#include
+
+///
+/// Converts a unit to a different type.
+///
+/// Pointer to the FootClass instance to convert
+/// Pointer to the target TechnoTypeClass
+/// true if conversion was successful, false otherwise
+DEFINE_EXPORT(bool, ConvertToType_Phobos, FootClass* pThis, TechnoTypeClass* toType);
diff --git a/src/Utilities/Macro.h b/src/Utilities/Macro.h
index a0bdefff07..d1f756b5fc 100644
--- a/src/Utilities/Macro.h
+++ b/src/Utilities/Macro.h
@@ -3,6 +3,13 @@
#include
#include "Patch.h"
+// Export / calling-convention macros for public C ABI functions
+//
+// Usage:
+// DEFINE_EXPORT(return_type, Name, arglist...)
+// Expands to: PHOBOS_EXPORT return_type PHOBOS_DECL Name(arglist)
+#define DEFINE_EXPORT(ret, name, ...) extern "C" __declspec(dllexport) ret __stdcall name(__VA_ARGS__)
+
#define GET_REGISTER_STATIC_TYPE(type, dst, reg) static type dst; _asm { mov dst, reg }
template
From 25bfb775cd599e790a3ad17a6184a1f0585b7cf9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E8=88=AA=E5=91=B3=E9=BA=BB=E9=85=B1?=
<93972760+TaranDahl@users.noreply.github.com>
Date: Sun, 29 Mar 2026 16:26:24 +0800
Subject: [PATCH 2/5] versioning
---
Phobos.vcxproj | 2 +
README.md | 2 +-
docs/General-Info.md | 9 ++
docs/Interoperability.md | 234 +++++++++++++++++++++++++++++++++++++++
docs/index.md | 1 +
src/Interop/Version.cpp | 21 ++++
src/Interop/Version.h | 24 ++++
7 files changed, 292 insertions(+), 1 deletion(-)
create mode 100644 docs/Interoperability.md
create mode 100644 src/Interop/Version.cpp
create mode 100644 src/Interop/Version.h
diff --git a/Phobos.vcxproj b/Phobos.vcxproj
index 2adaf9e2fa..f49a3600ed 100644
--- a/Phobos.vcxproj
+++ b/Phobos.vcxproj
@@ -34,6 +34,7 @@
+
@@ -229,6 +230,7 @@
+
diff --git a/README.md b/README.md
index e99a76ab2c..4c13c31b19 100644
--- a/README.md
+++ b/README.md
@@ -97,7 +97,7 @@ This project was founded by [@Belonit](https://github.com/Belonit) (Gluk-v48) an
### Interoperability
-Phobos has opened the external interfaces of some key components. If you are also developing your own engine extension and wish to use Phobos at the same time, please check out [Interop](https://github.com/Phobos-developers/Phobos/tree/develop/src/Interop).
+Phobos has opened the external interfaces of some key components. If you are also developing your own engine extension and wish to use Phobos at the same time, please check out [Interoperability](/docs/Interoperability.md).
### Maintenance crew
diff --git a/docs/General-Info.md b/docs/General-Info.md
index 5b1dc16cde..85fabcc1ef 100644
--- a/docs/General-Info.md
+++ b/docs/General-Info.md
@@ -28,3 +28,12 @@ Phobos fully supports saving and loading thanks to prototype code from publicly
While Phobos is standalone, it is designed to be used alongside [Ares](https://ares.strategy-x.com) and [CnCNet5 spawner](https://github.com/CnCNet/cncnet-for-ares). Adding new features or improving existing ones is done with compatibility with those in mind.
While we would also like to support HAres we can't guarantee compatibility with it due to the separation of it's userbase and developers from international community. We welcome any help on the matter though!
+
+### Interop API Compatibility
+
+Phobos exports a set of C/C++ interop functions for external tools and mod loaders (see [Interoperability](Interoperability.md#api-version-tracking)).
+
+- **Version discovery:** External code should call `GetInteropBuildNumber()` before relying on specific API features.
+- **API lifecycle:** All exported APIs are labeled with their availability range [startBuild, endBuild) in the Interoperability documentation.
+- **Deprecated APIs:** When an API is deprecated, its function stub remains but calling it triggers a fatal error with a clear message. This ensures broken integrations fail fast with actionable guidance.
+- **Recommended usage:** Check the API availability table in [Interoperability](Interoperability.md#api-availability-table) to ensure your target API is supported in the Phobos build you're using.
diff --git a/docs/Interoperability.md b/docs/Interoperability.md
new file mode 100644
index 0000000000..98faa464fa
--- /dev/null
+++ b/docs/Interoperability.md
@@ -0,0 +1,234 @@
+
+# Interoperability
+
+This page documents the exported interfaces in [Interop](https://github.com/Phobos-developers/Phobos/tree/develop/src/Interop).
+
+## API Version Tracking
+
+Phobos tracks Interop API versions using build numbers. Each exported API is labeled with its availability range:
+- **[startBuild, endBuild)**: API is available from build `startBuild` up to (but not including) build `endBuild`.
+- **[startBuild, ∞)**: API is still active and has no planned removal date.
+
+### GetInteropBuildNumber
+
+```cpp
+unsigned int GetInteropBuildNumber()
+```
+
+Returns the current Phobos Interop build number. Use this to detect which APIs are available.
+
+**Availability:** [48, ∞)
+
+### Deprecated API Handling
+
+When an API is deprecated, its function stub is retained but with a fatal error handler. The calling application will receive a descriptive error message and must stop execution. This ensures:
+1. Broken links are immediately detected at runtime (not a crash).
+2. Clear messaging guides developers to the replacement API.
+3. Migration timeline is documented in the error message.
+
+**Example** (not currently in use):
+
+```cpp
+// DEPRECATED: Removed after build 52. Use NewAPI instead.
+// Calling this will trigger a fatal error with the message:
+// "SomeOldAPI_Deprecated has been removed (was available until build 52). Please use NewAPI."
+```
+
+### API Availability Table
+
+| API | Signature | Availability | Notes |
+|-----|-----------|---------------|-------|
+| AE_Attach | `int AE_Attach(...)` | [48, ∞) | Still active |
+| AE_Detach | `int AE_Detach(...)` | [48, ∞) | Still active |
+| AE_DetachByGroups | `int AE_DetachByGroups(...)` | [48, ∞) | Still active |
+| AE_TransferEffects | `void AE_TransferEffects(...)` | [48, ∞) | Still active |
+| ConvertToType_Phobos | `bool ConvertToType_Phobos(...)` | [48, ∞) | Still active |
+| Bullet_SetFirerOwner | `bool Bullet_SetFirerOwner(...)` | [48, ∞) | Still active |
+| EventExt_AddEvent | `bool EventExt_AddEvent(...)` | [48, ∞) | Still active |
+
+## New type and entity
+
+### AttachEffect
+
+AttachEffect interop APIs allow external code to attach, detach, and transfer attach effects on units.
+
+#### AE_Attach
+
+```cpp
+int AE_Attach(
+ TechnoClass* pTarget,
+ HouseClass* pInvokerHouse,
+ TechnoClass* pInvoker,
+ AbstractClass* pSource,
+ const char** effectTypeNames,
+ int typeCount,
+ int durationOverride,
+ int delay,
+ int initialDelay,
+ int recreationDelay
+)
+```
+
+Attaches one or more AttachEffect types to the target.
+
+**Availability:** [48, ∞)
+
+- Parameters:
+ - pTarget: Target unit to receive effects.
+ - pInvokerHouse: Invoker house context.
+ - pInvoker: Invoker techno context.
+ - pSource: Optional source object context.
+ - effectTypeNames: Array of AttachEffect type names.
+ - typeCount: Number of entries in effectTypeNames.
+ - durationOverride: If non-zero, duration override is applied.
+ - delay: If >= 0, delay override is applied.
+ - initialDelay: If >= 0, initial delay override is applied.
+ - recreationDelay: If >= -1, recreation delay override is applied.
+- Returns:
+ - > 0 on success (value returned by internal attach routine).
+ - 0 on failure.
+- Fails when:
+ - pTarget is null.
+ - effectTypeNames is null.
+ - typeCount <= 0.
+ - No valid effect type name resolves to an existing AttachEffectType.
+
+#### AE_Detach
+
+```cpp
+int AE_Detach(
+ TechnoClass* pTarget,
+ const char** effectTypeNames,
+ int typeCount
+)
+```
+
+Detaches effects by explicit effect type names.
+
+- Parameters:
+ - pTarget: Target unit to remove effects from.
+ - effectTypeNames: Array of AttachEffect type names to remove.
+ - typeCount: Number of entries in effectTypeNames.
+**Availability:** [48, ∞)
+- Returns:
+ - > 0 on success (value returned by internal detach routine).
+ - 0 on failure.
+- Fails when:
+ - pTarget is null.
+ - effectTypeNames is null.
+ - typeCount <= 0.
+ - No valid effect type name resolves to an existing AttachEffectType.
+
+#### AE_DetachByGroups
+
+```cpp
+int AE_DetachByGroups(
+ TechnoClass* pTarget,
+ const char** groupNames,
+ int groupCount
+)
+```
+
+Detaches effects by AttachEffect group name.
+
+- Parameters:
+ - pTarget: Target unit to remove effects from.
+ - groupNames: Array of group names.
+ - groupCount: Number of entries in groupNames.
+**Availability:** [48, ∞)
+- Returns:
+ - > 0 on success (value returned by internal detach-by-group routine).
+ - 0 on failure.
+- Fails when:
+ - pTarget is null.
+ - groupNames is null.
+ - groupCount <= 0.
+ - groupNames contains no non-null entries.
+
+#### AE_TransferEffects
+
+```cpp
+void AE_TransferEffects(
+ TechnoClass* pSource,
+ TechnoClass* pTarget
+)
+```
+
+Transfers all attached effects from source to target.
+
+- Parameters:
+ - pSource: Source unit.
+ - pTarget: Target unit.
+
+**Availability:** [48, ∞)
+
+- Behavior:
+ - No operation if pSource or pTarget is null.
+
+## Vanilla class extension
+
+### TechnoExt
+
+#### ConvertToType_Phobos
+
+```cpp
+bool ConvertToType_Phobos(FootClass* pThis, TechnoTypeClass* toType)
+```
+
+Converts a FootClass instance to another TechnoType.
+
+- Parameters:
+ - pThis: Unit to convert.
+ - toType: Destination TechnoType.
+
+**Availability:** [48, ∞)
+
+- Returns:
+ - true if conversion succeeds.
+ - false if conversion fails.
+- Notes:
+ - This API forwards directly to TechnoExt::ConvertToType.
+
+### BulletExt
+
+#### Bullet_SetFirerOwner
+
+```cpp
+bool Bullet_SetFirerOwner(BulletClass* pBullet, HouseClass* pHouse)
+```
+
+Updates the recorded firer house for a bullet extension.
+
+- Parameters:
+ - pBullet: Bullet instance.
+ - pHouse: New firer house (can be null if caller intentionally clears ownership).
+
+**Availability:** [48, ∞)
+
+- Returns:
+ - true if the bullet extension is found and updated.
+ - false on failure.
+- Fails when:
+ - pBullet is null.
+ - No BulletExt entry exists for pBullet.
+
+### EventExt
+
+#### EventExt_AddEvent
+
+```cpp
+bool EventExt_AddEvent(EventExt* pEventExt)
+```
+
+Invokes AddEvent on an EventExt object.
+
+- Parameters:
+ - pEventExt: Event extension instance.
+
+**Availability:** [48, ∞)
+
+- Returns:
+ - false if pEventExt is null.
+ - Otherwise, returns the result of pEventExt->AddEvent().
+
+
diff --git a/docs/index.md b/docs/index.md
index 6fd2d8e666..45f870286e 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -16,6 +16,7 @@ New / Enhanced Logics
Fixed / Improved Logics
AI Scripting and Mapping
User Interface
+Interoperability
Miscellanous
```
diff --git a/src/Interop/Version.cpp b/src/Interop/Version.cpp
new file mode 100644
index 0000000000..dd0b127ab1
--- /dev/null
+++ b/src/Interop/Version.cpp
@@ -0,0 +1,21 @@
+#include "Version.h"
+#include "../Phobos.version.h"
+#include "Utilities/Debug.h"
+
+DEFINE_EXPORT(unsigned int, GetInteropBuildNumber)
+{
+ return BUILD_NUMBER;
+}
+
+// ============================================================================
+// Deprecated API implementation example (commented out for future use)
+// ============================================================================
+/*
+DEFINE_EXPORT(int, SomeOldAPI_Deprecated, int param1)
+{
+ // Trigger fatal error with explanation.
+ Debug::Fatal("SomeOldAPI_Deprecated has been removed (was available until build 52). "
+ "Please update your code to use the new API.");
+ return 0; // unreachable
+}
+*/
diff --git a/src/Interop/Version.h b/src/Interop/Version.h
new file mode 100644
index 0000000000..1159935e09
--- /dev/null
+++ b/src/Interop/Version.h
@@ -0,0 +1,24 @@
+#pragma once
+
+#include "Utilities/Macro.h"
+
+///
+/// Returns the current Phobos Interop build number.
+/// Each exported API is annotated with availability range [startBuild, endBuild).
+/// If endBuild == 0, the API is still active.
+///
+DEFINE_EXPORT(unsigned int, GetInteropBuildNumber);
+
+// ============================================================================
+// Deprecated API example (commented out for future use)
+// When an API reaches end-of-life, keep its function stub but call fatal error.
+// ============================================================================
+/*
+///
+/// DEPRECATED: Removed after build 52. Use SomeNewAPI instead.
+/// Calling this function will trigger a fatal error.
+///
+DEFINE_EXPORT(int, SomeOldAPI_Deprecated,
+ int param1
+);
+*/
From 97df101675f77266b0e354dd2365543eafeb195f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E8=88=AA=E5=91=B3=E9=BA=BB=E9=85=B1?=
<93972760+TaranDahl@users.noreply.github.com>
Date: Sun, 29 Mar 2026 17:07:30 +0800
Subject: [PATCH 3/5] Update pr-doc-checker.yml
---
.github/workflows/pr-doc-checker.yml | 1 +
1 file changed, 1 insertion(+)
diff --git a/.github/workflows/pr-doc-checker.yml b/.github/workflows/pr-doc-checker.yml
index 90a22d82ef..4b7e86e567 100644
--- a/.github/workflows/pr-doc-checker.yml
+++ b/.github/workflows/pr-doc-checker.yml
@@ -77,6 +77,7 @@ jobs:
-e ^docs/Fixed-or-Improved-Logics.md$ \
-e ^docs/AI-Scripting-and-Mapping.md$ \
-e ^docs/User-Interface.md$ \
+ -e ^docs/Interoperability.md$ \
-e ^docs/Miscellanous.md$ \
)
then
From de1f4087c3644e7da24379ab2541e209e9007bc0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E8=88=AA=E5=91=B3=E9=BA=BB=E9=85=B1?=
<93972760+TaranDahl@users.noreply.github.com>
Date: Sun, 29 Mar 2026 17:45:05 +0800
Subject: [PATCH 4/5] Update README.md
Co-authored-by: Noble Fish <89088785+DeathFishAtEase@users.noreply.github.com>
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 4c13c31b19..e56af57c5f 100644
--- a/README.md
+++ b/README.md
@@ -97,7 +97,7 @@ This project was founded by [@Belonit](https://github.com/Belonit) (Gluk-v48) an
### Interoperability
-Phobos has opened the external interfaces of some key components. If you are also developing your own engine extension and wish to use Phobos at the same time, please check out [Interoperability](/docs/Interoperability.md).
+Phobos has opened the external interfaces of some key components. If you are also developing your own engine extension and wish to use Phobos at the same time, please check out [Interoperability](Interoperability.md).
### Maintenance crew
From b0a4ce6e8cba4059bde846d963df0108574474ba Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E8=88=AA=E5=91=B3=E9=BA=BB=E9=85=B1?=
<93972760+TaranDahl@users.noreply.github.com>
Date: Thu, 9 Apr 2026 22:14:39 +0800
Subject: [PATCH 5/5] Callback
---
src/Ext/Techno/Body.h | 2 +-
src/Ext/Techno/Hooks.TargetEvaluation.cpp | 6 ++++++
src/Interop/TechnoExt.cpp | 11 +++++++++++
src/Interop/TechnoExt.h | 13 ++++++++++++-
src/Utilities/Macro.h | 6 ++++++
5 files changed, 36 insertions(+), 2 deletions(-)
diff --git a/src/Ext/Techno/Body.h b/src/Ext/Techno/Body.h
index bfc21c09d1..82b1d62b50 100644
--- a/src/Ext/Techno/Body.h
+++ b/src/Ext/Techno/Body.h
@@ -1,4 +1,4 @@
-#pragma once
+#pragma once
#include
#include
diff --git a/src/Ext/Techno/Hooks.TargetEvaluation.cpp b/src/Ext/Techno/Hooks.TargetEvaluation.cpp
index 4ffddf15c2..85ad897768 100644
--- a/src/Ext/Techno/Hooks.TargetEvaluation.cpp
+++ b/src/Ext/Techno/Hooks.TargetEvaluation.cpp
@@ -1,4 +1,5 @@
#include "Body.h"
+#include "Interop/TechnoExt.h"
// Cursor & target acquisition stuff not directly tied to other features can go here.
@@ -506,6 +507,11 @@ DEFINE_HOOK(0x70CF87, TechnoClass_ThreatCoefficient_CanAttackMeThreatBonus, 0x9)
};
ApplyLastTargetDistanceBonus();
+ for (auto const& cb : TechnoExtInterop::CalculateExtraThreatCallbacks)
+ {
+ totalThreat = cb(pThis, pTarget, totalThreat);
+ }
+
return 0;
}
diff --git a/src/Interop/TechnoExt.cpp b/src/Interop/TechnoExt.cpp
index 643c081629..57d8856c13 100644
--- a/src/Interop/TechnoExt.cpp
+++ b/src/Interop/TechnoExt.cpp
@@ -1,7 +1,18 @@
#include "TechnoExt.h"
#include
+#include
+
+std::vector TechnoExtInterop::CalculateExtraThreatCallbacks = {};
+
+
DEFINE_EXPORT(bool, ConvertToType_Phobos, FootClass* pThis, TechnoTypeClass* toType)
{
return TechnoExt::ConvertToType(pThis, toType);
}
+
+DEFINE_EXPORT(void, RegisterCalculateExtraThreatCallback, CalculateExtraThreatCallback callback)
+{
+ if (callback)
+ TechnoExtInterop::CalculateExtraThreatCallbacks.push_back(callback);
+}
diff --git a/src/Interop/TechnoExt.h b/src/Interop/TechnoExt.h
index c956c0551d..6cf6c3f24b 100644
--- a/src/Interop/TechnoExt.h
+++ b/src/Interop/TechnoExt.h
@@ -12,10 +12,19 @@
// the same signature as used internally (FootClass*, TechnoTypeClass*).
#include "Utilities/Macro.h"
+#include
+
+#include
#include
-#include
+DEFINE_CALLBACK(int, CalculateExtraThreatCallback, TechnoClass* pThis, ObjectClass* pTarget, double originalThreat);
+
+class TechnoExtInterop
+{
+public:
+ static std::vector CalculateExtraThreatCallbacks;
+};
///
/// Converts a unit to a different type.
@@ -24,3 +33,5 @@
/// Pointer to the target TechnoTypeClass
/// true if conversion was successful, false otherwise
DEFINE_EXPORT(bool, ConvertToType_Phobos, FootClass* pThis, TechnoTypeClass* toType);
+
+DEFINE_EXPORT(void, RegisterCalculateExtraThreatCallback, CalculateExtraThreatCallback callback);
diff --git a/src/Utilities/Macro.h b/src/Utilities/Macro.h
index d1f756b5fc..b6801b0800 100644
--- a/src/Utilities/Macro.h
+++ b/src/Utilities/Macro.h
@@ -10,6 +10,12 @@
// Expands to: PHOBOS_EXPORT return_type PHOBOS_DECL Name(arglist)
#define DEFINE_EXPORT(ret, name, ...) extern "C" __declspec(dllexport) ret __stdcall name(__VA_ARGS__)
+// Callback typedef helper
+// Usage:
+// DEFINE_CALLBACK(return_type, TypeName, arglist...)
+// Expands to: typedef return_type (__stdcall* TypeName)(arglist);
+#define DEFINE_CALLBACK(ret, name, ...) typedef ret (__stdcall* name)(__VA_ARGS__)
+
#define GET_REGISTER_STATIC_TYPE(type, dst, reg) static type dst; _asm { mov dst, reg }
template