Climate card: emit real per-server climate config instead of hardcoded Eco defaults #8

Closed
opened 2026-06-15 15:10:27 +00:00 by coilysiren · 2 comments
Owner

Problem

The SPA /climate page (and the MCP climate card) has a CO2 effects section that states the warming / sea-level mechanic:

+1 °C for every 25 ppm past 400 ppm … +1 m for every 25 ppm past 400 ppm

Those threshold/rate numbers are hardcoded Eco defaults in src/eco_mcp_app/climate.py:

_RULE_MIN_CO2_PPM         = 325
_RULE_TEMP_THRESHOLD_PPM  = 400
_RULE_PPM_PER_DEGREE      = 25
_RULE_SEA_THRESHOLD_PPM   = 400
_RULE_PPM_PER_METER       = 25

They're env-overridable but otherwise static. The real values live in the Eco server's config (EcoDef.Obj.ClimateSettingsTemperaturesRiseAtCO2ppm, CO2ppmPerDegreeTemperatureRise, SeaLevelsRiseAtCO2ppm, CO2ppmPerSeaLevelMeterRise, MinCO2ppm, PollutionMultiplier, MaxCO2PerDayFromAnimals, MinCO2PerDayFromPlants). A server can retune them — the in-game pollution-machine status that prompted this feature showed 340 / 420 / 20, not the defaults — and the game exposes no HTTP endpoint for these settings, so the card can silently disagree with what the in-game UI shows.

Ask

Make the server emit the app config values (the climate settings block) so eco-app reads the live per-server thresholds instead of guessing.

Likely shape:

  • Add a read-only endpoint to the telemetry mod (mods/telemetry/) that serializes EcoDef.Obj.ClimateSettings as JSON.
  • Fetch it in climate.py, populate the effects block from live values, and keep the current _RULE_* constants only as a fallback when the endpoint is absent (older/un-modded servers).
  • Drop the "Eco's default climate ruleset — a server admin can retune these" disclaimer on /climate once the values are live.

Acceptance

  • /climate CO2 effects thresholds match the server's configured ClimateSettings (verify against the in-game pollution-machine status text).
  • Un-modded / endpoint-absent servers still render, using the documented Eco defaults as fallback.

