The key-hash round replaced: the shipped rotl29((h ^ w) * m) against the 128-bit product folded to 64 bits
((h ^ w) as u128 * m, lo ^ hi). lexindex 71363e5 + the branch that makes the change, Ryzen 7 5800HS, Fedora 44,
2026-09-10 04:00-04:45, editor open, nothing else running during the timed parts.

Why. CompactHashIndex::build_to_file over 10^8 generated `word.word` keys reported 99 999 999 distinct while the
generator yields 100 000 000 distinct strings (local/gencount.rs, no index involved); 9 short at 10^9. The pairs:
  "sering.dampening" / "sering.tamponing"   byte 7 d^t = 0x10, byte 11 e^o = 0x0a   (both hashes equal -> one id)
  "mutely.solodization" / "mutely.colonization"   byte 7 0x10, byte 11 0x0a   (slot hash only)
  "cheveret.Timonian" / "cheverel.Tironian"       byte 7 0x18, byte 11 0x1f   (slot hash only)
A difference confined to the top k bits of a word stays in the top k bits of a 64-bit product; the rotate moved
those bits to [29-k, 29), byte 3 of the next word, where that word's own difference cancelled them -- in both
hashes, whatever their constants.

Differential probability (local/collide.rs diff; 4096 random 16-byte lowercase keys, byte 7 flipped by d0, byte
11 by every d1; "P(both)" = some d1 cancels both hashes):
  d0 0x80: P(both) 1.0000 (d1 0x10 always)     d0 0x40: 0.4988    d0 0x20: 0.3840    d0 0x10: 0.1565
  d0 0x08: 0.1311    d0 0x30: 0.0881    d0 0x50: 0.3433       slot hash alone: 1.0000 for every d0 >= 0x08
  new round: 0 for every d0 (no d1 ever cancels either hash in 4096 x 255 trials per d0)

Single-bit scan (local/collide.rs scan; 64 random 24-byte keys, every (position p, bit) against every other
position q and every single-byte change there, both hashes):
  shipped round      35 weak position pairs  (8i+7 <-> 8i+11 both ways, 8i+6 -> 8i+10, ...)
  rotate-then-multiply  38 weak pairs  (8i+3/8i+4 <-> 8(i+1)+7)  -- rejected
  128-bit fold        0 weak pairs

Same 10^8 generated keys (local/collide.rs 100000000):
  shipped     slot-only collisions 2   full pair collisions 1
  128-bit fold                     0                        0
  rotate-then-multiply             1 ("heals.stabilized" / "heads.stabilizes": bytes 3 and 15)   0
Real corpora, shipped round: /usr/share/dict/words 479 823 keys -> 0; local/paths.txt 909 776 keys -> 0.

