Repository navigation
Expand file tree
/
Copy pathmeta.lua
More file actions
626 lines (576 loc) · 46.5 KB
/
Copy pathmeta.lua
File metadata and controls
626 lines (576 loc) · 46.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
---@meta
---@module "solver/problem_generator"
---@alias FilterType "item"|"fluid"|"recipe"|"machine"|"virtual_material"|"virtual_recipe"|"transfer"
-- Constraint-picker tab selector. "external" is a UI-only view over virtual_recipe
-- (the source/sink recipes), not a real material/recipe FilterType — the typed
-- names it yields are still "virtual_recipe".
---@alias FilterTab FilterType|"external"
---@alias LimitType "upper"|"lower"|"equal"
---@alias TimeScale "second"|"five_seconds"|"minute"|"ten_minutes"|"hour"|"ten_hours"|"fifty_hours"|"two_hundred_fifty_hours"|"thousand_hours"
---@alias AmountUnit "time"|"belt"|"storage"
---@alias EnergyType "electric"|"burner"|"heat"|"fluid"|"void"
-- "freeze" marks a solution whose quantity_of_machines_required was imported
-- from an external solver (the headless reference solver's shared strings):
-- the on_tick pump skips it and bulk re-arms (configuration change, research
-- bonus change) keep it frozen, so the imported values display as-is. Any
-- user edit re-arms it to "ready" like every other solution, replacing the
-- frozen values with a normal in-game solve.
---@alias SolverState "ready"|"calculating"|"finished"|"unfinished"|"singular"|"unbounded"|"unfeasible"|"freeze"
-- Per-solution import/dump balancing norm. The first five are user-selectable
-- in the UI; "cascade" is the retired staged rescue, kept dispatchable but
-- hidden. "l2_baseline" is the "l2" QP shaping WITHOUT the post-solve mode-
-- compression fold -- exposed so the fold's effect can be compared side by
-- side against "l2" on the same solution.
---@alias SolverNorm "l1"|"l2"|"l2_baseline"|"linf"|"legacy"|"cascade"|"batch"|"smart"
---@alias Craft LuaItemPrototype|LuaFluidPrototype|LuaRecipePrototype|LuaEntityPrototype|VirtualMaterial|VirtualRecipe
-- Fluid temperature is carried range-only: a point temperature is the degenerate
-- range minimum_temperature == maximum_temperature. There is no single-value
-- `temperature` field; that asymmetry lived only in the raw recipe layer
-- (Product.temperature) and is collapsed to [T,T] at the normalization boundary.
---@alias TypedName { type: FilterType, name: string, quality: string, minimum_temperature: number?, maximum_temperature: number? }
---@alias ProductEx Product|VirtualProduct
---@alias IngredientEx Ingredient|VirtualIngredient
---@class EventDataTrait
---@field element LuaGuiElement
---@field mod_name string?
---@field name string | defines.events
---@field player_index integer
---@field tick integer
---@class Storage
---@field players table<integer, PlayerLocalData>
---@field forces table<integer, ForceLocalData>
---@field virtuals Virtuals
__factory_solver__storage = {}
---@class PlayerLocalData
---@field selected_solution string
---@field selected_filter_type FilterTab
---@field selected_filter_group table<FilterTab, string>
---@field unresearched_craft_visible boolean
---@field hidden_craft_visible boolean
---@field time_scale TimeScale
---@field amount_unit AmountUnit
---@field presets Presets
---@field opened_gui string[]
---@field machine_clipboard MachineClipboard?
---@field module_clipboard TypedName?
---@field build_assistant_mode BuildAssistantMode
---@field expanded_constraint_index integer? Transient: which constraints row currently has its amount quick-set popup (slider + belt/pump throughput buttons) open. nil = none. Not migrated; nil is the valid "closed" state.
---@field picker_builds table<string, PickerBuildState>? In-flight tick-split picker builds (ui/picker_build.lua), keyed by a per-build string (production_line_adder: the section frame name recipe_for_*, up to 4 concurrent; constraint_adder: "constraint_picker", one at a time). nil when none. Plain string/number tables only (no LuaObject, no live iterator), so it is storage / on_load safe and resumable after save/load.
---Captured, storage-safe context for a tick-split picker build (ui/picker_build.lua).
---Common fields are spec_id / needle / dialog_name; the rest are read only by the
---spec that produced the request (production_line_adder uses kind / reference /
---section_name, constraint_adder uses filter_type / group_name).
---@class PickerBuildRequest
---@field spec_id string Registry key for the picker's callback spec.
---@field needle string Folded name-filter ("" = none).
---@field dialog_name string Root element name to re-find the dialog.
---@field kind string? production_line_adder: product | spent | ingredient | fuel.
---@field reference TypedName? production_line_adder: the clicked material (plain table).
---@field section_name string? production_line_adder: section frame name to re-find the target table.
---@field filter_type string? constraint_adder: item | fluid | recipe | virtual_recipe | external.
---@field group_name string? constraint_adder: selected item-group whose subgroups are built.
---@field preset_type string? machine_presets: fuel | fluid_fuel | resource | machine | fixed_recipe.
---Deterministic, storage-safe build plan: entry names in final display order plus
---the grouping key each belongs to (group change drives open_group / close_group).
---is_hidden / is_unresearched are recomputed in the build phase (the relation cache
---is stable mid-build -- a cache rebuild closes all GUIs), so they are not stored here.
---@class PickerBuildPlan
---@field entries string[] Entry names, bucketed into contiguous sort-runs (subgroups) in group/subgroup order but UNSORTED within a run (the run is sorted lazily in the build phase by spec.sort_run, so the one-shot plan tick doesn't pay the whole sort).
---@field group_of string[] Parallel: grouping key per entry (item-group for production_line_adder, subgroup for constraint_adder); drives open_group / close_group.
---@field sort_of string[]? Parallel: sort-run key per entry (subgroup name). A change marks a new run that spec.sort_run sorts in place. nil = entries are already sorted (no in-build sort).
---@field is_material boolean[]? Parallel: constraint_adder virtual_recipe tab only -- true if the entry resolves from storage.virtuals.material (else .recipe), since material / recipe names share one namespace.
---@field typed_name_of TypedName[]? Parallel: machine_presets -- resolved TypedName per entry (crafts mix items / fluids / entities, so the type is precomputed).
---@field is_hidden_of boolean[]? Parallel: machine_presets -- is_hidden per entry (computed in plan from the craft).
---@field is_unresearched_of boolean[]? Parallel: machine_presets -- is_unresearched per entry.
---@field row_caption table<string, LocalisedString>? machine_presets -- row label caption keyed by group_of (read by open_group).
---@field row_selected table<string, TypedName>? machine_presets -- selected preset per row key (drives build-time toggled).
---Progress state for one section's tick-split build.
---@class PickerBuildState
---@field req PickerBuildRequest
---@field plan PickerBuildPlan? nil during the plan phase.
---@field phase string "plan" | "build" | "done".
---@field cursor integer 1-based next entry to build (dense array we own, O(1) resume).
---@field current_group string? Grouping key of the currently open slot (re-acquired by structure, not a stored handle); drives open_group / close_group on change.
---@field current_sort string? Sort-run key already sorted (drives spec.sort_run on change). Persists so a save/load mid-run does not re-sort.
---Which interaction style the docked Build assistant uses. "blueprint" hands
---the player a temporary blueprint for construction-robot placement; "manual"
---hands the actual machine/module item (or a ghost) via the engine's smart
---pipette, for the early game before construction robots.
---@alias BuildAssistantMode "manual"|"blueprint"
---Per-player clipboard for the Shift+Click copy/paste flow over production
---line rows in solution_editor. A copy snapshots the whole machine-side
---configuration of the source line; the `mode` flag selects which subset
---paste applies to the target line. Storing both halves regardless of mode
---keeps the data structure mode-independent and lets the user copy once and
---decide later — at click time — which subset to paste where.
---@class MachineClipboard
---@field mode "machine_fuel"|"module_beacon"
---@field machine_typed_name TypedName
---@field fuel_typed_name TypedName?
---@field substrate_tile_name string?
---@field module_typed_names table<string, TypedName>
---@field affected_by_beacons AffectedByBeacon[]
---@class Presets
---@field fuel table<string, TypedName>
---@field fluid_fuel TypedName
---@field resource table<string, TypedName>
---@field machine table<string, TypedName>
---@field pump table<string, TypedName>
---@field lab table<string, TypedName>
---@field fixed_recipe table<string, TypedName>
---@class ForceLocalData
---@field relation_to_recipes RelationToRecipes
---@field relation_to_recipes_needs_updating boolean
---@field relation_build RelationBuildState? In-flight tick-split relation build (on_tick, manage/relation.lua). nil when none in progress. Plain tables only (no LuaObject, no live pairs iterator -- each phase resumes from a stored string key), so it is storage / on_load safe and survives save/load mid-build.
---@field group_infos GroupInfos
---@field group_infos_needs_updating boolean
---@field solutions table<string, Solution>
---@field research_bonuses ResearchBonuses
---Per-force snapshot of research-derived bonuses. Always opt-in: defaults to
---all zero / "normal" only, populated only when the user clicks Apply in the
---Research bonuses dialog. The solver reads this snapshot; LuaForce runtime
---values are never read inside the solve path.
---@class ResearchBonuses
---@field recipe_productivity table<string, number>
---@field mining_drill_productivity number
---@field laboratory_productivity number
---@field laboratory_speed number
---@field beacon_distribution number
---@field research_unit_energy number
---@field unlocked_qualities table<string, boolean>
---@class RelationToRecipes
---@field enabled_recipe table<string, boolean>
---@field item table<string, RelationToRecipe>
---@field fluid table<string, RelationToRecipe>
---@field virtual_recipe table<string, RelationToRecipe>
---@field virtual_recipe_researched table<string, boolean>
---@field virtual_material_researched table<string, boolean> Per virtual_material researched state (derived from its source fluid/entity craftable_count). Cached so apply_research_change can detect researched flips for the group_infos incremental update; recomputed in full by recompute_relation_dynamic.
---@field recipes_by_category table<string, string[]> Real recipe names grouped by recipe_category; lets fuel consumers expand lazily (RelationToRecipe.fuel_consumer_categories) instead of materializing the recipe x fuel product. See relation.expand_fuel_consumers.
---@field contributes table<string, RelationContribution[]> Recipe name -> the materials it makes craftable when visible (its products plus the spent-fuel residues of the fuels it burns). Drives craftable_count updates -- full (recompute_relation_dynamic) and incremental (apply_research_change) -- without re-reading recipe.products or re-resolving fuels.
---@class RelationContribution
---@field type FilterType
---@field name string
---@class RelationToRecipe
---@field craftable_count integer
---@field recipe_for_product string[]
---@field recipe_for_burnt_result string[] Recipes that emit this material only as a burnt fuel residue (spent cell / ash), kept apart from recipe_for_product so the picker lists them in their own section instead of burying the real producers.
---@field recipe_for_spent_fluid string[] Fluid analog of recipe_for_burnt_result: recipes whose fluid-burning machine emits this fluid as a spent-fluid residue (FluidPrototype::spent_fluid, 2.1.9). Populated on fluid RelationToRecipe entries only; shares the picker's "spent" section with recipe_for_burnt_result.
---@field recipe_for_ingredient string[]
---@field fuel_consumer_categories string[] Recipe categories whose machines burn this material as fuel; expand to real recipe names via relation_to_recipes.recipes_by_category. Stored as category keys, not flattened recipe names: every recipe in a category shares its fuel set, so flattening is the recipe x fuel product (~389k entries on a pyanodon set). See relation.expand_fuel_consumers.
---@field fuel_consumer_virtual_recipes string[] Virtual recipes that consume this material as fuel (machine-derived, listed directly -- no category indirection).
---Progress state for the tick-split relation build (manage/relation.lua's
---build_relation_init / build_relation_step). create_relation_to_recipes' heavy
---recipe-set listing is spread across ticks at RELATION_BUILD_BUDGET entries per
---tick instead of one synchronous pass. Every field is a plain string / number /
---boolean table (no LuaObject, no live pairs iterator) so it is storage-safe and
---resumable after save/load: each phase walks its source table directly via a
---stored-key pairs resume (step_table), and the recipe set is stable across research
---so the walk never goes stale mid-build.
---@class RelationBuildState
---@field phase string Current build phase: prep_fuel_crafting / prep_fuel_resource / prep_alloc_item / prep_alloc_fluid / prep_alloc_vmat / real_recipes / fuel_reverse / virtual_recipes / finalize_real / finalize_vrecipe / finalize_vmat / done.
---@field rel RelationToRecipes Partially built relation cache (filled phase by phase; enabled-dependent fields stay all-false until the finalize phases credit them).
---@field cursor_key string? Key last processed in the current phase's source table; the next step resumes the pairs iterator after it. nil = start of phase (also reset to nil when a phase is exhausted).
---@field structure_ready boolean? Set true when the listing phases finish (entering finalize_real): contributes / seeds / virtual tables are complete, so apply_research_change is structurally safe and a research finishing during the tick-split finalize is applied to rel incrementally instead of skipped. nil/false during listing.
---@field fuel RelationBuildFuelCache cache_fuel_names' result, built incrementally across the prep_fuel phases; reused by the real / virtual recipe passes.
---@field burnt_result_names table<string, string> Fuel item name -> burnt_result item name, built incrementally in prep_alloc_item.
---@field spent_fluid_names table<string, string> Fuel fluid name -> fluid-level spent_fluid fluid name (FluidPrototype::spent_fluid, 2.1.9), built incrementally in prep_alloc_fluid. The fluid analog of burnt_result_names.
---cache_fuel_names' five return values bundled for storage. All name-string
---arrays / maps, so storage-safe.
---@class RelationBuildFuelCache
---@field crafting_fuels table<string, string[]>
---@field resource_fuels table<string, string[]>
---@field crafting_fluid_fuels table<string, string[]>
---@field resource_fluid_fuels table<string, string[]>
---@field any_fluid_fuels string[]
---@class GroupInfos
---@field item table<string, GroupInfo>
---@field fluid table<string, GroupInfo>
---@field recipe table<string, GroupInfo>
---@field virtual_recipe table<string, GroupInfo>
---@field external table<string, GroupInfo>
---@class GroupInfo
---@field hidden_count integer
---@field unresearched_count integer
---@field researched_count integer
---@class Solution
---@field name string
---@field constraints Constraint[]
---@field production_lines ProductionLine[]
---@field quantity_of_machines_required table<string, number>
---@field inactive_recipe_variables table<string, true>?
---@field problem Problem?
---@field solver_state SolverState
---@field solver_iteration integer?
---@field solver_norm SolverNorm? Per-solution import/dump balancing norm (manage/pre_solve.lua dispatch). "l1" un-gated baseline (linear elastic), "l2" QP least-norm on the violation elastics, "l2_baseline" the same QP shaping as "l2" but WITHOUT the post-solve mode-compression fold (a comparison baseline), "linf" lexicographic min-max, "batch" / "smart" the sparse elastic placements (all-elastic L2 base, then a restricted L2 on a Phase-I-measured-and-pruned / representative-derived escape placement with a physical guard), "legacy" the hard reachability gate + two-pass. "cascade" is the retired staged rescue, kept dispatchable (not exposed in the UI) so its fixtures keep validating. nil = "legacy" (the default; backfilled on load).
---@field raw_variables PackedVariables?
---@field done_lines table<string, true>?
---@field forced_imports table<string, true>? Legacy two-pass reclassify (manage/pre_solve.lua, used only when observe_price is disabled): avoidable cheats diagnosed from pass 1, re-seeded as |initial_source| imports for pass 2. nil during pass 1 / a clean solve.
---@field reclassify_pending boolean? Legacy two-pass internal flag: set when the restart re-arms solver_state="ready" for pass 2, so forwerd_solve keeps forced_imports instead of clearing them as it would for a fresh solve.
---@field observe_price ObservePriceState? In-flight observe-price fixed point (manage/pre_solve.lua / solver/observe_price.lua). nil before the baseline solve; a "done" sentinel once the loop settles. Plain tables only, so it rides storage without a metatable.
---@field op_restart boolean? Internal flag: set when observe_price advances and re-arms solver_state="ready", so the rebuild keeps the in-flight plan instead of dropping it as it would for a fresh edit.
---@field target_rescue TargetRescueState? In-flight lexicographic target rescue (manage/pre_solve.lua M.target_rescue_step). nil before the baseline solve; a "done" sentinel once settled. Plain table, storage-safe.
---@field tr_restart boolean? Internal flag: set when the target rescue re-arms solver_state="ready", so the rebuild keeps the in-flight rescue state instead of dropping it as it would for a fresh edit.
---@field cascade CascadeState? In-flight cascade staged rescue (manage/pre_solve.lua M.cascade_step / solver/cascade.lua), the shipped replacement for observe_price. nil before the baseline solve; a "done" sentinel once settled. Plain tables only (it carries a PackedVariables snapshot), so it rides storage without a metatable.
---@field cc_restart boolean? Internal flag: set when the cascade advances and re-arms solver_state="ready", so the rebuild keeps the in-flight cascade state instead of dropping it as it would for a fresh edit.
---@field linf LinfState? In-flight L∞ (min-max) two-stage solve (manage/pre_solve.lua M.linf_step), only for solver_norm == "linf". nil before the baseline solve; a "done" sentinel once settled. Plain table, storage-safe.
---@field lf_restart boolean? Internal flag: set when the L∞ stages re-arm solver_state="ready", so the rebuild keeps the in-flight linf state instead of dropping it as it would for a fresh edit.
---@field l2_compress L2CompressState? In-flight L2 mode compression (manage/pre_solve.lua M.l2_compress_step / solver/mode_compress.lua), only for solver_norm == "l2": once the target-rescued L2 baseline finishes, sibling violation channels (same base material AND same kind) fold to one per group and the problem re-solves without the losers. nil before the baseline solve; a "done" sentinel once settled. Plain tables (while in flight it carries the baseline Problem; manage/save.lua re-attaches its metatable on load).
---@field lc_restart boolean? Internal flag: set when the compression re-arms solver_state="ready", so the rebuild keeps the in-flight compress state AND the settled target rescue (the compressed build must stay locked on the rescued target -- deleting a spreading channel raises the survivors' quadratic marginal cost past the target's linear worth, so an unlocked re-solve can rationally trade the target away) instead of dropping them as it would for a fresh edit.
---@field l2_lock L2LockState? In-flight L2 two-stage violation lock (manage/pre_solve.lua M.l2_lock_step), for solver_norm == "l2" / "l2_baseline": stage 1 measures the violation optimum with the recipe tier dropped to a face regularizer, stage 2 re-solves at ship costs with each violation group capped at that optimum -- so the machine tie-break epsilon can no longer buy violation units on machine-heavy chains (the eps*M-vs-quad equilibrium). nil before the measurement solve; a "done" sentinel (still carrying the caps for the compress rebuild) once settled. Plain table, storage-safe.
---@field ll_restart boolean? Internal flag: set when a lock stage re-arms solver_state="ready", so the rebuild keeps the in-flight lock state AND the settled target rescue instead of dropping them as it would for a fresh edit (same preserve-list role as lf_restart / lc_restart).
---@field placement PlacementState? In-flight sparse elastic placement (manage/pre_solve.lua M.placement_step / solver/placement.lua), for solver_norm == "batch" / "smart": the all-elastic L2 base solve is held as the physical guard reference, then the L2 re-solves restricted to a sparse escape placement (Phase-I-measured or statically derived from the necessity law), widening on a guard failure. nil before the base solve; a "done" sentinel once settled. Plain tables (while in flight it carries the base Problem; manage/save.lua re-attaches its metatable on load).
---@field pm_restart boolean? Internal flag: set when the placement re-arms solver_state="ready", so the rebuild keeps the in-flight placement state AND the settled target rescue instead of dropping them as it would for a fresh edit (same preserve-list role as lf_restart / lc_restart / ll_restart).
---@class PlacementState
---@field phase "phase1"|"prune"|"restricted"|"done" "phase1" while a Phase-I feasibility measurement is in flight ("batch" only; one group opened per solve); "prune" while the irreducibility probes re-close each opening solo and drop the redundant ones ("batch" only); "restricted" while a placement-restricted L2 re-solve runs; "done" once the sparse answer stands (or the base was restored).
---@field prune_list string[]? The openings still to probe (physically smallest first, so a substitutable pair keeps its larger carrier), while phase == "prune".
---@field prune_index integer? 1-based cursor into prune_list.
---@field groups table<string, string[]>? Violation-group index of the base build: base material (Primal.material_base grain) -> its variant material rows, the unit the placement/exclusion works in.
---@field placed table<string, true>? The placement: groups whose escapes the restricted build keeps. Everything else is excluded via CreateProblemOptions.hatch_exclude / sink_exclude.
---@field gp table<string, number>? Per-group physical violation of the base solve (the widening rank and the structural carriers' reference).
---@field base_imp number? Physical import total of the base solve (guard reference).
---@field base_dmp number? Physical dump total of the base solve (guard reference).
---@field t_limit number? Target budget threaded into every placement build (the linf t_limit move): the settled rescue budget, else the base solve's achieved relaxation + margin -- neither the Phase-I nor a sparse re-solve may trade a target away past the base.
---@field rates table<string, number>? The base solve's recipe activity (solver/placement.lua M.recipe_rates), held as lower-limit floors on every placement build -- the shipped analog of the research forcerec, so the sparse re-solve cannot shut down the factory parts whose escapes it removed.
---@field rounds integer? Guard widening rounds spent ("smart" only; "batch" stands on its definition-exact set).
---@field ratio number? The finished restricted solve's physical import+dump total over the base's, recorded as information (a future UI hint; under "batch" a high ratio is the honest price of the definition-exact placement).
---@field p1iters integer? Phase-I iterations spent ("batch").
---@field arts {pos: string, neg: string, row: string, scale: number}[]? The Phase-I artificial keys of the in-flight "phase1" / "prune" build (solver/placement.lua M.shape_phase1; row-scaled, see there), read back by M.phase1_support.
---@field units string[][]? SCC units (base-material member lists) of the static law placement ("smart"), walked by the structural widening.
---@field junctions string[][]? Multi-output junction output lists ("smart"), same role as units.
---@field saved {problem: Problem, raw_variables: PackedVariables, machines: table<string, number>}? The finished base answer, restored verbatim when the restricted solve cannot be widened back to convergence (the L2CompressState.saved pattern).
---@class L2LockState
---@field phase "locked"|"fallback"|"done" "locked" while the ship-epsilon re-solve under the stage-1 caps is in flight; "fallback" while the no-cap ship re-solve after a non-finished stage runs (the pre-two-stage single solve, convergence robustness only); "done" once settled.
---@field caps table<string, number>? Physical-unit violation cap per (kind, base-material) group (create_problem.plan_violation_locks), applied to every later l2 rebuild -- the mode-compress re-solve included -- via create_problem.apply_violation_locks. Kept on the "done" sentinel for exactly that rebuild; dropped on the fallback path.
---@class L2CompressState
---@field phase "compress"|"done" "compress" while the folded re-solve is in flight; "done" once the compressed answer stands (or the baseline was restored / nothing folded).
---@field hatch table<string, true>? Materials whose |shortage_source| import hatch the compressed build omits (import-side group losers), threaded into CreateProblemOptions.hatch_exclude on the lc_restart rebuild.
---@field sink table<string, true>? Materials whose |surplus_sink| dump escape the compressed build omits (dump-side group losers), threaded into CreateProblemOptions.sink_exclude.
---@field saved {problem: Problem, raw_variables: PackedVariables, machines: table<string, number>}? The finished baseline answer, held while the compress solve is in flight so a non-finished compress solve restores it verbatim (no third solve). Dropped when the phase settles.
---@class LinfState
---@field phase "minmax"|"capped"|"done" Which L∞ stage the next finished solve belongs to. "minmax" minimizes the peak violation t; "capped" minimizes total violation under t <= t_min; "done" is the settled sentinel.
---@field t_limit number The target budget threaded into both stage builds as create_problem's target_budget row (so meeting the targets stays tier-1 above the min-max / capped objectives). The rescue's budget when one fired; otherwise locked at the baseline's achieved relaxation (the cascade's `rescue_budget or budget(T)` move), so the capped stage's L1 re-cost cannot re-enter the target collapse.
---@field t_budget number? The locked peak cap (stage-1 optimum plus margin), threaded into the capped stage. Set when minmax finishes.
---@class TargetRescueState
---@field phase "stage1"|"resolve"|"restore"|"done" Which rescue stage the next finished solve belongs to. "resolve" is the budget-locked re-solve at ship costs; "restore" is the plain re-solve when stage 1 found no headroom; "done" is the settled sentinel.
---@field t0 number? The baseline target relaxation that triggered the rescue.
---@field budget number? The locked sum(elastic) cap (stage-1 optimum plus margin), threaded into every later rebuild as create_problem's target_budget. nil when no rescue was needed or no headroom was found.
---@class ObservePriceState
---@field phase "observe"|"verify"|"finalize"|"done" Which loop stage the next finished solve belongs to. "finalize" is the single cheap-import-only solve when nothing qualifies to fabricate; "done" is the settled sentinel.
---@field plan ObservePricePlan? The fabricate plan (nil when only cheap-imports apply).
---@field imports table<string, true>? Two-pass diagnose's cheap-import set (avoidable export-feasible cheats observe-price does not fabricate), applied as forced_imports on every rebuild.
---@field group_index integer? 1-based index into plan.groups currently being observed.
---@field round integer? Verify/correct round counter.
---@field baseline_cheat number? shortage+elastic mass of the baseline solve, for the keep-best guard.
---@field reverted boolean? Set when the keep-best guard discarded the fabricate overrides and fell back to cheap-importing every avoidable cheat.
---@class ObservePricePlan
---@field keys ObservePriceKey[]
---@field groups string[] SCC group ids, in observe order.
---@field exclude table<string, true> The plan's own shortage variable keys (for the escape-cost delta).
---@field escape_cost_before number Baseline escape cost (escape_cost units), the dCost baseline.
---@field relax0 number Baseline target-relaxation mass.
---@field threshold number Baseline park threshold.
---@class ObservePriceKey
---@field key string The |shortage_source| variable name.
---@field material string The material variable it stands in for (the shortage_cost_overrides key).
---@field qty number Baseline shortage value.
---@field ceiling number Max multiplier (target_cost / (elastic_cost * qty)).
---@field group string SCC group id.
---@field mult number Current price multiplier.
---@field frozen boolean Frozen back to flat import (cone-over-promise / unavoidable).
---@field resolved_round integer Round the key's shortage parked (-1 = unresolved).
---In-flight cascade staged rescue (solver/cascade.lua). A plain working set --
---no metatable -- so it rides storage and survives save/load. Most fields are
---transient bookkeeping for whichever stage is running; the cross-tick-durable
---ones are `phase`, `build`, the per-tier lock keys/limits (threaded into later
---builds), `adopted_raw`/`adopted_build` (the answer to fall back to), and the
---`hatch_exclude`/`relay` fallback markers. `*_verdicts` cache per-material fix
---test outcomes so a grown universe does not re-screen them.
---@class CascadeState
---@field phase string Which build the next M.advance consumes ("start"|"vp_*"|"vf_*"|"vc_*"|"polish"|"restore"|"done").
---@field solves integer Solves spent so far (diagnostic).
---@field build CascadeBuild? The next solve to run, or nil once settled / on restore.
---@field intermediates table<string, true> Produced-and-consumed material set (M.intermediates), the import-accounting key.
---@field t_limit number The target budget threaded into every cascade build.
---@field adopted_raw PackedVariables The current best answer, restored when a later stage is rejected.
---@field adopted_build CascadeBuild The build that produced adopted_raw (rebuilt on restore).
---@field current_adopted boolean True when the held solution equals the adopted one (no restore needed).
---@field imports CascadeEntry[] Sorted import hatches of the adopted build.
---@field sinks CascadeEntry[] Sorted surplus sinks of the adopted build.
---@field recipes string[] Sorted recipe keys (the machine count).
---@field m_adopted number Machine total of the adopted answer.
---@field hatch_exclude table<string, true>? Deletion-fallback: import hatches removed structurally (sticky once set).
---@field relay boolean? Staged-relay fallback: every later stage adopts its own stage solution.
---@field vp_univ table<string, true>? Producibility classification universe (flowing imports + probe discoveries).
---@field vp_members table<string, true>? Producible members of vp_univ.
---@field vp_verdicts table<string, boolean>? Cached individual producibility verdicts.
---@field vp_pending string[]? Materials awaiting an individual producibility screen.
---@field vp_coupled boolean? A violator re-joined: the demote-one phase must run.
---@field vp_demoted string[]? Phase-2 demotions (the phase-3 promotion candidates).
---@field vp_demote_rounds integer? Demote-one round counter.
---@field vp_promote_queue string[]? Remaining phase-3 promotion candidates.
---@field vp_sweeps_left integer? Phase-3 sweep budget.
---@field vp_promoted_any boolean? Whether the current sweep promoted anything.
---@field vp_support_done boolean? The one support probe has run.
---@field vp_priced string[]? The priced producible (+ never-flowed) hatch keys.
---@field vp0 number? Baseline priced-import sum.
---@field vpmin number? Stage-optimal priced-import sum.
---@field vp_lock_keys string[]? Vp budget lock membership (nil after the deletion fallback).
---@field vp_lock_limit number? Vp budget lock limit.
---@field vp_rescued integer? 1 applied / -1 attempted-but-kept-baseline / nil not triggered.
---@field vp_deleted integer? 1 deletion final / 2 staged relay.
---@field vp_stage_raw PackedVariables? Held Vp stage answer (the staged-relay adoption source).
---@field vp_stage_build CascadeBuild? The Vp stage build.
---@field vf_lock_keys string[]? Makeup-import (Vf) lock membership.
---@field vf_lock_limit number? Vf budget lock limit.
---@field vf0 number? Baseline makeup sum.
---@field vfmin number? Stage-optimal makeup sum.
---@field vf_rescued integer? 1 applied / -1 kept baseline.
---@field vc_univ table<string, true>? Consumability classification universe (flowing dumps + probe discoveries).
---@field vc_members table<string, true>? Consumable members of vc_univ.
---@field vc_verdicts table<string, boolean>? Cached individual consumability verdicts.
---@field vc_pending string[]? Materials awaiting an individual consumability screen.
---@field vc_support_done boolean? The Vc support probe has run.
---@field vc_lock_keys string[]? Consumable-dump (Vc) lock membership.
---@field vc_lock_limit number? Vc budget lock limit.
---@field vc0 number? Baseline priced-surplus sum.
---@field vcmin number? Stage-optimal priced-surplus sum.
---@field vc_rescued integer? 1 applied / -1 kept baseline.
---@field m0 number? Machine total before the polish.
---@field mmin number? Machine total the polish reached.
---@field polish integer? 1 adopted / -1 kept baseline.
---One cascade solve, described as plain data (storage-safe). A build is the
---bare structural problem when `fix` is set (the reference's escape-hatch-off
---shape), else the un-gated ship shape carrying `target_budget`, the sticky
---`hatch_exclude`, the budget-lock `locks`, and one stage objective
---(`stage_keys` prices those escapes / `stage_machine` prices recipes).
---@class CascadeBuild
---@field fix CascadeFixTest? A producibility / consumability fix test (synthetic demand probes).
---@field stage_keys string[]? Escapes to price at 1 (a stage objective).
---@field stage_machine boolean? Price recipes at 1 (the machine polish objective).
---@field locks CascadeLock[]? Budget-lock rows to add.
---@field target_budget number? The target elastics' summed cap.
---@field hatch_exclude table<string, true>? Materials whose import hatch is structurally omitted.
---@field cold boolean? A classification-determining build (the support probe) that must NOT warm-start: its nonzero set is read vertex-dependently (universe growth), so a warm seed picks a different degenerate vertex than cold/ref and corrupts the classification. Fix-test builds (`fix ~= nil`) are cold for the same reason; M.is_cold folds both.
---@class CascadeFixTest
---@field priced_kind string The escape kind that must stay at zero for membership ("shortage_source" | "surplus_sink").
---@field demand_sign number The synthetic probe's material-row coefficient (-1 producibility / +1 consumability).
---@field priced string[] Materials whose escape is priced.
---@field demand string[] Materials a unit of demand is forced through.
---@class CascadeLock
---@field tier string "vp" | "vf" | "vc" (the budget dual key suffix).
---@field keys string[] Escape keys summed by the lock row.
---@field limit number The summed-escape cap.
---@class CascadeEntry
---@field key string The primal variable key.
---@field material string The base material variable key it stands in for.
---@class Constraint
---@field type FilterType
---@field name string
---@field quality string
---@field minimum_temperature number?
---@field maximum_temperature number?
---@field limit_type LimitType
---@field limit_amount_per_second number
---@class ProductionLine
---@field recipe_typed_name TypedName
---@field machine_typed_name TypedName
---@field module_typed_names table<string, TypedName>
---@field affected_by_beacons AffectedByBeacon[]
---@field fuel_typed_name TypedName?
---@field substrate_tile_name string?
---@class AffectedByBeacon
---@field beacon_typed_name TypedName?
---@field beacon_quantity integer
---@field module_typed_names table<string, TypedName>
---Per-source module aggregation produced by accessor.get_total_modules.
---machine_modules contains modules sitting in the machine's own slots.
---beacon_groups holds one entry per beacon attached to the line, with
---per-module counts already scaled by that beacon's distribution effectivity.
---Splitting by source lets get_total_effectivity apply
---(recipe ∩ machine).allowed_effects to machine modules and
---(recipe ∩ beacon).allowed_effects to each beacon's modules independently.
---@class TotalModules
---@field machine_modules table<string, table<string, number>>
---@field beacon_groups { beacon: LuaEntityPrototype, modules: table<string, table<string, number>> }[]
---@class PackedVariables
---@field x table<string, number>
---@field y table<string, number>
---@field s table<string, number>
---Reconstruction map produced by solver\substitution.lua when proportional
---doubleton rows are folded out of the LP. eliminated[k] = { rep, k } means the
---folded variable k equals k * x_rep; unfold resolves chains transitively. order
---records the elimination sequence for deterministic replay / inspection.
---@class Reconstruction
---@field eliminated table<string, { rep: string, k: number }>
---@field order string[]
---@class NormalizedProductionLine
---@field recipe_typed_name TypedName
---@field products NormalizedAmount[]
---@field ingredients NormalizedAmount[]
---@field fuel_ingredient NormalizedAmount?
---@field fuel_burnt_result NormalizedAmount?
---@field fuel_spent_fluid NormalizedAmount? Fluid residue emitted per unit of fluid fuel burned (FluidPrototype / FluidEnergySource / Generator spent_fluid, 2.1.9). The fluid analog of fuel_burnt_result; a point-temperature fluid, so unlike the always-item burnt result it also participates in temperature bridging.
---@field power_per_second number
---@field pollution_per_second number
---@field is_bridge boolean? Temperature bridge injected by create_temperature_bridges (LP-internal plumbing, not a user recipe).
---@field is_source boolean? User-placed infinite-source virtual recipe; propagated from VirtualRecipe so readers need not parse the "<source>" name prefix.
---@field is_sink boolean? Infinite-sink virtual recipe; propagated from VirtualRecipe so readers need not parse the "<sink>" name prefix.
---@class NormalizedAmount
---@field type "item"|"fluid"|"virtual_material"
---@field name string
---@field quality string
---@field amount_per_second number
---@field minimum_temperature number?
---@field maximum_temperature number?
---Factorio 2.1.9 runtime concept (LuaFluidPrototype.spent_fluid /
---LuaFluidEnergySourcePrototype.spent_fluid / LuaEntityPrototype.spent_fluid):
---the fluid a fluid-burning machine emits per unit of fuel consumed. Mirrored
---here so the accessor annotations resolve on the pre-2.1 API stubs too (where
---the concept is absent); sumneko merges it with the real concept when present.
---@class SpentFluidSpecification
---@field name string Spent fluid's prototype name.
---@field amount number Amount produced per 1 unit of fuel fluid consumed.
---@field temperature number Temperature the spent fluid is emitted at.
---@class Virtuals
---@field material table<string, VirtualMaterial>
---@field recipe table<string, VirtualRecipe>
---@field fuel_categories_dictionary table<string, { [string]: true }>
---@field recipe_categories_dictionary table<string, string[]> Every distinct recipe-category *combination* actually used by some real recipe (accessor.recipe_categories' sorted output, joined with "|"), mapping the joined key back to its category array -- same pattern as fuel_categories_dictionary, but for a recipe's (possibly multiple, 2.1 `.categories` / 2.0 `additional_categories`) crafting categories rather than a machine's fuel categories. A single-category recipe's key is just its bare category name, so this is a superset of the pre-multi-category `prototypes.recipe_category` enumeration.
---@field machine_ingredient_tiers table<string, integer[]> Keyed by the same joined combination key as recipe_categories_dictionary (a single-category combination's key is the bare category name, matching prior behaviour).
---@field shared_fixed_recipes table<string, true>
---@class VirtualMaterial
---@field type "virtual_material"
---@field name string
---@field sprite_path string
---@field tooltip LocalisedString?
---@field elem_tooltip ElemID?
---@field order string
---@field group_name string
---@field subgroup_name string
---@field hidden boolean
---@field source_entity_name string?
---@field source_fluid_name string?
--- The temperature range a fluid temperature variant covers. Set only for the
--- fluid temperature variants register_fluid_temperature_range builds (nil on
--- <heat> / <research> / space-rocket materials). Derived from the same clamped
--- lo/hi that builds the "fluid/x@[lo,hi]" name key at that single construction
--- point, so the fields and the name can never diverge. A point temperature is
--- the degenerate range minimum_temperature == maximum_temperature. Prefer these
--- over re-parsing the name string, so temperature matching is grep-discoverable.
---@field minimum_temperature number?
---@field maximum_temperature number?
---Virtual recipes describe machine-driven conversions that have no LuaRecipePrototype
---(boiler heating, generator fuel burn, mining, plant growth, lab research,
---rocket launches, ...). Unlike real recipes, the per-second rate is not
---encoded in `products[i].amount` / `ingredients[i].amount`; those fields
---hold per-craft ratios (typically 1 for shape-only recipes, or a recipe-
---specific stoichiometry like 1/heat_capacity for boilers). The per-second
---throughput baseline is supplied at pre_solve time by
---accessor.get_virtual_recipe_rates(machine, quality), which dispatches per
---machine type to whichever quality-aware runtime API actually scales for
---that entity. accessor.normalize_production_line then multiplies the ratio
---by the rate to get per-second values for the LP.
---@class VirtualRecipe
---@field type "virtual_recipe"
---@field name string
---@field sprite_path string
---@field tooltip LocalisedString?
---@field elem_tooltip ElemID?
---@field order string
---@field group_name string
---@field subgroup_name string
---@field products ProductEx[]
---@field ingredients IngredientEx[]
---@field fixed_crafting_machine TypedName?
---@field resource_category string?
---@field pumped_fluid_name string?
---@field consumed_pack_name string?
---@field crafting_speed_cap number?
---@field pollution_per_craft number?
---@field hidden boolean
---@field source_entity_name string?
---@field source_planet_names string[]?
---@field is_spoilage boolean?
---@field is_source boolean?
---@field is_sink boolean?
---@class VirtualProduct
---@field type "virtual_material"
---@field name string
---@field amount number?
---@field amount_min number?
---@field amount_max number?
---@field probability number
---@field ignored_by_productivity number?
---@field temperature number?
---@class VirtualIngredient
---@field type "virtual_material"
---@field name string
---@field amount number
---@field minimum_temperature number?
---@field maximum_temperature number?
---Project-local mirror of `flib.GuiElemDef`. flib defines its type as a
---single `@class` inheriting from a union of `LuaGuiElement.add_param.*`
---variants, but since factoriomod-debug 2.0.11 those variants are
---`(exact)` and LuaLS no longer narrows `type` correctly through that
---inheritance — every site assigning `type = "frame"` etc. is flagged
---as `assign-type-mismatch` against `"button"` (the first variant). The
---workaround is to express the same shape as an `@alias` union of
---per-variant classes, which LuaLS narrows on the literal `type`. Each
---variant combines the upstream `(exact)` add_param class with the flib
---extras (`handler`, `children`, etc.) via multiple inheritance.
---
---`fs_util.add_gui` casts to `flib.GuiElemDef` at the actual flib
---boundary so flib's signature is satisfied at runtime.
---@class fs.GuiElemDef.extras
---@field style_mods LuaStyle?
---@field elem_mods LuaGuiElement?
---@field drag_target string?
---@field handler any?
---@field children fs.GuiElemDef[]?
---@field tab fs.GuiElemDef?
---@field content fs.GuiElemDef?
---@class fs.GuiElemDef.button: LuaGuiElement.add_param.button, fs.GuiElemDef.extras
---@class fs.GuiElemDef.camera: LuaGuiElement.add_param.camera, fs.GuiElemDef.extras
---@class fs.GuiElemDef.checkbox: LuaGuiElement.add_param.checkbox, fs.GuiElemDef.extras
---@class fs.GuiElemDef.choose_elem_button: LuaGuiElement.add_param.choose_elem_button, fs.GuiElemDef.extras
---@class fs.GuiElemDef.drop_down: LuaGuiElement.add_param.drop_down, fs.GuiElemDef.extras
---@class fs.GuiElemDef.flow: LuaGuiElement.add_param.flow, fs.GuiElemDef.extras
---@class fs.GuiElemDef.frame: LuaGuiElement.add_param.frame, fs.GuiElemDef.extras
---@class fs.GuiElemDef.line: LuaGuiElement.add_param.line, fs.GuiElemDef.extras
---@class fs.GuiElemDef.list_box: LuaGuiElement.add_param.list_box, fs.GuiElemDef.extras
---@class fs.GuiElemDef.minimap: LuaGuiElement.add_param.minimap, fs.GuiElemDef.extras
---@class fs.GuiElemDef.progressbar: LuaGuiElement.add_param.progressbar, fs.GuiElemDef.extras
---@class fs.GuiElemDef.radiobutton: LuaGuiElement.add_param.radiobutton, fs.GuiElemDef.extras
---@class fs.GuiElemDef.scroll_pane: LuaGuiElement.add_param.scroll_pane, fs.GuiElemDef.extras
---@class fs.GuiElemDef.slider: LuaGuiElement.add_param.slider, fs.GuiElemDef.extras
---@class fs.GuiElemDef.sprite: LuaGuiElement.add_param.sprite, fs.GuiElemDef.extras
---@class fs.GuiElemDef.sprite_button: LuaGuiElement.add_param.sprite_button, fs.GuiElemDef.extras
---@class fs.GuiElemDef.switch: LuaGuiElement.add_param.switch, fs.GuiElemDef.extras
---@class fs.GuiElemDef.tab: LuaGuiElement.add_param.tab, fs.GuiElemDef.extras
---@class fs.GuiElemDef.table: LuaGuiElement.add_param.table, fs.GuiElemDef.extras
---@class fs.GuiElemDef.text_box: LuaGuiElement.add_param.text_box, fs.GuiElemDef.extras
---@class fs.GuiElemDef.textfield: LuaGuiElement.add_param.textfield, fs.GuiElemDef.extras
---@class fs.GuiElemDef.base: LuaGuiElement.add_param.base, fs.GuiElemDef.extras
---@alias fs.GuiElemDef
---| fs.GuiElemDef.button
---| fs.GuiElemDef.camera
---| fs.GuiElemDef.checkbox
---| fs.GuiElemDef.choose_elem_button
---| fs.GuiElemDef.drop_down
---| fs.GuiElemDef.flow
---| fs.GuiElemDef.frame
---| fs.GuiElemDef.line
---| fs.GuiElemDef.list_box
---| fs.GuiElemDef.minimap
---| fs.GuiElemDef.progressbar
---| fs.GuiElemDef.radiobutton
---| fs.GuiElemDef.scroll_pane
---| fs.GuiElemDef.slider
---| fs.GuiElemDef.sprite
---| fs.GuiElemDef.sprite_button
---| fs.GuiElemDef.switch
---| fs.GuiElemDef.tab
---| fs.GuiElemDef.table
---| fs.GuiElemDef.text_box
---| fs.GuiElemDef.textfield
---| fs.GuiElemDef.base