ba00889fb9
dist_build() sorted the entire 151936-entry vocab by probability (qsort) on every sampled token whenever 0 < g_nuc < 1 — the serve default — and again per draft position under rejection sampling. Measured cost: 5.6-8.0 ms/call; the actual work is finding the few-hundred-token head whose cumulative mass reaches g_nuc. Replace the full qsort + linear scan with a max-heap partial select: - Floyd heapify g_pidx over V by descending g_pbuf prob (O(V), cache-friendly) - pop winners to the array's high end until cum >= g_nuc (k * O(log V)) - the remaining heap prefix IS the tail -> zero it, renormalize the head Winners land in g_pidx[out..V-1] in descending order, so s2 accumulates in the same order as before -> head is unchanged on tie-free shapes (ties were already unspecified under the unstable qsort). All four dist_build/dist_sample contract properties hold: g_pbuf stays id-indexed, g_pidx stays internal, the tail is fully zeroed, the head renormalizes to 1. No API change, no caller change, no new globals. c/tests/test_topp.c (new): drives the REAL dist_build via the include-glm.c pattern against an independent double-precision reimplementation of the OLD algorithm. 123 cases: 6 sizes (1..1519) x 5 shapes (uniform/peaked/geometric/ plateau/sharptail) x 4 nuc values, plus the g_nuc>=1 guard-off paths and V=1. Tie-free shapes compare head values within 1e-6 rel (float vs double renorm noise); tie shapes compare value-multisets. No scratch files -> builds clean on Windows MinGW without the unmerged mkdtemp shim (#352).