Skip to content

Mod Inspector: "recipe smelter" false conflict when 4 mods add distinct inputs to the vanilla Smelter #2

Description

@XonofiliusPL

Summary

The ANALYSIS tab reports:

1 CONFLICT: recipe smelter registered by Sandustry: Critical Matter and Platinum Industry and Atomic Age and Red Sand Production - the later one wins or throws

This is a false positive. The four mods each call api.structures.recipes.register("smelter", …) with distinct input elements — they attach recipes to the same vanilla machine, which is the supported case. There is no runtime collision.

Environment

  • Sandustry 0.5.6, Mod Inspector 0.3.0, SandKit apiVersion 1
  • Mods: Atomic Age (3784544373), Sandustry: Critical Matter (3787956085), Red Sand Production (3788826976), Platinum Industry (3792673946)

Root cause

The scan in main.js:

{ kind: "recipe", regex: /\brecipes\s*\.\s*register\s*\(\s*["'`]([^"'`]+)["'`]/g },

captures the literal "smelter" — but smelter is the machine id, not a recipe id. The engine (recipes.register in the game bundle, ~@903849) keys the recipe table by machine and stores one recipe per input element; a second register for the same machine only replaces an existing recipe when the input matches:

// K(state, machineId, def): switch(machineId) -> condensers|steamDryers|synthesizers|snowmakers|smelters
let i = -1; for (let e = 0; e < a.length; e++) if (a[e].input === r.input) { i = e; break; }
i === -1 ? a.push(r) : a[i] = r;

Only those machine ids are legal keys for recipes.register; anything else throws TypeError at load. So renaming the literal would break every recipe involved — the mods cannot "fix" this from their side.

Actual inputs (no two overlap)

Mod Smelter recipes (input → outputs)
Atomic Age (uolkx.atomic-age) sand → silicon, oil → sulfur, basalt → sulfur
Sandustry: Critical Matter (superman4eg.critical-matter) fuelPellet → liquidReactorFuel, moxBlend → liquidMoxFuel
Red Sand Production (sandustry-labs.red-sand-production) gold → liquidGold (intentionally retunes vanilla, 0.5 chance)
Platinum Industry (my3blkaht.platinum-industry) platinum → liquidPlatinum

All four behave correctly together and are keyed by distinct inputs.

Suggested fix

Treat recipes attached to the known vanilla machine ids as shared-by-design, the same way hooks/events already are (SHARED_KINDS): keep them out of registryConflicts/countProblems and list them under the "SHARED BY DESIGN" info section. A real conflict only exists when the same machine and the same input both match — inputs are runtime element ids, so this could be checked against sandkit.mods.recipes instead of a source regex.

 const SHARED_KINDS = new Set(["hook", "event"]);
+// recipes.register() is keyed off the *machine* id, not a recipe id - the
+// engine accepts exactly these machine types and stores one recipe per input
+// element. Several mods attaching distinct inputs to the same vanilla machine
+// (Smelter, Synthesizer, ...) is the normal case, so these can never collide at
+// runtime unless two mods pick the same input, which a source scan cannot see.
+const SHARED_RECIPE_MACHINES = new Set([
+	"condenser",
+	"steamDryer",
+	"synthesizer",
+	"snowmaker",
+	"smelter",
+]);
+const sharedByDesign = (kind, id) =>
+	SHARED_KINDS.has(kind) || (kind === "recipe" && SHARED_RECIPE_MACHINES.has(id));

 	for (const [kind, byId] of owners) {
 		for (const [id, modIds] of byId) {
 			if (modIds.length < 2) continue;
-			(SHARED_KINDS.has(kind) ? sharedHooks : registryConflicts).push({ kind, id, modIds });
+			sharedByDesign(kind, id)
+				? sharedHooks.push({ kind, id, modIds })
+				: registryConflicts.push({ kind, id, modIds });
 		}
 	}

And mirror it in the two "WHAT EACH MOD REGISTERS" filters:

- const registered = owned.filter((entry) => !SHARED_KINDS.has(entry.kind)).length;
+ const registered = owned.filter((entry) => !sharedByDesign(entry.kind, entry.id)).length;
- const owned = (analysis.perMod.get(mod.id) || []).filter((entry) => !SHARED_KINDS.has(entry.kind));
+ const owned = (analysis.perMod.get(mod.id) || []).filter((entry) => !sharedByDesign(entry.kind, entry.id));

Verified locally: patch passes node --check, and re-running the scanning logic over all four mods' sources drops countProblems to 0 (the entry moves to the shared-by-design bucket).

Reproduce

  1. Subscribe to the four mods listed above.
  2. Open Mod Inspector → ANALYSIS.
  3. Result: 1 CONFLICT (recipe smelter), while all smelter recipes (silicon, sulfur, liquid reactor fuel, liquid platinum, liquid gold) actually work in-game.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions