fgf 3.0.2

Faster Galois Fields: SIMD and portable finite-field arithmetic (binary towers and prime fields) and vector kernels
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
# Changelog

All notable changes to this project are documented here. The format follows
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and releases follow
[Semantic Versioning](https://semver.org/).

## [Unreleased]

## [3.0.2] - 2026-10-20

Equal-degree maps use `Isomorphism`; `Embedding` accepts proper inclusions
only. Consumers of 3.0.1 migrate equal-degree construction and application
as described below. The release also caps extension degrees at 64 and uses
portable scalar kernels on AArch64 and Wasm32.

### Added

- Batch reciprocal and division rows `ops::inv_into` / `ops::inv_assign`
  and `ops::div_elementwise` / `ops::div_elementwise_assign`: Montgomery
  batch inversion with zero-preserving totality (`inv(0) == 0`,
  `x / 0 == 0`), chunked through a 512-byte stack scratch (64 elements of
  up to 8 bytes, fewer for wider component elements) so steady state never
  touches the heap. Names follow the `mul_elementwise` family spellings.
- Byte-lane inner product `ops::dot_product`: folds two packed rows into
  one `Elem` as `sum(a[i] * b[i])`, empty rows returning zero. The name
  keeps the short scalar-noun spelling (`pack`, `mul_elementwise`) and
  stays distinct from bit-packed `bits::dot_product`, which folds an AND
  parity over live bits; the old N-to-1 overwrite gather of the same
  name (1.x) is now `ops::mul_into_gather` under the current grammar.
- `Elem<Mersenne31>::canonical` and `Elem<Goldilocks>::canonical` return the
  canonical form of a prime row element, matching the residue, binary, tower,
  and quadratic-extension rows.
- Mixed embedding composition over a sealed `Postcompose` right-hand
  operand: `Embedding::then` accepts a proper inclusion or an
  isomorphism out of the middle row, and
  `Isomorphism::then_embedding` composes an isomorphism into a proper
  inclusion.
- Prepared Frobenius maps with byte-row forms: `Frobenius` compiles one
  characteristic-power automorphism for repeated scalar and row use, and
  `ops::frobenius_into_with` / `ops::frobenius_assign_with` apply it
  across stable byte lanes.
- Prepared embedding and isomorphism maps with the complete byte-row
  surface: `PreparedEmbedding` compiles one canonical inclusion for
  repeated `embed`, `contains`, `restrict`, `frobenius`, `trace`, and
  `norm` use, `PreparedIsomorphism` compiles one presentation change for
  repeated `apply` / `unapply` use, and the `ops` row functions apply
  them across stable byte lanes with a validity bitmap for partial
  restriction.
- Bit-packed GF(2) map rows in `ops::map_bits`: `embed_into_with`,
  `trace_into_with`, `norm_into_with`, `restrict_into_with`, and
  `contains_into_with` move one prepared GF(2)-sourced map across packed
  bits and target lanes. Packed sides follow the `bits` LSB-first layout
  with caller-maintained zero padding; packed outputs are exactly
  `bits::bytes_for(bits)` bytes with cleared tail bits, lane sides hold
  whole target elements exactly, and empty rows are valid.
- Prepared and overwrite forms of the offset-addressed matrix operations:
  `ops::mul_add_matrix_at_with`, `ops::mul_into_matrix_at`, and
  `ops::mul_into_matrix_at_with` complete the `mul_add_matrix_at` family with
  the same geometry checks and argument order as the contiguous forms. GF(2^8)
  dispatches the register-blocked GFNI and AVX-512 scattered kernels with
  overwrite seeded from zero in registers; every other field routes the
  dispatched single-row kernels over the named rows.
- Missing operation siblings: `ops::sub_assign_rows` subtracts pairwise rows
  with the same geometry checks and argument order as `ops::add_assign_rows`
  (identical to addition in characteristic two, canonicalizing subtraction
  in the prime fields); `ops::mul_into_scatter` overwrites each row with
  `coeffs[j] * src`, the overwrite counterpart of `ops::mul_add_scatter`
  with zero-fill and source-copy fast paths; and the alloc-gated
  `ops::unpack_to_vec` decodes packed bytes into a new element vector, the
  counterpart of `ops::pack_to_vec`.

### Changed

- `wiki/Operation-suffixes.md` covers destination, shape, `_with`, `_at`/`_offsets`,
  and allocation suffixes.
- **Breaking:** the total extension degree cap is 64 at every
  characteristic. Declaring or validating a field whose total prime
  degree exceeds 64 is a build-time configuration error and panics:
  odd-characteristic extensions above degree 64 are no longer accepted,
  and index conversions share the same 64-coordinate capacity. Migrate
  wider odd towers (for example GF(3^66)) to degrees at or below 64.
- **Breaking:** `Embedding` names proper inclusions only: equal-degree
  construction reports `EmbeddingError::EqualDegree`, and
  `Isomorphism` is the only equal-degree map with `apply`/`unapply`.
  `Isomorphism::from_embedding` and `Isomorphism::to_embedding` are
  removed. Migrate equal-degree `Embedding::new`/`from_images` to
  `Isomorphism::new`/`from_images`, `embed` to `apply`, and `restrict`
  to `unapply` at equal degree.
- The wiki embedding guide covers the proper-inclusion and isomorphism
  split, mixed composition through `then` and `then_embedding`, and the
  `EqualDegree` and `IncompatibleDegree` rejection pair.
- The `alloc` feature documentation names the full prepared surface:
  the prepared maps, the `ops::map` and `ops::map_bits` modules, the
  prepared coefficient collections, the prepared scatter/gather/matrix
  `_with` forms, and `pack_to_vec`.

### Removed

- The `QuadraticExtension` public trait from the quadratic-conveniences
  proposal: no new public API ships. Degree-2 `Embedding` maps route
  internally through the Vieta closed forms with no public spelling
  change; `QuadMersenne31` keeps its inherent `const` conjugate/norm and
  drops the proposed inherent `trace` (use the embedding trace map).
- NEON and `simd128` acceleration: the `aarch64` and `wasm32` kernel
  subtrees and their dispatch arms are gone, and `internals` no longer
  exposes `kernel::{aarch64, wasm32}` or the `Neon`/`NeonAes`/`Wasm128`
  tokens. AArch64 and Wasm32 targets compile and run the portable scalar
  bodies; `backend_for::<F>()` reports `Backend::Scalar` for every field
  there (`backend()` still reports `simdispatch`'s process selection).

### Fixed

- Mersenne31 x86 multiplication accepts lanes already inside its redundant
  residue range directly and normalizes wider raw lanes before multiplication,
  preserving arbitrary raw inputs and canonical outputs.
- Pinned extension scalar arithmetic exposes its instance bodies for
  cross-crate inlining, removing runtime instance dispatch from specialized
  QuadMersenne31 operator multiplication. Investigation and merge gates:
  [Issue #47]https://github.com/nanithefkuc/fgf/issues/47.
- QuadMersenne31 scalar products defer reduction across the real and imaginary
  product sums instead of reducing each base-field product separately.
- Mersenne31 multiply-accumulate accepts redundant destination zero, and
  elementwise products share input-range checks across vector tiles while
  retaining ISA-width remainders.
- QuadMersenne31 vector products retain redundant zero internally; scalar
  arithmetic consumes canonical element storage without repeating entry
  reductions. Constructors and packed-buffer decodes retain normalization.
- Mersenne31 AVX-512 products use explicit two-source word permutations,
  and multiply-accumulate screens each register group for the high bit,
  canonicalizing the group before any store when a lane carries it and
  retaining ISA-width tails. Measurements:
  [PR measurement record]https://github.com/nanithefkuc/fgf/pull/48#issuecomment-6163124505.
- Mersenne31 and QuadMersenne31 AVX-512 multiplication screens operand
  lanes for the high bit instead of canonicalizing every load: lanes
  already inside the redundant range multiply as loaded. A lane outside it
  triggers canonicalized recomputation of its register group for the
  in-place entries and of the whole call for the out-of-place entries,
  before any final store. Arbitrary raw inputs and canonical outputs are
  preserved.
- Goldilocks scalar multiplication feeds the operand product directly to
  the wide reduction, which is total over `u128` and needs no canonical
  operands.

## [3.0.1] - 2026-10-19

3.0.1 is the first supported v3 release. The `3.0.0` published before it went
out by accident and was yanked; 3.0.1 is not API-compatible with that artifact
(see the 3.0.0 entry), and code locked to it must migrate. The supported
upgrade path is from 2.x: follow
[Migrating from 2.x](https://github.com/nanithefkuc/fgf/wiki/Migrating-from-2.x)
and the breaking entries below.

### Added

- The canonical generic field family `FiniteField<const P: u64, const N: u32,
  R: FiniteFieldRepr<P, N> = Residue>` with the `Residue` presentation at
  degree one over every `u64` prime. Each row exposes the full scalar and
  `const` element surface (`from_raw`, checked `from_residue`, `to_residue`,
  arithmetic, `pow`, `canonical`),
  total arithmetic under the crate-wide conventions (`inv(0) == 0`,
  `x / 0 == 0`, `0^0 == 1`, with the explicit inverse-zero branch at
  `P = 2`), a little-endian wire lane sized to the characteristic —
  characteristic two uses an eight-byte lane, and an odd characteristic
  uses one, two, four, or eight bytes as its residues fit within `2^8`,
  `2^16`, `2^32`, and `u64` — with `FieldBuffer::decode`/`encode` as the
  byte API of these rows, the checked packed
  operation surface with prepared coefficients, and vector routing on
  capable x86-64 hosts — AVX2 single-row kernels, AVX-512 under `simd512` —
  with `Scalar` routing elsewhere. Broadcast add/sub take the modular path
  for every characteristic, so packed lanes stay canonical at `P = 2`.
  Values live in
  `field::finite_field`, re-exported at the root.
- Sealed `PrimeField` marker for degree-one fields, the validated `const`
  integer reductions `field::prime::reduce` and `reduce_wide`, `RangeError`
  for out-of-range conversions, and the `PrimeIdentity` characteristic
  facts `BITS`, `TWO_ADICITY`, and `RESIDUE_MOD_8`.
- `just compile-fail` compiles the negative consumer snippets under
  `tests/compile_fail/` on the crate MSRV, each required to fail with its
  declared crate panic fragment, or simply to be rejected for trait-bound
  and privacy cases, and `just msrv-accept` runs
  those snippets plus the `tests/consumers.rs` compile-and-run matrix on
  the MSRV toolchain.
- Wiki source guides cover the generic type model, field presentations,
  embeddings, custom towers and scalar fields, packed workflows, preparation,
  prime and bit-packed arithmetic, deployment, and migration from 2.x.
  `wiki/Home.md` and the sidebar organize the reader paths.
- Sealed presentations `MersenneFold` (label `mersenne-fold`) and
  `GoldilocksFold` (label `goldilocks-fold`), re-exported at the root. Both
  rows implement `PrimeField` and gain `const` `from_residue(u64) ->
  Result<Self, RangeError>` and `to_residue(self) -> Option<u64>`. The
  default `Residue` row at either modulus remains a distinct type with its
  own lane width; values move between the two by residue conversion.
- The binary degree-one rows `FiniteField<2, 1, R>` implement `PrimeField`
  and gain `const` `from_residue(u64) -> Result<Self, RangeError>` and
  `to_residue(self) -> Option<u64>`; they stay distinct from
  `FiniteField<2, 1, Residue>`, which stores a `u64` residue.
- `just external-check` type-checks every package under `external/`
  against the working copy.
- The generic extension presentation `FiniteField<P, N, Extension<S, D>>`
  with data-only `ExtensionSpec` providers, the sealed storage family
  (`Components`, `Packed`, `PackedPadded`), the opaque const-constructible
  `Relation`, generic arithmetic under the crate-wide total conventions,
  component bytes and the checked packed surface with `Scalar` routing,
  `ExtensionField` component conversions, and `BinaryField` on
  characteristic-two rows over binary bases. The surface lives in
  `field::extension`, re-exported at the root. `EmbeddingError` gains the
  `NoCanonicalMap` variant for dividing degrees with no canonical map.
- `ComplexMersenne31`, the `ExtensionSpec<2>` of `QuadMersenne31`:
  `Mersenne31` extended by `t^2 + 1` with `Components` storage,
  re-exported at the root.
- An extension row whose base presentation, relation, and storage equal a
  shipped instance's runs that instance's specialized scalar arithmetic and
  reaches its kernel arms; every other extension row runs the generic body
  and reports `Scalar`.
- `FieldElem::from_residue`/`to_residue` and `from_index`/`to_index` on
  every row: the prime-subfield value and the presentation index share one
  generic body over the crate-private row hooks, and the inherent `const`
  residue constructors check through the same bound.
- The characteristic-independent relation surface in `field::embedding`,
  re-exported at the root: `Embedding<S, T>` over every pair of rows of
  equal characteristic whose degrees divide, with `from_images`,
  `to_images`, `generator_count`, `generator`, `then`, and
  `Isomorphism<S, T>` with `apply`, a total `unapply`,
  `inverse`, and composition; and the `NoCanonicalMap` and `InvalidImages`
  variants of `EmbeddingError`. Maps come from structural inclusions, the
  frozen binary ladder, or validated caller images; every pre-cutover
  binary result is unchanged.
- Wiki reference for operation suffixes, destination behavior, prepared state,
  row addressing, and allocation.
- One-time field migration verification is recorded in
  [Issue #41]https://github.com/nanithefkuc/fgf/issues/41, rather than a
  permanent expanded regression corpus.
- Behavioral regressions cover irreducible and reducible extension relations,
  relation-field arithmetic, ordered-basis subfield embeddings, rejected
  restrictions and degree mismatches, and quadratic prime identity predicates.
- Raw kernel dispatch is unreachable outside the crate: every `FieldKernels`
  kernel entry takes the crate-private `RawDispatch` proof argument, so code
  holding an `F: FieldKernels` bound — including under `internals` — cannot
  call the raw entries. The negative consumer snippet
  `tests/compile_fail/raw_dispatch_without_proof.rs` pins the rejection.
- Three examples join the educational set, each running against the library
  with `std` and `alloc` disabled: `custom_extensions` defines a custom
  `ExtensionSpec` field, `basis_maps` moves values through `Embedding`
  between basis presentations, and `packed_no_alloc` runs packed row
  operations without an allocator. `just examples-portable` runs them in
  that configuration.
- `just release-accept` runs the crate-specific release acceptance, separate
  from the shared pull-request gate `just validate`. Its pieces also stand
  alone: `just test-gather-validation-32` runs the gather validation as a
  release i686 build with overflow checks disabled — the configuration the
  gather fix below required — and CI runs it as its own job;
  `just benchmark-check` validates portable benchmark fixtures and compares
  their inventory with compiler-derived public API declarations;
  `just benchmark-check-native` validates the GF16 presentation comparisons
  and existing native geometries on capable hosts. `just isa-accept EXPECTED`
  requires the requested process backend and positive execution evidence
  before running the full tier matrix. Native release acceptance uses
  dedicated Willow Cove (`v4x`) and Golden Cove (`v3_gfni_crypto`) hosts.
- The `surfaces` benchmark artifact measures the public API end to end:
  scalar arithmetic, packed single-row and multi-row operations, prepared
  coefficient forms, and `Embedding`/`Isomorphism` maps, over representative
  fields including the AES byte field, the Rijndael sixteen tower, and the
  `MersenneFold`/`GoldilocksFold` prime rows. `--inventory` prints every
  registered measurement without timing, `--check` runs each fixture's
  correctness check once, and `--smoke` shortens repetition without
  dropping cases. Each timed record names its public callable paths, exact
  registered field label, scope, logical byte numerator, lane and
  source/destination counts, and the resolved backend; a byte-XOR control
  identifies itself with `control` and `phase` fields, and zero-work APIs
  report latency instead of throughput.
- The native GF16 comparison gains `--presentations`, a separate output mode
  beside the legacy format so existing parsing is unchanged. It covers
  representative shipped Rijndael and Fan-Paar fields, custom RS-rooted
  fields, normal and Cantor bases, and competitor-matched fields, with
  duplicate `fgf` control arms, interleaved arms and fixed/cycling
  coefficients, and every map and fixture validated outside timing;
  `--presentations --check` validates every new geometry without timing,
  and `--presentations --smoke` shortens the sampling budget without
  dropping a registered case. Both output modes open with a
  `BENCHMARK_MODE` banner (`full`, `smoke`, or `check`), and each
  `GF16_SURFACE` record carries its sample count and calibrated batch size
  as `samples` and `batch_reps`. Presentation conversions run public scalar
  caller loops.
- The capture family `surfaces` records the artifact and the GF16
  `--presentations` mode for current v3 in five rounds per host, published
  in the v3 record (`benchmarks/v3/public-api.md`, `benchmarks/v3/maps.md`,
  and the GF16 presentation panels). No released-v2 counterpart is
  fabricated for the new public APIs.

### Changed

- **Breaking:** generic residue rows size their byte lane to the
  characteristic instead of always storing eight bytes: characteristic
  two keeps an eight-byte lane, and an odd characteristic uses one, two,
  four, or eight bytes as its residues fit within `2^8`, `2^16`, `2^32`,
  and `u64` — for example `FiniteField<7, 1>` moves one byte per element
  and `FiniteField<1_000_000_007, 1>` moves four. `FieldBuffer::decode`
  and `encode` are the byte API of these rows. Migration: the inherent
  fixed-array `from_bytes`/`to_bytes` are absent for these rows (every
  characteristic, `P = 2` included), and no `const` byte form exists for
  them — size buffers from `FieldBuffer::BYTES`, decode and encode
  through `FieldBuffer`, and in `const` code build from `from_raw` with
  `u64::from_le_bytes` and read back through `to_raw`. Extensions over a
  residue base shrink with the base lane; the named prime rows, binary
  rows, towers, and `QuadMersenne31` keep their lane widths and fixed-array
  methods.
- **Breaking:** `Mersenne31` and `Goldilocks` are type aliases of the
  canonical family rows `FiniteField<0x7FFF_FFFF, 1, MersenneFold>` and
  `FiniteField<0xFFFF_FFFF_0000_0001, 1, GoldilocksFold>` instead of unit
  marker structs. Moduli, raw widths, bytes, generators, arithmetic, and
  kernel routing are unchanged. The aliases are no longer value
  expressions: replace `Mersenne31` or `Goldilocks` used as a value with
  `Mersenne31::default()` (or drop the value where only the type was
  needed). Marker `Debug` now prints the family spelling, e.g.
  `FiniteField<2147483647, 1, mersenne-fold>`. Kernel receivers move to the
  rows with the aliases, so `FieldKernels` calls through the alias names
  compile unchanged.
- **Breaking:** the `Binary<N, R>` marker is removed without an alias;
  binary fields are the canonical family rows `FiniteField<2, N, R>`.
  Replace `Binary<8, Polynomial<AES>>` with `FiniteField<2, 8,
  Polynomial<AES>>` and `Binary::<…>` with `FiniteField::<2, …>`, and
  import `FiniteField` instead of `Binary`. Raw widths, bytes, descriptors,
  coordinates, embeddings, bit-packed buffers, and kernel routing are
  unchanged.
- **Breaking:** `BinaryRepr<const N: u32>` and `BinaryDegree<const N: u32>`
  take `u32` degree parameters instead of `u8`; generic code declaring
  `const N: u8` against them changes the parameter type.
- **Breaking:** `BinaryRepr` now extends `FiniteFieldRepr<2, N>`, which
  owns `NAME` and `VALID`. Replace `<R as BinaryRepr<N>>::NAME` and
  `::VALID` with `<R as FiniteFieldRepr<2, N>>::NAME` and `::VALID`;
  `DESCRIPTION` and `LOG_EXP` stay on `BinaryRepr`.
- **Breaking:** binary marker `Debug` prints the family spelling with the
  presentation label, e.g. `FiniteField<2, 8, GF(2^8)/0x11B>`, instead of
  the label alone. Element `Debug` and every wire format are unchanged.
- **Breaking:** binary rows validate their defining data at every entry
  point: `ZERO_RAW`, `ONE_RAW`, and every raw `Field` method now fail const
  evaluation for an invalid polynomial or basis, as `from_raw` already did.
  Valid presentations are unaffected.
- **Breaking:** `Field`, `HasGenerator`, and `FieldBuffer` are sealed.
  Implementing any of them outside the crate no longer compiles. Migrate a
  custom prime field to the family's degree-one row `FiniteField<P, 1>`
  (the default `Residue` presentation) at any checked `u64` prime: it
  supplies `Field`, `PrimeField`, `FieldBuffer`, the checked packed
  operation surface, and the `const` element API. Migrate a custom
  extension to an `ExtensionSpec` over a crate row.
- **Breaking:** the shipped towers are extension rows: `FiniteField<2, N,
  Tower<S>>` is spelled `FiniteField<2, N, Extension<S, 2>>` for `S` in
  `Rijndael16`, `Rijndael32`, `Rijndael64`, and `FanPaar2` through
  `FanPaar64`, each now an `ExtensionSpec<2>` with `Packed<W>` storage
  (`PackedPadded<u8>` for `FanPaar2` and `FanPaar4`). Raw words, bytes,
  coordinate words, generators, embeddings, and kernel routing are
  unchanged, and each instance keeps its `const` element surface and
  `mul_alpha`. Replace `Tower<S>` with `Extension<S, 2>` and import
  `Extension`.
- **Breaking:** the wide shipped instances' component conversions take
  and return arrays: `Elem::<F>::from_components(a, b)` becomes
  `from_components([a, b])` and `to_components()` returns `[a, b]` instead
  of `(a, b)`, matching `ExtensionField<2>`.
- **Breaking:** towers leave `BinaryRepr`: `BinaryRepr<N>` covers the flat
  presentations only, and no flat presentation exists at degrees 16, 32,
  and 64. Replace generic bounds `R: BinaryRepr<N>` that reached towers with
  field-level bounds (`F: BinaryDegree<N>` or `F: BinaryField`) and
  `<Tower<S> as BinaryRepr<N>>::DESCRIPTION` with `F::DESCRIPTION`.
- **Breaking:** `QuadMersenne31` is a type alias of
  `FiniteField<0x7FFF_FFFF, 2, Extension<ComplexMersenne31, 2>>` instead of
  a unit marker struct, and its raw word is `[u32; 2]` instead of
  `(u32, u32)`, with the same limbs in the same order. Replace
  `Elem::<QuadMersenne31>::from_raw(re, im)` with `from_raw([re, im])`,
  `to_raw().0` and `.1` with `to_raw()[0]` and `[1]`, `from_components(re,
  im)` with `from_components([re, im])`, and tuple destructuring of
  `to_components()` with array destructuring. Element `Display` prints
  `[re, im]` in the base field's form instead of `(re+imi)`; bytes,
  arithmetic, the generator, `I`, `conjugate`, `norm`, and kernel routing
  are unchanged. Replace `QuadMersenne31` used as a value with
  `QuadMersenne31::default()`.
- `BinaryDescription::inv` takes canonical coordinate words. On a
  polynomial-root description, a word with bits at or above `degree()`
  inverts its low `degree()` bits instead of returning an unspecified
  value; on other roots such a word still gives an unspecified result.
  Results on canonical words are unchanged.
- **Breaking:** `FieldElem` owns the residue and index conversions
  `from_residue`/`to_residue` and `from_index`/`to_index`, shared by every
  row. `BinaryField::to_coordinates`/`from_coordinates` are removed: use
  `to_index`/`from_index`, identical words below degree 64 widened to
  `u128`. `CoordinateError` is replaced by `fgf::RangeError` (also
  `fgf::field::RangeError`). Residues and indices are distinct: the index
  of one is the presentation's coordinate encoding of one.
- Binary embedding evaluation no longer uses precomputed linear columns:
  maps evaluate by Horner substitution and restrict through trace-dual
  bases, with unchanged results on every pair.
- **Breaking:** the `field::binary` embedding paths are removed without
  an alias: `fgf::field::binary::embedding`, `Embedding`, and
  `EmbeddingError` under it. Import from the crate root or
  `field::embedding` instead; the struct bounds widen from `BinaryField`
  to `Field`, and `Embedding` keeps `Clone`, `Copy`, and `Debug`.
- The dependency lock records published sources for locked MSRV checks;
  umbrella-local patch resolution is excluded from the publication lock.
- Public documentation leads with `FiniteField`, its representations, and
  `Elem<F>`; named prime families appear as specializations. Wiki guides
  follow field selection, scalar arithmetic, packing, row operations,
  preparation, and deployment, with task-oriented navigation and a custom
  prime-base extension example.
- Benchmark publication preserves the complete public-operation, field-map,
  GF16-presentation, size-sweep, and competitor inventory in task-oriented
  pages. Shared geometry is prose; varying counts occupy named columns.
  Exact callable paths, setup, resolved backends, and controls are expandable.
  SVG overviews complement exact figures. Public v2 and comparison pages are
  removed; their samples remain in raw campaign evidence.
- Wiki guides use 3.0.1 as the base v3 release and lead from field construction
  and arithmetic through presentations, embeddings, and custom fields.
  Migration guidance goes directly from 2.x to 3.0.1.
- Wiki navigation groups the reading path in the home page and sidebar instead
  of per-page `Next:` footers.
- AVX-512 generic residue kernels under `simd512`: nibble-table `u8`,
  Shoup and Barrett `u32`, Montgomery `u64`, and delayed-reduction prepared
  matrices. Capable residue rows report `V4x`; short rows retain narrower
  or portable routes, and zero/unit coefficient byte behavior is unchanged.
- Four-byte generic residue rows vectorize elementwise multiplication on
  AVX2-capable x86 hosts.
- One-byte generic residue rows at moduli up to 127 vectorize prepared
  fixed-coefficient scaling through nibble-table lookups, four-byte rows
  through Shoup multiplication, and eight-byte rows at odd moduli through
  doubleword Montgomery multiplication; those rows report the vector
  backend, and vector-routed operations produce byte-identical output to
  the portable path on every input, applying the documented zero/unit
  shortcuts before vector dispatch.
- Four-byte generic residue rows delay reduction across shared sources in
  prepared overwrite/accumulate four-output matrices with enough shared
  sources and modulus headroom, as recorded in
  [the residue benchmark record]benchmarks/v3/residue.md, with delayed
  accumulation falling back to the repeated single-row path when an output
  row's coefficients are all zero.
- Operation implementations move from `ops.rs` into the `ops/` coefficient,
  single-row, scatter, gather, matrix, and packing modules, retaining public
  `ops::*` paths. Byte dispatch moves into `kernel/bytes.rs`.
- Generic residue single-row AVX2 kernels move from `residue/avx2/single.rs`
  into `residue/avx2/single/{u8,u32,u64}.rs`.
- **Breaking:** Unstable residue kernel entries use `u8`, `u32`, and `u64`
  lane names instead of `byte`, `word`, and `doubleword`; `WordParams`
  becomes `U32Params`. Migrate direct `internals` callers to the explicit
  lane-width spellings.
- **Breaking:** Unstable residue routing queries report an actual
  `Backend` through `single_row_backend` and `delayed_matrix_backend`.
  Replace boolean selector calls with comparisons to `Backend::Scalar`;
  use `avx2_floor_op_len` for the AVX2-specific row floor.

### Removed

- **Breaking:** `Tower`, `TowerSpec`, and `TowerGeneratorSpec` are removed
  without an alias. A custom tower becomes an `ExtensionSpec<2>`: `impl
  TowerSpec for S` with `LINEAR_COEFFICIENT = A` and
  `CONSTANT_COEFFICIENT = B` becomes `impl ExtensionSpec<2> for S` with
  the same `Base` and `NAME`, `type Storage = Packed<W>` at the field's
  width (`PackedPadded<u8>` below one byte), and `const RELATION: Relation
  = Relation::from_binary_words::<Base>(&[B, A])` — constant coefficient
  first, the reverse of the old field order. The row is
  `FiniteField<2, N, Extension<S, 2>>`.
- **Breaking:** custom extension rows carry no generator:
  `TowerGeneratorSpec` has no replacement, and `HasGenerator` belongs to the
  shipped instances only. Request a shipped instance for a presentation that
  needs a checked generator.
- **Breaking:** custom extension rows have no `const` element surface:
  `from_raw`, `from_bytes`, `to_bytes`, the arithmetic methods, and the
  component conversions that custom towers exposed as `const fn` are gone.
  Use the `FieldElem` methods and operators, `FieldBuffer::decode` and
  `encode`, and `ExtensionField::from_components` and `to_components` at
  run time; `Elem::ZERO`, `Elem::ONE`, and `to_raw` stay `const`.
- The wiki migration guide written for the withdrawn v3 release candidate.

### Fixed

- `ops::add_gather_offsets` prevalidated its source spans with unchecked
  addition: on 32-bit targets, `offset + dst.len()` wraps when overflow
  checks are disabled, so a release build could admit a source row that
  reaches past `region` and panic mid-fold with the destination already
  partially mutated. The offset-plus-span check now uses checked arithmetic
  on every target, and the direct AVX2 gather prevalidates every offset —
  an invalid later offset included — before the first destination write.
  Invalid geometry still panics before any mutation; the defect was a
  partial write before the panic, never a memory-safety violation.
- Default-feature 32-bit x86 builds use the portable backend. X64 capability
  tokens and their architecture kernels compile only for `x86_64`; enabling
  `simd` or `internals` on i686 no longer imports unavailable x64 entries.

## [3.0.0] - 2026-10-07 [YANKED]

This release was published to crates.io by accident and then yanked; it was
never a supported release. It exported the `Binary<N, R>` field markers, the
recursive tower surface (`Tower<S>`, the open `TowerSpec`, and
`TowerGeneratorSpec`), and an open, downstream-implementable `Field` trait.
3.0.1 removes and seals those items and is therefore not API-compatible with
this artifact: do not build on the yanked version. Migrate locked code through
[Migrating from 2.x](https://github.com/nanithefkuc/fgf/wiki/Migrating-from-2.x),
using the 3.0.1 breaking entries for the remaining differences.

### Added

- Version-independent benchmark labels and separate absolute `benchmarks/v2/`
  and `benchmarks/v3/` records, with matched speed factors under
  `benchmarks/comparison/`. Released v2.1.0 replaces the generic-API baseline.
- Reproducible benchmark-only v2 preparation, paired host campaigns, and a
  fail-closed publisher with a raw-record cell evidence index.
- Custom RS-rooted `gf16d` one-shot, prepared, and scalar measurements, with
  no fabricated released-v2 counterpart.
- Explicit binary embeddings: `Embedding<S, T>` prepares the canonical
  inclusion of one binary field into another whose degree is a multiple of
  it, for every supported presentation — polynomial, ordered-basis, and
  tower, custom `TowerSpec` instances included. One frozen reference per
  degree (`Polynomial<3>`, `Polynomial<0x7>`, `Polynomial<0x13>`, the AES
  field, and the Rijndael towers `Binary<16, Tower<Rijndael16>>`,
  `Binary<32, Tower<Rijndael32>>`, and `Binary<64, Tower<Rijndael64>>`) fixes the
  consecutive inclusions: the identity into degree two, smallest-raw-root
  inclusions below the AES field, and low-component inclusions above it.
  `new` returns `EmbeddingError::IncompatibleDegree` when the source degree
  does not divide the target degree. `embed`, `contains`, `restrict`,
  `frobenius`, `trace`, and `norm` apply the prepared column arrays without
  allocating; equal presentation descriptions embed identically, and
  `Embedding<Binary<1, Polynomial<3>>, T>` supplies the absolute trace and norm.
- Recursive quadratic towers through degree 64. One open `TowerSpec`
  supplies `Base`, `LINEAR_COEFFICIENT`, `CONSTANT_COEFFICIENT`, and `NAME`
  for every quadratic tower at degrees 2, 4, 8, 16, 32, and 64 over a
  supported base of half the degree; `TowerGeneratorSpec` optionally pins a
  multiplicative generator checked to full order against the complete prime
  factors of `2^N - 1` at each degree. Shipped specs root at the AES field
  (`Rijndael16`, `Rijndael32`, `Rijndael64`) or nest from GF(2) through the
  Wiedemann relation (`FanPaar2` through `FanPaar64`, the degree-two and
  degree-four levels joining the ladder). Custom specs validate through the
  field description: the base first, excess coefficient bits, `A != 0`, and
  absolute `trace(B/A^2) == 1` for the relation `t^2 + A*t + B = 0`.
- Custom tower bulk operations: one dispatch implementation per degree
  serves every representation, routing pinned Rijndael and Fan-Paar
  descriptions to their vector kernels and every other tower to the typed
  scalar fallback with `backend_for` reporting `Scalar`; degree-eight
  towers join the single degree-eight dispatch. Towers at degrees 8, 16,
  32, and 64 implement `FieldBuffer`.
- Ordered-basis byte presentations: `Normal<P, E>` wraps a polynomial
  element into its conjugate basis (bit `i` selects `E^(2^i)`; one is all
  bits set) and `Cantor<P, SEED>` builds the chain `c_(N-1) = SEED`,
  `c_i = c_(i+1)^2 + c_(i+1)` (bit `i` selects `c_i`; validity asserts
  `c_0 == ONE`), at degrees 2, 4, and 8. Basis rank and inverse columns are
  checked by bounded enumeration; generators are the transported polynomial
  generators, verified to full order. Polynomial presentations extend to
  degrees two (`Polynomial<0x7>`) and four (`Polynomial<0x13>`,
  `Polynomial<0x19>`, `Polynomial<0x1F>`); degrees 1, 2, and 4 are
  scalar-only. Basis bulk operations run the typed scalar fallback;
  polynomial arithmetic never touches basis coordinates.
- Open scalar field definitions. `Field` is implementable downstream;
  `PrimeCharacteristic<P>` supplies a checked prime identity at any `u64`
  prime through deterministic Miller-Rabin, and the central validation
  block — reached from every generic construction — checks the
  characteristic identity, a positive degree, `ORDER ==
  CHARACTERISTIC^DEGREE` with overflow checking, and the binary degree cap
  of 64. Binary fields convert through `BinaryField::to_coordinates` and
  `from_coordinates`, which reject excess bits above the degree through
  `CoordinateError`.
- `just cross-check` compiles the AArch64 and Wasm library paths, including
  the explicit Wasm SIMD configuration, and `just bench-build NAME` builds a
  portable benchmark artifact without running it.
- `just bench-gf16-comp` runs an interleaved native GF(2^16) field
  comparison against GF-Complete, `reed-solomon-erasure`,
  `reed-solomon-simd`, and Leopard-RS 1.x; the harness validates basis maps
  and region outputs against independent arithmetic, includes
  duplicate-operation controls, and reports unavailable primitives
  separately. `bench-gf-comp` includes the same harness in its shuffled
  rounds.
- The benchmark record is split by field family. `BENCHMARKS.md` indexes
  the pages under `benchmarks/` and holds the hosts, shared method, number
  format, and reproduction commands; each page carries its own setup,
  self-timings, and competitor tables. Every value in a table has the same
  digit count, so paired Tiger Lake / Golden Cove cells line up down each
  column.

### Changed

- **Breaking:** fields have no public type aliases; `Binary<N, R>` is the
  only spelling. `Gf1`, `Gf16`, `Gf32`, `Gf64`, and `FanPaar2` through
  `FanPaar64` (the field aliases) are not provided: replace each with its
  canonical type — `Binary<1, Polynomial<3>>`,
  `Binary<16, Tower<Rijndael16>>`, `Binary<32, Tower<Rijndael32>>`,
  `Binary<64, Tower<Rijndael64>>`, and `Binary<N, Tower<FanPaarN>>` — or
  declare a local alias such as
  `type Gf2P16 = Binary<16, Tower<Rijndael16>>;`. The Fan-Paar spec markers
  are named like `Rijndael16`: `FanPaar2Spec` through `FanPaar64Spec` become
  `FanPaar2` through `FanPaar64`, so Fan-Paar GF(2^16) is
  `Binary<16, Tower<FanPaar16>>`.
- **Breaking:** `Binary<N, R>` replaces `Gf<N, R>` and explicitly names
  characteristic-two fields; `BinaryField` remains the capability trait.
  `Polynomial<P>` replaces `Poly<P>`, and `RS` replaces `REED_SOLOMON`
  for the unchanged `0x11D` polynomial convention. The `Gf8<R>` transition
  alias is removed: replace it with `Binary<8, R>`, including associated
  item paths such as `Binary::<8, Polynomial<AES>>::ORDER`. For example,
  `Elem<Gf8<Poly<REED_SOLOMON>>>` becomes
  `Elem<Binary<8, Polynomial<RS>>>`. No compatibility aliases remain.
- **Breaking:** `TowerSpec::LINEAR_COEFFICIENT` and
  `TowerSpec::CONSTANT_COEFFICIENT` replace `TowerSpec::A` and
  `TowerSpec::B`. Rename the associated constants in custom specs and their
  qualified uses; values remain raw coordinates in `Base` for the relation
  `t^2 + A*t + B = 0`. `TowerSpec` and `TowerGeneratorSpec` remain separate:
  arithmetic requires no selected generator. Field arithmetic, wire bytes,
  selected generators, and backend routing are unchanged.
- The canonical $GF(2)$ benchmark label is `gf1`, denoting extension degree.
  Its pages move from `gf2.md` to `gf1.md` in the v2, v3, and comparison
  directories; the released-v2 API mapping remains `Gf2`.
- **Breaking:** one element wrapper, `Elem<F>`, replaces every concrete
  element path. `mersenne31::Elem`, `goldilocks::Elem`,
  `quad_mersenne31::Elem`, the binary `Elem<N, R>` spelling, the
  `gf16`/`gf32`/`gf64` and Fan-Paar element structs, and `gf2::Elem` are
  removed, as are the module `GENERATOR` constants (`Elem::<F>::GENERATOR`
  replaces them all). `Gf2` becomes `Binary<1, Polynomial<3>>`, and
  `bits::dot_product` returns `Elem<Binary<1, Polynomial<3>>>`. Migrate
  `gf2::Elem` to `Elem<Binary<1, Polynomial<3>>>`, `mersenne31::Elem` to
  `Elem<Mersenne31>`, `gf16::Elem` to `Elem<Binary<16, Tower<Rijndael16>>>`,
  `fan_paar::fp16::Elem` to `Elem<Binary<16, Tower<FanPaar16>>>`, and the
  binary `Elem<8, Poly<POLY>>` to `Elem<Binary<8, Polynomial<POLY>>>`;
  spell elements of the wider towers and the prime families the same way.
  Inherent `const` arithmetic sits on the `Elem<F>` types; `ZERO`, `ONE`,
  `GENERATOR`, and `to_raw` are the generic items and are not redeclared
  per family. `Debug`
  for every element is the generic `NAME(raw)` format; family `Display`
  formats are unchanged. The scalar-arithmetic trait `field::Elem` is
  renamed `FieldElem` (re-exported as `fgf::FieldElem`): replace
  `use fgf::field::Elem;` trait imports with `use fgf::field::FieldElem;`.
  Wire bytes, generators, and relations are unchanged.
- **Breaking:** `Field` is split into three contracts. `Field` keeps scalar
  algebra and metadata (`Raw`, `Characteristic`, `NAME`, `DEGREE`, `ORDER`,
  `ZERO_RAW`, `ONE_RAW`, `VALID`, raw arithmetic); `FieldBuffer` carries the
  byte encoding (`BYTES`, `STORAGE_BITS`, `decode`, `encode`), implemented
  by binary degrees 8/16/32/64 and the prime families; `HasGenerator`
  carries the selected generator (`GENERATOR_RAW`). `Field::Elem`,
  `Field::BITS`, `Field::CHARACTERISTIC`, `Field::GENERATOR`, and
  `Field::elem_count` are removed. Generic code reads the characteristic as
  `<F::Characteristic as PrimeIdentity>::CHARACTERISTIC`, the storage width
  as `FieldBuffer::STORAGE_BITS`, and the generator as
  `Elem::<F>::GENERATOR` under a `HasGenerator` bound. `FieldKernels` and
  every `ops` operation — `pack`, `unpack`, and `pack_to_vec` included —
  bound on `FieldBuffer` and take `Elem<F>`. `FieldElem` is sealed and
  implemented only by the blanket `impl<F: Field> FieldElem for Elem<F>`;
  downstream custom fields implement the new `Field` contract and receive
  the trait. The trait exponent is `u128` (`pow(u128)`), the inherent
  `const pow` on every family takes `u128`, and `QuadMersenne31::pow_u128`
  is removed in its favour.
- **Breaking:** prime `from_raw` canonicalizes and `to_raw` returns
  canonical storage. In 2.x, `from_raw` and `decode` retained the
  unreduced lane and `to_raw` exposed the stored bits; every safely
  constructed element now holds canonical coordinates, so comparison,
  hashing, and packing see the reduced lane. Packed prime operations accept
  every lane bit pattern a decode accepts and emit canonical lanes on
  output — accumulate, in-place, and tails included — and zero-coefficient
  accumulation and unit-coefficient in-place scaling are defined no-ops
  that preserve their inputs byte for byte. Migrate by deleting manual
  canonicalization after construction; published canonical byte fixtures
  and mathematical values are unchanged. The inherent `canonical()` methods
  are removed; `pack`/`unpack` round-trip canonical lanes.
- **Breaking:** binary representations are descriptor-backed. `Repr<N>` and
  `ByteRepr` are removed in favour of `BinaryRepr<N>`, which carries the
  presentation `NAME`, an opaque `BinaryDescription`, an optional
  `ByteLogExp` table slot at degree eight, and the use-time `VALID` check;
  `BinaryField` adds the canonical coordinate conversions and
  `BinaryDegree<N>` pins the exact degree a generic tower row may build on.
  Migrate `Repr<8>`/`ByteRepr` bounds to `BinaryRepr<8>` and read tables
  through `LOG_EXP`; the kernel table banks
  move with the representations. The representations live in the binary
  subtree: `field::binary` (`Binary`, the coordinate surface),
  `field::binary::poly`, `field::binary::normal`, `field::binary::cantor`,
  and `field::binary::description` replace the removed `field::repr`,
  `field::poly`, and `field::gf1` modules.
- **Breaking:** `Poly<const P: u32>` becomes `Polynomial<const P: u128>`,
  and the `AES` and renamed `RS` constants widen to `u128` with the same
  values. `Polynomial` implements `BinaryRepr` at degrees 1, 2, 4, and 8;
  only `Polynomial<3>` is the degree-one form, giving GF(2) as
  `Binary<1, Polynomial<3>>`. Generic flat byte fields support any
  irreducible degree-eight polynomial — Data Matrix `0x12D` and the CCSDS
  basis `0x187` included — with x86 GFNI elementwise multiplication deriving
  an isomorphism onto the AES field for each polynomial. Migrating from the
  2.x names: `Gf8B`/`Gf8D` become `Binary<8, Polynomial<AES>>`/
  `Binary<8, Polynomial<RS>>`, `gf8b::Elem`/`gf8d::Elem` become
  `Elem<Binary<8, Polynomial<AES>>>`/`Elem<Binary<8, Polynomial<RS>>>`,
  and other conventions spell `Polynomial<0x12D>` or `Polynomial<0x187>`
  directly; import the constant (`Polynomial<AES>`) rather than qualifying
  it (`Polynomial<fgf::AES>`) in const-generic position. Values, wire
  encodings, and generators are unchanged.
- **Breaking:** the Rijndael and Fan-Paar hierarchies collapse into the
  recursive tower. `RijndaelTower` is renamed `Rijndael16` and joined by
  `Rijndael32` and `Rijndael64`: 2.x `Gf16`, `Gf32`, and `Gf64` become
  `Binary<16, Tower<Rijndael16>>`, `Binary<32, Tower<Rijndael32>>`, and
  `Binary<64, Tower<Rijndael64>>`. The `gf16`, `gf32`, and `gf64` modules,
  `quad_tower!`, the concrete Fan-Paar element and marker types, the
  `field::fan_paar` and `field::wiedemann` modules, and their
  `DELTA`/`ALPHA`/`GENERATOR` module constants are removed. Migrate the
  relation constants to `LINEAR_COEFFICIENT`/`CONSTANT_COEFFICIENT` in the
  spec; wire bytes, generators, and relations are unchanged. The
  independent Fan-Paar recurrence stays available as
  `field::binary::tower::{fp_multiply, fp_square, fp_invert,
  fp_mul_alpha}`, re-exported under `internals` as
  `internals::field::fan_paar`. `TowerSpec` is open: `LINEAR_COEFFICIENT`
  and `CONSTANT_COEFFICIENT` are `u64` base-coordinate words, the base
  bound becomes `BinaryField` at half the degree, and the generator moves
  to the optional `TowerGeneratorSpec`. The unstable
  `internals::kernel::fan_paar` dispatch
  module merges into `internals::kernel::tower::{gf16, gf32, gf64}`, whose
  `Prepared` enums carry raw words and tiles; `x86::fan_paar` keeps its
  token-bearing entries unchanged. Path moves: `field::tower` →
  `field::binary::tower`; `field::wiedemann` → the recurrence functions in
  `field::binary::tower`; the `kernel::fan_paar` dispatch merges into
  `kernel::tower`.
- Bulk routing follows the field description. Ordered-basis byte fields,
  degree-eight towers, custom tower specs, and towers over basis bases
  route every bulk operation and tail through the typed scalar fallback,
  with `backend_for` reporting `Scalar`; the GFNI degree-sixteen route that
  served non-AES polynomial bases through the AVX2 tables kernels is
  removed in favour of that fallback. `backend_for::<F>()` states the tier
  actually serving `F`'s bulk operations and
  `has_vector_elementwise::<F>()` reports elementwise eligibility
  separately. XOR-shaped operations — row addition, broadcast scalars, and
  `add`/`sub_assign` — stay field-independent and serve basis coordinates
  on the vector units.
- **Breaking (unstable surface only):** the direct kernels under
  `fgf::internals::kernel` are laid out by field family, then instruction
  set, with one entry per operation and ISA, representation-erased. The
  GFNI and AVX-512 fixed-coefficient entries take `Prepared` —
  unparameterized, built with `Prepared::new` — instead of a typed element
  or `Prepared<POLY>`; the GFNI byte bodies select their multiply through a
  `const NATIVE: bool` parameter (`true` for the AES encoding, `false` for
  the affine map). `scale_table_8d` and `affine_8b`/`affine_8d` become
  `scale_table` and `affine_map`. Elementwise ISA entries take the
  isomorphism onto the AES field and the reduction byte as explicit
  arguments (`isomorphism_to_aes::<POLY>()` and the polynomial's low byte)
  instead of a polynomial const parameter; the GF(2^16) elementwise entries
  take the relation's constant byte and the base reduction byte. The
  multi-row x86, AArch64, and Wasm entries take prepared coefficients — a
  `Matrix<Prepared>` provider such as `FlatMatrix<Prepared>` or a slice of
  `(&[Prepared], &[u8])` pairs — instead of element slices; the
  element-taking matrix wrappers (`mul_add_matrix_gfni`,
  `mul_into_matrix_avx512`, and kin) are removed in favour of the `_with`
  provider entries. The GF(2^16) kernel surface carries raw-erased
  payloads: `TowerCoeff` the raw coefficient word and the two broadcast
  words, `TowerTables` the raw word and four nibble tables, both built with
  `new` (representation inferred from the element);
  `internals::kernel::gf16::Prepared` carries those raw forms, with
  `Plain(u16)` for the fallback. The scatter, gather, and matrix entries
  take coefficients through provider forms — an element slice (resolved on
  access), a materialized table slice, or a matrix provider such as
  `FlatMatrix` or `gf16::FlatResolved`. Supported public API paths are
  unchanged.

## [2.1.0] - 2026-10-02

### Added

- Self-contained, runnable examples cover scalar arithmetic across every field,
  parity-shard encoding and erasure recovery, streaming GF(2^16) parity updates,
  bit-packed Hamming syndromes, extension-field embeddings and trace/norm, and
  canonical prime-field evaluation-vector arithmetic.
- `just example NAME [ARGS]` and `just examples [ARGS]` run one tutorial or the
  complete educational set, including portable builds with `alloc`.
- `QuadMersenne31` dispatches `V4x` under `simd512`: `mul_into`, `mul_add`,
  and `mul_elementwise` run AVX-512 kernels, and `backend_for::<QuadMersenne31>()`
  reports `v4x` on those hosts. The remaining operations run the AVX2 kernels.

### Changed

- `BENCHMARKS.md` presents a complete Tiger Lake / Golden Cove snapshot of
  public API and interleaved competitor timings. Measurement history and
  dispatch-threshold experiments are excluded from the public tables.
- Prime-field vector multiplication is faster. `Mersenne31` AVX2 and AVX-512
  multiplies skip re-folding canonical operands, and the AVX-512 product uses a
  masked recombination; `QuadMersenne31` AVX2 complex products defer reduction
  until four partial products are summed. `Goldilocks` AVX2 addition and
  subtraction use fewer compare steps. Outputs remain canonical, and the
  raw-destination guarantees of `add_assign_scalar` and `sub_assign_scalar`
  are unchanged. See `BENCHMARKS.md`.
- `bench-m31-comp` runs only the five-round public snapshot campaign;
  the `prime_ntt` threshold investigation remains a separate benchmark.
- The AVX-512 GF(2^8) matrix kernels (`Gf8D` and `Gf8B`, contiguous,
  scattered-row, provider, and prepared forms) use direct coefficient reads
  for short rows and resolve each row group's coefficients into map words
  once per chunk of terms for longer rows, folding terms in pairs. Results
  are unchanged.
- The GFNI and AVX-512 byte-field matrix kernels, contiguous and
  scattered-row, process long rows in column blocks when the destination
  spans more than one row group, so each source block is reused from cache
  across every row group instead of being re-read from memory per group.
  Results are unchanged.
- The GFNI byte-field matrix kernels prefetch each source ahead of the tile
  loop on long rows whose terms fit in one resolve chunk, and run the column
  blocks above only for larger term counts. Results are unchanged.
- The GFNI byte-field matrix kernels (`Gf8D` and `Gf8B`, contiguous,
  scattered-row, provider, and prepared forms) split the destination rows
  into the fewest width-balanced row groups of up to six rows, or four with
  one or two terms, instead of fixed groups of four, two, and one, so the
  sources are streamed once per group and fewer groups run. Overwrite seeds
  multi-row groups from the first product rather than from zero. Results are
  unchanged. `BENCHMARKS.md` is refreshed on both hosts for this change.
- The GFNI byte-field matrix kernels run a three-row group with three
  32-byte lanes per row, folding terms two at a time, when its terms number
  from six through one resolve chunk and its rows are cache-resident; other
  three-row groups keep two lanes. This covers three-destination calls and
  the trailing group of seven-destination calls, contiguous and
  scattered-row. Results are unchanged.

### Fixed

- The Plonky3 competitor harness aligns packed quadratic-extension fixtures
  for native AVX-512 builds and reports the actual packing widths.
- CI coverage uses `just cover`, including the portable feature run, and
  exports LCOV through `just cover-lcov` with the same exclusion set. The
  AVX-512-only `QuadMersenne31` kernel is excluded on incapable coverage
  runners; the AVX2 kernel and mixed-ISA dispatch remain included.
- NEON and Wasm row-geometry checks compile only on their consumer targets,
  rather than appearing as unreachable x86 coverage obligations.
- The GFNI and AVX-512 GF(2^8) matrix kernels over a generic `Matrix`
  provider check the length of each source slice they stage, so a provider
  that returns a shorter slice than the one the entry validated panics
  instead of reading out of bounds.

## [2.0.0] - 2026-09-30

### Added

- 64-byte AVX-512 kernels for both byte fields (`Gf8B`, `Gf8D`) behind the
  `simd512` feature: single-buffer `mul_add`/`mul_assign`/`mul_into`,
  blocked scatter/gather, blocked matrix (`mul_add`, `mul_into`, prepared
  `CoeffMatrix` forms, and `_at` scattered rows), and elementwise products
  (`mul_elementwise`, `mul_elementwise_assign`). `Gf8D` elementwise products
  conjugate `GF2P8MULB` by the field isomorphism onto `0x11B`; results are
  unchanged. With `simd512` on an AVX-512+GFNI host the new `V4x` tier
  resolves, both byte fields dispatch every operation there, and
  `has_vector_elementwise` reports `true` for both. Without `simd512` the
  tier never resolves and dispatch is unchanged.
- `Gf16` dispatches AVX-512 GFNI single-row multiplication, elementwise
  products, and register-blocked matrix accumulation (including prepared
  matrices) on `V4x`. Scatter and gather retain their existing GFNI kernels;
  non-`V4x` backends are unchanged. The new kernels use safe Archmage
  reference-based loads and stores.
- On `V4x`, misaligned scatter, gather, and matrix rows peel their heads to
  a 64-byte boundary above per-shape length floors set by measurement.
  `Gf16` `mul_add`
  destinations peel the same way above their own measured floor, and rows
  shorter than one 64-byte lane dispatch straight to the 32-byte GFNI
  kernel.
- On `V4x`, byte XOR — `add_assign` and `sub_assign` for every binary field,
  `bits::xor_assign`, and the other bit-packed XOR paths — runs 64-byte
  AVX-512 lanes from a measured length floor, with a 64-byte destination
  alignment peel on longer buffers. Shorter buffers keep the AVX2 kernel but
  pass one extra length test.
- On `V4x`, `bits::weight` counts whole words with `VPOPCNTQ`.
- `Mersenne31` and `Goldilocks` gain 64-byte AVX-512 kernels for every
  operation that had an AVX2 kernel, dispatched on `V4x`. Results are
  unchanged; `backend_for` reports `V4x` for both fields on that tier.

### Changed

- The prime-field competitor harness builds `fgf` with `simd512`, so a
  Tiger Lake run measures the `V4x` tier it resolves; the family recipes
  pass the feature. Tiger Lake prime columns recorded before this change
  measured `fgf` at `v3_gfni_crypto`.
- The `Gf16` AVX-512 matrix kernels broadcast each coefficient pair once
  per term block instead of once per destination lane.

- The binary-field competitor harnesses are one three-arm binary,
  `external/bench-trio/`, replacing `bench-isal` and `bench-klauspost`: it
  interleaves `fgf`, Intel ISA-L, and klauspost/reedsolomon over one fixture
  set, rotating through all six arm orders, and `just bench-gf-comp` runs it
  beside the self suite. `BENCHMARKS.md` carries the single-matrix record.
- **Breaking (unstable surface only):** the direct kernels under
  `fgf::internals::kernel::{x86,aarch64,wasm32}` are laid out by field
  family, then instruction set. Every entry is named operation, instruction
  set, field marker, then `_with`, and its instruction-set suffix matches
  its file. Supported public API paths are unchanged. Migration:
  - `x86::prime::*_gld_{sse42,avx2,avx512}` → `x86::goldilocks::*_{sse42,avx2,avx512}`,
    `*_m31_*` → `x86::mersenne31::*`, and `*_qm31_avx2` →
    `x86::quad_mersenne31::*_avx2` (for example `mul_add_gld_avx2` →
    `goldilocks::mul_add_avx2`).
  - `x86::bytes512::{xor512, count_ones512}` → `x86::bytes::{xor_avx512,
    count_ones_avx512}`.
  - `x86::fan_paar::{mul_add,mul_assign,mul_into}_{avx2,ssse3}` (GF(2^16)) →
    `{mul_add,mul_assign,mul_into}_{avx2,ssse3}_fp16`, and
    `*_{fp32,fp64}_avx2` → `*_avx2_{fp32,fp64}`.
  - `x86::gf16::{mul_add512, mul_assign512, mul_into512, mul_elementwise512,
    mul_elementwise_assign512, mul_add_matrix512, mul_add_matrix512_with}` →
    `mul_add_avx512`, `mul_assign_avx512`, `mul_into_avx512`,
    `mul_elementwise_avx512`, `mul_elementwise_assign_avx512`,
    `mul_add_matrix_avx512`, and `mul_add_matrix_avx512_with`.
  - `x86::gf8` 64-byte entries replace `affine512`/`gfni512` with `avx512`:
    `mul_{add,assign,into}_affine512`, `mul_add_{scatter,gather}_affine512`,
    `mul_{add,into}_matrix_affine512{,_with}`, `mul_add_matrix_at_affine512`,
    and `mul_elementwise{,_assign}_gfni512` become
    `mul_{add,assign,into}_avx512`, `mul_add_{scatter,gather}_avx512`,
    `mul_{add,into}_matrix_avx512{,_with}`, `mul_add_matrix_at_avx512`, and
    `mul_elementwise{,_assign}_avx512`.
  - `x86::gf8` `Gf8D` entries mark the field with `_8d` instead of naming the
    multiply strategy: `affine` becomes `gfni_8d` (`mul_add_affine` →
    `mul_add_gfni_8d`, `mul_add_matrix_at_affine` →
    `mul_add_matrix_at_gfni_8d`, `mul_add_matrix_affine_with` →
    `mul_add_matrix_gfni_8d_with`, and likewise for the `mul_assign`,
    `mul_into`, scatter, gather, and matrix entries);
    `mul_elementwise{,_assign}_iso_8d` → `mul_elementwise{,_assign}_gfni_8d`;
    `mul_elementwise{,_assign}_iso512_8d` →
    `mul_elementwise{,_assign}_avx512_8d`; and
    `mul_{add,into}_matrix_affine512_prepared_with` →
    `mul_{add,into}_matrix_avx512_8d_with`.
  - `x86::gf8::multiply_vectors_sse` → `x86::gf8::multiply_vectors_ssse3`.
  - `aarch64::{gf8,gf16}` and `wasm32::{gf8,gf16}` name the operation in
    full: `{scatter,gather,matrix,elementwise}_{neon,simd128}` →
    `mul_add_scatter_*`, `mul_add_gather_*`, `mul_add_matrix_*`, and
    `mul_elementwise_*`; `aarch64::gf8::elementwise_pmull` →
    `mul_elementwise_neon_aes`, named for the tier its token proves.
  - Every other entry keeps its path.

**Breaking:** `simdispatch` moves `=0.1.0` to `=0.2.0`. `fgf::Backend` is the
upstream type re-exported, so crates pinning both must move together; pin
`simdispatch = "=0.2.0"` alongside this `fgf` (no API names change; the
ladder gains `V4x`/`V4`).

### Removed

- The deferred `internals` entry `x86::avx512::proven::xor` and its
  unexported body. The dispatched `x86::bytes::xor_avx512` replaces it and
  carries no unsafe code.
- **Breaking (unstable surface only):** the rest of the deferred
  `internals` module `x86::avx512` (`gf8_*` and `gf16_*` single-buffer,
  scatter, gather, matrix, and elementwise entries, and `proven::*`). The
  dispatched `x86::gf8` and `x86::gf16` AVX-512 entries cover every shape
  it held.
- **Breaking (unstable surface only):** the `internals` measurement variants
  whose decisions are recorded: `x86::gf8::{mul_add_gather_affine_8b,
  prepare_affine_8b, Affine8BFactor, mul_add_gather_gfni_axpy_tail,
  mul_add_gather_gfni_tile, mul_add_gather_gfni_split,
  mul_into_matrix_chunk_8d, mul_into_matrix_external_grouped_8d,
  mul_into_matrix1_external_8d, mul_into_matrix1_fullinit_8d,
  mul_into_matrix1_chunk_8d, mul_into_matrix1_external_chunk_8d,
  mul_into_matrix1_tile_8d, mul_into_matrix2_8b, mul_into_matrix2_8d,
  mul_into_matrix2_shuffle_packed, mul_into_matrix6_shuffle_8b,
  mul_into_matrix6_shuffle_8d, mul_into_matrix6_shuffle_packed_8b,
  mul_into_matrix6_shuffle_packed_8d, resolve_probe_8d}`, the AVX2
  prepared-affine matrix `x86::gf8::{mul_add,mul_into}_matrix_affine_prepared_with`,
  the AVX2 blocked `Gf16` gather and matrix
  `x86::gf16::{mul_add_gather_avx2, mul_add_matrix_avx2,
  mul_add_matrix_avx2_with}` (dispatch serves both shapes with repeated
  AXPY), and the row-interleaved XOR `x86::bytes::{xor_rows_avx2,
  xor_rows_sse2}`. The `affine` and `dot_product` benchmarks and the
  `perf_p2` example that drove them are removed with them.
- **Breaking (unstable surface only):** `x86::gf8::{mul_add_matrix_at_gfni_with,
  mul_add_matrix_at_affine_with}`. No operation hands a prepared provider to
  scattered rows; a prepared scattered-row operation over `CoeffMatrix` was
  measured against the raw-term `mul_add_matrix_at` and not adopted.
  The raw-coefficient `_at` entries remain.

### Fixed

- `ops::mul_add_matrix_at` returned wrong results for `Mersenne31`,
  `Goldilocks`, and `QuadMersenne31` whenever a coefficient was one: the
  per-row fallback took the binary fields' XOR shortcut instead of modular
  addition. Every field without a blocked scattered kernel (`Gf16`, `Gf32`,
  `Gf64`, the Fan–Paar fields, the prime fields, and both byte fields on
  shuffle, `AArch64`, and Wasm backends) now runs one dispatched `mul_add`
  per row, which fixes the prime-field results and replaces the element-wise
  scalar loop with the field's vector kernel.
- The 32-byte GFNI and AVX2 kernels peel half-lane destinations: byte XOR,
  both byte fields' `mul_add`/`mul_assign`/`mul_into`, and the `Gf16`
  single-row kernels pay one 16-byte head when the destination sits 16
  bytes into a 32-byte lane, so their stores stop splitting cache lines
  above the shared measured floor. The AVX-512 `Gf16` `mul_into` gains the
  destination peel its `mul_add` already had.
- Prepared overwrite gathers route through the register-blocked one-row
  overwrite matrix where the change covers them: `Gf8D`'s prepared form
  fell back to the per-source AXPY loop on `V3GfniCrypto`, and `Gf8B`'s
  overwrite gather filled the destination and accumulated on `V4x` instead
  of overwriting in one pass.
- Half-lane-skewed GFNI in-place scaling and short blocked scatter avoid
  repeated split-line destination accesses. The AVX-512 scatter path avoids
  peeling when a quarter-line row pitch cannot align its row group.
- The AVX-512 peel-floor benchmark now compares the same offsets with and
  without peeling. The matched comparison supports the existing gather and
  matrix floors.

## [1.2.2] - 2026-09-30

### Changed

- `ops::add_assign_scalar` and `ops::sub_assign_scalar` dispatch the
  field-independent vector broadcast-XOR kernels for every binary field;
  `Gf32`, `Gf64`, and the Fan–Paar fields previously ran the portable
  byte-at-a-time loop. Results are unchanged.
- **Breaking (unstable surface only):** `internals`
  `x86::bytes::xor_broadcast_sse42` → `x86::bytes::xor_broadcast_sse2`,
  which takes an `X64V1Token`: the kernel uses only SSE2.

### Fixed

- `ops::mul_add_matrix_at` returned wrong results for `Mersenne31`,
  `Goldilocks`, and `QuadMersenne31` whenever a coefficient was one: the
  per-row fallback took the binary fields' XOR shortcut instead of modular
  addition. Every field without a blocked scattered kernel (`Gf16`, `Gf32`,
  `Gf64`, the Fan–Paar fields, the prime fields, and both byte fields on
  shuffle, `AArch64`, and Wasm backends) now runs one dispatched `mul_add`
  per row, which fixes the prime-field results and replaces the element-wise
  scalar loop with the field's vector kernel.

## [1.2.1] - 2026-09-24

### Fixed

- `ops::add_assign_scalar` and `ops::sub_assign_scalar` for the binary fields
  (`Gf8B`, `Gf8D`, `Gf16`) fell to the byte-at-a-time portable broadcast loop
  on every backend, while the prime fields dispatched to vector kernels.
  They now dispatch to field-independent vector broadcast-XOR kernels —
  AVX2/SSE4.2 on x86, NEON on AArch64, `simd128` on wasm32 — at the same
  tiers the fields' other byte kernels run; scalar fallbacks are unchanged.
  Measured throughput appears in `BENCHMARKS.md`, "Packed operations".

## [1.2.0] - 2026-09-24

### Added

- `ops::mul_elementwise_assign`: `dst[i] *= src[i]`, the in-place two-slice
  form of `mul_elementwise`. Existing three-slice callers that wrote the
  product over their first operand had to stage a second buffer; this
  spelling updates the accumulator in place.
- `ops::add_assign_scalar` and `ops::sub_assign_scalar`: add or subtract one
  field element broadcast across every lane. Consumers composing
  lane-parallel Horner steps no longer fill a broadcast buffer and re-read it
  through `add_assign`.
- Field-family benchmark recipes `bench-gf`, `bench-gdl`, `bench-m31`, and
  `bench-gf2`, each with a `-comp` variant adding every competitor harness
  wired for the family, and `--gf`, `--gdl`, `--m31`, and `--gf2` panel
  selection flags on the `kernels` bench target. A recorded campaign for one
  field family no longer re-runs every panel.
- Prime-field competitor benchmarking against Plonky3:
  `external/prime-bench/` compares `fgf` against `p3-goldilocks`,
  `p3-mersenne-31`, and its packed quadratic extension over the recorded
  Mersenne31, Goldilocks, and QuadMersenne31 shapes on each library's
  native layout, and `bench-gdl-comp` / `bench-m31-comp` interleave it with
  the self suite. `BENCHMARKS.md` carries the prime-field competitor
  matrix.

### Changed

- The direct `aarch64` and wasm32 `simd128` kernel entries under the
  `internals` feature are now safe
  [`archmage`]https://docs.rs/archmage capability-token functions: each
  takes the exact token its instructions require (`NeonToken`,
  `NeonAesToken`, or `Wasm128Token`) as its first argument and validates its
  own geometry, replacing the `proven` token-proven compatibility facades,
  which are gone. The wasm32 subtree holds no unsafe code, and the
  module-level `#![allow(unsafe_code)]` over it is removed. `internals`
  carries no compatibility promise; entries may change or disappear in any
  release.
- The competitor harnesses now live in `external/`, one unpublished package
  per harness with its own build script (`bench-isal`, `bench-klauspost`,
  `prime-bench`). The `bench-isal` and `bench-klauspost` recipes
  are gone: `just bench-gf-comp` builds and runs both wired harnesses at the
  record's repetition.
- The `bench-<field>-comp` recipes run five complete rounds interleaving the
  family's self suite with every wired competitor harness, with the unit
  order shuffled within each round and printed into the run log, instead of
  one self pass followed by sequential harness runs.
- Benchmark timing carries `f64` nanosecond samples end to end instead of
  `Duration` values divided by the repetition count, which quantized every
  per-iteration estimate to whole nanoseconds; small-shape estimates and
  their displayed times keep sub-nanosecond precision.
- Campaigns now keep full-statistics records — run-level observations and
  spread statistics for every category — in the git-ignored
  `bench-records/` directory alongside the published `BENCHMARKS.md`
  medians.
- The Miri suite is split by what it can execute. The scalar run keeps one
  representative per memory-access shape — contract-sensitive coefficients,
  boundary lengths, and the empty, single, and group-plus-one row counts —
  with the full sweeps continuing in ordinary tests; the Goldilocks leg runs
  in ordinary tests only. A new `unsafe-check-gfni` recipe interprets the
  dispatched GFNI kernels under Miri with the tier promoted at compile time,
  covering the offset-addressed-row residue the scalar run cannot reach.
  Non-temporal stores stay proof-review-only. The residue ledger in
  `AGENTS.md` now enumerates the `x86` unsafe items alongside the `aarch64`
  ones.

### Removed

- The Plonky2 arm of the prime-field comparison: `plonky2_field` carries no
  packed field kernels worth timing, so `external/prime-bench/` compares
  against Plonky3 alone.
- The `reed-solomon-erasure` comparison from the benchmark suite: its
  development dependency, its arms in `benches/compare.rs`, and its column in
  `BENCHMARKS.md`. The ISA-L and `klauspost/reedsolomon` harnesses cover the
  same shapes with stronger aggregation; `compare` now reports `fgf`
  self-numbers over the competitor fixture family only.
- The `external-bench` microbenchmark lab: the gf-complete, FLINT, NTL, and
  Leopard comparators, their build artifacts, and the `bench-prime-ntt`
  recipe with its `external-bench/prime-ntt/` NTT consumer package. The
  retired comparators do not reach the tier of the surviving harnesses, and
  the NTT consumer measured `butterfly-fft` rather than `fgf`.
- The `isal-fgf` ISA-L attribution harness. The wired `bench-isal` harness
  covers the comparison role with its paired protocol and validation gate,
  and the internal attribution questions its panels priced are concluded.

## [1.1.1] - 2026-09-18

### Changed

- **Breaking (unstable surface only):** the `internals` feature is now a
  re-export-only facade at `fgf::internals`; implementation modules live in
  their private homes and are grouped under
  `fgf::internals::kernel::{gf8, gf16, gf32, gf64, tower, fan_paar, prime,
  scalar, tables}` (plus architecture modules under `simd` on their targets),
  with `fgf::internals::kernel::{xor, Matrix, FlatMatrix}`, the capability
  tokens, and `fgf::internals::field::wiedemann`. The old unstable
  `fgf::kernel::{…}` and `fgf::field::wiedemann` paths no longer resolve
  without the feature, and the feature no longer activates `archmage` (it is
  visibility-only: `internals = []`). Migration: `fgf::kernel::x86::gf8::…`
  becomes `fgf::internals::kernel::x86::gf8::…` (same shape for every other
  kernel module), and `fgf::field::wiedemann` becomes
  `fgf::internals::field::wiedemann`. Bench, example, and test targets that
  reach the facade now declare it in `required-features`; `tests/zero_alloc.rs`
  and `tests/goldilocks_wrap.rs` keep their public tests ungated, with the
  direct-kernel cases moved to `zero_alloc_internals` and
  `goldilocks_wrap_internals` targets. Supported public API paths are
  unchanged.

## [1.1.0] - 2026-09-16

### Fixed

- The AVX2 Goldilocks kernels computed wrong values whenever a product's low
  64 bits fell below its high 32: the split-fold reduction corrected a
  wrapped subtraction by adding where it must subtract, so every such lane
  shipped a canonical-looking result off by exactly `2^32 − 1`. Random
  operands hit the branch with probability ≈ 2⁻³³ per lane, but operands
  concentrated on powers of two — twiddle tables in transform consumers —
  hit it at ≈ 2⁻⁷, which is how the defect reached NTT workloads while
  every random-value differential passed. The SSE4.2 kernels were correct.
  A wrap-boundary regression against an independent `u128 % p` oracle now
  pins both widths; the operand family is `tests/goldilocks_wrap.rs`.

### Added

- AVX2 kernels for `QuadMersenne31` on `V3`/`V3GfniCrypto` hosts, four
  complex elements per vector over the Mersenne31 lane discipline, with a
  complex-by-coefficient multiply costing two limb multiplies. Production
  dispatch takes over at one full vector for the multiplies and at two
  vectors for add and sub.
- `vector_elementwise_min_bytes::<F>()`: the row length at which `F`'s
  elementwise multiplication dispatches to a vector kernel on this host
  (zero when no threshold applies), so schedule-selecting consumers route
  sub-vector rows to a scalar schedule instead of paying vector entry
  overhead over the scalar body.

- `bench-klauspost/`, an in-process competitor comparison against
  `klauspost/reedsolomon` v1.14.2 over GF(2^8)/`0x11D`, run with
  `FEC_GOLDEN_CORE=<cpu> just bench-klauspost`. The package links the Go
  library as a C archive, interleaves it with the matched `fgf` operations
  over one fixture set, and validates every arm byte-for-byte before any
  timing. It replaces `catid/leopard` as the named codec-level competitor.

### Changed

- The x86 fused overwrite kernels prefetch their destination. `mul_into`
  writes lines it never reads, so each ordinary store waited on the
  read-for-ownership fetch of the line it was about to replace whole.
  Naming those lines ahead of the cursor lifts single-source GF(2^8) and
  GF(2^16) overwrite throughput by about a third from 24 KiB up, and brings
  GF(2^8) to parity with Intel ISA-L's streaming `gf_vect_mul` without
  evicting the destination. Results are unchanged; buffers below 24 KiB and
  the non-temporal bodies above `NT_STORE_MIN` are untouched.

## [1.0.0] - 2026-09-14

First stable release. The public API — fields, `ops`, `bits`, backend
reporting, and every stable byte encoding — is now under semantic versioning.
The `internals` feature stays explicitly unstable.

**Breaking: the MSRV is Rust 1.93.** The x86 kernels call safe intrinsics
inside their capability-token entries, which earlier compilers still require
an `unsafe` block for; 1.92 and below cannot build the crate.

### Added

- Inherent `neg` and `core::ops::Neg` on the binary-field elements — both
  flat GF(2^8) fields, the three tower levels, and the four Fan-Paar levels.
  In characteristic two negation is the identity, so `-x == x`; generic code
  over `F::Elem` can now reach for `-x` in every field the crate exposes.

- `field::gf8`: an umbrella module holding both flat GF(2^8) fields, since
  they are one construction under two reduction polynomials. `gf8b` and
  `gf8d` are now submodules of it, re-exported from `field` so every
  existing path — `fgf::gf8b::Elem`, `fgf::field::gf8d::Gf8D` — resolves
  unchanged.
- `field::tower` and `kernel::tower`: umbrella modules for the
  Rijndael-rooted quadratic tower. `gf16`, `gf32`, and `gf64` are now
  submodules of them, re-exported from `field` and `kernel` so every
  existing path resolves unchanged. The three levels share one macro-emitted
  scalar implementation, and the two GFNI-only kernel levels share one
  macro-emitted dispatch.

- The `alloc` feature: the prepared coefficient collections (`CoeffVec`,
  `CoeffMatrix`) and `pack_to_vec` own dynamically sized storage and now
  need only a heap, not an operating system; `std` implies `alloc`, so
  default-feature consumers are unaffected.
- `ops::CoeffMatrix::source` composes with `mul_add_scatter_with`: the
  borrowed `CoeffVecRef` view of one source's coefficients is exactly what
  the vector-shaped prepared operations consume, so a matrix column drives
  the scatter kernel with no copying or restaging.

- `Field::CHARACTERISTIC`: the field's characteristic as a first-class
  constant — `2` for every binary tower, the base prime `2^31 − 1` for
  `Mersenne31` and the quadratic extension `QuadMersenne31` (whose `ORDER`
  is `p²`), and the Goldilocks prime for `Goldilocks`. A required associated
  constant, so external `Field` implementers must add it; it cannot be
  derived from `ORDER`, which is the defect it exists to prevent.
- `ops::add_assign_rows`: pairwise row addition over two equal flat
  row buffers, `dst_row[j] += src_row[j]`. Semantically identical to
  `add_assign` (and currently implemented through it on every backend);
  the checked row geometry documents caller intent so backends may
  interleave independent row streams where a platform measures a win.
  The defaulted internal row-addition method carries the semantic
  contract; experimental four-stream interleaved XOR kernels for x86
  AVX2/SSE2 and an AArch64 NEON sketch are exposed behind `internals`,
  differentially tested, and deliberately not wired — they measured at
  parity or behind the flat XOR from L1 to DRAM on the reference host,
  and the additive-FFT derivative gap they targeted turned out to be a
  first-touch page-fault effect of out-of-place output buffers, not
  memory-level parallelism.
- `bits::XorRange` and `bits::xor_range_with`: the prepared form of
  `bits::xor_range`. The bit range's byte window and end masks are derived
  once and applied to many buffer pairs, the same prepare/apply split `ops`
  uses for coefficients; buffers of different lengths are accepted as long
  as each covers the planned window. `xor_range` itself now routes through
  the shared byte-window core, which replaces the word-assembly path for
  sub-word ranges (one or two masked byte operations) and the masked end
  words of longer ranges.
- `internals` entries for the crossed resolution x chunk panel:
  `matrix_overwrite1_fullinit_8d` (the production one-row path with the
  original full-array scratch policy), `matrix_overwrite1_chunk_8d` and
  `matrix_overwrite_chunk_8d` (resolved path at chunk 32/64/96, one row and
  grouped), `matrix_overwrite1_external_chunk_8d` and
  `matrix_overwrite_external_grouped_8d` (the production body over externally
  resolved maps, chunked or single-pass). Benchmark evidence only; production
  dispatch is unchanged.
- `internals` entries for the plan-hoisting experiment:
  `matrix_affine_prepared_with` and `matrix_overwrite_affine_prepared_with`
  run the production row-group bodies over `Prepared8D` coefficients (the
  affine word read from the prepared form). Measured and rejected for the
  plan entries — parity-to-worse against the term-list baseline on both
  hosts, closing none of the gap to the resolution-free body — so production
  still resolves from scalar values.
- CI now builds the docs with `-D warnings` in every feature configuration
  the test jobs exercise (default, `--no-default-features`,
  `--no-default-features --features std`, `--all-features`), compiles the
  `simd` feature for `wasm32-unknown-unknown` with `+simd128` enabled
  explicitly, and the MSRV job runs
  `cargo check --locked --all-features --all-targets` on Rust 1.89.

### Changed

- `README.md` leads with the crate's capabilities, installation, and operation
  tables; `BENCHMARKS.md` is a current two-host measurement record for the
  public operation shapes, replacing the accumulated experiment log.

- The Miri gate targets the scalar operation suite instead of interpreting
  every exhaustive algebra test and rustdoc example. The focused run keeps
  the checked geometry and portable-kernel coverage while removing work that
  the native validation stages already perform.

- **The `kernel::x86` subtree is restructured.** `x86/mod.rs` becomes
  `x86.rs` beside its directory, the field-independent XOR kernels move to
  `x86/bytes.rs`, and the three oversized kernel files are split along a
  named axis — multiply strategy first (`gfni`, `nibble`), then fan shape
  (`scatter`, `gather`, `matrix`) — leaving every implementation module at
  or under roughly 500 lines. GF(2^8)'s internals-only benchmark variants
  are gathered under `x86/gf8/experiments/`. No kernel body changed.

- **Kernel names converge on the `ops` grammar.** The domain operation now
  leads every x86 kernel name, and the shape qualifier follows it:
  `matrix_overwrite*` is `mul_into_matrix*`, `matrix_scattered*` is
  `mul_add_matrix_at*`, and `scatter_*`/`gather_*`/`matrix_*`/`elementwise_*`
  gain their `mul_add_`/`mul_elementwise_` prefixes. The AVX-512 family
  follows suit (`gf8_scatter` is `gf8_mul_add_scatter`). Renamed throughout
  the direct `kernel::x86` modules, so an `internals` consumer updates its
  paths.

- **The SSE Mersenne31 and Goldilocks kernels are named for the feature they
  require.** Every `*_sse41` entry point is `*_sse42`: the bodies enable
  `sse4.2` and depend on `pcmpgtq`, so the old name named the wrong ISA.

- **The x86 unsafe surface is countable.** The module-level
  `#![allow(unsafe_code)]` at the root of the subtree, which silently
  covered every child module, is replaced by a per-item
  `#[allow(unsafe_code)]` on each unsafe block and each `unsafe fn`.

- `kernel::x86::bytes::xor_avx2` and `xor_sse2` are direct kernel entries
  rather than wrappers. Both are safe `#[arcane]` functions that take a
  capability token and assert their own geometry. `xor_sse2` consequently
  takes the `X64V1Token` its instructions need instead of an `X64V2Token` it
  immediately narrowed.

- `gf2::Elem` stores a canonical byte as a type invariant: `to_raw` is now
  documented to return exactly `0` or `1` rather than "the byte as stored".
  Equality, hashing, and ordering are derived rather than masking on every
  comparison, and `canonical` is the identity. No reachable value changes;
  no constructor ever produced a wider pattern.

- The x86 byte-XOR kernels now use Archmage capability tokens and safe
  reference-based SIMD memory operations. Backend resolution caches the
  matching token with the selected tier, and differential coverage now spans
  unaligned inputs plus every AVX2/SSE2 lane and unroll boundary.

- All dispatched x86 kernel families now expose direct Archmage
  capability-token entries with release-mode geometry validation. Backend
  resolution caches the exact selected token once; steady-state calls pass
  that proof directly. The deferred AVX-512 compatibility entries are nested
  in `kernel::x86::avx512`, beside the implementation they validate.

- **Breaking: `ops::Plan` is split into `CoeffVec` and `CoeffMatrix`.** The
  one type served two incompatible interpretations — a flat coefficient
  vector and a source-major matrix — and reported dimensions
  `(1, coeffs.len())` for vector plans, so a gather plan's `source_count()`
  was really its output count. `CoeffVec::new(&[F::Elem])` is the flat
  vector, with no axes and no `source`/`get_at` accessors;
  `CoeffMatrix::from_source_major(sources, outputs, &[F::Elem])` is the
  source-major matrix. The prepared `mul_*_with` operations take
  `CoeffVecRef` by value at the vector shapes and `&CoeffMatrix` at the
  matrix shapes.
- **Breaking: the overwrite ops follow the ecosystem naming grammar.**
  `dot_product` → `mul_into_gather`, `dot_product_with` →
  `mul_into_gather_with`, `dot_product_matrix` → `mul_into_matrix`, and
  `dot_product_matrix_with` → `mul_into_matrix_with`: `mul_into_*` is the
  overwrite counterpart of `mul_add_*`. `mul_add_matrix_scattered` →
  `mul_add_matrix_at`, where `_at` marks the destination rows as addressed
  by explicit offsets. The sealed kernel-trait methods rename the same way.
- **Breaking: destination-first argument order for the row ops.**
  `ops::add_gather` is `ops::add_gather_offsets` (the name it already
  shared with the kernel-trait method) and now takes
  `(dst, region, offsets)` — `_offsets` marks the *sources* as
  offset-addressed into one backing region — and `ops::add_assign_rows`
  now takes `(dst, row_len, src)`, geometry second.
- **Breaking: `bits` surface renames and one reorder.** `bits::xor` is
  `bits::xor_assign`, `bits::parity_dot` is `bits::dot_product`, and
  `bits::RangeXor` is `bits::XorRange`; `bits::xor_gather` now takes
  `(dst, bits, selector, srcs)` — geometry, then the GF(2) coefficient
  vector, then sources.
- **Breaking: `Field::read`/`Field::write` are now `decode`/`encode`**, and
  the tower coordinate accessor `components()` is `to_components()`,
  pairing with `from_components()`.
- **Breaking: prime-field integer helpers share one verb.** The free
  `goldilocks::canonical` is `goldilocks::reduce` and
  `goldilocks::reduce128` is `goldilocks::reduce_wide`; `reduce*` maps a
  machine integer into the residue range while the inherent
  `Elem::canonical` normalizes an element (those inherent methods are
  unchanged, as is `mersenne31::reduce`).
- **Breaking: `QuadMersenne31::Elem::norm` returns the base-field
  element** `mersenne31::Elem` instead of a raw `u32`; the computation is
  unchanged.
- **Breaking: `FieldKernels::active_backend()` is
  `FieldKernels::backend()`.** The free functions `kernel::backend()` and
  `kernel::backend_for::<F>()` keep their names, and `FGF_TIERS` is
  re-exported at the crate root beside `Backend`.
- The flat GF(2^8) reference internals (`Elem::mul_xtime`,
  `Elem::inv_xtime`, `REDUCTION_LOW`) are `pub(crate)` rather than public,
  and the `$field::field_poly()` accessor is removed — the module const
  `REDUCTION_POLY` remains the one home for that fact.

- The GFNI GF(2^8) matrix kernels resolve each destination row group's
  coefficients into one stack array before their tile loops run, instead
  of loading a coefficient inside the loop. The per-tile coefficient load,
  its bounds check, the term pointer walk and the dependent affine-map
  lookup all leave the multiply loop; grouping, tile widths, and every
  public contract are unchanged, and the steady state stays allocation-free.
  Both GF(2^8) fields, overwrite and accumulate, plan forms and scattered
  rows inherit it. Measured on two GFNI microarchitectures — Core Ultra 7
  258V (Lunar Lake) at 1.07-1.34x and i7-12700K (Golden Cove) at 1.05-1.29x
  the previous throughput across 4-64 KiB rows, 6-16 sources and one to six
  output rows, with no shape regressing on either host.
  Coefficient counts above 32 terms per call fold in several passes over
  the destination, so a caller that folds hundreds of sources into one call
  sees the destination read once per 32-term pass.
- The GFNI GF(2^8) resolve-chunk scratch is written only for the occupied
  chunk terms (`MaybeUninit` staging) instead of declaring the full arrays
  initialized. Measured kernel-neutral on both GFNI hosts — the full-init
  cost the old resolve probe showed was the probe's own artifact — but the
  probe now mirrors production, and `RESOLVE_CHUNK` stays 32: chunk 64/96
  fail the both-host noninferiority gate at 65 sources. No observable
  behaviour change.
- **Breaking: scalar element tuple fields are now private.** Construct and
  read elements through the named `const` conversions — `Elem::from_raw` /
  `Elem::to_raw`, with `from_components` / `to_components` on the towers. Raw
  byte encodings and scalar arithmetic are unchanged; the migration is
  mechanical (`Elem(x)` → `Elem::from_raw(x)`, `e.0` → `e.to_raw()`).
- **Breaking: `Gf2` and the prime-family elements (`Mersenne31`,
  `Goldilocks`, `QuadMersenne31`) now compare, hash, and order by canonical
  field value**, so equivalent raw lanes (a prime lane holding `p` and one
  holding `0`) denote the same element. Binary-tower equality is unchanged:
  those fields still compare representations, which is value equality
  there. `to_raw` still returns the raw lane as stored — it is not a
  canonicalizing accessor; compare `to_raw()` values directly when raw
  representation equality is what you need.
- **Clarified packed prime-field inputs:** canonical input lanes produce
  canonical output lanes. Packed calls do not normalize arbitrary input
  bytes; raw-lane totality remains a scalar-arithmetic contract.
- **Breaking: `FieldKernels` remains a usable generic public bound, but its
  raw unchecked dispatch and preparation entry points are no longer exposed
  to normal consumers**, and `PreparedCoefficient` no longer lets consumers
  extract the implementation's prepared form. The checked operations retain
  their one-shot and borrowed prepared-coefficient usage flows; matrix-plan
  signature changes are listed below.
- `bits` padding is caller-maintained. Range operations leave padding
  untouched; whole-buffer operations preserve zero padding for valid
  inputs, but do not promise to sanitize nonzero padding. This corrects
  the earlier blanket zero-padding guarantee.
- `internals` is documented as explicitly unstable — nothing behind it is a
  compatibility promise — while remaining subject to Rust's safety and
  soundness contracts.
- Documentation corrections: the `no_std` story now states that the crate's
  own portable configuration exists but the `simdispatch`/`archmage`
  dependency graph still enables `std`, so bare-metal support awaits a
  separate dependency review; the fields table gained the missing
  `QuadMersenne31` row; value equality/hash/ordering, canonical prime
  lanes, caller-maintained bit padding, fused `+=` arithmetic (XOR only in
  characteristic two), and a crate-wide no-constant-time guarantee are
  stated explicitly.
- `cargo package` now uses an explicit include allowlist (source, the
  intentional tests/benches/examples, and the public doc/license set);
  local-only working files, markdownlint configuration, CI workflow
  definitions, and `external-bench` are excluded.
- `mul_add_matrix_with` and `mul_into_matrix_with` no longer take the
  row-count argument; it derives from the matrix's `output_count()`.
- Direct architecture entrypoints under `internals` now live in
  `kernel::{architecture}::proven` and require genuine Archmage capability
  tokens. Safe entrypoints validate geometry; generic raw-provider calls
  additionally require an unsafe caller contract. The normal dispatch path
  retains its existing once-per-process capability selection.
- `internals` enables optional `archmage =0.9.29`, matching the version
  already used by `simdispatch`. Default builds retain only `simdispatch`
  as a direct runtime dependency.
- **Breaking:** `bits::weight` returns `usize` rather than `u32`.

### Fixed

- Valid zero-byte scatter and matrix shapes are no-ops on every backend,
  while malformed coefficient/source geometry still panics.
- Prepared-matrix source lookup checks logical bounds before arithmetic,
  including zero-output matrices and `usize::MAX` indices.
- Bit population counts no longer overflow at `2^32` set bits; the public
  operation sums bounded segments without changing the inner kernel.
- Direct overwrite entrypoints replace addressed rows rather than accumulate
  into their previous contents, including zeroing them for an empty source
  set; surplus destination bytes remain untouched.
- GF16 scatter accepts surplus destination rows in debug builds, matching
  the checked buffer contract and release behavior.

### Removed

- The `internals` benchmark bodies for four candidates that two GFNI
  microarchitectures rejected: dense prepared affine-map records
  (`matrix_overwrite_affine_prepared`, `matrix_overwrite{1,2}_8d_prepared`,
  `gather_affine_prepared`, `gather_affine_prepared_tile`), ISA-L's
  three-row grouping (`matrix_overwrite{3,6_33}_8d_prepared`), the 32-byte
  replicated affine-map store (`Map32`, `prepare_map32_8d`,
  `dot_overwrite_replicated_8d`), and the pre-resolved reference body that
  the production path superseded (`dot_overwrite_compact_8d`). Nothing behind
  `internals` is a compatibility promise. The remaining measurement bodies —
  `matrix_overwrite1_tile_8d`, `matrix_overwrite1_external_8d` and
  `resolve_probe_8d` — stay.

## [0.7.1] - 2026-09-09

### Changed

- The `simdispatch` dependency now uses the exact crates.io `=0.1.0`
  release for the stack's published backend-selection source.

## [0.7.0] - 2026-08-24

### Added

- Native GF(2). The `Gf2` marker with `gf2::Elem` scalar arithmetic (add is
  XOR, multiply is AND, negation/squaring the identity, `const` throughout,
  total over raw bytes), and the `bits` module: a bit-packed vector surface
  over `&[u8]` buffers holding one element per bit LSB-first — `xor`,
  `xor_range`, `and_into`/`and_assign`/`andnot_assign`,
  `clear_range`/`set_range`, `weight`, `dot_product`, and `xor_gather`, with
  an explicit bit count where the byte length cannot recover it and padding
  bits kept zero on every output. The bit order is a frozen wire convention.
  `bits::xor_assign` reuses the dispatched byte-XOR kernel; the other kernels are
  portable `u64` word loops (intrinsic acceleration is measured-only and
  the bandwidth-bound shapes are expected to stay portable).
- CI now measures line coverage with `cargo-llvm-cov` and fails below 95%,
  merging one test run per forced `SIMD_BACKEND` tier so every dispatchable
  backend counts toward the total.
- Test coverage for surfaces the suites previously drove only through
  inherent methods: the `field::Elem` trait and its defaulted bodies, the
  `core::ops` operator overloads, `Sum`/`Product` folds, `Display`, the
  prime-field `Neg`, prepared-plan `_with` operations for every kernel
  family, `Coeff`/`Plan` accessors, the geometry-validation panic arms of
  every `ops` entry point, and runtime re-evaluation of the `const`
  table builders against the committed banks.

### Changed

- `ops::add_gather` (and `FieldKernels::add_gather_offsets`): the
  unit-coefficient gather — fold byte-offset rows of one backing region
  into one row. Fields whose addition is XOR dispatch to a new blocked
  kernel that holds the destination in AVX2 registers across the whole
  source list; prime fields fold per source through their canonical
  addition. Offsets are `u32` byte offsets, so callers fold an index table
  without staging a slice per source.

- `Gf8D`'s N-to-1 gather takes the source-fused body on short rows, the
  same selection rule `Gf8B`'s `GF2P8MULB` gather has used since the
  short-row measurement. It was wired to the unfused specialization when
  the blocked-affine seam landed, so rows below the 128-byte main tile fell
  through to one single-source AXPY per source. Results are unchanged;
  `Gf8B` is untouched.

- The bit-packed GF(2) folds run split accumulators: `bits::weight` uses
  eight independent `popcount` lanes and `bits::dot_product` four independent
  AND-XOR lanes, folded once at the end, so the loops saturate execution-port
  throughput instead of riding one register's dependency chain. Measured
  1.2–1.5x (`weight`) and 1.2–2.0x (`dot_product`) across L1/DRAM buffer
  sizes on the reference host. Results are unchanged.

- `QuadMersenne31` kernels canonicalize each limb once per load instead of
  re-reducing operands in every helper: `mul_add`, `mul_assign`, `mul_into`,
  and `mul_elementwise` run about 1.2–1.4x faster on 64 KiB buffers.
  Raw-lane totality is unchanged. `Elem::square` uses a dedicated
  `(a²−b²) + 2ab·i` form (3 base multiplies instead of 4).

### Added

- `QuadMersenne31`, the quadratic extension GF((2^31 − 1)²) over `Mersenne31` with `i² = −1` (the QM31 construction). 8-byte interleaved `re,im` little-endian encoding, `const` scalar arithmetic (schoolbook multiply, conjugate/norm inverse), generator `(1,12)` of order `p²−1`, total over raw limbs, and the complete checked ops surface composed from the `Mersenne31` lanes (portable `scalar` today, no new `unsafe`).

## [0.6.0] - 2026-08-23

This release adds `QuadMersenne31`, the degree-2 extension of `Mersenne31` (`i²=−1`) used by circle STARKs. The binary-tower and prime fields are byte-identical to 0.5.0.

### Added

- `QuadMersenne31` (GF((2^31−1)²)). See the 0.7.0 entry above.

## [0.5.0] - 2026-08-23

This release widens the crate from binary tower fields to also cover prime
fields: `Mersenne31` (GF(2^31 − 1)) and `Goldilocks` (GF(2^64 − 2^32 + 1)),
lane-packed integer arithmetic with x86 integer-SIMD kernels. The binary-tower
fields are byte-identical to 0.4.0.

### Added

- `Mersenne31`, the prime field GF(2^31 − 1). Full `const` scalar arithmetic
  over `u32` lanes with a Mersenne fold (`2^31 ≡ 1`), the complete checked ops
  surface, and x86 AVX2 (`v3`) / SSE4.2 (`v2`) integer-SIMD kernels for
  add/sub/multiply/elementwise using `vpmuludq` even/odd lane products. It is
  total over raw lanes and variable-time (not for secret data).
- `Goldilocks`, the prime field GF(2^64 − 2^32 + 1). Full `const` scalar
  arithmetic over `u64` lanes with the split-fold reduction (`2^64 ≡ 2^32 − 1`),
  the complete checked ops surface, and x86 AVX2/SSE4.2 kernels building the
  128-bit product from four `vpmuludq` half-products. Total over raw lanes and
  variable-time.
- `Elem::neg`, the additive inverse, defaulting to the identity in
  characteristic two and overridden by the prime fields.

### Changed

- `FieldKernels` now requires `add_assign`/`sub_assign` so `ops::add_assign`
  and `ops::sub_assign` route through the field: XOR for the binary towers,
  a modular fold for the prime fields. The binary-field bytes are unchanged.
- `Field::BITS` documents the element storage width (`8 * BYTES`); for the
  binary towers this still equals the extension degree over GF(2), while the
  prime fields set the lane width (32/64), not `log2(ORDER)`. No binary value
  changed.
- On non-x86 targets the prime fields use the portable path and report
  `scalar`, consistent with the crate's rule that a cross-compiled kernel is
  never promoted without validation on executing hardware.
- Faster prime-field x86 kernels: `Mersenne31` add/sub/multiply rebuilt on
  the Mersenne fold with a doubled-odd-limb multiply and loop-invariant
  coefficient canonicalization, and `Goldilocks` AVX2 multiplies rebuilt in
  the bias-shifted domain so borrow/carry detection is a single signed
  compare. On the reference AVX2 host the 64 KiB overwrite region runs about
  1.9x faster for `Mersenne31` and 1.3x for `Goldilocks`, accumulate about
  1.3x and 1.2x. The binary-tower paths are untouched.

### Fixed

- `Mersenne31` and `Goldilocks` now override `FieldKernels::mul_into`.
  The overwrite region op previously fell back to the copy-then-scale
  default, two passes over every buffer; it is now a single fused pass on
  every x86 backend, and the kernel differential tests cover it.

## [0.4.0] - 2026-08-22

This release adds `Gf8D`, a second GF(2^8) representation under the
Reed–Solomon polynomial `0x11D` for byte-identical interop, and the overwrite
operation shapes — `mul_into_gather`, `mul_into_matrix`, and
`mul_add_matrix_at` — for erasure encoding and in-place reconstruction.
The two GF(2^8) fields are now named by polynomial (`Gf8B`/`Gf8D`), a breaking
rename of the former `Gf8`.

### Added

- `mul_add_matrix_at` reconstructs disjoint destination rows directly
  in their final positions. GF(2^8) uses the register-blocked x86 GFNI matrix
  kernel; other backends use the portable path.
- `Gf8D`, a second GF(2^8) field under the polynomial `0x11D` (generator `2`),
  for byte-identical Reed–Solomon interop with ISA-L and
  `klauspost/reedsolomon`. It carries the full `const` scalar arithmetic and
  the complete checked ops surface: on x86 GFNI hosts it multiplies through
  `VGF2P8AFFINEQB` affine maps const-derived from its own scalar oracle
  (`GF2P8MULB` is the AES field and is never used), including the
  register-blocked scatter/gather/matrix shapes; elsewhere it runs the
  field-agnostic split-nibble shuffle kernels. Elementwise multiplication is
  vectorized on every x86 backend through a branchless shift/reduce that
  threads the `0x11D` reduction byte (the AES `GF2P8MULB` cannot serve it).
- `mul_into_gather` and `mul_into_gather_with` overwrite one destination with an
  N-to-1 field dot product, distinct from accumulating `mul_add_gather`.
  Empty/all-zero inputs clear the destination, one source uses `mul_into`, and
  prepared plans remain allocation-free.
- `mul_into_matrix` and `mul_into_matrix_with` overwrite multiple rows
  with field dot products for erasure encoding. On x86 GFNI, `Gf8B` and
  `Gf8D` seed blocked accumulators from zero in registers, eliminating the
  separate zero-fill and destination read; other backends use the equivalent
  zero-then-accumulate composition.

### Changed

- **Breaking: `Gf8` renamed to `Gf8B`, module `gf8` to `gf8b`.** The two
  GF(2^8) representations are now named consistently by polynomial
  (`Gf8B`/`0x11B`, `Gf8D`/`0x11D`). Update `use fgf::{Gf8, gf8}` to
  `use fgf::{Gf8B, gf8b}`; the field's bytes, tables, and kernels are unchanged.
- The flat GF(2^8) markers expose their reduction polynomial through
  `field_poly()` and the `REDUCTION_POLY` module constant, for compile-time
  representation introspection.
- Native GFNI N-to-1 gathers now keep 32/64/96-byte multi-source rows fused
  across sources instead of composing single-source AXPY calls. Measured
  16-byte and body-plus-tail variants remain on the prior path.
- `Gf8B` remains on native `GF2P8MULB` for every production gather shape.
  An internals-only prepared-affine gather and benchmark record the rejected
  fixed-map policy without changing public dispatch.
- The production GFNI gather retains its static 128-byte tile. Internals-only
  32/64/96-byte and split-accumulator bodies support page-controlled benchmark
  reruns; measured wins reversed across neighboring layouts.

## [0.3.0] - 2026-08-08

This release renames the crate from `fff` to `fgf` (Faster Galois Fields) and
moves backend detection onto the extracted Level 0 `simdispatch` crate. The
field arithmetic, kernels, and operation surface are unchanged.

### Changed

- **Breaking: crate renamed `fff` → `fgf`.** The repository moves to
  [github.com/nanithefkuc/fgf]https://github.com/nanithefkuc/fgf. Imports
  change from `use fff::…` to `use fgf::…` and the dependency key to `fgf`;
  the features (`std`, `simd`, `internals`) and the API are otherwise
  unchanged. The internals-only tier list `FFF_TIERS` is now `FGF_TIERS`.
- **Backend migration onto `simdispatch`.** `Backend` is now a
  re-export of `simdispatch::Backend`; detection, ordering, the `BACKEND`
  static, and the downgrade-only override are owned by `simdispatch` (Level 0,
  via `archmage` `summon()`). This crate's `detect()`, `resolve_backend()`,
  `BACKEND`, `Backend::ALL`, `is_for_current_arch`, local
  `Display`/`FromStr`/`ParseBackendError`, and the `FFF_BACKEND` override are
  deleted. Selection resolves `simdispatch::Selection` over `FGF_TIERS` once
  per process and is cached. The field-kernel policy methods `has_native_mul`
  and `has_blocked_rows` stay in this crate as the new `KernelBackend`
  extension trait (they are policy, not hardware facts).
- Tiers renamed to the `archmage` ladder: `Gfni` → `V3GfniCrypto` (`v3_gfni_crypto`),
  `Avx2` → `V3` (`v3`), `Ssse3` → `V2` (`v2`), `Pmull` → `NeonAes` (`neon_aes`),
  `Simd128` → `Wasm128` (`wasm128`). The 64-byte AVX-512 tier (`Avx512`) is
  deferred (V4x): its kernels remain under the `internals` feature,
  cross-compile-only, and are not in the ladder until validated on executing
  hardware; AVX-512 hosts resolve to the 32-byte GFNI tier.
- The `SIMD_BACKEND` environment variable replaces `FFF_BACKEND`, applied by
  `simdispatch`. Results are unchanged on hosts without AVX-512 (same kernels
  selected); a `simdispatch` git dependency (pinned to `v0.0.0`) is added —
  re-pin consuming crates in the same sitting.
- Tests: the cfg-based `is_for_current_arch` assertions are deleted; the
  per-backend differential-test gates now resolve `simdispatch` selection
  (`host_supports`) instead of `std::is_*_feature_detected!`. The AVX-512
  kernel differential test is gated behind `internals` (no tier to resolve).

## [0.2.0] - 2026-07-31

This release adds vector kernels for the fields that had none, widens the
shapes the existing kernels cover, and tunes x86 store and alignment
behaviour.

### Added

- Vector kernels for GF(2^32) and GF(2^64) on x86 GFNI, replacing the scalar
  path for `mul_add`, `mul_assign`, and `mul_into`.
- Vector kernels for the canonical Fan–Paar fields on x86: `FanPaar16` on AVX2
  and SSSE3, `FanPaar32` and `FanPaar64` on AVX2. `FanPaar8` keeps the portable
  path, which is already table-driven.
- `mul_elementwise` vector kernels for GF(2^8) and GF(2^16) on AVX2 and SSSE3.
  `has_vector_elementwise` now reports `true` for those fields on those
  backends.
- `Backend::Pmull`, the AArch64 NEON plus PMULL combination. It replaces a
  per-call feature probe inside `mul_elementwise`, so the capability is
  detected once per process and `FFF_BACKEND=neon` can turn it off. Accepted by
  `FFF_BACKEND`, `Backend::ALL`, `Display`, and `FromStr` like every other
  backend.
- Multi-row GF(2^8) kernels (`mul_add_scatter`, `mul_add_gather`,
  `mul_add_matrix`) and dedicated GF(2^16) scatter, gather, and matrix kernels
  for WebAssembly `simd128`. These shapes previously fell back to per-row or
  scalar loops.
- Fused GF(2^16) `mul_into` for NEON and WebAssembly `simd128`, which used
  copy-then-scale before.
- `internals` feature, exposing the kernel modules and coefficient preparation
  types for downstream crates that build directly on the kernels. It is not a
  stable semver surface.
- Differential kernel coverage for `mul_into` on every backend that implements
  it; the shape had no direct test before.
- Benchmark sections for the shapes the new tuning policies are set from:
  large destinations, destination alignment, small multi-row shapes,
  preparation crossover, and blocked kernels against repeated AXPY.

### Changed

- **Breaking:** `Backend::ALL` is `[Backend; 8]`, up from `[Backend; 7]`.
- **Breaking:** AArch64 hosts with the PMULL extension now report
  `Backend::Pmull` from `backend()` rather than `Backend::Neon`.
- **Breaking:** `has_vector_elementwise::<Gf8>()` and `::<Gf16>()` return
  `true` on AVX2 and SSSE3 hosts, where they previously returned `false`.
- GF(2^16) scalar kernels, including the tail of every vector kernel, multiply
  through the shared nibble tables instead of a per-element Karatsuba multiply.
- x86 `mul_into` uses non-temporal stores once the destination is large enough
  that an ordinary store would evict more than it saves. `mul_add` and
  `mul_assign` read their destination and are unchanged.
- The GF(2^16) multi-row scatter kernels on GFNI and AVX2 peel a misaligned
  destination before the main pass, generalizing the existing GF(2^8) peel.
- The NEON and WebAssembly `simd128` GF(2^16) kernels process two lanes per
  iteration.
- GF(2^16) `mul_elementwise` no longer uses PMULL; the NEON path serves PMULL
  hosts. GF(2^8) `mul_elementwise` keeps it.
- GF(2^16) GFNI `mul_add_gather` and GF(2^8) AVX2 `mul_add_matrix` use their
  register-blocked kernels instead of dispatching to repeated AXPY.
- GF(2^16) 256-bit kernels descend through a 128-bit vector before the scalar
  tail, so short remainders are no longer fully scalar.

### Fixed

- WebAssembly `simd128` test build, which referenced GF(2^8) multi-row kernels
  that did not exist. The comparison dev-dependency is now scoped to non-wasm
  targets so `cargo test --target wasm32-*` builds.
- `--no-default-features --all-targets` builds. The bench targets use
  `ops::Plan` and the comparison dev-dependency, both of which need `std`, so
  they now declare `required-features = ["std"]`; a Fan–Paar test import that
  is only reachable on x86 SIMD builds is cfg-gated to match.

### Removed

- crates.io publishing metadata and docs.rs configuration; the crate is
  distributed through git only.

## [0.1.1] - 2026-07-29

### Added

- Prepared `Plan` consumers for scatter, gather, and matrix operations.
- Stable `pack`, `unpack`, and `pack_to_vec` element/buffer conversions.
- `Backend::ALL`, `Display`, `FromStr`, per-field `backend_for`, and
  `has_vector_elementwise` capability reporting.
- Uniform field element `Display`, assignment, iterator, byte-conversion,
  component, and raw-representation APIs.
- Fan–Paar level modules (`fan_paar::fp8` through `fp64`).
- Release metadata, CI, contributing guidance, benchmarks guide, and MIT
  license.

### Changed

- The public `FieldKernels` trait is sealed and re-exported from the crate root.
- The `Elem` trait remains at `fff::field::Elem` instead of colliding with
  concrete `Elem` types at the crate root.
- Row geometry and panic messages are consistent across vector operations.
- Internal scalar kernels, SIMD table layouts, and raw XOR dispatch are no
  longer public semver surface.
- x86 GF(2^8) kernels retain SIMD processing through 16-byte tails.
- Prepared GF(2^8) and GF(2^16) plans retain register-blocked x86 kernels
  where they outperform repeated prepared AXPY.

## [0.1.0] - 2026-07-29

Initial public release.

[Unreleased]: https://github.com/nanithefkuc/fgf/compare/v3.0.2...HEAD
[3.0.2]: https://github.com/nanithefkuc/fgf/compare/v3.0.1...v3.0.2
[3.0.1]: https://github.com/nanithefkuc/fgf/compare/v3.0.0...v3.0.1
[3.0.0]: https://github.com/nanithefkuc/fgf/compare/v2.1.0...v3.0.0
[1.0.0]: https://github.com/nanithefkuc/fgf/compare/v0.7.1...v1.0.0
[0.7.1]: https://github.com/nanithefkuc/fgf/compare/v0.7.0...v0.7.1
[0.6.0]: https://github.com/nanithefkuc/fgf/compare/v0.5.0...v0.6.0
[0.5.0]: https://github.com/nanithefkuc/fgf/compare/v0.4.0...v0.5.0
[0.4.0]: https://github.com/nanithefkuc/fgf/compare/v0.3.0...v0.4.0
[0.3.0]: https://github.com/nanithefkuc/fgf/compare/v0.2.0...v0.3.0
[0.2.0]: https://github.com/nanithefkuc/fgf/compare/v0.1.1...v0.2.0
[0.1.1]: https://github.com/nanithefkuc/fgf/compare/v0.1.0...v0.1.1
[0.1.0]: https://github.com/nanithefkuc/fgf/releases/tag/v0.1.0

[1.1.0]: https://github.com/nanithefkuc/fgf/compare/v1.0.1...v1.1.0
[1.1.1]: https://github.com/nanithefkuc/fgf/compare/v1.1.0...v1.1.1
[1.2.0]: https://github.com/nanithefkuc/fgf/compare/v1.1.1...v1.2.0
[1.2.1]: https://github.com/nanithefkuc/fgf/compare/v1.2.0...v1.2.1
[1.2.2]: https://github.com/nanithefkuc/fgf/compare/v1.2.1...v1.2.2
[2.0.0]: https://github.com/nanithefkuc/fgf/compare/v1.2.2...v2.0.0
[2.1.0]: https://github.com/nanithefkuc/fgf/compare/v2.0.0...v2.1.0