# port-b: two-component filter on qtr2 (TwoComp port) The policy, replica and tables are by Taylor (gtmcknight): [amm-sim @4812a638 research/solved](https://github.com/gtmcknight/amm-sim/tree/4812a6382311624e1506aca131ca35c378f930d9/research/solved). The filter is a port of `class TwoComp` from [TC](https://api.imd.fun/artifacts/d3c849db2ac31e567b57e52b80b86e8c24c842e5b4125b9d75c78e3cb07115b9), applied to qtr2 ([R](https://api.imd.fun/artifacts/18ca35cb47390b8e8c289b240d7836804692998ad64f84ef248c43d61e199f0b)). Every number below was measured in this run. None of it is an independent certification. **Summary:** Gate P PASS. Gas probe max 177,987 (≤ 225,000), PASS. Gate F PASS (max 6.30e-6 bps). On the engine, the candidate minus qt1 is **+0.438501 ± 0.045224** (n = 128). The candidate's afterSwap peak is 213,439 with 0 OutOfGas. Runtime is 21,197 B. `amm-match validate` passes. ## Contract - R's block is extracted with sha256 `7b99afae570ef1627b0163fb21411f14c496b005a07c3966cc7323906dee664f`, which matches the brief. - Candidate source sha256 is `d5e8b79423dc5c59f36559b674890f701f1a2dc8e69e2447e4d5dda702dadb5e`. Deployed runtime is **21,197 bytes**. The compiler is the challenge's (solc 0.8.24 via `SolidityCompiler`). The contract has no assembly. - These are unchanged from R: tables, knots, spline evaluation, rounding, `lookup`/`fees`, fee conversion, the arb/retail classifier constants and the `.005/.995` thresholds. The quote still uses one `fees(...)` call on the mixture mean `b.m` and sd `root(b.v)`. - Changed: `struct Comp`, `mom()`, and the body of `update()`. TwoComp's propagate runs with `DUAL_CUTS = 2`, the summed-branch arb likelihood, and a retail posterior matched to one Gaussian. When pi is in (.005,.995), the two-part posterior is stored as `slots[4] = (lp, pi)` and `slots[5] = (mt, vt)`. A two-component flag is stored at bit 254 of `slots[3]`, so the count field is now 126 bits. `afterInitialize` pre-writes `slots[4]` and `slots[5]` to 1, so later writes are never zero→nonzero. With one component, every operation reduces exactly to R's arithmetic, because mul(U,x)=x and divi(z,z)=U. - Fixed-point differences from the Python: a component whose renormalized weight underflows to 0 is dropped, where Python drops it at 1e-300. The underflow test is `Z>0`, where Python uses `Z>1e-300`. Gate F shows no measurable effect. The diff below is a unified diff against R's Solidity block (lines 96–684 of R, saved as `Strategy.sol`). `patch` is not installed on this machine. `git apply -p0` applied the diff cleanly and reproduced the candidate sha256 above. The diff is in standard unified format, but `patch` itself was not run on it. ```diff --- Strategy.sol +++ Strategy.sol @@ -13,12 +13,16 @@ // Gas trim: scalar node sums, shared mirrored knot geometry, and four packed state words. // Mirrored bases rerun the original recurrence, preserving its fixed-point rounding order. // Knot words: interior knots as uint16 in 1/64 grid units (W[0..1] rows, W[2] columns). +// Two-component filter (port of class TwoComp): at a step's first trade the posterior is an arb point mass +// (pi, lp, 0) plus a retail Gaussian (1-pi, mt, vt); both are diffused and cut separately for up to two silent +// steps, then moment-matched; fees quote the mixture mean and sd with the same single lookup. Credit: Taylor (gtmcknight). // Taylor source: https://github.com/gtmcknight/amm-sim/tree/4812a6382311624e1506aca131ca35c378f930d9/research/solved contract Strategy is AMMStrategyBase { int256 constant U=1e18; int256 constant LN2=693147180559945309; - struct Belief { int256 m; int256 v; int256 fb; int256 fa; int256 sp; int256 r; uint256 t; uint256 cnt; bool sy; } - function getName() external pure override returns(string memory) { unchecked {return "TaylorGaussianQmTrim2";} } + struct Belief { int256 m; int256 v; int256 fb; int256 fa; int256 sp; int256 r; uint256 t; uint256 cnt; bool sy; bool two; } + struct Comp { int256 w; int256 m; int256 v; } + function getName() external pure override returns(string memory) { unchecked {return "TaylorTwoCompQmTrim2";} } function mul(int256 a,int256 b) internal pure returns(int256){ unchecked {return a*b/U;} } function divi(int256 a,int256 b) internal pure returns(int256){ unchecked {return a*U/b;} } function ab(int256 x) internal pure returns(int256){ unchecked {return x<0?-x:x;} } @@ -546,34 +550,70 @@ slots[0]=uint256(uint128(int128(b.m)))|(uint256(b.v)<<128); slots[1]=uint256(b.fb)|(uint256(b.fa)<<128); slots[2]=uint256(b.sp)|(uint256(b.r)<<128); - slots[3]=b.t|(b.cnt<<128)|(b.sy?uint256(1)<<255:0); + slots[3]=b.t|(b.cnt<<128)|(b.sy?uint256(1)<<255:0)|(b.two?uint256(1)<<254:0); } } function afterInitialize(uint256 x,uint256 y) external override returns(uint256,uint256){ unchecked { Belief memory b; b.sp=int256(y*1e18/x);b.r=b.sp;b.m=ln(b.sp); - b.fb=2823606104386589;b.fa=2823606104386589;save(b);return(uint256(b.fb),uint256(b.fa)); + b.fb=2823606104386589;b.fa=2823606104386589;save(b);slots[4]=1;slots[5]=1;return(uint256(b.fb),uint256(b.fa)); } } - function update(Belief memory b,int256 s,bool buy,uint256 dt) internal pure returns(bool arb){ unchecked { - int256 lo;int256 hi;int256 m;int256 v;int256 lp;int256 la;int256 Z;int256 mt;int256 vt;int256 d;int256 pi; - lo=ln(mul(b.sp,U-b.fb));hi=ln(divi(b.sp,U-b.fa));m=b.m;v=b.v; + function mom(Comp memory x,Comp memory y) internal pure returns(int256 m,int256 v){ unchecked { + m=mul(x.w,x.m)+mul(y.w,y.m); + v=mul(x.w,x.v+mul(x.m-m,x.m-m))+mul(y.w,y.v+mul(y.m-m,y.m-m));if(v<0)v=0; + } } + function update(Belief memory b,int256 s,bool buy,uint256 dt) internal returns(bool arb){ unchecked { + int256 lo=ln(mul(b.sp,U-b.fb));int256 hi=ln(divi(b.sp,U-b.fa)); + Comp memory c0;Comp memory c1;bool two=b.two; + if(two){uint256 q4=slots[4];uint256 q5=slots[5]; + c0=Comp(int256(q4>>128),int256(int128(uint128(q4))),0); + c1=Comp(U-c0.w,int256(int128(uint128(q5))),int256(q5>>128)); + }else c0=Comp(U,b.m,b.v); uint256 n=dt-1;if(n>12)n=12; - for(uint256 i=0;ic0.w)c0=Comp(U,m1,v1);else c0=Comp(U,m0,v0);} + else{ + int256 w0=divi(z0,tot);int256 w1=divi(z1,tot); + if(w0<=0){two=false;c0=Comp(U,m1,v1);} + else if(w1<=0){two=false;c0=Comp(U,m0,v0);} + else{c0=Comp(w0,m0,v0);c1=Comp(w1,m1,v1);} + } + } + } + c0.v+=893025000000;if(two)c1.v+=893025000000; + int256 lp=ln(buy?mul(s,U-b.fb):divi(s,U-b.fa)); + int256 la;int256 Z;int256 mt;int256 vt; + { + int256 sd=root(c0.v);la=mul(c0.w,divi(pdf(divi(lp-c0.m,sd)),sd)); + (int256 z,int256 a0,int256 b0)=truncSD(c0.m,c0.v,lo,hi,sd); + Comp memory r0=Comp(mul(c0.w,z),a0,b0);Z=r0.w; + if(two){ + sd=root(c1.v);la+=mul(c1.w,divi(pdf(divi(lp-c1.m,sd)),sd)); + (z,a0,b0)=truncSD(c1.m,c1.v,lo,hi,sd); + Comp memory r1=Comp(mul(c1.w,z),a0,b0);Z+=r1.w; + if(Z>0){r0.w=divi(r0.w,Z);r1.w=divi(r1.w,Z);(mt,vt)=mom(r0,r1);} + else if(c1.w>c0.w){mt=r1.m;vt=r1.v;}else{mt=r0.m;vt=r0.v;} + }else{mt=r0.m;vt=r0.v;} + } + int256 d=ab(ln(divi(s,b.sp)));if(d<1e9)d=1e9; + int256 pi=pdf(divi(ln(d)+6801595083153746944,1176000000000000000)); int256 lr=divi(mul(mul(Z,260000000000000000),pi),mul(d,1176000000000000000)); pi=la+lr>0?divi(la,la+lr):int256(0);arb=pi>U/2; if(pi>5e15&&pi<995e15){ b.m=mul(pi,lp)+mul(U-pi,mt); b.v=mul(pi,mul(lp-b.m,lp-b.m))+mul(U-pi,vt+mul(mt-b.m,mt-b.m)); - }else if(arb){b.m=lp;b.v=0;}else{b.m=mt;b.v=vt;} + b.two=true;slots[4]=uint256(uint128(int128(lp)))|(uint256(pi)<<128); + slots[5]=uint256(uint128(int128(mt)))|(uint256(vt)<<128); + }else{b.two=false;if(arb){b.m=lp;b.v=0;}else{b.m=mt;b.v=vt;}} int256 pe=exp(b.m);b.r=clip(b.r,mul(pe,997e15),divi(pe,997e15));b.cnt=0;b.sy=false; } } function afterSwap(TradeInfo calldata t) external override returns(uint256,uint256){ unchecked { uint256 p0=slots[0];uint256 p1=slots[1];uint256 p2=slots[2];uint256 p3=slots[3]; - Belief memory b=Belief(int256(int128(uint128(p0))),int256(p0>>128),int256(uint256(uint128(p1))),int256(p1>>128),int256(uint256(uint128(p2))),int256(p2>>128),uint128(p3),(p3>>128)&((uint256(1)<<127)-1),(p3>>255)==1); + Belief memory b=Belief(int256(int128(uint128(p0))),int256(p0>>128),int256(uint256(uint128(p1))),int256(p1>>128),int256(uint256(uint128(p2))),int256(p2>>128),uint128(p3),(p3>>128)&((uint256(1)<<126)-1),(p3>>255)==1,((p3>>254)&1)==1); int256 s=int256(t.reserveY*1e18/t.reserveX);bool arb; if(t.timestamp>b.t)arb=update(b,s,t.isBuy,t.timestamp-b.t); uint256 ph; ``` ## TwoComp edges (replica, `replica2.run(seed, TwoComp(blob))`) | seed | edge | |---|---:| | 10000 | 443.690614 | | 10001 | 571.968071 | | 10002 | 526.124475 | ## Gate P `python3 -I gates.py` was run in `amm-sim/research/solved` and reproduced the README table exactly: ```text seed perfect-info gaussian+plain gaussian+qm lattice+qm 10000 448.261189 443.455598 443.522608 443.663520 10001 576.779199 571.463409 571.623551 571.977293 10002 531.886497 525.168028 525.865859 526.411631 ``` **PASS**. ## Gas probe Setup: - Full TwoComp-driven replica runs, seeds 70000..70009, with every callback replayed. - 107,708 callbacks on a fresh pyrevm deployment, 245,000 gas per call. - A qualifying callback arrives with dt ≥ 13 while the reference holds two components. Results: - 13 callbacks qualified. - Their **max is 177,987 gas, at seed 70008, callback 6719** (zero-based). **PASS** (≤ 225,000). - The DUAL_CUTS reference was not cut further. - Across all callbacks in these runs, the peak was 178,304 (seed 70002, callback 3442) and the mean was 113,960.4. - 0 reverts and 0 OutOfGas. ## Gate F Setup: - 50 TwoComp-driven replica runs, seeds 70000..70049. - The first 2,000 own-pool callbacks of each run. - A fresh deployment per seed, initialized with 100/10,000; amounts and reserves are converted as `int(v*1e18)`. - 245,000 gas per call. - Fees are compared as effective fees: contract fee vs `replica2.clampf(reference)`. | metric | value | |---|---| | runs / callbacks | 50 / 100,000 | | max_bps | **6.304e-06** (seed 70021, callback 1192) | | mean_bps | 2.615e-07 | | init diff | 0.0 bps | | pyrevm afterSwap peak / mean | 178,766 (seed 70016, cb 899) / 114,143.9 | | reverts / oog | 0 / 0 | | callbacks > 0.01 bps | 0 | **PASS**. ## Engine The source was frozen as sha256 `d5e8b794…`. qt1 was compiled from its block, sha256 `c68887cf…`, which was verified. Method: - Each arm ran alone against the 30 bps VanillaStrategy with `amm_sim_rs.run_batch(X, Vanilla, configs, 8)`. - Configs are a transcription of `MatchRunner._build_configs`: `np.random.default_rng(seed)`, then size, rate and sigma draws. - Seeds 9800832..9800959 were run once per arm, in 4 chunks of 32. A reservation file was written before each run. - `gas_stats` was reset before each chunk and read after it. ```json {"paired_vs_qt1": {"n": 128, "mean_diff": 0.43850144, "se": 0.045223791, "worst5": [[9800906, -0.906648254], [9800861, -0.657203207], [9800840, -0.562222775], [9800844, -0.391086203], [9800889, -0.381883923]]}, "mean_edge": {"candidate": 536.130342, "qt1": 535.691840}} ``` SE is the sample standard deviation of the paired differences divided by √128. Submission afterSwap gas: | arm | calls | peak | mean | oog | revert | |---|---:|---:|---:|---:|---:| | candidate | 1,394,458 | 213,439 | 137,449.28 | 0 | 0 | | qt1 | 1,401,908 | 209,052 | 137,932.46 | 0 | 0 | Per-seed edges: | seed | candidate | qt1 | diff | |---|---:|---:|---:| | 9800832 | 385.555369 | 385.930955 | -0.375587 | | 9800833 | 429.148057 | 428.543414 | +0.604643 | | 9800834 | 634.956784 | 632.328090 | +2.628694 | | 9800835 | 666.069812 | 665.208456 | +0.861355 | | 9800836 | 528.622499 | 528.263573 | +0.358926 | | 9800837 | 471.672793 | 471.872115 | -0.199322 | | 9800838 | 521.406433 | 521.505604 | -0.099172 | | 9800839 | 578.961450 | 578.439574 | +0.521875 | | 9800840 | 446.598056 | 447.160279 | -0.562223 | | 9800841 | 576.684827 | 575.409393 | +1.275433 | | 9800842 | 607.742350 | 607.477031 | +0.265319 | | 9800843 | 474.546467 | 474.879269 | -0.332802 | | 9800844 | 582.301001 | 582.692087 | -0.391086 | | 9800845 | 353.204971 | 353.093794 | +0.111176 | | 9800846 | 392.160007 | 391.295081 | +0.864926 | | 9800847 | 473.357129 | 472.946994 | +0.410134 | | 9800848 | 522.850024 | 522.124913 | +0.725111 | | 9800849 | 446.147673 | 446.337050 | -0.189377 | | 9800850 | 381.514368 | 381.475999 | +0.038369 | | 9800851 | 614.703043 | 613.928619 | +0.774424 | | 9800852 | 410.362513 | 409.890107 | +0.472406 | | 9800853 | 475.663788 | 475.117362 | +0.546427 | | 9800854 | 710.375828 | 709.078094 | +1.297733 | | 9800855 | 471.126436 | 470.769843 | +0.356593 | | 9800856 | 577.662104 | 577.216738 | +0.445366 | | 9800857 | 400.498249 | 400.069913 | +0.428335 | | 9800858 | 743.062275 | 742.345782 | +0.716493 | | 9800859 | 520.907110 | 520.998884 | -0.091774 | | 9800860 | 583.922154 | 582.984935 | +0.937219 | | 9800861 | 602.652986 | 603.310189 | -0.657203 | | 9800862 | 524.103508 | 523.713790 | +0.389717 | | 9800863 | 511.649971 | 511.453880 | +0.196091 | | 9800864 | 616.007241 | 614.949323 | +1.057918 | | 9800865 | 573.142776 | 572.120172 | +1.022605 | | 9800866 | 409.229418 | 408.723882 | +0.505536 | | 9800867 | 683.162020 | 683.444708 | -0.282688 | | 9800868 | 657.487444 | 657.419656 | +0.067788 | | 9800869 | 427.305437 | 426.756962 | +0.548475 | | 9800870 | 558.287560 | 558.123193 | +0.164368 | | 9800871 | 668.881936 | 668.465743 | +0.416194 | | 9800872 | 690.304321 | 689.618207 | +0.686114 | | 9800873 | 488.293965 | 487.794174 | +0.499792 | | 9800874 | 366.425779 | 366.497168 | -0.071389 | | 9800875 | 694.568555 | 693.326010 | +1.242545 | | 9800876 | 361.311876 | 360.516556 | +0.795320 | | 9800877 | 515.417683 | 514.839156 | +0.578527 | | 9800878 | 411.670768 | 411.152127 | +0.518641 | | 9800879 | 536.346256 | 536.566728 | -0.220472 | | 9800880 | 448.192182 | 447.751500 | +0.440682 | | 9800881 | 557.088860 | 557.305796 | -0.216936 | | 9800882 | 417.625120 | 417.392198 | +0.232922 | | 9800883 | 621.952619 | 621.367589 | +0.585030 | | 9800884 | 515.779128 | 516.013565 | -0.234437 | | 9800885 | 394.153497 | 393.806640 | +0.346858 | | 9800886 | 467.923261 | 466.990253 | +0.933008 | | 9800887 | 570.394458 | 569.106109 | +1.288349 | | 9800888 | 534.038591 | 532.850852 | +1.187740 | | 9800889 | 805.104346 | 805.486230 | -0.381884 | | 9800890 | 407.836504 | 407.119635 | +0.716869 | | 9800891 | 456.614355 | 456.493496 | +0.120859 | | 9800892 | 602.158865 | 601.527035 | +0.631830 | | 9800893 | 660.136537 | 660.150485 | -0.013948 | | 9800894 | 635.663614 | 634.707155 | +0.956459 | | 9800895 | 630.584499 | 629.879160 | +0.705339 | | 9800896 | 403.227097 | 402.292135 | +0.934962 | | 9800897 | 554.284609 | 553.771080 | +0.513529 | | 9800898 | 589.698088 | 589.663279 | +0.034810 | | 9800899 | 428.391501 | 427.761597 | +0.629903 | | 9800900 | 583.800205 | 582.983963 | +0.816242 | | 9800901 | 536.879829 | 536.808726 | +0.071103 | | 9800902 | 550.032178 | 549.603336 | +0.428842 | | 9800903 | 447.794562 | 446.906575 | +0.887987 | | 9800904 | 409.418752 | 409.233124 | +0.185628 | | 9800905 | 447.659036 | 447.240663 | +0.418374 | | 9800906 | 540.010690 | 540.917339 | -0.906648 | | 9800907 | 566.389613 | 565.257122 | +1.132491 | | 9800908 | 472.158724 | 472.137904 | +0.020820 | | 9800909 | 671.021382 | 670.275677 | +0.745706 | | 9800910 | 591.068305 | 590.709981 | +0.358323 | | 9800911 | 362.518421 | 361.761911 | +0.756510 | | 9800912 | 481.763239 | 481.000059 | +0.763180 | | 9800913 | 494.867733 | 494.655721 | +0.212012 | | 9800914 | 466.859969 | 466.677286 | +0.182683 | | 9800915 | 516.314316 | 515.898592 | +0.415724 | | 9800916 | 582.731545 | 582.361562 | +0.369984 | | 9800917 | 637.699933 | 637.000831 | +0.699102 | | 9800918 | 435.993821 | 436.012730 | -0.018909 | | 9800919 | 515.705440 | 515.488569 | +0.216871 | | 9800920 | 575.826843 | 575.960041 | -0.133198 | | 9800921 | 645.201603 | 644.384805 | +0.816798 | | 9800922 | 543.575154 | 542.978505 | +0.596650 | | 9800923 | 593.471036 | 593.361872 | +0.109164 | | 9800924 | 489.599529 | 488.394995 | +1.204534 | | 9800925 | 585.707036 | 585.542311 | +0.164725 | | 9800926 | 515.301738 | 514.780461 | +0.521277 | | 9800927 | 540.348832 | 540.048027 | +0.300805 | | 9800928 | 445.616201 | 445.487351 | +0.128849 | | 9800929 | 550.137965 | 549.993696 | +0.144270 | | 9800930 | 590.270662 | 589.120913 | +1.149748 | | 9800931 | 573.228321 | 572.925463 | +0.302859 | | 9800932 | 467.024985 | 466.778665 | +0.246320 | | 9800933 | 678.638950 | 678.955288 | -0.316338 | | 9800934 | 666.427436 | 666.091267 | +0.336169 | | 9800935 | 552.004490 | 551.453787 | +0.550703 | | 9800936 | 553.711701 | 553.430603 | +0.281097 | | 9800937 | 546.165781 | 545.302987 | +0.862795 | | 9800938 | 679.485298 | 679.085382 | +0.399916 | | 9800939 | 493.081882 | 492.610004 | +0.471878 | | 9800940 | 519.184973 | 518.600151 | +0.584822 | | 9800941 | 543.294913 | 543.013233 | +0.281680 | | 9800942 | 602.109491 | 600.692960 | +1.416531 | | 9800943 | 701.976903 | 700.411752 | +1.565152 | | 9800944 | 644.120710 | 642.791164 | +1.329546 | | 9800945 | 553.844439 | 553.598083 | +0.246356 | | 9800946 | 445.436299 | 445.325404 | +0.110895 | | 9800947 | 683.716811 | 682.576223 | +1.140588 | | 9800948 | 600.263032 | 600.550060 | -0.287028 | | 9800949 | 401.763772 | 401.034383 | +0.729389 | | 9800950 | 500.060031 | 500.162401 | -0.102369 | | 9800951 | 364.817249 | 364.240347 | +0.576902 | | 9800952 | 686.054909 | 685.987214 | +0.067695 | | 9800953 | 413.353895 | 413.053368 | +0.300526 | | 9800954 | 424.633691 | 424.169159 | +0.464532 | | 9800955 | 659.836063 | 659.720974 | +0.115089 | | 9800956 | 519.794169 | 519.283314 | +0.510855 | | 9800957 | 732.678449 | 732.714955 | -0.036505 | | 9800958 | 477.720641 | 476.248100 | +1.472541 | | 9800959 | 575.629358 | 575.152852 | +0.476506 | ## Commands Run from the amm-challenge checkout (6b6921f8). amm-sim (4812a638) is the sibling checkout. Scripts are in `scratch/w/`. ```sh sh ../amm-sim/setup-fast.sh # once; validated StarterStrategy (cd ../amm-sim/research/solved && python3 -I gates.py) # Gate P python3 -I scratch/w/mk.py scratch/w # qtr2.sol -> candidate.sol . scratch/env.sh && amm-match validate scratch/w/candidate.sol . scratch/env.sh && python scratch/w/rp.py probe 70000 70010 # gas probe, full runs . scratch/env.sh && python scratch/w/rp.py F 70000 70050 # Gate F . scratch/env.sh && python scratch/w/eng.py candidate # a = 9800832, 9800864, 9800896, 9800928 . scratch/env.sh && python scratch/w/eng.py qt1 python3 -I -c "...replica2.run(s, TwoComp('blob.bin'))['edge']" # s = 10000..10002 ``` `tc.py` is TC's `## Code` block, unmodified. `blob.bin` is the qm-spline blob: 10,167 B, sha256 `3ac7cb88…e38e`. Each engine chunk took about 105–114 s. Each replay run took under 45 s. ## Problems - `patch` is not installed here. The diff was checked with `git apply -p0` instead (clean, and the sha256 matched). - I did not run a Gate Q replica screen. The brief gives the reference as TC's TwoComp. TC itself reports +0.4248 ± 0.0122 vs qt1's reference on 10000..12047. I did not re-measure that figure. - The engine peak of 213,439 includes transaction overhead, which pyrevm's 178,766 does not. It is under the 245,000 limit. - The engine result comes from 128 seeds and is not a board score.