applyV18OptionDefaults function

void applyV18OptionDefaults(
  1. Map<String, dynamic> options,
  2. Map<String, dynamic> targets
)

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;
}