-- oeProTuneShared.lua - single source of truth for OE ProTune's validation data (V2.9.7).
--
-- Required by BOTH Lua VMs (lua/common is on package.path in each):
--   * vehicle VM: lua/vehicle/controller/oeProTune.lua  (the authority - clamps/rejects on receipt)
--   * GE VM:      lua/ge/extensions/oeProTuneRemote.lua (the typed WebSocket pre-filter)
--
-- Before V2.9.7 the remote hand-copied these tables with a "keep in lock-step" comment, and they
-- drifted anyway (popsLevel integer-forcing rejected the UI's half-steps; rotIdleChop was
-- integer-forced remotely but not in-game). One shared file ends the drift by construction.
--
-- The two sides deliberately KEEP their different behaviors - the remote REJECTS out-of-range
-- values with typed errors, the in-game path CLAMPS. Only the data is shared.

local M = {}

-- map table definitions (rows = MAP/load axis, cols = RPM axis): Fuel Main is a 1.0-centered
-- multiplier, ignition is a degree trim (0 = stock), boost is a kPa target, and lambda is a target.
M.mapMeta = {
  fuel     = { min = 0.5,   max = 2.0,  step = 0.01, unit = "x",   default = 1.0 },  -- scalar: 1.0 = follow lambda, 1.10 = +10% fuel
  ignition = { min = -20,   max = 30,   step = 0.5,  unit = "deg", default = 0 },  -- asymmetric: up to -20 retard (the table only TRIMS the base map players cant edit, so they need more pull authority to tame an aggressive map). Advance opened to +30 (was +10) now that the power model has a real MBT: past ~+7.5 power FALLS and knock rises, so physics polices the top end instead of the slider - flood it until the engine can bear no more (community request)
  boost    = { min = 60,    max = 1000, step = 5,    unit = "kPa", default = 100 },  -- V3.0: 1000 kPa absolute (~130 psi gauge) ceiling, was 600 (~72 psi) - competition diesel pull rigs really run 100-150 psi, so the old cap was the software limiting reality (community request). The turbo hardware stays the true limit: the ECU can only close the wastegate, never make boost the compressor can't. (UI reads this meta)
  ve       = { min = 30,    max = 130,  step = 1,    unit = "%",   default = 85 },   -- V2.8 speed density: the player's MODEL of how the engine breathes (built matched to the real curve; only used in Modelled fuel mode)
  wgduty   = { min = 0,     max = 100,  step = 1,    unit = "%",   default = 50 },   -- V2.8 (Liqurdz): per-RPM/target wastegate duty OFFSET around the Base duty slider - 50 = neutral, like a real EBC duty table (tames big-turbo high-rpm hunting)
  lambda   = { min = 0.68,  max = 1.10, step = 0.01, unit = "",    default = 1 }
}