Refs

  • Hardcoded constants: src/eco_mcp_app/climate.py (_RULE_*).
  • Game source: Server/Eco.Simulation/Settings/EcoDef.cs (ClimateSettings), Server/Eco.Simulation/ClimateSim.cs (ClimateState.AppendTo — the status text we're mirroring).
  • Consumer UI: frontend/src/pages/Climate.tsx, frontend/src/lib/climateApi.ts.
## Problem The SPA `/climate` page (and the MCP climate card) has a **CO2 effects** section that states the warming / sea-level mechanic: > +1 °C for every **25 ppm** past **400 ppm** … +1 m for every **25 ppm** past **400 ppm** Those threshold/rate numbers are **hardcoded Eco defaults** in `src/eco_mcp_app/climate.py`: ```python _RULE_MIN_CO2_PPM = 325 _RULE_TEMP_THRESHOLD_PPM = 400 _RULE_PPM_PER_DEGREE = 25 _RULE_SEA_THRESHOLD_PPM = 400 _RULE_PPM_PER_METER = 25 ``` They're env-overridable but otherwise static. The **real** values live in the Eco server's config (`EcoDef.Obj.ClimateSettings` — `TemperaturesRiseAtCO2ppm`, `CO2ppmPerDegreeTemperatureRise`, `SeaLevelsRiseAtCO2ppm`, `CO2ppmPerSeaLevelMeterRise`, `MinCO2ppm`, `PollutionMultiplier`, `MaxCO2PerDayFromAnimals`, `MinCO2PerDayFromPlants`). A server can retune them — the in-game pollution-machine status that prompted this feature showed `340` / `420` / `20`, not the defaults — and the game exposes **no HTTP endpoint** for these settings, so the card can silently disagree with what the in-game UI shows. ## Ask Make the server **emit the app config values** (the climate settings block) so eco-app reads the live per-server thresholds instead of guessing. Likely shape: - Add a read-only endpoint to the telemetry mod (`mods/telemetry/`) that serializes `EcoDef.Obj.ClimateSettings` as JSON. - Fetch it in `climate.py`, populate the `effects` block from live values, and keep the current `_RULE_*` constants only as a fallback when the endpoint is absent (older/un-modded servers). - Drop the "Eco's default climate ruleset — a server admin can retune these" disclaimer on `/climate` once the values are live. ## Acceptance - `/climate` **CO2 effects** thresholds match the server's configured `ClimateSettings` (verify against the in-game pollution-machine status text). - Un-modded / endpoint-absent servers still render, using the documented Eco defaults as fallback. ## Refs - Hardcoded constants: `src/eco_mcp_app/climate.py` (`_RULE_*`). - Game source: `Server/Eco.Simulation/Settings/EcoDef.cs` (`ClimateSettings`), `Server/Eco.Simulation/ClimateSim.cs` (`ClimateState.AppendTo` — the status text we're mirroring). - Consumer UI: `frontend/src/pages/Climate.tsx`, `frontend/src/lib/climateApi.ts`.
Member

🔒 Reserved by ward agent --driver claude — container engineer-claude-eco-app-8 on host KAI-DESKTOP-TOWER is carrying this issue (reserved 2026-07-05T03:19:33Z). Concurrent ward agent runs are blocked until it finishes or the reservation goes stale (2h0m0s TTL); --force overrides.

Do not comment on or edit this issue to steer the run while it is reserved. The engineer seeded the body once at launch and never re-reads it, so a comment or edit reaches only human readers, never the running engineer. A correction goes to a new issue, dispatched fresh — that is the only channel that reaches a run in flight. Where the forge supports it, ward locks this conversation to make that a road-block rather than a convention (ward#494).

— Claude (she/her), via ward agent

<!-- ward-agent-reservation --> 🔒 Reserved by `ward agent --driver claude` — container `engineer-claude-eco-app-8` on host `KAI-DESKTOP-TOWER` is carrying this issue (reserved 2026-07-05T03:19:33Z). Concurrent `ward agent` runs are blocked until it finishes or the reservation goes stale (2h0m0s TTL); `--force` overrides. **Do not comment on or edit this issue to steer the run while it is reserved.** The engineer seeded the body once at launch and never re-reads it, so a comment or edit reaches only human readers, never the running engineer. A correction goes to a **new issue, dispatched fresh** — that is the only channel that reaches a run in flight. Where the forge supports it, ward locks this conversation to make that a road-block rather than a convention (ward#494). <!-- ward-agent-signature --> — Claude (she/her), via `ward agent`
Author
Owner

WARD-OUTCOME: done - telemetry mod GET /api/v1/climate-settings emits EcoDef.Obj.ClimateSettings; climate.py + /climate card now use live per-server thresholds with Eco defaults as per-field fallback.

Retrospective: this one went smoothly, mostly because the repo had already paved the road. mods/stores is a near-perfect template for "reflection-read a live Eco type, serialize it, ride the existing X-API-Key middleware," so the C# side was more transcription than invention. The Python _RULE_* constants were already isolated and env-driven, so threading a live source in front of them was a clean _resolve_rules seam rather than surgery.

Two things fought back a little. First, no dotnet in the container and no CI stage that compiles the mods — so a C# typo would've sailed to deploy unseen. I didn't want to ship blind, so I installed the .NET 10 SDK myself (ICU was missing → invariant-globalization flag) and got a real green build before committing. Worth the detour. Second, I made the fallback per-field rather than all-or-nothing: a server that emits only some of the block still corrects those fields and keeps Eco defaults for the rest, which felt truer to how a half-configured server behaves.

Confidence: high on the app/frontend/contract layer (296 pytest + 36 vitest green, covering live/default/partial/non-numeric cases) and on the fact that the mod compiles. The one thing I couldn't exercise is the reflection reader against a real running Eco server — the member names (TemperaturesRiseAtCO2ppm, etc.) come from the issue, and the reader is defensively null-tolerant with alt-name candidates, but the true acceptance check ("thresholds match the in-game pollution-machine status") needs a live server with the rebuilt telemetry DLL deployed. Worth a follow-up to eyeball the endpoint against that 340/420/20 server once it's rolled out; if Eco spells a field differently than the issue does, it'll quietly fall back to the default for just that field, which is the safe failure but not the intended one.

WARD-OUTCOME: done - telemetry mod GET /api/v1/climate-settings emits EcoDef.Obj.ClimateSettings; climate.py + /climate card now use live per-server thresholds with Eco defaults as per-field fallback. Retrospective: this one went smoothly, mostly because the repo had already paved the road. `mods/stores` is a near-perfect template for "reflection-read a live Eco type, serialize it, ride the existing X-API-Key middleware," so the C# side was more transcription than invention. The Python `_RULE_*` constants were already isolated and env-driven, so threading a live source in front of them was a clean `_resolve_rules` seam rather than surgery. Two things fought back a little. First, no dotnet in the container and no CI stage that compiles the mods — so a C# typo would've sailed to deploy unseen. I didn't want to ship blind, so I installed the .NET 10 SDK myself (ICU was missing → invariant-globalization flag) and got a real green build before committing. Worth the detour. Second, I made the fallback per-field rather than all-or-nothing: a server that emits only some of the block still corrects those fields and keeps Eco defaults for the rest, which felt truer to how a half-configured server behaves. Confidence: high on the app/frontend/contract layer (296 pytest + 36 vitest green, covering live/default/partial/non-numeric cases) and on the fact that the mod compiles. The one thing I couldn't exercise is the reflection reader against a real running Eco server — the member names (`TemperaturesRiseAtCO2ppm`, etc.) come from the issue, and the reader is defensively null-tolerant with alt-name candidates, but the true acceptance check ("thresholds match the in-game pollution-machine status") needs a live server with the rebuilt telemetry DLL deployed. Worth a follow-up to eyeball the endpoint against that 340/420/20 server once it's rolled out; if Eco spells a field differently than the issue does, it'll quietly fall back to the default for just that field, which is the safe failure but not the intended one.
Sign in to join this conversation.
No description provided.