Skip to content

Commit e26f6dc

Browse files
committed
fix(solver): drop phantom dust flows; remove whole-machine mode; purge issue #s from UI
Three block-page fixes from live review: 1. A boundary flow smaller than a tiny fraction of an item's gross in-block throughput is now dropped as numerical dust. HiGHS returns rates with ~1e-7 relative error; on a covered item (block 20: water made by a pump, consumed by a barreler) the net is a near- cancellation of large equal terms — 275.0004 produced − 275 consumed — which surfaced a phantom 0.0004 water export. A relative floor (fraction of gross traffic) drops it without hiding real small flows. 2. Remove whole-machine mode. It only ceil()'d the displayed count while leaving the material flows at exact demand — 'artificially ceil for display,' as the user put it. Making buildings truly run at 100% would create surplus that Factorio actually throttles away in steady state, so it'd misrepresent the factory. The honest number is the fractional requirement; the factory page still shows the whole-machine build target in the count tooltip, and users round up themselves. Drops the MIP path from the LP, the doc flag, the toolbar toggle, and the sub-block wiring. 3. Sweep issue numbers out of rendered UI strings. Only one had leaked — the 'Whole machines (#98)' toolbar tooltip — removed with the mode. (Code comments keep their #refs; those aren't displayed.) Refs #91
1 parent 827febb commit e26f6dc

13 files changed

Lines changed: 87 additions & 171 deletions

File tree

app/src/components/block/block-toolbar.tsx

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,6 @@ export function BlockToolbar({
6969
onOpenIconPicker: () => void;
7070
}) {
7171
const blockName = useStore(doc.store, (s) => s.blockName);
72-
const wholeMachines = useStore(doc.store, (s) => s.wholeMachines);
7372
const customIcon = useStore(doc.store, (s) => s.customIcon);
7473
return (
7574
<div className="mb-3 flex flex-wrap items-center gap-2">
@@ -118,18 +117,6 @@ export function BlockToolbar({
118117
""
119118
)}
120119
</span>
121-
<Button
122-
variant={wholeMachines ? "default" : "outline"}
123-
size="sm"
124-
onClick={() => {
125-
doc.setWholeMachines(!wholeMachines);
126-
doc.note(wholeMachines ? "Fractional machine counts" : "Whole-machine counts");
127-
}}
128-
title="Whole machines (#98): every row's building count becomes an integer the solve commits to — machines may idle, rates stay exact. Off = exact fractional counts."
129-
className={wholeMachines ? "" : "text-muted-foreground"}
130-
>
131-
132-
</Button>
133120
<Button
134121
variant="outline"
135122
size="icon-sm"

app/src/components/block/doc-store.ts

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,6 @@ export type BlockDocState = {
5050
made: ReadonlySet<string> | null;
5151
/** per-row pins (#91): fixed/cap building counts, consumer shares */
5252
pins: DocPin[];
53-
/** whole-machine mode (#98): integer building counts per row */
54-
wholeMachines: boolean;
5553
/** legacy dispositions payload, kept verbatim until `made` is adopted so the
5654
* server keeps deriving from it; dropped from the doc after adoption */
5755
dispositions: Record<string, Disposition>;
@@ -77,7 +75,6 @@ const EMPTY: BlockDocState = {
7775
disabled: new Set(),
7876
made: null,
7977
pins: [],
80-
wholeMachines: false,
8178
dispositions: {},
8279
spoilRates: {},
8380
rowGroups: [],
@@ -107,7 +104,6 @@ export function solveInputOf(s: BlockDocState): SolveInput {
107104
// shipping their dispositions so the server can derive
108105
...(s.made ? { made: [...s.made].sort() } : {}),
109106
...(s.pins.length ? { pins: s.pins } : {}),
110-
...(s.wholeMachines ? { wholeMachines: true } : {}),
111107
...(!s.made && Object.keys(s.dispositions).length ? { dispositions: s.dispositions } : {}),
112108
...(Object.keys(s.machines).length ? { machines: s.machines } : {}),
113109
...(Object.keys(s.fuels).length ? { fuels: s.fuels } : {}),
@@ -158,7 +154,6 @@ export function createBlockDocStore() {
158154
disabled: new Set(d.disabledRecipes ?? []),
159155
made: d.made ? new Set(d.made) : null,
160156
pins: d.pins ?? [],
161-
wholeMachines: d.wholeMachines ?? false,
162157
dispositions: (d.dispositions ?? {}) as Record<string, Disposition>,
163158
spoilRates: d.spoilRates ?? {},
164159
machines: d.machines ?? {},
@@ -375,8 +370,6 @@ export function createBlockDocStore() {
375370
pin,
376371
],
377372
})),
378-
/** whole-machine mode (#98): integer building counts on every row */
379-
setWholeMachines: (on: boolean) => edit(() => ({ wholeMachines: on })),
380373
clearPin: (recipe: string, share?: { item: string }) =>
381374
edit((s) => ({
382375
pins: s.pins.filter((p) =>

app/src/components/block/pin-dialog.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import { Icon } from "../../lib/icons";
1515
import type { BlockDocStore } from "./doc-store.ts";
1616
import type { SolveResult } from "./solve-view.ts";
1717

18-
/** Pin editor for one recipe row (#91/#98): fixed building count (always run
18+
/** Pin editor for one recipe row (#91): fixed building count (always run
1919
* exactly N — supply-push), built cap (at most N — the reality ceiling), and
2020
* consumer share pins (this row takes a % of an ingredient's in-block
2121
* production — the byproduct-routing gesture). One count-or-cap pin per row;

app/src/components/block/row-menu.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ export function RowMenu({
2828
onNewGroup: () => void;
2929
onJoinGroup: (groupId: number) => void;
3030
onLeaveGroup: () => void;
31-
/** open the pin editor (#91/#98): fixed/cap counts, input shares */
31+
/** open the pin editor (#91): fixed/cap counts, input shares */
3232
onOpenPins: () => void;
3333
onClose: () => void;
3434
}) {

app/src/components/factory/machines-card.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ const HEAD_W: Record<string, string> = {
7070
short: "w-24 justify-end",
7171
};
7272

73-
/** A required count: whole-machine blocks (#98) show the integer as-is;
73+
/** A required count: whole (built) counts show the integer as-is;
7474
* fractional solves show the exact ratio with the build target in the title. */
7575
function RequiredCount({ n }: { n: number }) {
7676
if (isWholeCount(n)) return <>{fmtMachineCount(n)}</>;

app/src/db/schema.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -451,9 +451,6 @@ export type BlockData = {
451451
| { kind: "count" | "cap"; recipe: string; count: number }
452452
| { kind: "share"; recipe: string; item: string; share: number; base?: "total" | "remaining" }
453453
)[];
454-
// Whole-machine mode (#98): building counts are integers the solve commits
455-
// to (rows may idle; rates stay exact). Off = exact fractional counts.
456-
wholeMachines?: boolean;
457454
machines?: Record<string, string>; // recipe → chosen machine
458455
fuels?: Record<string, string>; // recipe → chosen fuel
459456
// Reactor farm layout per reactor recipe row (#94): the assumed x×y grid whose

app/src/lib/machine-count.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { describe, expect, it } from "vite-plus/test";
33
import { fmtMachineCount, isWholeCount, wholeMachines } from "./machine-count";
44

55
/** Machine counts arrive in two shapes (#80): fractional from the default solve
6-
* (7.28 assemblers) and integer from whole-machine mode (#98) or the game's
6+
* (7.28 assemblers) and integer from the game's
77
* built counts. Both must render sanely. */
88
describe("machine-count helpers", () => {
99
it("renders whole counts plain — no fake decimals", () => {

app/src/lib/machine-count.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/**
22
* Machine-count rendering helpers. Block solves report machine counts that are
33
* either fractional (the default solve — 7.28 assemblers is the exact ratio) or
4-
* integer (whole-machine mode, #98) — both flow into the factory machines
4+
* integer (from the game's built counts) — both flow into the factory machines
55
* table, so it renders each sanely: integers plain, fractions at adaptive
66
* precision with the whole-machine build target alongside.
77
*/
@@ -10,7 +10,7 @@ import { formatQty } from "./format";
1010
/** Whole machines you must place to cover a (possibly fractional) requirement. */
1111
export const wholeMachines = (n: number): number => Math.ceil(n - 1e-6);
1212

13-
/** True when a count is (numerically) a whole number — whole-machine-mode
13+
/** True when a count is (numerically) a whole number — the game's built-count
1414
* blocks and built counts from the game; fractional solves are not. */
1515
export const isWholeCount = (n: number): boolean => Math.abs(n - Math.round(n)) < 1e-6;
1616

app/src/routes/block.$id.tsx

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,6 @@ function Block({ blockId }: { blockId: number }) {
8181
recipes,
8282
made,
8383
pins,
84-
wholeMachines,
8584
spoilRates,
8685
rowGroups,
8786
recipeGroups,
@@ -119,7 +118,7 @@ function Block({ blockId }: { blockId: number }) {
119118
const [renamingGroup, setRenamingGroup] = useState<number | null>(null);
120119
// right-click menu on a recipe row (sub-block actions)
121120
const [rowMenu, setRowMenu] = useState<{ x: number; y: number; name: string } | null>(null);
122-
// pin editor (#91/#98): the recipe whose pins are being edited
121+
// pin editor (#91): the recipe whose pins are being edited
123122
const [pinFor, setPinFor] = useState<string | null>(null);
124123
// snapshot-history drawer (#85)
125124
const [historyOpen, setHistoryOpen] = useState(false);
@@ -328,7 +327,6 @@ function Block({ blockId }: { blockId: number }) {
328327
spoilRates,
329328
made,
330329
pins,
331-
wholeMachines,
332330
machineSel,
333331
fuelSel,
334332
moduleSel,

app/src/server/block-compute.server.ts

Lines changed: 8 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -224,9 +224,6 @@ export type SolveInput = {
224224
* count = always run exactly N buildings; cap = at most N buildings (built
225225
* ceiling); share = this consumer takes a % of the item's production. */
226226
pins?: DocPin[];
227-
/** whole-machine mode (#98): every row's building count is an integer the
228-
* solve commits to (machines may idle); rates stay exact. */
229-
wholeMachines?: boolean;
230227
machines?: Record<string, string>; // recipe → chosen machine (else fastest)
231228
fuels?: Record<string, string>; // recipe → chosen fuel (else cheapest available)
232229
// Reactor farm layout per reactor recipe row (#94): the assumed x×y grid whose
@@ -625,22 +622,12 @@ export async function computeBlock(rawData: SolveInput) {
625622
rate: p.count * perBuilding,
626623
});
627624
}
628-
// whole-machine mode (#98): hand the LP each row's real per-building rate so
629-
// it can commit to integer counts
630-
const machineRates = data.wholeMachines
631-
? Object.fromEntries(
632-
defs.flatMap((d) => {
633-
const per = craftRate(d.name);
634-
return per != null && per > 0 ? [[d.name, per]] : [];
635-
}),
636-
)
637-
: undefined;
638625
const defaultTemp = (f: string) => q.getFluid(f)?.defaultTemperature ?? null;
639626
// Sub-blocks v2 (#76): a COMPOSED group is solved as its own module and pulled
640627
// out of the parent solve, replaced by a synthetic recipe carrying only its
641-
// boundary contract (net imports → net exports). Member recipes, pins and
642-
// whole-machine rates route into their module; the parent solves normally over
643-
// its own recipes + these synthetics. Display-only groups (#7) are untouched.
628+
// boundary contract (net imports → net exports). Member recipes and pins route
629+
// into their module; the parent solves normally over its own recipes + these
630+
// synthetics. Display-only groups (#7) are untouched.
644631
const composedGroups: ComposedGroup[] = (data.rowGroups ?? [])
645632
.filter((g) => g.composed)
646633
.map((g) => ({
@@ -653,11 +640,10 @@ export async function computeBlock(rawData: SolveInput) {
653640
...(g.made ? { made: g.made } : {}),
654641
}));
655642
const compose = composedGroups.length
656-
? await composeSubBlocks({ defs, groups: composedGroups, pins, machineRates, defaultTemp })
643+
? await composeSubBlocks({ defs, groups: composedGroups, pins, defaultTemp })
657644
: {
658645
parentDefs: defs,
659646
parentPins: pins,
660-
parentMachineRates: machineRates,
661647
subs: [] as SubBlockSolve[],
662648
memberGroupOf: new Map<string, number>(),
663649
};
@@ -677,10 +663,7 @@ export async function computeBlock(rawData: SolveInput) {
677663
{ goals, recipes: compose.parentDefs, made: parentMade, pins: compose.parentPins },
678664
defaultTemp,
679665
);
680-
const lpInput: LpBlockInput = {
681-
...expandedInput,
682-
...(compose.parentMachineRates ? { machineRates: compose.parentMachineRates } : {}),
683-
};
666+
const lpInput: LpBlockInput = expandedInput;
684667
const rawResult = await solveBlockLp(lpInput);
685668
// root-cause cards for the balance card — every member is a clickable gesture.
686669
// Fold synthetic goods back to the bare fluid (the doc's made marks are bare
@@ -778,9 +761,9 @@ export async function computeBlock(rawData: SolveInput) {
778761
autoModules,
779762
} = setup.get(rr.recipe)!;
780763
const speed = (chosen?.craftingSpeed ?? 1) * fx.speedMult;
781-
// whole-machine mode (#98): the LP committed to an integer count (the row
782-
// may idle); otherwise the exact fractional requirement
783-
const count = result.wholeMachines?.[rr.recipe] ?? rr.machines1x / speed;
764+
// fractional building requirement (machine-seconds/sec ÷ speed); the UI
765+
// shows this and the whole-machine build target alongside it
766+
const count = rr.machines1x / speed;
784767
const powerW = (chosen?.energyUsageW ?? 0) * count * fx.consMult;
785768
const pollutionPerMin =
786769
(chosen?.pollutionPerMin ?? 0) * Math.max(0, count) * fx.consMult * fx.pollutionMult;

0 commit comments

Comments
 (0)