-- calibration grid size limits (V3.0.7). ONE definition of how many breakpoints an axis may carry.
-- Before this, "4 to 12" was hand-written in six places across five files (two in the controller's
-- axis setters, two in app.js, one in the remote's typed boundary, two in remote.html) and every one
-- of them had to agree or a legitimate tune became unloadable - which is exactly the failure the
-- V3.0 MAP-ceiling bug caused (see validSnapshotAxis in app.js). The JS/HTML sides cannot `require`
-- this file, so they read it off the controller state (`axisLimits`, published in publishState);
-- their fallback literals carry a comment pointing here.
--
-- max = 20 gives headroom above the 16x16 "Fine" preset so a player editing breakpoints by hand is
-- not sitting on the ceiling. It is NOT a claim that 20x20 is a good idea: a real Link G4X main table
-- reaches 22x22, but every cell here costs share-code length and divides Mixture Map sample density.
M.axisLimits = { min = 4, max = 20 }

-- numeric clamp ranges for setSetting (mirrors the Assetto applyTuneData clamps)
M.settingRanges = {
  launchLimit = { 2500, 9000 }, flatShiftRpm = { 3000, 11000 }, launchSoundVol = { 0, 10 }, drivetrainLoss = { 0, 35 },   -- high maxima for big drag builds (10k+ stock limiters); per-car clamps in applyLimiter keep them sane
  -- V3.1 tunable hard cut. limiterRange is the hysteresis band the bounce falls through before the
  -- cut releases - real standalones expose this as the limiter's control range. Minimum is 100, NOT
  -- 50: the ignition cut arms 0.05 s of zero torque and the game's own rpmDrop limiter has its own
  -- release band, and both swamp anything under about 150 rpm, so a lower floor would be a slider
  -- whose bottom third does nothing. 150 = the constant this replaces, so the default is a no-op.
  -- limiterCutMode is 1=Ignition (spark cut: bangs + flames, today's behaviour) / 2=Fuel (quiet).
  -- There is deliberately NO third "Both": this controller removes torque through exactly one
  -- channel (model.coef), so a third mode would be byte-identical to Ignition - a mode that does
  -- nothing is worse than an absent one. MIRROR: app.js MAP_BANK_SETTING_RANGES + the sld() spec.
  limiterCutMode = { 1, 2 }, limiterRange = { 100, 600 },
  -- Spark-cut hold per bounce, ms. This is the DEAD TIME after the band releases before torque
  -- comes back, so it sets the ceiling on bounce cadence - narrowing limiterRange alone cannot
  -- beat it. Floor is 10 because applyLimiter runs from updateGFX (one graphics frame, ~7-17 ms):
  -- a shorter hold expires before the next evaluation and buys nothing. 50 = the old 0.05 s
  -- constant, so the default is a no-op. Up to 150 for a deliberately lazy, soft-limiter feel.
  limiterCutMs = { 10, 150 },
  launchFuel = { 0, 30 }, launchRetard = { 0, 15 }, compBrakeLevel = { 0, 100 }, dieselNozzleFlow = { 200, 6000 },
  shiftFirmness = { 0, 100 }, shiftTorqueMgmt = { 0, 100 }, torqueLimitNm = { 0, 6000 }, converterStall = { 0, 8000 },
  clutchUpgrade = { 1, 5 },
  globalFuelTrim = { -10, 10 }, globalIgnitionTrim = { -6, 6 },
  boostMode = { 1, 3 }, boostBaseDuty = { 0, 100 }, boostPGain = { 0, 2 }, boostIGain = { 0, 0.5 }, boostOvercut = { 5, 60 },
  boostShapeMode = { 1, 4 }, boostRampUpKpaS = { 0, 2000 }, boostRampDownKpaS = { 0, 2000 }, boostShiftHoldMs = { 0, 1000 },
  -- V3.0: target trims widened to ±500 kPa (~±72 psi), was ±200 (~±29 psi) - shaping a 130 psi base
  -- down for the low gears needs real authority. Duty trims stay ±100%: that is the physical range
  -- of a wastegate solenoid, not a chosen cap. UI mirror: MAP_BANK_SETTING_RANGES (app.js) + the
  -- registerBoostShapeEditor limits + the app.html ng-attr-min/max on the shaping tables.
  boostGear1TargetTrimKpa = { -500, 500 }, boostGear1DutyTrimPct = { -100, 100 },
  boostGear2TargetTrimKpa = { -500, 500 }, boostGear2DutyTrimPct = { -100, 100 },
  boostGear3TargetTrimKpa = { -500, 500 }, boostGear3DutyTrimPct = { -100, 100 },
  boostGear4TargetTrimKpa = { -500, 500 }, boostGear4DutyTrimPct = { -100, 100 },
  boostGear5TargetTrimKpa = { -500, 500 }, boostGear5DutyTrimPct = { -100, 100 },
  boostGear6TargetTrimKpa = { -500, 500 }, boostGear6DutyTrimPct = { -100, 100 },
  boostGear7TargetTrimKpa = { -500, 500 }, boostGear7DutyTrimPct = { -100, 100 },
  boostGear8TargetTrimKpa = { -500, 500 }, boostGear8DutyTrimPct = { -100, 100 },
  boostSpeed1TargetTrimKpa = { -500, 500 }, boostSpeed1DutyTrimPct = { -100, 100 },
  boostSpeed2TargetTrimKpa = { -500, 500 }, boostSpeed2DutyTrimPct = { -100, 100 },
  boostSpeed3TargetTrimKpa = { -500, 500 }, boostSpeed3DutyTrimPct = { -100, 100 },
  boostSpeed4TargetTrimKpa = { -500, 500 }, boostSpeed4DutyTrimPct = { -100, 100 },
  boostSpeed5TargetTrimKpa = { -500, 500 }, boostSpeed5DutyTrimPct = { -100, 100 },
  boostSpeed6TargetTrimKpa = { -500, 500 }, boostSpeed6DutyTrimPct = { -100, 100 },
  knockThreshold = { 0.4, 3.5 }, knockMaxRetard = { 0, 12 }, knockRecoveryRate = { 0.05, 1 }, egtLimit = { 700, 1100 },
  canMode = { 1, 3 }, canSpeed = { 1, 3 },
  auxFanOnTemp = { 80, 115 }, auxFuelPumpPrime = { 0, 5 }, auxShiftLight = { 3000, 11500 },
  sensorMapOffset = { -30, 30 }, sensorMapScale = { 0.85, 1.15 }, sensorTpsOffset = { -10, 10 }, sensorTpsScale = { 0.85, 1.15 },
  sensorIatBias = { -20, 20 }, sensorEctBias = { -20, 20 }, sensorLambdaBias = { -0.08, 0.08 }, sensorFuelPressureBias = { -80, 80 },
  crankEnrich = { -20, 60 }, postStartEnrich = { -10, 35 }, postStartDecay = { 2, 45 }, warmupEnrich = { -10, 35 }, warmupEndTemp = { 40, 95 },
  accelEnrich = { 0, 25 }, accelDecay = { 0.05, 2 }, wallWetting = { 0, 1 }, decelFuel = { -35, 0 },
  fuelEquationMode = { 1, 2 }, loadSource = { 1, 2 }, masterFuel = { 3, 20 }, injectorFlow = { 200, 6000 }, injectorDeadtime = { 0.2, 2.5 }, stoichRatio = { 6, 16 }, fuelType = { 1, 5 },
  cllMode = { 1, 3 }, cllEctLockout = { 30, 95 }, cllTpsLockout = { 10, 100 }, cllMapLockout = { 100, 500 },
  idleTargetRpm = { 600, 1800 }, idleBasePos = { 5, 80 },
  cylTrim1 = { -10, 10 }, cylTrim2 = { -10, 10 }, cylTrim3 = { -10, 10 }, cylTrim4 = { -10, 10 }, cylTrim5 = { -10, 10 }, cylTrim6 = { -10, 10 }, cylTrim7 = { -10, 10 }, cylTrim8 = { -10, 10 },
  antilagRetard = { 5, 35 }, antilagFuel = { 0, 30 },
  nitrousShot = { 0, 60 }, nitrousArmRpm = { 2000, 7500 }, nitrousArmTps = { 50, 100 }, nitrousFuel = { 0, 40 }, nitrousRetard = { 0, 15 },
  dieselMaxFuel = { 60, 260 }, dieselRailPressure = { 300, 2200 }, dieselSmokeLimit = { 12, 24 },
  vvtIntakeAdvance = { 0, 40 },
  ethrottleMode = { 1, 2 }, ethrottleCurve = { 1, 3 }, ethrottleTolerance = { 4, 30 },
  baseTiming = { 5, 35 },   -- V2.9.7: absolute base advance the ignition display is built on (deg BTDC; display-only calibration reference)
  triggerPattern = { 1, 3 }, popsLevel = { 0, 3 }, popSoundMode = { 1, 3 }, fxStyle = { 1, 3 }, wgType = { 1, 2 }, smokeDensity = { 0, 2 },
  rotIdleRpm = { 900, 2000 }, rotIdleChop = { 1, 10 },
  overrunThrottle = { 2, 20 }, overrunMinRpm = { 1500, 7000 }, overrunPopVol = { 0, 10 }
}

M.boolSettings = {
  enabled = true, protection = true, launchEnabled = true, knockControl = true,
  canRuntime = true, canLambda = true, canDash = true, auxCheckEngine = true,
  decelFuelCut = true, idleEnabled = true, idleIgnControl = true, antilagEnabled = true, nitrousEnabled = true, vvtEnabled = true,
  glowPlugs = true, compBrakeEnabled = true, dieselNozzleEnabled = true, dieselSmokeLimiter = true, torqueLimitLowGears = true, flatShiftEnabled = true, rollingEnabled = true, hardCut = true, rotIdleEnabled = true,
  knockAudio = true, autoFitAxes = true
}

-- integer ENUM settings (mode selectors): a typed command must send a whole number - a fractional
-- enum would clamp/store a meaningless in-between mode. In-game callers always send integers, so
-- this only rejects values nothing legitimate produces.
-- rotIdleChop: the UI slider steps by 1 (a fractional chop depth was never producible); before
-- V2.9.7 only the remote forced it while the in-game path clamped - now both sides agree.
-- popsLevel deliberately NOT here: the UI slider steps by 0.5 and the vehicle controller accepts
-- fractional intensity - integer-forcing it rejected every half-step drag from the remote Studio
-- while the same drag worked in-game (the drift bug that motivated this shared file).
M.enumSettings = {
  boostMode = true, boostShapeMode = true, canMode = true, canSpeed = true, fuelEquationMode = true, loadSource = true, fuelType = true,
  limiterCutMode = true,   -- V3.1: mode selector. limiterRange is deliberately NOT here - it is a continuous rpm band, and integer-forcing it would reject legitimate slider values (the popsLevel drift documented below).
  cllMode = true, ethrottleMode = true, ethrottleCurve = true, triggerPattern = true,
  popSoundMode = true, fxStyle = true, wgType = true, rotIdleChop = true, clutchUpgrade = true
}

return M
