applyV18OptionDefaults function
Implementation
void applyV18OptionDefaults(
Map<String, dynamic> options,
Map<String, dynamic> targets,
) {
final targetC = toD(targets['C']);
options['regularizeToDefault'] ??= false;
options['lambda'] ??= 0.000001;
options['roundStepG'] ??= 1.0;
options['macroTol'] ??= 1.0;
options['fiberTol'] ??= 0.25;
options['fiberWeight'] ??= 250.0;
options['simpleSugarWeight'] ??= 400.0;
options['maxIter'] ??= 12000;
options['weightMode'] ??= 'absolute';
options['mealStructureMode'] = 'standard';
options['mealStructureModeSource'] = 'normal_only';
options['lowCarbMaxC'] = -1.0;
options['hybridCarbMaxC'] = -1.0;
options['uiCarbSimplifiedMode'] = true;
options['carbAnchorMinC'] ??= 0.5;
options['carbRescueSmallDeficitC'] ??= 5.0;
options['carbRescueAnchorShareLargeDeficit'] ??= 0.80;
options['carbRescueWeightSelectedAnchor'] ??= 1.0;
options['carbRescueWeightOtherCarbMain'] ??= 0.75;
options['carbRescueWeightLegumineuse'] ??= 0.60;
options['carbRescueWeightCarbSecondary'] ??= 0.35;
options['carbRescueWeightVegStructural'] ??= 0.15;
options['carbRescueWeightLeafyFinish'] ??= 0.05;
options['carbRescueOverflowEnabled'] ??= true;
options['carbRescueSelectedAnchorOnlyEnabled'] ??= true;
options['carbRescueSelectedAnchorUnboundedEnabled'] ??= true;
options['carbRescueFallbackToWeightedPoolEnabled'] ??= false;
options['anchorSoloUnboundedEnabled'] ??= true;
options['carbMainAbsoluteMaxG'] ??= 280.0;
options['legumineuseAbsoluteMaxG'] ??= 340.0;
options['vegStructuralAbsoluteMaxG'] ??= 330.0;
options['leafyFinishAbsoluteMaxG'] ??= 260.0;
options['carbSecondaryAbsoluteSolverMaxG'] ??= 380.0;
options['carbSecondaryDensityCapsEnabled'] ??= true;
options['carbSecondaryDenseSupportSoftMaxC'] ??= 8.0;
options['carbSecondaryDenseSupportNormalMaxC'] ??= 10.0;
options['carbSecondaryDenseSupportAbsoluteMaxC'] ??= 15.0;
options['carbSecondaryDenseSupportAbsoluteMaxG'] ??= 240.0;
options['carbSecondaryDenseAnchorSoftMaxC'] ??= 12.0;
options['carbSecondaryDenseAnchorNormalMaxC'] ??= 18.0;
options['carbSecondaryDenseAnchorAbsoluteMaxC'] ??= 25.0;
options['carbSecondaryDenseAnchorAbsoluteMaxG'] ??= 280.0;
options['carbSecondaryLightSupportSoftMaxG'] ??= 130.0;
options['carbSecondaryLightSupportNormalMaxG'] ??= 150.0;
options['carbSecondaryLightSupportAbsoluteMaxG'] ??= 220.0;
options['carbSecondaryLightAnchorSoftMaxG'] ??= 150.0;
options['carbSecondaryLightAnchorNormalMaxG'] ??= 180.0;
options['carbSecondaryLightAnchorAbsoluteMaxG'] ??= 220.0;
options['carbSecondaryMediumSupportSoftMaxG'] ??= 120.0;
options['carbSecondaryMediumSupportNormalMaxG'] ??= 180.0;
options['carbSecondaryMediumSupportAbsoluteMaxG'] ??= 250.0;
options['carbSecondaryMediumAnchorSoftMaxG'] ??= 150.0;
options['carbSecondaryMediumAnchorNormalMaxG'] ??= 220.0;
options['carbSecondaryMediumAnchorAbsoluteMaxG'] ??= 280.0;
options['carbSecondaryDenseAutoThresholdC100'] ??= 6.0;
options['carbSecondaryLightAutoThresholdC100'] ??= 3.5;
options['carbMainMinWhenLegumePresentEnabled'] ??=
options['carbMainMinGWhenLegumePresentEnabled'] ?? true;
options['carbMainMinWhenLegumePresentG'] ??=
options['carbMainMinGWhenLegumePresentG'] ?? 20.0;
options['carbMainMinGWhenLegumePresentEnabled'] ??=
options['carbMainMinWhenLegumePresentEnabled'];
options['carbMainMinGWhenLegumePresentG'] ??=
options['carbMainMinWhenLegumePresentG'];
options['proteinSupplementUseHardMax'] ??= false;
options['proteinSupplementMinG'] ??= 15.0;
options['proteinSupplementDefaultG'] ??= 20.0;
options['proteinSupplementSoftCenterG'] ??=
options['proteinSupplementDefaultG'] ?? 20.0;
options['proteinSupplementSoftMaxG'] ??= 20.0;
options['proteinSupplementHardMaxG'] =
toB(options['proteinSupplementUseHardMax'], false)
? (options['proteinSupplementHardMaxG'] ?? 30.0)
: null;
options['proteinSupplementRescueFallbackMaxG'] ??=
options['proteinSupplementRescueMaxG'] ?? 240.0;
options['proteinSupplementRescueMaxG'] ??=
options['proteinSupplementRescueFallbackMaxG'];
options['proteinSupplementCenterWeight'] ??= 0.20;
options['proteinSupplementSoftMinWeight'] ??= 0.35;
options['proteinSupplementSoftMaxWeight'] ??= 0.80;
options['proteinSupplementRescueEnabled'] ??= true;
options['proteinSupplementFallbackWhenNoProteinMainEnabled'] ??= true;
options['fatOilAbsoluteMaxG'] ??= 20.0;
options['fatButterAbsoluteMaxG'] ??= 18.0;
options['fatOtherAbsoluteMaxG'] ??= 120.0;
options['fatDairySoftAbsoluteMaxG'] ??= 160.0;
options['fatRescueOverflowEnabled'] ??= true;
options['fatExcessReducePureFatEnabled'] ??= true;
options['recipeBlocksEnabled'] ??= true;
options['sweetPreparedBatchCarbSystemV17Enabled'] ??= true;
options['sweetPreparedBatchCarbSystemV17bEnabled'] ??= true;
options['sweetPreparedBatchCarbSystemV18Enabled'] ??= true;
options['useBlockCouplingPenalty'] ??= true;
options['blockCouplingEnabled'] ??= true;
options['preparedBatchCouplingEnabled'] ??= true;
options['preparedBatchSweetCouplingEnabled'] ??= true;
options['sweetBatchMinG'] ??= 70.0;
options['sweetBatchSoftMinG'] ??= 105.0;
options['sweetBatchCenterG'] ??= 150.0;
options['sweetBatchSoftMaxG'] ??= 215.0;
options['sweetBatchHardMaxG'] ??= 230.0;
options['sweetBatchHardMaxWhenSimpleCarbG'] ??= 215.0;
options['sweetBatchCenterWeightWhenSimpleCarb'] ??= 0.035;
options['sweetBatchSoftMaxWeightWhenSimpleCarb'] ??= 0.25;
options['sweetBatchSoftMinWeight'] ??= 0.02;
options['sweetBatchCenterWeight'] ??= 0.012;
options['sweetBatchSoftMaxWeight'] ??= 0.09;
options['sweetFruitSoftMinG'] ??= 50.0;
options['sweetFruitCenterG'] ??= 120.0;
options['sweetFruitGoodMaxG'] ??= 180.0;
options['sweetFruitWarningG'] ??= 220.0;
options['sweetFruitStrongWarningG'] ??= 300.0;
options['sweetFruitNoSimpleAbsoluteMaxG'] ??= 450.0;
options['sweetFruitSoftMinWeight'] ??= 0.01;
options['sweetFruitCenterWeight'] ??= 0.018;
options['sweetFruitSoftMaxWeight'] ??= 0.16;
options['sweetFruitFallbackPriorityWeight'] ??= 1.0;
options['sweetSimpleCarbMinG'] ??= 0.0;
options['sweetSimpleCarbTasteDoseG'] ??= 0.5;
options['sweetSimpleCarbCenterG'] ??= 3.0;
options['sweetSimpleCarbSoftMaxG'] ??= 12.0;
options['sweetSimpleCarbAbsoluteMaxG'] ??= math.max(80.0, targetC * 1.8);
options['sweetSimpleCarbHighWarningG'] ??= 25.0;
options['sweetSimpleCarbStrongWarningG'] ??= 45.0;
options['sweetSimpleCarbCenterWeight'] ??= 0.006;
options['sweetSimpleCarbSoftMaxWeight'] ??= 0.012;
options['sweetSimpleCarbPriorityWeight'] ??= 3.0;
options['sweetSimpleSugarWeight'] ??= 35.0;
options['sweetSimpleSugarTol'] ??= 1.0;
options['legumeTransferBypassCapEnabled'] ??= true;
options['legumeTransferAbsMaxG'] ??= 340.0;
options['legumeTransferCapBufferG'] ??= 0.0;
// ── vegetable_only_balanced mode (v0.8.0) ────────────────────────────────
// Auto-activated when targetC > carbAnchorMinC and the recipe contains no
// carb_main and no legumineuse. Distributes C across vegetable groups by
// share + applies categorical caps + escape-overflow without 1e9 sentinel.
options['vegetableOnlyBalancedEnabled'] ??= true;
options['vegetableOnlyDisableAnchorUnbounded'] ??= true;
options['vegetableOnlyEscapeOverflowEnabled'] ??= true;
options['vegetableOnlyEscapeOverflowMarginG'] ??= 20.0;
// ──────────────────────────────────────────────────────────────────────
// support_veg_culinary_portions (v0.8.3) — FLOOR_5050.
//
// Mutually exclusive with vegetableOnlyBalanced (which activates when no
// carb_main is present). This mode activates the opposite case: carb_main
// present alongside selected veg supports. It imposes a 50% C-target
// floor on the carb_main (riz adapts upward when C is high) and gives
// each support veg a culinary hard min + softCenter + hard cap at
// softCenter × 1.3 to prevent inflation.
// ──────────────────────────────────────────────────────────────────────
options['supportVegCulinaryPortionsEnabled'] ??= true;
options['carbExcessReduceMainEnabled'] ??= true;
options['carbExcessReduceMainSafetyMinG'] ??= 0.0;
// Group shares (case 1 — dense present). Tightened in 0.8.1 so the dense
// carrier dominates more, structural is held back to its true role
// (volume/fibre, not C porter), and the explicit overflow share is
// redistributed afterwards.
options['vegetableOnlyDenseShare'] ??= 0.63;
options['vegetableOnlyMediumShare'] ??= 0.15;
options['vegetableOnlyStructuralShare'] ??= 0.12;
options['vegetableOnlyLightShare'] ??= 0.15;
options['vegetableOnlyLeafyShare'] ??= 0.05;
options['vegetableOnlyOverflowShare'] ??= 0.10;
// Fallback shares — case 2 (no dense, medium primary) and case 3 (no dense
// or medium, light primary).
options['vegetableOnlyMediumFallbackShare'] ??= 0.50;
options['vegetableOnlyLightFallbackShare'] ??= 0.45;
// Overflow redistribution weights (0.8.1+). When the sum of group shares
// leaves an unallocated overflow C, distribute it across the available
// groups using these weights. Each tier (dense / medium / light /
// structural) has its own weight; they should sum to ~1.0 for a given
// case but only the tiers that are actually present contribute.
options['vegetableOnlyOverflowDenseWeight'] ??= 0.70;
options['vegetableOnlyOverflowMediumWeight'] ??= 0.45;
options['vegetableOnlyOverflowLightWeight'] ??= 0.20;
options['vegetableOnlyOverflowStructuralWeight'] ??= 0.10;
options['vegetableOnlyOverflowLightFallbackWeight'] ??= 0.45;
// Categorical caps per group (post-mode). Structural lowered in 0.8.1
// so a veg_structural row cannot be pushed beyond ~160 g in the normal
// phase. It can still go higher in escape overflow (with warning).
options['vegetableOnlyDenseMaxG'] ??= 380.0;
options['vegetableOnlyMediumMaxG'] ??= 280.0;
options['vegetableOnlyStructuralMaxG'] ??= 160.0;
options['vegetableOnlyLightMaxG'] ??= 250.0;
options['vegetableOnlyLeafyMaxG'] ??= 120.0;
// Soft-bound shaping (0.8.1+). Per-group SoftMaxOverhead and SoftMaxWeight
// replace the single global SoftMaxOverhead used in 0.8.0. The previous
// global option (vegetableOnlySoftMaxOverhead) is still consulted as a
// fallback for callers that overrode it.
options['vegetableOnlySoftCenterWeight'] ??= 0.05;
options['vegetableOnlySoftMinWeight'] ??= 0.04;
options['vegetableOnlySoftMinFraction'] ??= 0.40;
options['vegetableOnlySoftMaxOverhead'] ??= 1.30;
// Per-group softMaxG overhead (multiplier applied to the row's softCenterG
// when computing softMaxG). Tighter for structural and leafy because they
// should remain visually small; looser for dense (the primary carrier).
options['vegetableOnlyDenseSoftMaxOverhead'] ??= 1.25;
options['vegetableOnlyMediumSoftMaxOverhead'] ??= 1.20;
options['vegetableOnlyLightSoftMaxOverhead'] ??= 1.15;
options['vegetableOnlyStructuralSoftMaxOverhead'] ??= 1.12;
options['vegetableOnlyLeafySoftMaxOverhead'] ??= 1.05;
// Per-group softMax penalty weight (passed straight to the optimizer's
// score function). Higher = stronger pull back toward softMaxG once the
// row exceeds it. Structural / leafy get the heaviest weights so the
// solver can't quietly push a fiber-only row up to absorb carbs.
options['vegetableOnlyDenseSoftMaxWeight'] ??= 0.18;
options['vegetableOnlyMediumSoftMaxWeight'] ??= 0.22;
options['vegetableOnlyLightSoftMaxWeight'] ??= 0.25;
options['vegetableOnlyStructuralSoftMaxWeight'] ??= 0.35;
options['vegetableOnlyLeafySoftMaxWeight'] ??= 0.40;
// Per-group softCenterWeight (0.8.1+). Anchors each row near its target
// share. Much heavier for structural / leafy so the solver can't quietly
// drop them way below their soft center to save total weight. Dense stays
// gentle so it can absorb overflow flex.
options['vegetableOnlyDenseSoftCenterWeight'] ??= 0.05;
options['vegetableOnlyMediumSoftCenterWeight'] ??= 0.12;
options['vegetableOnlyLightSoftCenterWeight'] ??= 0.12;
options['vegetableOnlyStructuralSoftCenterWeight'] ??= 0.30;
options['vegetableOnlyLeafySoftCenterWeight'] ??= 0.25;
// Per-group softMinFraction (0.8.1+). softMinG = softCenterG × fraction.
// Very tight for structural because veg_structural rows should stay
// visible in the recipe. Dense can drop further if needed.
options['vegetableOnlyDenseSoftMinFraction'] ??= 0.40;
options['vegetableOnlyMediumSoftMinFraction'] ??= 0.60;
options['vegetableOnlyLightSoftMinFraction'] ??= 0.55;
options['vegetableOnlyStructuralSoftMinFraction'] ??= 0.90;
options['vegetableOnlyLeafySoftMinFraction'] ??= 0.50;
// Per-group softMin penalty weight (0.8.1+). Higher = stronger pull back
// up when a row drops below its softMin.
options['vegetableOnlyDenseSoftMinWeight'] ??= 0.04;
options['vegetableOnlyMediumSoftMinWeight'] ??= 0.10;
options['vegetableOnlyLightSoftMinWeight'] ??= 0.10;
options['vegetableOnlyStructuralSoftMinWeight'] ??= 0.30;
options['vegetableOnlyLeafySoftMinWeight'] ??= 0.15;
// Total veg-weight warning thresholds (reported by the pre-solve builder).
options['vegetableOnlyTotalVegWeightWarningG'] ??= 800.0;
options['vegetableOnlyTotalVegWeightStrongWarningG'] ??= 1000.0;
options['targetC'] = targetC;
}