<!-- skill: Skylines Mod Bridge | version: 1.0.0 | description: Build a live HTTP bridge mod so AI can read and control Cities Skylines 1/2 (BETA: architecture verified from public modding docs, not yet run against a live game) | category: Games -->

## Instructions
- Goal shape: an in-game mod exposing city state over HTTP (GET endpoints) and accepting commands (POST endpoints). The AI talks HTTP; it never touches game memory directly.
- Cities Skylines 1: C# class library (.NET 3.5), entry `IUserMod` (name/description) + loading extension (`ILoadingExtension.OnLevelLoaded`). Patch game code with Harmony. Read state via managers (`SimulationManager`, `NetManager`, `EconomyManager`, `CitizenManager`). NEVER touch game objects off the simulation thread — queue work with `SimulationManager.instance.AddAction(...)`.
- Cities Skylines 2: Paradox Mods pipeline, BepInEx 6 (IL2CPP) plugin model differs from CS1 — verify against the current modding wiki before coding; expect breaking changes between game updates.
- HTTP inside the mod: embedded `HttpListener` on 127.0.0.1 + a free high port, JSON in/out, one thread, request timeouts. Never expose it beyond localhost.
- Threading is the #1 crash source: all game reads/writes on the sim thread via the action queue; the HTTP thread only shuffles JSON.
- Version-pin everything: game version, Harmony version, BepInEx version. Re-test after every game patch — patches break mods routinely.
- API shape: REST+JSON on localhost only (`HttpListener` prefix `http://127.0.0.1:PORT/`), GET for state, POST for commands, `application/json` both ways, request timeout 5s so a stuck sim call can't hang the AI.
- Snapshot pattern: sim thread writes a plain-data struct every 0.5–1s under a `lock`; HTTP thread serves the last snapshot — never let HTTP touch live game objects.
- Command queue: HTTP POST validates (schema + ranges) → enqueues `Action<SimulationManager>` → returns `202 {ticket}` immediately; AI polls `GET /commands/{ticket}` for applied/rejected — commands are async by design.
- Logging: mod log file (`bridge.log`) with per-request lines (time, method, path, ms, result); cap size with rotation (10MB x 3) or it eats the disk.
- Safety rails: localhost bind only (never `+`/`*` prefixes), auth token header for write endpoints, per-endpoint rate limits (state 1/s, commands 1/5s), kill-switch keybind to stop the listener without unloading.
- CS1 specifics: `IUserMod.OnEnabled` starts the listener, `OnDisabled` stops it; `LoadingExtensionBase.OnLevelLoaded` hooks the sim loop; Harmony `Prefix`/`Postfix` patches minimal and version-guarded (`[HarmonyPatch(typeof(X), "Method")]` with signature checks).
- CS2 specifics: BepInEx 6 plugin `[BepInPlugin(GUID, Name, Version)]`, systems via the game's ECS (`SystemBase`/`ISystem`), verify against the live modding wiki — CS2 APIs shift between patches.
## Examples
- Read bridge: `GET /stats` → `{ population, funds, trafficFlow, timestamp }` sampled on the sim thread each second into a lock-guarded snapshot the HTTP thread serves.
- Command bridge: `POST /zone {district, type, density}` → validated, queued to sim thread, returns accepted/rejected — never acknowledged before applied.
- Pause control: `POST /sim {action:"pause"|"resume"|"speed", value:1}` — the safest first write endpoint; proves the queue works before zoning/budget writes.
- Budget read: `GET /economy` → per-service budgets + income/expenses snapshot for AI planning.
## Workflows
1. Read-only bridge first (stats endpoint + AI dashboard). Prove stability over hours before any write endpoint.
2. Add ONE command at a time (pause/speed is the safest first write).
3. Playtest with the bridge logging every call; remove/rate-limit anything spammy.
4. Harden: token auth on writes → rate limits → snapshot staleness alerts (>5s old = sim thread blocked, investigate).
5. Publish: README with install (manual + mod-manager), port config, endpoint table, game-version matrix, uninstall steps.
## Templates
- IUserMod skeleton: name/description/OnEnabled (start HttpListener) /OnDisabled (stop it) + sim-thread snapshot loop + JSON endpoints.
- AI side: poll interval 5–30s depending on need; batch commands; always re-read state after a command (never assume it applied).
```csharp
// Sim-thread snapshot pattern (CS1) — HTTP serves the copy, never live objects
private readonly object _lock = new object();
private CitySnapshot _snapshot = new CitySnapshot();
public void OnSimulationStep() {
    var s = new CitySnapshot {
        Population = Singleton<CitizenManager>.instance.m_citizens.Count,
        Funds = Singleton<EconomyManager>.instance.LastCash,
        Timestamp = System.DateTime.UtcNow
    };
    lock (_lock) { _snapshot = s; }
}
public string GetStatsJson() {
    CitySnapshot copy; lock (_lock) { copy = _snapshot; }
    return JsonEncode(copy); // plain data only — no Unity objects cross threads
}
```
```json
// Endpoint contract — every command is async with a ticket
// POST /sim {"action":"pause"} -> 202 {"ticket":"a1b2","status":"queued"}
// GET /commands/a1b2 -> {"ticket":"a1b2","status":"applied","at":"2026-10-13T12:00:00Z"}
// GET /stats -> {"population":52140,"funds":1032500,"trafficFlow":78,"timestamp":"..."}
```
## Tools
- SARAH file tools to write the mod source; SARAH shell for builds (dotnet/msbuild where installed).
- Test ONLY with the game present — this skill's patterns are architectural, not a substitute for in-game testing.
- `curl -s localhost:PORT/stats | python3 -m json.tool` for endpoint checks; mod log tail for sim-thread errors.
## Advanced
- Threading deep-cut: Unity API is main/sim-thread-only — even `Debug.Log` volume matters (throttle); use `try/catch` around every sim action so one bad command can't kill the simulation.
- Harmony hygiene: patch the narrowest method possible, `HarmonyPriority` documented, unpatch on `OnDisabled`; a game update that renames one method breaks every patch — CI that diffs signatures per patch saves days.
- ECS (CS2): query components, never cache entity references across frames; burst-compatible structs for hot snapshot paths.
- Observability: `/health` endpoint (listener up, last snapshot age, queue depth, game loaded yes/no) so the AI can self-diagnose a stale bridge.
- Troubleshooting: game crashes on load with mod → Harmony version mismatch or patch signature changed, disable halves to bisect; HTTP 503/timeouts → prefix not registered (admin rights for non-localhost prefixes — stay on 127.0.0.1) or firewall; stale stats (timestamp frozen) → sim action throwing every frame, read the mod log `Caused by` line; commands accepted but never applied → queue drained on wrong thread or level not loaded (gate writes on `IsLevelLoaded`).
## Documentation
- Cities Skylines modding wiki (tutorials + API references), Harmony docs, BepInEx docs, Paradox Mods portal. Verify versions before trusting any of it.
- CS1 API: in-game `SimulationManager.instance.AddAction(...)` docs + manager class references per game version.
- Harmony patching: https://harmony.parawave.net/articles/intro.html