10^9 generated keys through CompactHashIndex::build_to_file (examples/peak.rs compact-stream, scratch on /home):
  shipped   n 997503996  build 603815.5 ms  peak 1717.1 MB
  new       n 997504005  build 592971.0 ms  peak 1702.2 MB     (997 504 005 = the generator's count)

Distribution gate (local/hashbench/src/bin/quality.rs and quality_mum.rs; "old" there is the pre-1.0 FNV chain):
                                    shipped round        128-bit fold
  avalanche worst |z|                   4.50                 4.38
  low-16-bit chi2, 479 823 words       z 0.54               z 0.07
  low-20-bit chi2, 1 M bigrams         z 0.88               z 0.26
  slot x fingerprint joint chi2        z -1.00              z -1.07
  64-bit collisions among 1 M bigrams  0                    0

Speed (local/hashbench/src/bin/mum.rs; A-B-A-B in one process, 4 rounds, median, with a control the change cannot
touch; ns per key):
  words   n 479 823  mean 9.3 B    hash_key  6.00 -> 5.71 (1.05x)   hash_pair  6.66 -> 6.89 (0.97x)   control 8.39
  bigrams n 1 M      mean 10.9 B   hash_key  3.46 -> 3.49 (0.99x)   hash_pair  4.68 -> 5.21 (0.90x)   control 3.74
  uri80   n 1 M      mean 80 B     hash_key  9.59 -> 10.88 (0.88x)  hash_pair 13.57 -> 15.31 (0.89x)  control 26.40

Pinned slot-only pair for the new round, Pollard rho (local/hashcollide-mum, seed 0):
  "lgywf6nnfq3in" / "sax4tnfbfpa7n"  hash_key 0x6e3065fe6c854ff2, fingerprints 0xed4f0957c6d42d03 / 0xebb975f22f342ed0

In situ (local/closedbench, /usr/share/dict/words, 479 823 members probed shuffled; the harness built against
71363e5 in a worktree = A, against the branch = B; one warm-up run, then A B B A A B B A, each a process of
3 rounds, its minimum per mode; ns per probe):
  A  closed.id 40.6  compact.id_unchecked 43.4  compact.id 71.2  closed.ids_of 14.0  compact.ids_of 27.5
  B  closed.id 40.1  compact.id_unchecked 42.1  compact.id 74.0  closed.ids_of 13.2  compact.ids_of 29.1
  B  closed.id 40.4  compact.id_unchecked 40.7  compact.id 74.1  closed.ids_of 13.8  compact.ids_of 28.8
  A  closed.id 41.6  compact.id_unchecked 40.9  compact.id 72.1  closed.ids_of 14.2  compact.ids_of 27.8
  A  closed.id 40.7  compact.id_unchecked 41.1  compact.id 70.4  closed.ids_of 13.2  compact.ids_of 27.9
  B  closed.id 40.4  compact.id_unchecked 41.0  compact.id 74.3  closed.ids_of 14.8  compact.ids_of 28.7
  B  closed.id 39.7  compact.id_unchecked 40.2  compact.id 74.7  closed.ids_of 14.8  compact.ids_of 28.0
  A  closed.id 40.1  compact.id_unchecked 42.6  compact.id 71.8  closed.ids_of 14.2  compact.ids_of 27.4
  means: compact.id 71.4 -> 74.3 (+2.9 ns), compact.ids_of 27.7 -> 28.7 (+1.0); the one-hash paths
  (closed.id 40.8 -> 40.2, id_unchecked 42.0 -> 41.0) within noise.

In situ again, 2026-09-10 08:10-08:25, after the harness gained a single-loop mode (local/closedbench, source
identical for both builds; M = main 0e177fd, U = this branch 54f37b8; the lookup code is the same in 71363e5 and
0e177fd, only the build changed). One warm-up of each, then M U M U M U, each a process of 7 rounds, minimum:
M	min: closed.id 40.0 ns  compact.id_unchecked 40.9 ns  compact.id 70.0 ns  closed.ids_of 13.4 ns  compact.ids_of 27.3 ns
U	min: closed.id 40.0 ns  compact.id_unchecked 39.9 ns  compact.id 69.6 ns  closed.ids_of 13.9 ns  compact.ids_of 27.9 ns
M	min: closed.id 40.7 ns  compact.id_unchecked 42.3 ns  compact.id 70.7 ns  closed.ids_of 13.6 ns  compact.ids_of 27.1 ns
U	min: closed.id 39.0 ns  compact.id_unchecked 39.2 ns  compact.id 70.1 ns  closed.ids_of 13.4 ns  compact.ids_of 28.3 ns
M	min: closed.id 40.9 ns  compact.id_unchecked 40.8 ns  compact.id 71.5 ns  closed.ids_of 12.9 ns  compact.ids_of 26.7 ns
U	min: closed.id 40.8 ns  compact.id_unchecked 39.7 ns  compact.id 69.1 ns  closed.ids_of 13.4 ns  compact.ids_of 28.3 ns
  means: compact.id 70.7 -> 69.6 (-1.1 ns), id_unchecked 41.3 -> 39.6, closed.id 40.5 -> 39.9, compact.ids_of
  27.0 -> 28.2 (+1.2), closed.ids_of 13.3 -> 13.6.
So the +2.9 ns above is not a property of the hash: rebuilt, the same harness puts the new round 1 ns *ahead* on
the same probe, consistently over three alternations, as the morning's run had it 2.9 behind over four. What
survives both is the batched path, +1.0-1.2 ns, and the hash's own latency, +0.6 ns on a word.
`perf stat` over the single loop (ONLY=compact.id REPS=20, M U M U):
=== M compact.id
compact.id: 71.8 ns/probe over 20 passes
     3,951,175,725      cycles                                                                  (83.47%)
     3,710,846,777      instructions                                                            (83.50%)
        18,845,441      branch-misses                                                           (83.51%)
=== U compact.id
compact.id: 70.2 ns/probe over 20 passes
     3,862,009,712      cycles                                                                  (83.43%)
     3,755,769,834      instructions                                                            (83.34%)
        18,488,071      branch-misses                                                           (83.24%)
=== M compact.id
compact.id: 71.0 ns/probe over 20 passes
     3,920,903,446      cycles                                                                  (83.77%)
     3,740,000,166      instructions                                                            (83.63%)
        18,461,084      branch-misses                                                           (83.16%)
=== U compact.id
compact.id: 70.1 ns/probe over 20 passes
     3,888,290,170      cycles                                                                  (83.53%)
     3,796,704,457      instructions                                                            (83.65%)
        18,356,093      branch-misses                                                           (83.50%)
  instructions +1.5 % for the new round (the 128-bit multiply is `mul` + `xor` with its register moves against
  `imul` + `rol`), cycles -1.5 %; branch misses and cache misses the same. The probe is bound by its misses; the
  extra instructions hide under them, and which build lands its loop on the better alignment decides the last
  nanosecond.
