Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
"i18next": "^25.6.3",
"i18next-browser-languagedetector": "^8.2.0",
"i18next-http-backend": "^3.0.2",
"javascript-lp-solver": "^1.0.3",
"lucide-react": "^0.552.0",
"radix-ui": "^1.4.3",
"react": "^19.1.1",
Expand Down
8 changes: 8 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions src/components/mappers/merged-mapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,12 @@ export function mapPlanToFlowMerged(
node.recipe.craftingTime,
) * node.facilityCount;

// Defensive: skip phantom sinks below display threshold. The calculator
// already filters via SURPLUS_EPSILON; this guards against any future
// path that injects a near-zero disposal recipe and prevents an isolated
// node from violating flow integrity.
if (disposalRate <= 0.001) return;

disposalSinkNodes.push(
createDisposalSinkNode(
disposalSinkId,
Expand Down
6 changes: 6 additions & 0 deletions src/components/mappers/separated-mapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -749,6 +749,12 @@ export function mapPlanToFlowSeparated(
node.recipe.craftingTime,
) * node.facilityCount;

// Defensive: skip phantom sinks below display threshold. The calculator
// already filters via SURPLUS_EPSILON; this guards against any future
// path that injects a near-zero disposal recipe and prevents an isolated
// node from violating flow integrity.
if (disposalRate <= 0.001) return;

disposalSinkNodes.push(
createDisposalSinkNode(
disposalSinkId,
Expand Down
2 changes: 0 additions & 2 deletions src/lib/calculator-types.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import type { ItemId, RecipeId, FacilityId, Item, Recipe, Facility } from "@/types";

export type SystemRow = { row: number[]; rhs: number; itemId: ItemId };

export type ProductionMaps = {
itemMap: Map<ItemId, Item>;
recipeMap: Map<RecipeId, Recipe>;
Expand Down
11 changes: 10 additions & 1 deletion src/lib/calculator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,15 @@ import type {
InvalidSCCInfo,
} from "./calculator-types";

// Tolerance for floating-point residuals in surplus mass balance.
// LP facility counts can be fractions like 1/6 that don't have exact binary
// representations; recombining `production - consumption - target` can leave
// residuals on the order of 1e-13. Without this tolerance, a disposal recipe
// would be injected with facilityCount ≈ 0, rendering as a disconnected
// "0/min" sink in the UI (e.g. Xircon Effluent on Jade Gourd at 1/min).
// Matches `TARGET_VALIDATION_TOLERANCE` used by the LP solver.
const SURPLUS_EPSILON = 1e-6;

function injectDisposalRecipes(
graph: BipartiteGraph,
flowData: FlowData,
Expand Down Expand Up @@ -65,7 +74,7 @@ function injectDisposalRecipes(
const targetDemand = targets.find((t) => t.itemId === itemId)?.rate || 0;

const surplus = totalProduction - totalConsumption - targetDemand;
if (surplus <= 0) continue;
if (surplus <= SURPLUS_EPSILON) continue;

const disposalRecipe = Array.from(maps.recipeMap.values()).find(
(r) =>
Expand Down
Loading
Loading