{
 "design": "B: expected edge per side. Per side, starting at the retail-only optimum of a linear router-share model, f0=(1/(2K)+0.003-2e)/2 with e=+d (ask) or -d (bid), d=D_COEF*(spot-ref)/ref, ref=0.7*pHat+0.3*fastHat (V2 estimators). Walk an up-to-8-point grid f0+j*3bp, EV(f)=FLOW_COEF*lambdaHat*sizeHat*share(f)*(f+e) - 0.25*sig^2*H((-e-f)/sig), share=clamp(0.5+K*(0.003-e-f),0,1), sig=1.25*sigmaHat, H(z)=E[(z+Z)+^2] (17-bit packed table, linear interp), stop at first EV decrease. Replaces V2's whole fee mapping (dir/act/tox/stale/tail terms and slots 3,4,9 removed); V2 pHat/fastHat/sigmaHat/lambdaHat/sizeHat estimators kept.",
 "frozen_variant": "v5: K_SHARE=125, FLOW_COEF=0.25, SIG_MULT=1.25, GRID_STEP=3bp, D_COEF=0.5",
 "code": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.24;\n\nimport {AMMStrategyBase} from \"./AMMStrategyBase.sol\";\nimport {TradeInfo} from \"./IAMMStrategy.sol\";\n\ncontract Strategy is AMMStrategyBase {\n    uint256 constant ELAPSED_CAP = 8;\n    uint256 constant SIGNAL_THRESHOLD = WAD / 650;\n    uint256 constant DIR_DECAY = 750000000000000000;\n    uint256 constant ACT_DECAY = 530000000000000000;\n    uint256 constant SIZE_DECAY = 530000000000000000;\n    uint256 constant TOX_DECAY = 910000000000000000;\n    uint256 constant SIGMA_DECAY = 690000000000000000;\n    uint256 constant LAMBDA_DECAY = 990000000000000000;\n    uint256 constant SIZE_BLEND_DECAY = 828000000000000000;\n    uint256 constant TOX_BLEND_DECAY = 51000000000000000;\n    uint256 constant ACT_BLEND_DECAY = 985000000000000000;\n    uint256 constant PHAT_ALPHA = 270000000000000000;\n    uint256 constant PHAT_ALPHA_RETAIL = 60000000000000000;\n    uint256 constant PHAT_ALPHA_SAME = 120000000000000000;\n    uint256 constant REVERT_U = 0;\n    uint256 constant DIR_IMPACT_MULT = 4;\n    uint256 constant GATE_SIGMA_MULT = 7200000000000000000;\n    uint256 constant MIN_GATE = 30000000000000000;\n    uint256 constant RET_CAP = WAD / 35;\n    uint256 constant TRADE_RATIO_CAP = WAD / 5;\n    uint256 constant LAMBDA_CAP = 1820000000000000000;\n    uint256 constant STEP_COUNT_CAP = 64;\n    uint256 constant BASE_FEE = 30 * BPS;\n    // Design B: expected edge per side\n    int256 constant K_SHARE = 125;                 // d share / d fee (router slope)\n    int256 constant FLOW_COEF = 250000000000000000;            // retail flow per side = FLOW_COEF * lambdaHat * sizeHat\n    int256 constant ARB_COEF = 250000000000000000; // arb loss = 1/4 * reserveY * (gap - f)^2\n    uint256 constant SIG_MULT = 1250000000000000000;           // sigma_eff = SIG_MULT*sigmaHat\n    int256 constant D_COEF = 500000000000000000;                  // shrinkage on the estimate-vs-pool gap\n    int256 constant GRID_STEP = 30000000000000;\n    uint256 constant H_TABLE = 44170537481288035626752850111906864382662600987096059470870389265214965022720;\n    function afterInitialize(uint256 initialX, uint256 initialY) external override returns (uint256, uint256) {\n        slots[0] = BASE_FEE; slots[1] = BASE_FEE; slots[2] = 0;\n        slots[5] = initialX > 0 ? wdiv(initialY, initialX) : 100 * WAD;\n        slots[6] = 950000000000000; slots[7] = 800000000000000000; slots[8] = 2000000000000000;\n        slots[13] = 902500000000; slots[14] = slots[5]; slots[15] = 0; slots[10] = 0; slots[11] = 0; slots[12] = 0;\n        return (BASE_FEE, BASE_FEE);\n    }\n\n    function afterSwap(TradeInfo calldata trade) external override returns (uint256, uint256) {\n        uint256 prevBidFee = slots[0]; uint256 prevAskFee = slots[1]; uint256 lastTs = slots[2];\n        uint256 pHat = slots[5];\n        uint256 sigmaHat = slots[6]; uint256 lambdaHat = slots[7]; uint256 sizeHat = slots[8];\n        uint256 stepTradeCount = slots[10];\n\n        uint256 sigmaForFast = sigmaHat;\n        uint256 fastHat = slots[14];\n        uint256 uncertainty = slots[13];\n        uint256 silent = trade.timestamp > lastTs ? trade.timestamp - lastTs : 0;\n        bool isNewStep = trade.timestamp > lastTs;\n        if (isNewStep) {\n            uint256 elapsedRaw = trade.timestamp - lastTs;\n            uint256 elapsed = elapsedRaw > ELAPSED_CAP ? ELAPSED_CAP : elapsedRaw;\n            sizeHat = wmul(sizeHat, _powWad(SIZE_DECAY, elapsed));\n            if (stepTradeCount > 0 && elapsedRaw > 0) {\n                uint256 lambdaInst = (stepTradeCount * WAD) / elapsedRaw;\n                if (lambdaInst > LAMBDA_CAP) lambdaInst = LAMBDA_CAP;\n                lambdaHat = wmul(lambdaHat, LAMBDA_DECAY) + wmul(lambdaInst, WAD - LAMBDA_DECAY);\n            }\n            uncertainty += wmul(sigmaForFast, sigmaForFast) * elapsedRaw;\n            if (uncertainty > 100000000000000) uncertainty = 100000000000000;\n            stepTradeCount = 0;\n        }\n\n        bool firstInStep = stepTradeCount == 0;\n        uint256 spot = trade.reserveX > 0 ? wdiv(trade.reserveY, trade.reserveX) : pHat;\n        if (pHat == 0) pHat = spot;\n\n        uint256 feeUsed = trade.isBuy ? prevBidFee : prevAskFee;\n        uint256 gamma = feeUsed < WAD ? WAD - feeUsed : 0;\n        uint256 pImplied;\n        if (gamma == 0) { pImplied = spot; }\n        else { pImplied = trade.isBuy ? wmul(spot, gamma) : wdiv(spot, gamma); }\n\n        // S: infer a no-trade interval center from the last post-trade spot.\n        uint256 silenceScale = WAD;\n        if (firstInStep && silent > 1) {\n            uint256 g = silent - 1;\n            if (g > 1) g = 1;\n            uint256 w = WAD - _powWad(800000000000000000, g);\n            uint256 s = slots[16];\n            if (s == 0) s = pHat;\n            uint256 c = (wmul(s, WAD - prevBidFee) + wdiv(s, WAD - prevAskFee)) / 2;\n            if (c >= pHat) pHat += wmul(w, c - pHat);\n            else pHat -= wmul(w, pHat - c);\n            silenceScale = WAD - wmul(300000000000000000, w);\n        }\n\n        {\n            uint256 ret = pHat > 0 ? wdiv(absDiff(pImplied, pHat), pHat) : 0;\n            uint256 alpha = firstInStep ? PHAT_ALPHA : PHAT_ALPHA_RETAIL;\n            uint256 prePhat = pHat;\n            bool secondPrint = !firstInStep && stepTradeCount == 1;\n            bool sameDir = false; bool oppDir = false;\n            if (secondPrint) {\n                bool up2 = pImplied >= pHat;\n                uint256 d1 = slots[12];\n                if (d1 != 0) { sameDir = (d1 == 1) == up2; oppDir = !sameDir; }\n                if (sameDir) alpha = PHAT_ALPHA_SAME;\n            }\n            if (firstInStep) { slots[11] = pHat; slots[12] = pImplied >= pHat ? 1 : 2; }\n            uint256 adaptiveGate = wmul(sigmaHat, GATE_SIGMA_MULT);\n            if (adaptiveGate < MIN_GATE) adaptiveGate = MIN_GATE;\n            alpha = wmul(alpha, silenceScale);\n            adaptiveGate = wmul(adaptiveGate, silenceScale);\n            if (ret <= adaptiveGate) { pHat = wmul(pHat, WAD - alpha) + wmul(pImplied, alpha); }\n            if (oppDir && REVERT_U > 0) {\n                uint256 pre = slots[11];\n                if (pre > pHat) pHat = pHat + wmul(pre - pHat, REVERT_U);\n                else pHat = pHat - wmul(pHat - pre, REVERT_U);\n            }\n            if (firstInStep) {\n                if (ret > RET_CAP) ret = RET_CAP;\n                sigmaHat = wmul(sigmaHat, SIGMA_DECAY) + wmul(ret, WAD - SIGMA_DECAY);\n            }\n        }\n\n        // Interior retail routing equalizes fee-adjusted marginal prices.\n        // Subsequent trades in a step cannot be the initial arbitrage trade.\n        if (!firstInStep && feeUsed > 30 * BPS && gamma > 0) {\n            uint256 normalSpot = trade.isBuy\n                ? wdiv(wmul(spot, gamma), WAD - 30 * BPS)\n                : wdiv(wmul(spot, WAD - 30 * BPS), gamma);\n            pHat = wmul(pHat, 90 * WAD / 100) + wmul(normalSpot, 10 * WAD / 100);\n        }\n\n        {\n            uint256 ret = fastHat > 0 ? wdiv(absDiff(pImplied, fastHat), fastHat) : 0;\n            uint256 alpha;\n            if (firstInStep) {\n                uint256 processSigma = sigmaForFast;\n                uint256 obsVar = uncertainty + wmul(processSigma, processSigma);\n                uint256 probArb;\n                uint256 z2 = wdiv(wmul(ret, ret), obsVar);\n                uint256 density = _powWad(wdiv(WAD, WAD + z2 / 16), 8);\n                uint256 sigmaObs = sqrt(obsVar * WAD);\n                uint256 odds = wmul(density, wdiv(6700000000000000, sigmaObs));\n                bool arbFlag = ret <= wmul(sigmaForFast, 1220000000000000000);\n                uint256 prior = arbFlag ? 650000000000000000 : 400000000000000000;\n                uint256 tr = trade.reserveY > 0 ? wdiv(trade.amountY, trade.reserveY) : 0;\n                if (tr > 3000000000000000) prior = wmul(prior, 6 * WAD / 10);\n                if (silent > 1) prior = wmul(prior, 8 * WAD / 10);\n                odds = wmul(odds, wdiv(prior, WAD - prior));\n                probArb = wdiv(odds, WAD + odds);\n                alpha = wmul(probArb, 600000000000000000) + wmul(WAD - probArb, 80000000000000000);\n                slots[15] = pImplied >= fastHat ? 1 : 2;\n                uncertainty = wmul(uncertainty, WAD - alpha);\n            } else {\n                if ((slots[15] == 1) == (pImplied >= fastHat)) alpha = PHAT_ALPHA_RETAIL;\n            }\n            uint256 gate = wmul(sigmaHat, GATE_SIGMA_MULT);\n            if (gate < MIN_GATE) gate = MIN_GATE;\n            if (ret <= gate) fastHat = wmul(fastHat, WAD - alpha) + wmul(pImplied, alpha);\n            slots[13] = uncertainty;\n        }\n\n        if (!firstInStep && feeUsed > 30 * BPS && gamma > 0) {\n            uint256 normalObs = trade.isBuy\n                ? wdiv(wmul(spot, gamma), WAD - 30 * BPS)\n                : wdiv(wmul(spot, WAD - 30 * BPS), gamma);\n            if ((slots[15] == 1) == (normalObs >= fastHat)) {\n                fastHat = wmul(fastHat, 9 * WAD / 10) + wmul(normalObs, WAD / 10);\n            }\n        }\n        slots[14] = fastHat;\n        uint256 tradeRatio = trade.reserveY > 0 ? wdiv(trade.amountY, trade.reserveY) : 0;\n        if (tradeRatio > TRADE_RATIO_CAP) tradeRatio = TRADE_RATIO_CAP;\n\n        if (tradeRatio > SIGNAL_THRESHOLD) {\n            sizeHat = wmul(sizeHat, SIZE_BLEND_DECAY) + wmul(tradeRatio, WAD - SIZE_BLEND_DECAY);\n            if (sizeHat > WAD) sizeHat = WAD;\n        }\n\n        stepTradeCount = stepTradeCount + 1;\n        if (stepTradeCount > STEP_COUNT_CAP) stepTradeCount = STEP_COUNT_CAP;\n\n        // Design B: per side choose f maximising f*share(f)*flow - P(arb)*E[arb loss | gap, f].\n        uint256 bidFee; uint256 askFee;\n        {\n            uint256 ref = wmul(pHat, 700000000000000000) + wmul(fastHat, 300000000000000000);\n            int256 d = (int256(spot) - int256(ref)) * D_COEF / int256(ref); // spot vs fair, signed, shrunk\n            int256 sig = int256(wmul(SIG_MULT, sigmaHat));\n            if (sig < 1e14) sig = 1e14;\n            int256 fw = FLOW_COEF * int256(wmul(lambdaHat, sizeHat)) / int256(WAD);\n            askFee = _bestFee(d, sig, fw);\n            bidFee = _bestFee(-d, sig, fw);\n        }\n\n        slots[0] = bidFee; slots[1] = askFee; slots[2] = trade.timestamp;\n        slots[5] = pHat;\n        slots[6] = sigmaHat; slots[7] = lambdaHat; slots[8] = sizeHat;\n        slots[16] = spot;\n        slots[10] = stepTradeCount;\n\n        return (bidFee, askFee);\n    }\n\n    // e = per-unit retail edge offset on this side (+d ask, -d bid); arb gap mean is -e.\n    function _bestFee(int256 e, int256 sig, int256 fw) internal pure returns (uint256) {\n        int256 f = (int256(WAD) / (2 * K_SHARE) + 3e15 - 2 * e) / 2; // retail-only optimum (linear share)\n        if (f < 0) f = 0;\n        int256 best = type(int256).min; int256 bestF = f;\n        int256 sig2 = sig * sig / int256(WAD);\n        unchecked {\n        for (uint256 j = 0; j < 8; j++) {\n            int256 sh = 5e17 + K_SHARE * (3e15 - e - f);\n            if (sh < 0) sh = 0; else if (sh > int256(WAD)) sh = int256(WAD);\n            int256 ev = fw * sh / int256(WAD) * (f + e) / int256(WAD);\n            int256 z = (-e - f) * int256(WAD) / sig;\n            ev -= ARB_COEF * (sig2 * _h(z) / int256(WAD)) / int256(WAD);\n            if (ev > best) { best = ev; bestF = f; } else break; // EV unimodal along grid\n            f += GRID_STEP;\n        }\n        }\n        if (bestF > int256(MAX_FEE)) bestF = int256(MAX_FEE);\n        return uint256(bestF);\n    }\n\n    // H(z) = E[(z+Z)_+^2], Z~N(0,1); table on z=-4..3 step 0.5, then z^2+1.\n    function _h(int256 z) internal pure returns (int256) {\n        if (z <= -4e18) return 0;\n        if (z >= 3e18) return z * z / int256(WAD) + int256(WAD);\n        uint256 u = uint256(z + 4e18) * 2; // units of 0.5 in WAD\n        uint256 i = u / WAD; uint256 fr = u % WAD;\n        int256 h0 = int256((H_TABLE >> (17 * i)) & 0x1FFFF);\n        int256 h1 = int256((H_TABLE >> (17 * (i + 1))) & 0x1FFFF);\n        return (h0 * int256(WAD) + (h1 - h0) * int256(fr)) / 1e4;\n    }\n\n    function _powWad(uint256 factor, uint256 exp) internal pure returns (uint256 result) {\n        result = WAD;\n        while (exp > 0) {\n            if (exp & 1 == 1) result = wmul(result, factor);\n            factor = wmul(factor, factor);\n            exp >>= 1;\n        }\n    }\n\n    function getName() external pure override returns (string memory) { return \"design_b_edge\"; }\n}\n",
 "sha256": "42938ca2214742654b303fe3dc87d284ef9db318e0239f2801f06fd7a2bf815a",
 "v2_sha256": "28417e116f57f1029fdd1803832e852574902c1ae7253c05657f317db76da4ab",
 "tuning": [
  {
   "variant": "v5",
   "n": 256,
   "mean_diff": -43.549,
   "se": 0.643,
   "oog": 0,
   "max_ok_gas": 231003
  },
  {
   "variant": "v6",
   "n": 256,
   "mean_diff": -49.399,
   "se": 0.578,
   "oog": 0,
   "max_ok_gas": 231003
  },
  {
   "variant": "v7",
   "n": 256,
   "mean_diff": -94.959,
   "se": 1.464,
   "oog": 0,
   "max_ok_gas": 231006
  }
 ],
 "top2_stage": "NOT RUN: seeds 42000..42511 were skipped for lack of machine time (about 0.56-0.9 s per simulation on 8 threads); v5 was frozen directly from the 40000..40255 tuning block, where every variant was 43 to 95 below V2.",
 "paired": {
  "n": 400,
  "seed_range": [
   4070000,
   4070399
  ],
  "mean_diff": -43.975,
  "se": 0.481,
  "worst5": [
   {
    "seed": 4070035,
    "diff": -83.666
   },
   {
    "seed": 4070154,
    "diff": -79.314
   },
   {
    "seed": 4070292,
    "diff": -73.594
   },
   {
    "seed": 4070284,
    "diff": -71.384
   },
   {
    "seed": 4070316,
    "diff": -69.876
   }
  ],
  "candidate_mean_edge": 475.074,
  "V2_mean_edge": 519.049
 },
 "gas": {
  "candidate": {
   "max_ok": 231212,
   "oog": 0,
   "other_halts": 0,
   "reverts": 0,
   "tuning_block_40000_40255": {
    "max_ok": 231003,
    "oog": 0,
    "other_halts": 0,
    "reverts": 0
   }
  },
  "V2": {
   "max_ok": 243469,
   "oog": 5,
   "other_halts": 0,
   "reverts": 0,
   "tuning_block_40000_40255": {
    "max_ok": 243701,
    "oog": 7,
    "other_halts": 0,
    "reverts": 0
   }
  }
 },
 "notes": "NEGATIVE RESULT: design B is far below V2 and should not be pursued in this form. Confirm is INCOMPLETE: only seeds 4070000..4070399 (n=400) of the 2000 asked for were run, because simulation took about 0.56 s (V2) to 0.9 s (candidate) per seed on 8 threads and a 10-minute command limit killed one 800-sim chunk. The candidate was frozen before any confirm seed was run and was not reselected. Tuning: 3 variants on 40000..40255 (v5 K=125,D=0.5; v6 K=80,D=0.5; v7 K=100,D=0.25). Before those, an 8-seed smoke test on 40000..40007 of earlier builds (with no gap shrinkage) found 32104 OOG when sigma came from a sqrt and the grid had no early stop; after the fix, 0 OOG and about -45 vs V2 on 8 seeds. Those builds were overwritten and are not in the table. Gas was counted with atomic counters added to amm_sim_rs/src/evm/strategy.rs::call (local scratch build only): OOG halts, other halts and reverts on every 250k-limit afterSwap call of both pools; max_ok is revm gas_used of successful calls, which includes the 21k intrinsic cost. V2 itself had 7 OOG calls on 40000..40255 (max_ok 243701) and also OOG calls on the confirm block (see gas.V2), so V2's own results include callbacks that fell back to the previous fees. The candidate had 0 OOG with max 231212, below V2's peak. Likely reasons it loses: the linear share model (slope K from R/(4T) with T=20) puts the retail optimum near 35 bps minus the gap and shifts fees one-for-one with the estimated gap. After an arb, the spot sits at the band edge, so this ratchets the arbed side's fee up and pulls the other side down. The arb-loss term is tiny unless the gap is near f, so in practice the rule is just 'fee = const - gap' and does not use V2's flow, activity or toxicity responses. More shrinkage on the gap (D 0.25) was much worse (-95), and a lower K (higher base fee, v6) was worse (-49). The top-2 stage on 42000..42511 was not run. Setup: amm-challenge cloned at 6b6921f8 under test/scratch, the given setup.sh ran with exit 0, StarterStrategy validated. Configs were built per MatchRunner._build_configs with default_rng(seed) and cfg.seed=seed, and each strategy ran alone against VanillaStrategy via run_batch(...,8)."
}