DESIGN.md covers the three-layer architecture (facts in code, Jev for tactics, gated escalation for macro). research/ documents the engine and mod surface, the Jev classifier's measured behavior, the STS2MCP HTTP interface, state shapes, failure modes, decision architecture, and a run log of the first four sessions.
174 lines
6.3 KiB
Markdown
174 lines
6.3 KiB
Markdown
# 01 — Game engine and mod surface
|
|
|
|
Measured on game build `v0.107.1` (commit `59260271`), macOS arm64, Steam.
|
|
|
|
## Engine
|
|
|
|
Slay the Spire 2 is a **Godot 4.5.1 (.NET)** game, not Java like the original.
|
|
This matters: the gameplay code is plain managed C# IL, so it can be read
|
|
directly with no decompiler.
|
|
|
|
| Item | Value |
|
|
|---|---|
|
|
| Engine | Godot 4.5.1 (.NET build) |
|
|
| Runtime | .NET 9.0.7, self-contained `osx-arm64` |
|
|
| Root namespace | `MegaCrit.Sts2.*` (2,751 types) |
|
|
| Bundle id | `com.megacrit.SlayTheSpire2` |
|
|
|
|
## Layout inside the app bundle
|
|
|
|
```
|
|
SlayTheSpire2.app/Contents/
|
|
MacOS/
|
|
Slay the Spire 2 179 MB Mach-O universal (x86_64 + arm64)
|
|
mods/ <- mods load from here
|
|
Frameworks/
|
|
libfmod.dylib, libfmodstudio.dylib
|
|
libGodotFmod.macos.template_release.framework
|
|
libspine_godot.macos.template_release.framework
|
|
libSentry.dylib
|
|
Resources/
|
|
Slay the Spire 2.pck 1.9 GB Godot pack
|
|
data_sts2_macos_arm64/ .NET assemblies + runtime
|
|
sts2.dll 9.3 MB main game assembly
|
|
sts2.xml 5.3 MB XML docs, 19,635 members
|
|
GodotSharp.dll, 0Harmony.dll, MonoMod.Backports.dll,
|
|
Steamworks.NET.dll, Sentry.dll, ...
|
|
data_sts2_macos_x86_64/ same, other arch
|
|
```
|
|
|
|
### The free win: `sts2.xml`
|
|
|
|
`sts2.xml` is the .NET XML documentation file, shipped un-stripped. It gives
|
|
**19,635 documented members** with prose summaries. Example, verbatim:
|
|
|
|
> `ModInitializerAttribute` — *"Declares a class as the main entry point for
|
|
> the mod. If this is present, then upon loading the mod, we'll call the method
|
|
> named `initializerMethod` within the class. Otherwise, we'll create a harmony
|
|
> instance for the mod and call `Harmony.PatchAll`."*
|
|
|
|
This removes most of the guesswork from modding. Query it with
|
|
`~/sts2-re/docdump.py` or any XML parser.
|
|
|
|
### Harmony and MonoMod ship in the game
|
|
|
|
`0Harmony.dll` and `MonoMod.*.dll` are present in the shipping build. That is
|
|
a strong signal that runtime patching is a supported path, not an accident.
|
|
|
|
## Official mod loader
|
|
|
|
The game has first-class mod support. Namespace `MegaCrit.Sts2.Core.Modding`.
|
|
|
|
| Type | Purpose |
|
|
|---|---|
|
|
| `ModManager` | Discovers and loads mods |
|
|
| `ModInitializerAttribute` | Declares a mod entry point |
|
|
| `ModManifest` | The JSON manifest |
|
|
| `ModHelper` | Content registration and hook subscription |
|
|
| `ModSource` | `ModsDirectory` or `SteamWorkshop` |
|
|
| `SettingsSaveMod` | Per-mod enable/disable and manual load order |
|
|
|
|
`ModManager.Initialize` is documented as loading mods *"from the 'mods'
|
|
directory next to the executable, as well as Steam workshop files"* and is
|
|
*"called as early as possible in the game's initialization process"*.
|
|
|
|
**Consequence:** mods load at process start. Changing a mod requires a full
|
|
restart. "Save and quit" to the main menu is not enough.
|
|
|
|
### Mod directories
|
|
|
|
| Source | Path |
|
|
|---|---|
|
|
| Local (macOS) | `SlayTheSpire2.app/Contents/MacOS/mods/` |
|
|
| Steam Workshop | `Steam/steamapps/workshop/content/2868840/` |
|
|
|
|
### Manifest schema (verified from a working mod)
|
|
|
|
```json
|
|
{
|
|
"id": "STS2_MCP",
|
|
"name": "STS2 MCP",
|
|
"author": "kunology",
|
|
"description": "MCP server bridge for Slay the Spire 2",
|
|
"version": "0.4.0",
|
|
"has_pck": false,
|
|
"has_dll": true,
|
|
"affects_gameplay": false
|
|
}
|
|
```
|
|
|
|
Note **snake_case** in JSON (`affects_gameplay`, `has_dll`, `has_pck`), which
|
|
differs from the C# property names (`affectsGameplay`). The manifest filename
|
|
must match the mod id: `STS2_MCP.json` for id `STS2_MCP`.
|
|
|
|
### Mod loading, from the live log
|
|
|
|
```
|
|
Found mod manifest file .../mods/STS2_MCP.json
|
|
Mods have been re-sorted because we detected a change or dependency order was broken.
|
|
[WARN] Mod STS2_MCP does not declare min game version. Assuming that it is supported.
|
|
Loading assembly DLL .../mods/STS2_MCP.dll
|
|
Calling initializer method of type STS2_MCP.McpMod for STS2_MCP, Version=1.0.0.0
|
|
Finished mod initialization for 'STS2 MCP' (STS2_MCP).
|
|
--- RUNNING MODDED! --- Loaded 1 mods (1 total)
|
|
[Sentry.NET] Is running modded
|
|
```
|
|
|
|
`minGameVersion` is optional but its absence produces a warning. It is worth
|
|
declaring in any mod we write.
|
|
|
|
### Consent flow
|
|
|
|
`NConfirmModLoadingPopup` is documented as *"Vertical popup used to get player
|
|
confirmation before loading mods. Renders above the capstone screens (above top
|
|
bar)."* It appears **over the main menu, at launch**, and only when mods are
|
|
present. Consent persists as `mod_settings: {"mods_enabled": true}` in
|
|
`settings.save`.
|
|
|
|
There is also a `nomods` command-line argument that makes `ModManager` skip
|
|
initialization entirely (`ModManagerState.Skipped`).
|
|
|
|
## Save layout
|
|
|
|
Modded and vanilla progress are **isolated**:
|
|
|
|
```
|
|
~/Library/Application Support/SlayTheSpire2/steam/<steamid>/
|
|
settings.save {"mod_settings": {"mods_enabled": true}, ...}
|
|
profile.save
|
|
profile1/saves/ vanilla progress
|
|
modded/profile1/saves/ MODDED progress
|
|
prefs.save
|
|
progress.save unlocks, card/enemy/encounter stats
|
|
current_run.save the run in progress
|
|
history/<ts>.run completed run records
|
|
```
|
|
|
|
Modded runs never touch vanilla saves. This was confirmed by the log line
|
|
`Profile-scoped data path initialized: user://steam/<id>/modded/profile1`.
|
|
|
|
## Bundle signing
|
|
|
|
The app is **ad-hoc signed** (`Signature=adhoc`, `flags=0x10002(adhoc,runtime)`)
|
|
and `spctl -a` already returns `rejected`. There is no quarantine attribute
|
|
(only `com.apple.provenance`). Adding files to `Contents/MacOS/` breaks the
|
|
sealed-resource count but there is no enforced signature, and Steam launches
|
|
the binary directly.
|
|
|
|
**Do not run Steam's "Verify integrity of game files"** — it deletes the mods
|
|
directory, because those files are not in the depot manifest.
|
|
|
|
## Useful namespaces for a bot
|
|
|
|
| Namespace | Why it matters |
|
|
|---|---|
|
|
| `Core.Modding` | Official mod API |
|
|
| `Core.AutoSlay` | MegaCrit's own auto-play bot, shipped in the build |
|
|
| `Core.DevConsole.ConsoleCommands` | ~40 built-in debug commands |
|
|
| `Core.GameActions` | Command pattern for game actions |
|
|
| `Core.Runs` / `Core.Combat` | Run and combat state |
|
|
| `Core.Saves` | Source-generated JSON serialization |
|
|
|
|
`AutoSlayer.Start(seed, ...)` runs a complete automated game with handlers for
|
|
every screen and room. It is MegaCrit's smoke-test harness and is the best
|
|
available reference for driving the game programmatically.
|