Add design doc and research notes
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.
This commit is contained in:
commit
fb32822468
9 changed files with 2341 additions and 0 deletions
174
docs/research/01-game-engine-and-mod-surface.md
Normal file
174
docs/research/01-game-engine-and-mod-surface.md
Normal file
|
|
@ -0,0 +1,174 @@
|
|||
# 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.
|
||||
Loading…
Add table
Add a link
Reference in a new issue