Skip to content

robert-c-ewing/scheduleobserved

Repository files navigation

ScheduleObserved

Production-grade observability for a single-player drug economy simulator.

ScheduleObserved is a MelonLoader mod for Schedule 1 that exposes the in-game economy as Prometheus metrics and Loki log streams, with a prebuilt Grafana dashboard. Player finances, customer relationships, product flow, dealer balances, and per-deal geolocation events — all queryable, all graphable, all labeled by save slot.

Built for fun, partially as an excuse to practice observability tooling on a non-trivial data source, and partially because when you install SRE reflexes on a video game you get charts like dealer cash-on-hand with a green-to-red gradient and you cannot help yourself.

What it exposes

Prometheus metrics (/metrics on localhost:9184)

All per-customer / per-dealer / per-product metrics are labeled with save_slot so you can filter by game instance.

Metric Type Labels Description
s1_player_cash_balance gauge save_slot Player cash on hand
s1_player_online_balance gauge save_slot Player ATM balance
s1_player_rank gauge save_slot, rank Player rank (enum name in label)
s1_player_xp gauge save_slot XP within the current tier
s1_player_total_xp gauge save_slot Cumulative XP
s1_player_xp_to_next_tier gauge save_slot XP threshold for next tier
s1_customer_weekly_budget gauge save_slot, customer Lerp'd weekly spend
s1_customer_per_order_cap gauge save_slot, customer Per-deal price ceiling
s1_customer_addiction gauge save_slot, customer 0-1 addiction level
s1_customer_relationship gauge save_slot, customer Raw relationship delta
s1_customer_orders_per_week gauge save_slot, customer Effective weekly order days
s1_customer_spend_base_total counter save_slot, customer Pre-bonus payment accumulation
s1_customer_spend_total counter save_slot, customer Post-bonus payment accumulation
s1_customer_deals_completed_total counter save_slot, customer Completed deals
s1_product_sold_total counter save_slot, product_id, drug_type Units sold
s1_dealer_cash gauge save_slot, dealer Per-recruited-dealer cash
s1_unlocked_customers gauge save_slot Count of unlocked customers
s1_mod_info gauge version Static metadata

Loki log events (UserData/ScheduleObserved/events.jsonl)

One JSON object per completed deal, tailed by Promtail and pushed to Loki:

{
  "ts": "2026-04-20T22:09:12.4321000Z",
  "event": "deal",
  "save_slot": "Little Buds",
  "customer": "Jessi Waters",
  "product_id": "girlscoutcookies",
  "drug_type": "Marijuana",
  "base": 450.00,
  "units": 5,
  "lat": 0.4231,
  "lon": 1.1094
}

lat / lon come from the customer's world-space position at handover, scaled by 1/1000 so they land in the valid WGS-84 degree range. They are not real earth coordinates. The dashboard's Geomap panel will display them in the Atlantic Ocean off the coast of Ghana, or you can layer a custom tile server with the actual Schedule 1 city map underneath.

Counters persist per save

Counter state is serialized as TSV into <savefolder>/ScheduleObserved/counters.tsv on every game save. Loaded on every save load. Counters are scoped by save slot so switching games doesn't pollute totals.

Emergent side effect

Customer.ProcessHandover fires for every customer handover in the game world, not just the player's. Rival cartel activity — deals between NPC dealers and their customers — shows up in your metrics alongside your own. Unintentional SIGINT as entertainment.

Install

  1. Install MelonLoader 0.6+ on Schedule 1. Works on either the Mono (alternate Steam beta) or IL2CPP (default) branch.
  2. Grab ScheduleObserved.dll from Releases (or build yourself — see below).
  3. Drop it into Schedule I/Mods/.
  4. Launch the game. The [ScheduleObserved] section appears in UserData/MelonPreferences.cfg; the metrics endpoint starts on http://localhost:9184/metrics.

Stack

cd monitoring
cp .env.example .env
# edit .env: set GAME_DIR to your Schedule 1 install
docker-compose up -d
Service URL Notes
Prometheus http://localhost:9090 scrape target localhost:9184
Loki http://localhost:3100 log ingest
Grafana http://localhost:3000 admin / scheduleobserved

The dashboard "ScheduleObserved — Schedule 1 Economy" auto-provisions. Nine panels covering cash, XP, customers, products, dealers, and the deal-location Geomap.

All services run in host networking mode to dodge host.docker.internal resolution issues on older Docker Compose versions. This means ports 9090/3000/3100/9184 must be free on the host.

Build

Requires .NET 6 SDK. The project auto-detects which branch of the game is installed:

  • Mono branchnet472 DLL, references Schedule I_Data/Managed/*.dll.
  • IL2CPP branchnet6.0 DLL, references MelonLoader/Il2CppAssemblies/*.dll. Requires launching the game at least once so MelonLoader generates interop proxies.
dotnet build -c Release

Output: bin/Release/<tfm>/ScheduleObserved.dll, auto-deployed to <GameDir>/Mods/. Override the game path for non-standard installs:

dotnet build -c Release -p:GameDir="/other/path/Schedule I"

dev.sh wraps the common flow (build + stack + play) — see ./dev.sh help.

Architecture notes

  • Metrics exporter uses a raw TcpListener + manual HTTP framing. HttpListener under Wine (Proton-hosted Unity games) accepts connections but silently fails to dispatch requests; TcpListener is a thin BSD socket wrapper and works correctly.
  • Prometheus exposition format is \n-delimited. Wine's Environment.NewLine is \r\n, which Prometheus rejects with invalid metric type "gauge\r". Body is normalized once before serving.
  • Cross-branch source uses #if Mono to switch between ScheduleOne.* and Il2CppScheduleOne.* namespaces, between System.Collections.Generic and Il2CppSystem.Collections.Generic, and between direct property access and IL2CPP's synthesized sync___get_value_* accessors for FishNet-synced fields.
  • Gauge snapshots happen on Unity's main thread (OnUpdate, throttled by MetricsRefreshInterval). The HTTP accept loop runs on a background thread and serves an immutable string reference, so no locking is needed.

License

MIT. See LICENSE.


Built with Claude Code as a pair-programming partner and super-duper friendly buddy.

About

Production-grade observability for Schedule 1. Prometheus metrics, Loki event stream, and a prebuilt Grafana dashboard with a Geomap of deal locations.

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages