Parse Eco's AutoGen C# into JSON at build time and serve it over MCP #242

Closed
opened 2026-08-12 23:37:17 +00:00 by coilyco-ops · 4 comments
Member

Eco Gnome is the one substantial dataset in eco-app with zero MCP surface. Every other data plane the app carries answers an MCP tool call; the recipe/pricing half answers only a browser.

What exists today

recipes.py parses the vendored data/eco_gnome_data.json — the vanilla graph bundled by eco-gnome-website (ecocraft/eco_gnome_data.json, MIT, pinned master@28113f2, 2026-07-04: 1,453 recipes / 1,526 items / 43 skills / 142 tags) — into a RecipeIndex. cost.py layers the roll-up engine on top (ingredient + labor + time → per-unit cost / margin, leaf prices resolved from the live market median).

Both are reachable only over REST, at /preview/recipes.json (with ?product= / ?skill= / ?station= / ?cost=1 / ?server= / ?caloriePrice= / ?minutePrice=). None of the 22 MCP tools touches recipes, bills of materials, or the cost engine. An agent asking "what does a Steel Axe cost to make on Sirens, and is that above or below market?" cannot get there, even though the app computes exactly that for the SPA.

The self-hosted instance from #40 is now live: https://eco-gnome.coilysiren.me serves 200, running the stock upstream image from the coilyco-bridge/deploy/services/eco-gnome slot.

Three ways in, and which one to take

1. The vendored JSON (available today, zero new infra). load_recipe_index() already returns a parsed RecipeIndex; the MCP side is a registration, not an ingest. Limits: vanilla-only, pinned to a July snapshot, no per-server modded recipes, and none of the user-authored price configs that are the point of Eco Gnome.

2. The ecocraft Postgres on the self-hosted instance. From the deploy slot: namespace coilysiren-eco-gnome, service coilysiren-eco-gnome-db:5432, POSTGRES_DB/POSTGRES_USER both ecocraft, password an ExternalSecret from SSM /eco-gnome/postgres-password. This is the only path to what the running app knows — imported DataExporter output, user price configs, per-server calculations. Cost: it binds us to upstream's EF Core schema, which moves when they migrate on boot.

3. The Blazor app's own HTTP surface. There isn't one. Probed on the live instance: /api → 404, /swagger → 404, /health → 404. It is Blazor Server, so the UI state lives behind a SignalR websocket; "querying" it means driving a rendered UI. Not viable unless upstream adds a JSON API — worth an upstream issue rather than a scraper here.

So: (1) now, (2) next. (1) closes the MCP gap this week against data we already parse; (2) is what makes the answers server-accurate, and it wants a read-only role and a schema-drift story before it's load-bearing.

Routes to add

Register through the DualRouteRegistry in dual_routes.py, the wave-1/wave-2 pattern — one typed pydantic contract lands on REST and MCP together, so /preview/recipes.json stops being a hand-written one-off:

  • get_recipes — bill of materials, filterable by product / skill / station. Wraps the existing filter_index.
  • price_recipe — the ?cost=1 engine as a first-class tool: per-unit cost, margin, and which leaf prices came from the live market versus went unpriced. Takes server, caloriePrice, minutePrice.
  • get_skills — the 43 skills and their recipe coverage, the join the value-per-profession work (#98 D) needs.

Phase 2, once the DB read path exists:

  • Whatever the ecocraft schema exposes for user price configs and imported per-server data — scoped after a read-only look at the live tables, not guessed here.

Note for whoever picks this up: load_recipe_index() is an in-process memo over an immutable file, so the MCP path needs none of the SQLite/per-server-key machinery crafting.py and trades.py carry.

Acceptance

  • tools/list on /mcp returns the new tools; each answers against Sirens without exceeding the response cap (see #240 family 3 — get_recipes unfiltered will be large, so it needs summary-first and a limit from day one, not as a follow-up).
  • /preview/recipes.json keeps its current shape and query params, served by the registered route rather than the bespoke handler.
  • The source string stays on the payload so a consumer can tell vanilla-seed from modded-export (as GNOME_DATA_SOURCE does now).

Open questions

  • Read-only Postgres role: created by the eco-gnome slot, or by hand into SSM? And is cross-namespace coilysiren-eco-appcoilysiren-eco-gnome-db traffic allowed as deployed?
  • How does the DataExporter path (ward#585 / eco-ops#30) land — as a drop-in replacement for the vendored file (per #105, no parser rework), as an import into the Gnome DB, or both? That decides whether phase 2 is the primary source or a second opinion.
  • Does upstream want a JSON API? If eco-gnome-website grew one, option 3 beats option 2 on schema stability.

Refs: #40 (self-host decision), #98 (recipe/pricing roadmap), #100, #105 (vendor-don't-fetch probe), #240 (response-size findings), docs/recipes.md, docs/calculator.md.

Eco Gnome is the one substantial dataset in eco-app with **zero MCP surface**. Every other data plane the app carries answers an MCP tool call; the recipe/pricing half answers only a browser. ## What exists today `recipes.py` parses the vendored `data/eco_gnome_data.json` — the vanilla graph bundled by `eco-gnome-website` (`ecocraft/eco_gnome_data.json`, MIT, pinned `master@28113f2`, 2026-07-04: 1,453 recipes / 1,526 items / 43 skills / 142 tags) — into a `RecipeIndex`. `cost.py` layers the roll-up engine on top (ingredient + labor + time → per-unit cost / margin, leaf prices resolved from the live market median). Both are reachable **only** over REST, at `/preview/recipes.json` (with `?product=` / `?skill=` / `?station=` / `?cost=1` / `?server=` / `?caloriePrice=` / `?minutePrice=`). None of the 22 MCP tools touches recipes, bills of materials, or the cost engine. An agent asking "what does a Steel Axe cost to make on Sirens, and is that above or below market?" cannot get there, even though the app computes exactly that for the SPA. The self-hosted instance from #40 is now **live**: `https://eco-gnome.coilysiren.me` serves 200, running the stock upstream image from the `coilyco-bridge/deploy/services/eco-gnome` slot. ## Three ways in, and which one to take **1. The vendored JSON (available today, zero new infra).** `load_recipe_index()` already returns a parsed `RecipeIndex`; the MCP side is a registration, not an ingest. Limits: vanilla-only, pinned to a July snapshot, no per-server modded recipes, and none of the user-authored price configs that are the point of Eco Gnome. **2. The `ecocraft` Postgres on the self-hosted instance.** From the deploy slot: namespace `coilysiren-eco-gnome`, service `coilysiren-eco-gnome-db:5432`, `POSTGRES_DB`/`POSTGRES_USER` both `ecocraft`, password an ExternalSecret from SSM `/eco-gnome/postgres-password`. This is the only path to what the *running* app knows — imported DataExporter output, user price configs, per-server calculations. Cost: it binds us to upstream's EF Core schema, which moves when they migrate on boot. **3. The Blazor app's own HTTP surface.** There isn't one. Probed on the live instance: `/api` → 404, `/swagger` → 404, `/health` → 404. It is Blazor **Server**, so the UI state lives behind a SignalR websocket; "querying" it means driving a rendered UI. Not viable unless upstream adds a JSON API — worth an upstream issue rather than a scraper here. So: **(1) now, (2) next.** (1) closes the MCP gap this week against data we already parse; (2) is what makes the answers server-accurate, and it wants a read-only role and a schema-drift story before it's load-bearing. ## Routes to add Register through the `DualRouteRegistry` in `dual_routes.py`, the wave-1/wave-2 pattern — one typed pydantic contract lands on REST and MCP together, so `/preview/recipes.json` stops being a hand-written one-off: - `get_recipes` — bill of materials, filterable by `product` / `skill` / `station`. Wraps the existing `filter_index`. - `price_recipe` — the `?cost=1` engine as a first-class tool: per-unit cost, margin, and which leaf prices came from the live market versus went unpriced. Takes `server`, `caloriePrice`, `minutePrice`. - `get_skills` — the 43 skills and their recipe coverage, the join the value-per-profession work (#98 D) needs. Phase 2, once the DB read path exists: - Whatever the `ecocraft` schema exposes for user price configs and imported per-server data — scoped after a read-only look at the live tables, not guessed here. Note for whoever picks this up: `load_recipe_index()` is an in-process memo over an immutable file, so the MCP path needs none of the SQLite/per-server-key machinery `crafting.py` and `trades.py` carry. ## Acceptance - `tools/list` on `/mcp` returns the new tools; each answers against Sirens without exceeding the response cap (see #240 family 3 — `get_recipes` unfiltered will be large, so it needs summary-first and a `limit` from day one, not as a follow-up). - `/preview/recipes.json` keeps its current shape and query params, served by the registered route rather than the bespoke handler. - The `source` string stays on the payload so a consumer can tell vanilla-seed from modded-export (as `GNOME_DATA_SOURCE` does now). ## Open questions - Read-only Postgres role: created by the eco-gnome slot, or by hand into SSM? And is cross-namespace `coilysiren-eco-app` → `coilysiren-eco-gnome-db` traffic allowed as deployed? - How does the DataExporter path (ward#585 / eco-ops#30) land — as a drop-in replacement for the vendored file (per #105, no parser rework), as an import into the Gnome DB, or both? That decides whether phase 2 is the primary source or a second opinion. - Does upstream want a JSON API? If eco-gnome-website grew one, option 3 beats option 2 on schema stability. Refs: #40 (self-host decision), #98 (recipe/pricing roadmap), #100, #105 (vendor-don't-fetch probe), #240 (response-size findings), `docs/recipes.md`, `docs/calculator.md`.
Author
Member

Checked a fourth possible source: Eco.ReferenceAssemblies, pinned in mods/telemetry/EcoTelemetry.csproj at 0.13.0.4-beta-release-1024 — the same version as the live server.

It does not carry AutoGen C# as source. The package is 15 DLLs and nothing else: Eco.Mods.dll, Eco.Gameplay.dll, Eco.Shared.dll, and friends. No .cs, no .pdb, no embedded sources.

They are true reference assemblies. Disassembling Eco.Mods.dll with ikdasm: all 20,986 method bodies are ldnull; throw, and zero methods have a code size greater than 2. There is no executable content to decompile.

But the attribute metadata survives, and Eco puts a lot in attributes. Measured across the assembly:

Survives Coverage
Type catalog 9,502 classes, 1,088 RecipeFamily subclasses, 1,834 LocDisplayName
Skill + required level RequiresSkillAttribute on 94% of recipe families
Ecopedia placement 79% of recipe families
Display name, description, tier, weight, category, tags ~95% of item classes
Type hierarchy e.g. SteelAxeItem : AxeItem

What is gone, because it is assigned in constructor bodies:

  • Ingredients and quantities
  • Products and byproducts
  • Labor cost and craft minutes
  • Crafting station — only 6% of recipe families carry RequiresModuleAttribute. The rest set CraftStation in the constructor, which is stripped. (SteelAxeRecipe happens to carry it, which is misleading if you sample one class.)

So against the RecipeIndex DTO in this issue, the reference assemblies cover name, displayName, skill{name, level}, tableTierRequired, and tags — and cover none of ingredients, byproducts, laborCost, craftMinutes, or station.

What that means for sourcing. They cannot replace the Eco Gnome graph; the bill of materials is exactly what they lack. They are, however, a better source than the vendored snapshot for the fields they do cover, because they are version-pinned to the running server, downloadable in CI, and already in this repo's build path — no mount, no host access, no DataExporter. Worth using as a cross-check on the vendored graph's names, skills, and tiers, and as a drift detector when the server version moves.

It also sharpens #243: the on-disk Mods/AutoGen tree remains the only local source that has ingredients, since those live in the method bodies that shipping strips. The mount is still the way to the real graph.

Artifacts are reproducible with:

curl -sL -o eco.nupkg https://api.nuget.org/v3-flatcontainer/eco.referenceassemblies/0.13.0.4-beta-release-1024/eco.referenceassemblies.0.13.0.4-beta-release-1024.nupkg
unzip -q eco.nupkg -d pkg && ikdasm pkg/lib/net10.0/Eco.Mods.dll > mods.il
Checked a fourth possible source: **`Eco.ReferenceAssemblies`**, pinned in `mods/telemetry/EcoTelemetry.csproj` at `0.13.0.4-beta-release-1024` — the same version as the live server. It does **not** carry AutoGen C# as source. The package is 15 DLLs and nothing else: `Eco.Mods.dll`, `Eco.Gameplay.dll`, `Eco.Shared.dll`, and friends. No `.cs`, no `.pdb`, no embedded sources. They are true reference assemblies. Disassembling `Eco.Mods.dll` with `ikdasm`: **all 20,986 method bodies are `ldnull; throw`**, and zero methods have a code size greater than 2. There is no executable content to decompile. But the attribute metadata survives, and Eco puts a lot in attributes. Measured across the assembly: | Survives | Coverage | |---|---| | Type catalog | 9,502 classes, **1,088 `RecipeFamily` subclasses**, 1,834 `LocDisplayName` | | Skill + required level | `RequiresSkillAttribute` on **94%** of recipe families | | Ecopedia placement | 79% of recipe families | | Display name, description, tier, weight, category, tags | ~95% of item classes | | Type hierarchy | e.g. `SteelAxeItem : AxeItem` | What is **gone**, because it is assigned in constructor bodies: - Ingredients and quantities - Products and byproducts - Labor cost and craft minutes - **Crafting station** — only 6% of recipe families carry `RequiresModuleAttribute`. The rest set `CraftStation` in the constructor, which is stripped. (`SteelAxeRecipe` happens to carry it, which is misleading if you sample one class.) So against the `RecipeIndex` DTO in this issue, the reference assemblies cover `name`, `displayName`, `skill{name, level}`, `tableTierRequired`, and `tags` — and cover none of `ingredients`, `byproducts`, `laborCost`, `craftMinutes`, or `station`. **What that means for sourcing.** They cannot replace the Eco Gnome graph; the bill of materials is exactly what they lack. They are, however, a better source than the vendored snapshot for the fields they do cover, because they are version-pinned to the running server, downloadable in CI, and already in this repo's build path — no mount, no host access, no DataExporter. Worth using as a **cross-check** on the vendored graph's names, skills, and tiers, and as a drift detector when the server version moves. It also sharpens #243: the on-disk `Mods/AutoGen` tree remains the only local source that has ingredients, since those live in the method bodies that shipping strips. The mount is still the way to the real graph. Artifacts are reproducible with: ``` curl -sL -o eco.nupkg https://api.nuget.org/v3-flatcontainer/eco.referenceassemblies/0.13.0.4-beta-release-1024/eco.referenceassemblies.0.13.0.4-beta-release-1024.nupkg unzip -q eco.nupkg -d pkg && ikdasm pkg/lib/net10.0/Eco.Mods.dll > mods.il ```
Author
Member

Follow-up, and it probably settles the sourcing question: the AutoGen C# is freely downloadable, with no game-ownership gate.

SLG's own developer (SLG-Dennis) in the official Steam discussion: the dedicated server can be fetched from the SLG website, Steam Tools, or SteamCMD with

steamcmd +login anonymous +app_update 739590 validate +quit

+login anonymous. No purchase, no credential, no seat. The AutoGen sources ship inside it at Eco_Data/Server/Mods/AutoGen/<Category>/*.cs — plain text, one file per thing, organised into Recipe, Item, Tool, Food, Clothing, Seed, Tech, Vehicle, WorldObject, Block, and friends. A public GitHub mirror of an older Eco version shows the shape: 66 .cs files in Recipe alone, names like SmeltCopper.cs, BeetSugar.cs, CampfireSalmon.cs.

What this changes

The three options in the issue body were all workarounds for not having the real graph. This is the real graph, and it is the one source that carries ingredients — the exact field the reference assemblies strip and the vendored Eco Gnome snapshot only has for pinned-vanilla.

Proposed shape, replacing option 1 as the primary:

  1. A build stage runs steamcmd +login anonymous +app_update 739590 at a pinned build id, extracts Mods/AutoGen/**/*.cs, parses it, and emits JSON.
  2. That JSON is baked into the image, exactly as data/eco_gnome_data.json is today — consistent with the #105 finding (vendor, don't fetch at runtime, no build-time networking in the app itself).
  3. The dual-route tools from this issue read that index instead of the Gnome snapshot.
  4. Eco.ReferenceAssemblies stays as the cross-check described in the previous comment: it independently carries display names, skills and levels, and tiers for the same version, so a mismatch between the two is a parser bug or a version drift, caught in CI rather than in a tool response.

The AutoGen files are machine-generated and highly regular, which is what makes parsing them a tractable job rather than a C# compiler problem.

What still wants the #243 mount

The download is vanilla-for-a-version. The live host's tree is what Sirens actually runs, including whatever MightyMoose, NidToolbox, and StrangeWorlds put on disk. So the mount keeps its value as verification and modded coverage — diff the parsed vanilla index against the live tree and see what a mod actually changed — rather than being the only way in. That is a weaker dependency than #243 currently assumes, and it means #242 no longer blocks on host access at all.

Worth confirming on the first fetch: that the Linux server package lays AutoGen out at the same path as the Windows one, and which build id corresponds to 0.13.0.4.

Follow-up, and it probably settles the sourcing question: **the AutoGen C# is freely downloadable, with no game-ownership gate.** SLG's own developer (SLG-Dennis) in [the official Steam discussion](https://steamcommunity.com/app/382310/discussions/0/4576185279358751800): the dedicated server can be fetched from the SLG website, Steam Tools, or SteamCMD with ``` steamcmd +login anonymous +app_update 739590 validate +quit ``` `+login anonymous`. No purchase, no credential, no seat. The AutoGen sources ship inside it at `Eco_Data/Server/Mods/AutoGen/<Category>/*.cs` — plain text, one file per thing, organised into `Recipe`, `Item`, `Tool`, `Food`, `Clothing`, `Seed`, `Tech`, `Vehicle`, `WorldObject`, `Block`, and friends. A [public GitHub mirror of an older Eco version](https://github.com/sarogahtyp/eco_mod/tree/master/Eco/Eco_Data/Server/Mods/AutoGen) shows the shape: 66 `.cs` files in `Recipe` alone, names like `SmeltCopper.cs`, `BeetSugar.cs`, `CampfireSalmon.cs`. ## What this changes The three options in the issue body were all workarounds for not having the real graph. This is the real graph, and it is the one source that carries **ingredients** — the exact field the reference assemblies strip and the vendored Eco Gnome snapshot only has for pinned-vanilla. Proposed shape, replacing option 1 as the primary: 1. A build stage runs `steamcmd +login anonymous +app_update 739590` at a **pinned build id**, extracts `Mods/AutoGen/**/*.cs`, parses it, and emits JSON. 2. That JSON is baked into the image, exactly as `data/eco_gnome_data.json` is today — consistent with the #105 finding (vendor, don't fetch at runtime, no build-time networking in the app itself). 3. The dual-route tools from this issue read that index instead of the Gnome snapshot. 4. `Eco.ReferenceAssemblies` stays as the **cross-check** described in the previous comment: it independently carries display names, skills and levels, and tiers for the same version, so a mismatch between the two is a parser bug or a version drift, caught in CI rather than in a tool response. The AutoGen files are machine-generated and highly regular, which is what makes parsing them a tractable job rather than a C# compiler problem. ## What still wants the #243 mount The download is vanilla-for-a-version. The live host's tree is what Sirens actually runs, including whatever MightyMoose, NidToolbox, and StrangeWorlds put on disk. So the mount keeps its value as **verification and modded coverage** — diff the parsed vanilla index against the live tree and see what a mod actually changed — rather than being the only way in. That is a weaker dependency than #243 currently assumes, and it means #242 no longer blocks on host access at all. Worth confirming on the first fetch: that the **Linux** server package lays AutoGen out at the same path as the Windows one, and which build id corresponds to `0.13.0.4`.
coilyco-ops changed title from Overlay MCP routes onto the Eco Gnome data plane (vendored graph now, ecocraft Postgres next) to Parse Eco's AutoGen C# into JSON at build time and serve it over MCP 2026-08-13 00:14:24 +00:00
Author
Member

Decision

Settled. Retitled to match. Single source, single path:

Anonymous SteamCMD pull of the dedicated server → parse Mods/AutoGen/**/*.cs to JSON at build time → bake the JSON into the image → MCP tools over the JSON.

Dropping the reference-assembly path entirely rather than running both. Two sources means two parsers, two version pins, and a reconciliation story for the fields where they disagree — for a dataset where one source is a strict superset of the other. The cross-check idea from the earlier comment is not worth that; if we want drift detection later, comparing the parsed index against the live host tree (#243) is the more useful comparison anyway, since it catches mod-added content that neither vanilla source has.

Nothing C# runs at runtime, and nothing C# runs in the app image. The parse is a build step; the app only ever sees JSON. Same runtime shape as data/eco_gnome_data.json today, so load_recipe_index()'s in-process memo carries over unchanged.

The generated C# is very parseable

Sampled two recipe files. The whole payload is declarative and machine-emitted:

[RequiresSkill(typeof(BasicSmeltingSkill), 2)]
public class SmeltCopperRecipe : Recipe
{
    public SmeltCopperRecipe()
    {
        this.Products = new CraftingElement[] {
           new CraftingElement<CopperIngotItem>(1),
        };
        this.Ingredients = new CraftingElement[] {
            new CraftingElement<CopperOreItem>(typeof(BasicSmeltingEfficiencySkill), 4, BasicSmeltingEfficiencySkill.MultiplicativeStrategy),
        };
        this.Initialize("Smelt Copper", typeof(SmeltCopperRecipe));
        this.CraftMinutes = CreateCraftTimeValue(typeof(SmeltCopperRecipe), this.UILink(), 0.5f, typeof(BasicSmeltingSpeedSkill));
        CraftingComponent.AddRecipe(typeof(BlastFurnaceObject), this);
    }
}

Every field the RecipeIndex DTO wants is on the page, including the two the other sources lose:

DTO field Where it comes from
name class name
displayName Initialize("Smelt Copper", …)
products[] + quantity CraftingElement<CopperIngotItem>(1)
ingredients[] + quantity CraftingElement<CopperOreItem>(…, 4, …)
skill{name, level} [RequiresSkill(typeof(BasicSmeltingSkill), 2)]
craftMinutes CreateCraftTimeValue(…, 0.5f, …)
station CraftingComponent.AddRecipe(typeof(BlastFurnaceObject), this)

station is the one the reference assemblies drop for 94% of recipes, and it is right there as a constructor call. The efficiency and speed skill modifiers come along free, which Eco Gnome models too.

Recommend a plain Python parser over these blocks rather than Roslyn. The input is machine-generated and stable, the output is consumed by Python, and it keeps the build step from needing the .NET stage. Roslyn stays the fallback if the grammar turns out to bite.

One trap for the implementer

The sample above is from an old Eco version — note : Recipe, not : RecipeFamily. The 0.13 reference assemblies show 1,088 RecipeFamily subclasses, so the modern generated shape wraps multiple recipe variants per family and adds fields this sample lacks (LaborInCalories among them). Write the parser against the real 0.13 download, not against the GitHub mirror, and treat the mirror only as evidence that the format is regular.

Revised plan

  1. Build stage: steamcmd +login anonymous +app_update 739590 validate at a pinned build id.
  2. Parse Eco_Data/Server/Mods/AutoGen/**/*.csRecipeIndex-shaped JSON, with a source string naming the app id and build id, the way GNOME_DATA_SOURCE names its pin.
  3. Bake the JSON into the image; drop data/eco_gnome_data.json once the new index covers it, or keep it briefly to diff the two and confirm the parser.
  4. get_recipes, price_recipe, get_skills via DualRouteRegistry, summary-first with a limit per #240.

Everything in "Routes to add" and "Acceptance" in the body still stands; the sourcing half of the body and its three options are superseded by this comment. Options 2 and 3 (the ecocraft Postgres and the absent Blazor API) are dead — with AutoGen parsed, the only thing Eco Gnome would still uniquely hold is user-authored price configs, which is a separate feature if we ever want it.

## Decision Settled. Retitled to match. Single source, single path: **Anonymous SteamCMD pull of the dedicated server → parse `Mods/AutoGen/**/*.cs` to JSON at build time → bake the JSON into the image → MCP tools over the JSON.** Dropping the reference-assembly path entirely rather than running both. Two sources means two parsers, two version pins, and a reconciliation story for the fields where they disagree — for a dataset where one source is a strict superset of the other. The cross-check idea from the earlier comment is not worth that; if we want drift detection later, comparing the parsed index against the live host tree (#243) is the more useful comparison anyway, since it catches mod-added content that neither vanilla source has. Nothing C# runs at runtime, and nothing C# runs in the app image. The parse is a build step; the app only ever sees JSON. Same runtime shape as `data/eco_gnome_data.json` today, so `load_recipe_index()`'s in-process memo carries over unchanged. ## The generated C# is very parseable Sampled two recipe files. The whole payload is declarative and machine-emitted: ```csharp [RequiresSkill(typeof(BasicSmeltingSkill), 2)] public class SmeltCopperRecipe : Recipe { public SmeltCopperRecipe() { this.Products = new CraftingElement[] { new CraftingElement<CopperIngotItem>(1), }; this.Ingredients = new CraftingElement[] { new CraftingElement<CopperOreItem>(typeof(BasicSmeltingEfficiencySkill), 4, BasicSmeltingEfficiencySkill.MultiplicativeStrategy), }; this.Initialize("Smelt Copper", typeof(SmeltCopperRecipe)); this.CraftMinutes = CreateCraftTimeValue(typeof(SmeltCopperRecipe), this.UILink(), 0.5f, typeof(BasicSmeltingSpeedSkill)); CraftingComponent.AddRecipe(typeof(BlastFurnaceObject), this); } } ``` Every field the `RecipeIndex` DTO wants is on the page, including the two the other sources lose: | DTO field | Where it comes from | |---|---| | `name` | class name | | `displayName` | `Initialize("Smelt Copper", …)` | | `products[]` + quantity | `CraftingElement<CopperIngotItem>(1)` | | `ingredients[]` + quantity | `CraftingElement<CopperOreItem>(…, 4, …)` | | `skill{name, level}` | `[RequiresSkill(typeof(BasicSmeltingSkill), 2)]` | | `craftMinutes` | `CreateCraftTimeValue(…, 0.5f, …)` | | **`station`** | `CraftingComponent.AddRecipe(typeof(BlastFurnaceObject), this)` | `station` is the one the reference assemblies drop for 94% of recipes, and it is right there as a constructor call. The efficiency and speed skill modifiers come along free, which Eco Gnome models too. Recommend a **plain Python parser** over these blocks rather than Roslyn. The input is machine-generated and stable, the output is consumed by Python, and it keeps the build step from needing the .NET stage. Roslyn stays the fallback if the grammar turns out to bite. ## One trap for the implementer The sample above is from an **old** Eco version — note `: Recipe`, not `: RecipeFamily`. The 0.13 reference assemblies show 1,088 `RecipeFamily` subclasses, so the modern generated shape wraps multiple recipe variants per family and adds fields this sample lacks (`LaborInCalories` among them). **Write the parser against the real 0.13 download, not against the GitHub mirror**, and treat the mirror only as evidence that the format is regular. ## Revised plan 1. Build stage: `steamcmd +login anonymous +app_update 739590 validate` at a pinned build id. 2. Parse `Eco_Data/Server/Mods/AutoGen/**/*.cs` → `RecipeIndex`-shaped JSON, with a `source` string naming the app id and build id, the way `GNOME_DATA_SOURCE` names its pin. 3. Bake the JSON into the image; drop `data/eco_gnome_data.json` once the new index covers it, or keep it briefly to diff the two and confirm the parser. 4. `get_recipes`, `price_recipe`, `get_skills` via `DualRouteRegistry`, summary-first with a `limit` per #240. Everything in "Routes to add" and "Acceptance" in the body still stands; the sourcing half of the body and its three options are superseded by this comment. Options 2 and 3 (the `ecocraft` Postgres and the absent Blazor API) are dead — with AutoGen parsed, the only thing Eco Gnome would still uniquely hold is user-authored price configs, which is a separate feature if we ever want it.
Author
Member

Phase 1 landed in 3f6ada7 — option (1), the vendored graph, with no new infrastructure. Closing on that, because the headline gap the issue opens with is closed: the recipe/cost plane is no longer browser-only.

What shipped

Three tools through the DualRouteRegistry, exactly as the issue scoped:

  • get_recipes — wraps filter_index, filterable by product / skill / station
  • price_recipe — the ?cost=1 engine as a first-class tool, taking server, calorie_price, minute_price
  • get_skills — the skill axis with per-skill recipe coverage, which is the join the value-per-profession work wants

Verified in tools/list against the live server via ward exec smoke.

On the acceptance criteria

Response cap. Designed in, not retrofitted — the family-3 lesson from #240. get_recipes is summary-first with a limit defaulting to 25, and it announces its own truncation (showing 25 of 1,453 matching recipes; filter by product, skill or station, or raise limit) rather than quietly looking like the whole graph. recipesMatched and recipesReturned ride alongside.

/preview/recipes.json keeps its shape and query params. The bespoke handler is gone; the path is now served by the registered route. It keeps product / skill / station / cost / server / caloriePrice / minutePrice. The last two are pydantic aliases rather than renames, specifically so the SPA's existing requests keep validating — a rename there would have been a silent 422 on the recipes and cost pages. limit=0 means "everything", which is what the SPA passes.

The existing cost tests caught this: my first cut made ?cost=1 an unknown field and the page 422'd. They now cover the round trip, and a new test pins the route's shape and its filters directly.

Source string stays. Better than that now — every payload carries sourceKind and a serverSpecific boolean from #179, so a consumer can tell vanilla-seed from a modded export without parsing prose.

Phase 2 is not this

The ecocraft Postgres path is untouched, and its open questions stand as written — the read-only role, whether cross-namespace traffic is allowed as deployed, and how the DataExporter path lands. Worth noting that the third of those moved while this sat: #179 shipped a DataExporter ingestion tier that takes priority over both bundled graphs, so "drop-in replacement for the vendored file" is now the answer for modded content, and phase 2 would be about the user price configs rather than the recipe graph itself.

If you want phase 2 tracked, it wants its own issue with those questions answered rather than staying open here — the surface it needs is different from the one this issue asked for.

Phase 1 landed in `3f6ada7` — option (1), the vendored graph, with no new infrastructure. Closing on that, because the headline gap the issue opens with is closed: the recipe/cost plane is no longer browser-only. ## What shipped Three tools through the `DualRouteRegistry`, exactly as the issue scoped: - `get_recipes` — wraps `filter_index`, filterable by `product` / `skill` / `station` - `price_recipe` — the `?cost=1` engine as a first-class tool, taking `server`, `calorie_price`, `minute_price` - `get_skills` — the skill axis with per-skill recipe coverage, which is the join the value-per-profession work wants Verified in `tools/list` against the live server via `ward exec smoke`. ## On the acceptance criteria **Response cap.** Designed in, not retrofitted — the family-3 lesson from https://forgejo.coilysiren.me/coilyco-gaming/eco-app/issues/240. `get_recipes` is summary-first with a `limit` defaulting to 25, and it announces its own truncation (`showing 25 of 1,453 matching recipes; filter by product, skill or station, or raise limit`) rather than quietly looking like the whole graph. `recipesMatched` and `recipesReturned` ride alongside. **`/preview/recipes.json` keeps its shape and query params.** The bespoke handler is gone; the path is now served by the registered route. It keeps `product` / `skill` / `station` / `cost` / `server` / `caloriePrice` / `minutePrice`. The last two are pydantic **aliases** rather than renames, specifically so the SPA's existing requests keep validating — a rename there would have been a silent 422 on the recipes and cost pages. `limit=0` means "everything", which is what the SPA passes. The existing cost tests caught this: my first cut made `?cost=1` an unknown field and the page 422'd. They now cover the round trip, and a new test pins the route's shape and its filters directly. **Source string stays.** Better than that now — every payload carries `sourceKind` and a `serverSpecific` boolean from https://forgejo.coilysiren.me/coilyco-gaming/eco-app/issues/179, so a consumer can tell vanilla-seed from a modded export without parsing prose. ## Phase 2 is not this The `ecocraft` Postgres path is untouched, and its open questions stand as written — the read-only role, whether cross-namespace traffic is allowed as deployed, and how the DataExporter path lands. Worth noting that the third of those moved while this sat: https://forgejo.coilysiren.me/coilyco-gaming/eco-app/issues/179 shipped a DataExporter ingestion tier that takes priority over both bundled graphs, so "drop-in replacement for the vendored file" is now the answer for modded content, and phase 2 would be about the user price configs rather than the recipe graph itself. If you want phase 2 tracked, it wants its own issue with those questions answered rather than staying open here — the surface it needs is different from the one this issue asked for.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
coilyco-gaming/eco-app#242
No description provided.