metanum 1.4.0 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/format-metanum.js CHANGED
@@ -16,9 +16,9 @@ const FORMAT_OPTIONS = {
16
16
  sciSignificantDigits: 3, // 7. 科学计数法有效位数(=n则αEβ的α的小数部分保留n位)
17
17
  sciDecimalThreshold: 3, // 8. 科学计数法小数阈值(=n则αEβ的β>=10^n时不显示小数部分)
18
18
  singleLetterDigits: 3, // 9. 单字母计数法有效位数(αFβ,αGβ...αZβ中α的小数位数)
19
- repeatLetterThreshold: 3, // 10. 单字母重复阈值(=n则出现n个重复的单字母时用下一个字母计数法,n<2时以2计算)
19
+ repeatLetterThreshold: 3, // 10. 单字母重复阈值(=n则出现n+1个重复的单字母时用下一个字母计数法,n<2时以2计算)
20
20
  multiLetterDigits: 3, // 11. 多字母计数法有效位数(及以上的α的小数位数)
21
- multiLetterRepeatThreshold: 3, // 12. 多字母组合重复阈值(=n则出现n个重复的多字母组合时用下一个字母计数法,n<2时以2计算)
21
+ multiLetterRepeatThreshold: 3, // 12. 多字母组合重复阈值(=n则出现n+1个重复的多字母组合时用下一个字母计数法,n<2时以2计算)
22
22
  multiLetterLimit: 4, // 13. 多字母组合最大位数(=n则多字母组合的长度不超过n,超过则切换下一种计数法,n<2时以2计算)
23
23
  epsilonSignificantDigits: 6 // 14. epsilon有效位数(=n则αεβ中α的小数位数为n)
24
24
  }
@@ -288,6 +288,36 @@ function letterName(height) {
288
288
  return result
289
289
  }
290
290
 
291
+ // ─── Letter token with the multiLetterLimit symbol carry ──────────
292
+ // A k-letter combination (Aa, Aaa, Aaaa, …) sits at level ω^(k-1). Once the
293
+ // combination would be longer than FORMAT_OPTIONS.multiLetterLimit the
294
+ // notation switches to the next one instead: the symbol form !αAaβ. Per its
295
+ // definition (!1.2345Aa4 = 10{ω^4+ω^3*2+ω^2*3+ω*4+5}10) β is the top
296
+ // ω-exponent (k-1) and α's digits are the CNF coefficients — exactly the
297
+ // digits of the letter itself (uppercase index, then the lowercase indices),
298
+ // which keeps the diagonal mantissa inside [2,10) for the cascades.
299
+ function letterTokenOf(height) {
300
+ let plain = letterName(height)
301
+ let limit = Math.max(2, FORMAT_OPTIONS.multiLetterLimit | 0)
302
+ if (plain.length <= limit) return { sym: "", letter: plain, arg: 0, mant: null }
303
+ let mant = plain.charCodeAt(0) - 64 // coefficient of ω^(k-1)
304
+ for (let i = 1; i < plain.length; i++) {
305
+ mant += (plain.charCodeAt(i) - 97) * Math.pow(10, -i)
306
+ }
307
+ if (mant < 2) mant = 2 // diagonal [2,10) anchor
308
+ if (mant >= 10) mant = 9 + (mant - 10) / 170 // keep α ∈ [2,10)
309
+ return { sym: "!", letter: "Aa", arg: plain.length - 1, mant: mant }
310
+ }
311
+
312
+ // Emit α·letter·β for a token: the plain form is αΓβ, the carried form is the
313
+ // symbol-first !αAaβ whose α is the definition mantissa and β the ω-exponent.
314
+ function emitLetterToken(tok, alphaStr, betaStr, precision) {
315
+ if (!tok.sym) return alphaStr + tok.letter + betaStr
316
+ let a = (tok.mant === null || tok.mant === undefined) ? alphaStr
317
+ : regularFormat(tok.mant, precision)
318
+ return tok.sym + a + tok.letter + formatR0Arg(tok.arg, precision)
319
+ }
320
+
291
321
  // ─── Symbol Name: Map layer to symbol ────────────────────────────
292
322
  const SYMBOLS = "!@#$%&~<>?"
293
323
  function symbolName(layer) {
@@ -343,9 +373,219 @@ function getOrdinalLetter(ordRows) {
343
373
  return tokens.join("")
344
374
  }
345
375
 
376
+ // ─── Reference Polarize (verbatim port of PowiainaNum's myPolarize) ──
377
+ // Recovers the display α ("bottom") and the diagonal level ("arrows") of an
378
+ // r0 chain STRUCTURALLY, by climbing log10 level by level — the only method
379
+ // that stays exact at high levels where the engine's own fractional-arg
380
+ // bisect quantizes (level ≥ 20). Ground truth: format-powiainanum.js.
381
+ // their array = [base, [level, count, 1, 1], ...] sorted top-level-first
382
+ // ours: r0 = [base, cnt1, cnt2, ...] where cnt_i = count at level i
383
+ function refPolarizeArraySort(array) {
384
+ array.sort(function (a, b) {
385
+ if (typeof a == 'number') return
386
+ else if (a[3] > b[3]) return -1
387
+ else if (a[3] < b[3]) return 1
388
+ else if (a[2] == 'x' && b[2] != 'x') return -1
389
+ else if (a[2] != 'x' && b[2] == 'x') return 1
390
+ else if (a[2] == 'x' && b[2] == 'x') return -1
391
+ else if (a[2] > b[2]) return -1
392
+ else if (a[2] < b[2]) return 1
393
+ else if (a[0] == 'x' && b[0] != 'x') return -1
394
+ else if (a[0] != 'x' && b[0] == 'x') return 1
395
+ else if (a[0] == 'x' && b[0] == 'x') return -1
396
+ else if (a[0] > b[0]) return -1
397
+ else if (a[0] < b[0]) return 1
398
+ else if (a[1] > b[1]) return -1
399
+ else if (a[1] < b[1]) return 1
400
+ return -1
401
+ })
402
+ }
403
+ function refPolarizeArrayMerge(array) {
404
+ let elemOffset = 0
405
+ for (let i = 0; i < array.length - 2; ++i) {
406
+ if (array[i][0] == array[i + 1][0] &&
407
+ array[i][2] == array[i + 1][2] &&
408
+ array[i][3] == array[i + 1][3]) {
409
+ array[i][1] += array[i + 1][1]
410
+ array.splice(i + 1, 1)
411
+ --i
412
+ elemOffset++
413
+ }
414
+ }
415
+ return elemOffset
416
+ }
417
+ // our r0 + [count, level] finite rows → their [base, [level, count, 1, 1]...]
418
+ // (their row level = our level verbatim: their 3{21}3 rows top at [20,1] and their polarize
419
+ // returns arrows 20, displayed "…20" — no off-by-one)
420
+ function refPolarizeInput(r0, finiteRows) {
421
+ let arr = [r0[0]]
422
+ for (let i = 1; i < r0.length; i++) {
423
+ if (r0[i] > 0) arr.push([i, r0[i], 1, 1])
424
+ }
425
+ if (finiteRows) {
426
+ for (let i = 0; i < finiteRows.length; i++) {
427
+ let fr = finiteRows[i]
428
+ if (fr && fr.length === 2 && fr[0] > 0) arr.push([fr[1], fr[0], 1, 1])
429
+ }
430
+ }
431
+ return arr
432
+ }
433
+ function refPolarize(array, hasOperationRepeat = true) {
434
+ refPolarizeArraySort(array)
435
+ refPolarizeArrayMerge(array)
436
+ let ptr = array.length - 2
437
+ let b = () => array[array.length - 1]
438
+ let c = (x) => { array[array.length - 1] = x }
439
+ let repeatResult = 1000001
440
+ while (--repeatResult >= 0) {
441
+ if (b() >= 10) {
442
+ let a = Math.log10(b())
443
+ array.push([1, 1, 1, 1])
444
+ ptr++
445
+ refPolarizeArraySort(array)
446
+ let offset = refPolarizeArrayMerge(array)
447
+ ptr -= offset
448
+ c(a)
449
+ } else {
450
+ if (ptr == 0 && typeof array[ptr + 1] == 'number' &&
451
+ (hasOperationRepeat || (!hasOperationRepeat && array[ptr][1] == 1))) break
452
+ if (ptr != 0 && typeof array[ptr + 1] == 'number' && typeof array[ptr][0] == 'number' &&
453
+ array[ptr][1] == 1 &&
454
+ (array[ptr - 1][0] == 'x' || array[ptr - 1][2] > array[ptr][2] || array[ptr - 1][3] > array[ptr][3]) &&
455
+ array[ptr][0] > 2 && (ptr != 0 || array[ptr - 1][0] == 'x')) {
456
+ let arrow_count = array[ptr][0]
457
+ let base = b()
458
+ let Jx
459
+ if (arrow_count == 3) Jx = base
460
+ else Jx = arrow_count - 1 + Math.log(base / 2) / Math.log(5)
461
+ array[ptr][0] = 'x'
462
+ c(Jx)
463
+ refPolarizeArraySort(array)
464
+ let offset = refPolarizeArrayMerge(array)
465
+ ptr -= offset
466
+ }
467
+ if (typeof array[ptr + 1] == 'number' && array[ptr][0] == 'x' && b() < 10 && ptr != 0) {
468
+ let base = b()
469
+ let JRepeation = array[ptr][1]
470
+ let Kx = JRepeation + Math.log10(base)
471
+ array[ptr][1] = 1
472
+ array[ptr][0] = 1
473
+ array[ptr][2]++
474
+ c(Kx)
475
+ refPolarizeArraySort(array)
476
+ let offset = refPolarizeArrayMerge(array)
477
+ ptr -= offset
478
+ }
479
+ if (((ptr == 0 && !hasOperationRepeat && array[ptr][1] != 1) || ptr != 0) &&
480
+ array[ptr][0] != 'x' && b() < 10) {
481
+ if (b() == 1 && ptr > 0 && array[ptr - 1][0] > array[ptr][0] && array[ptr][1] == 1) {
482
+ array[ptr][0] = array[ptr - 1][0]
483
+ } else {
484
+ let right = array[ptr][1] + Math.log10(b())
485
+ array[ptr][0]++
486
+ array[ptr][1] = 1
487
+ c(right)
488
+ }
489
+ refPolarizeArraySort(array)
490
+ let offset = refPolarizeArrayMerge(array)
491
+ ptr -= offset
492
+ }
493
+ }
494
+ }
495
+ return {
496
+ bottom: b(),
497
+ repeation: array[ptr][1],
498
+ arrows: array[ptr][0],
499
+ }
500
+ }
501
+ // Convert a finite-chain polarize triple (bottom, repeation, arrows) into the
502
+ // dlsdl diagonal-letter display pair (mantissa, β) for a diagonal letter
503
+ // (Aa, Ba, Ca, Aaa, … — multi-letter tokens ending in 'a').
504
+ // The value behaves as 10{arrows+1}(t) with t = log10(bottom)+repeation.
505
+ // Pure dlsdl convention for diagonal letters (α = 2·5^f ∈ [2,10)):
506
+ // t ∈ [2,10) -> mantissa t, β = arrows
507
+ // (3{100}3 -> 2.376Aa99; 10{100}(10{93}10)-> 2.002Aa100)
508
+ // t ≥ 10 -> the count climbs one level per geometric smooth-log step
509
+ // f → 1+log10(f); the exact anchor t=10 is mantissa 2 at
510
+ // β=arrows+1 (10{100}10 -> 2.000Aa100, Aa100 itself)
511
+ // t ∈ [1,2) -> below the β anchor: mantissa 2·5^(t-1), β=arrows-1
512
+ // Truncation-marker normalization for finite rows: when the engine truncated
513
+ // an overflow cascade it reset r0 to [10] and bumped the LOWEST kept row's
514
+ // count by 1 over the uniform per-level fill (xCnt). That +1 marks the
515
+ // dropped tail, not a real operation — read the row as carrying xCnt so
516
+ // truncated displays match the untruncated anchor (cross-config stability).
517
+ function normalizedFiniteRows(r0, finiteRows) {
518
+ if (!finiteRows || finiteRows.length === 0 || r0[0] !== 10) return finiteRows
519
+ let sorted = finiteRows.slice().sort((a, b) => a[1] - b[1])
520
+ let c0 = sorted[0][0] || 0
521
+ let c1 = sorted.length > 1 ? (sorted[1][0] || 0) : 0
522
+ if (c0 === c1 + 1) {
523
+ let copy = finiteRows.slice()
524
+ for (let i = 0; i < copy.length; i++) {
525
+ if (copy[i][1] === sorted[0][1]) copy[i] = [c1, copy[i][1]]
526
+ }
527
+ return copy
528
+ }
529
+ return finiteRows
530
+ }
531
+
532
+ function aaDiagonalPair(pol) {
533
+ let t = Math.log10(pol.bottom) + pol.repeation
534
+ let beta = pol.arrows
535
+ // discrete integer argument (10{arrows+1}(t) with integer t): climb the
536
+ // raw count C=t-1 at level arrows; C ≤ 9 is the anchor band (one smooth
537
+ // application 10{arrows+1}2 … through arg 9) -> mantissa 2 at β=arrows+1
538
+ let discrete = Math.abs(pol.bottom - 1) < 1e-12 && pol.repeation === Math.floor(pol.repeation)
539
+ if (discrete && t - 1 <= 9 + 1e-9 && t >= 2) {
540
+ return { mant: 2, beta: pol.arrows + 1 }
541
+ }
542
+ if (t >= 9.999999999) {
543
+ // v = 10{arrows+1}(t) = {arrows}^{t-1}(10): the raw level-(arrows)
544
+ // application count is (t-1); run the geometric smooth-log climb
545
+ // from a scalar count all the way to the diagonal band at
546
+ // β = arrows+1. Integer arguments far below the next anchor land at
547
+ // mantissa 2.000 (10{100}10 … 10{100}100 are all 2.000Aa100).
548
+ let n = pol.arrows
549
+ let s = Math.log10(Math.max(t - 1, 1))
550
+ for (let level = 1; level < n + 2; level++) {
551
+ s = (s >= 9.999999999) ? 2 : 1 + Math.log10(s)
552
+ }
553
+ let mant = s < 2 ? 1 + s : s
554
+ return { mant: Math.min(mant, 10), beta: n + 1 }
555
+ }
556
+ if (t < 2) return { mant: 2 * Math.pow(5, t - 1), beta: beta - 1 }
557
+ // t in [2,10): genuinely smooth (fractional compact row, or a non-10 base
558
+ // fixed point such as 3{100}3 -> 2.376) is shown directly; the discrete
559
+ // integer-argument anchor case was handled at the top of the function.
560
+ return { mant: t, beta: beta }
561
+ }
562
+
563
+ // Structural α/diagonal recovery for a value whose finite-level chain (r0
564
+ // cols + [count, level] rows) reaches the Aa diagonal region. Mirrors the
565
+ // reference convention: 3{24}3 → bottom 2.375812, arrows 23 (the "2.376Aa23"
566
+ // display); 3{100}3 → bottom 2.375812, arrows 99 ("Aa2.376Aa99" inner part).
567
+ function polarizeFiniteChain(r0, finiteRows) {
568
+ try {
569
+ finiteRows = normalizedFiniteRows(r0, finiteRows)
570
+ let arr = refPolarizeInput(r0.slice(0), finiteRows)
571
+ if (arr.length === 1) return null
572
+ return refPolarize(arr, true)
573
+ } catch (e) {
574
+ return null
575
+ }
576
+ }
577
+
346
578
  // ─── Main Format Function ────────────────────────────────────────
347
579
 
348
- function format(num, precision=2, small=false) {
580
+ // Parse an already-formatted r0 argument back to a number (e.g. "1,000"
581
+ // → 1000, "1.000E1,000" → null: carries its own E structure)
582
+ function formatR0ArgStr(argStr) {
583
+ var s = String(argStr).replace(/,/g, "")
584
+ if (/^\d+(\.\d+)?$/.test(s)) return Number(s)
585
+ return null
586
+ }
587
+
588
+ function format(num, precision=3, small=false) {
349
589
  if (MetaNum.isNaN(num)) return "NaN"
350
590
  let sciSigDigits = FORMAT_OPTIONS.sciSignificantDigits // for E notation
351
591
  let sciPrecision = Math.max(sciSigDigits, precision)
@@ -383,8 +623,42 @@ function format(num, precision=2, small=false) {
383
623
  return prefix + regularFormat(mant, sciPrecision) + "E-" + commaFormat(expPart)
384
624
  }
385
625
  }
386
- // Tier 2: mag is large → E-<formatted mag (log10 of reciprocal)>
387
- var magStr = format(mag, precision, small)
626
+ // Tier 2: mag is large → "E-" + letter-law form of mag =
627
+ // log10 of the reciprocal base. Letter law (README): a chain
628
+ // of n same-level ops collapses to ONE next-level op with
629
+ // arg+n, so log10 shifts only the OUTERMOST letter:
630
+ // E-chain (EE1000): log10 = E1000 (one E stripped)
631
+ // F-chain (F500): log10 = F499 (arg −1, same letter)
632
+ // Γ ≥ G (G200): log10 = G200 (Γβ−1+1 = Γβ: the leading
633
+ // F(G(β−1)) collapses back to G(β))
634
+ // Roundtrip exact: "E-1.000G200" parses back to 1/G200,
635
+ // "E-1.000F499" to 1/F500 (test_mixed format block)
636
+ var recipStr = format(recipFmt, precision, small)
637
+ var magStr = recipStr
638
+ // leading run of E letters (e.g. "EE1.000E1,000"): strip one E
639
+ var eRun = recipStr.match(/^(E+)(.*)$/)
640
+ var rm = recipStr.match(/^(\d(?:\.\d+)?)([A-Z])((?:\d[\d,]*)?)$/)
641
+ if (eRun && !rm) {
642
+ var restStr = eRun[2]
643
+ if (eRun[1].length >= 2) {
644
+ // "EE…" → "E…" (log10 of an E-chain drops one E)
645
+ magStr = eRun[1].slice(0, -1) + restStr
646
+ } else if (eRun[1].length === 1) {
647
+ // single "EαEβ" / "Eβ": log10 is the β part
648
+ var betaPart = restStr.replace(/^\d(?:\.\d+)?(?=[A-Z])/, "")
649
+ if (/^[A-Z]/.test(betaPart) || /^[\d,]/.test(betaPart)) magStr = betaPart
650
+ }
651
+ } else if (rm) {
652
+ var rAlpha = rm[1], rLetter = rm[2], rArg = rm[3]
653
+ if (rLetter === "E" && rArg !== "") {
654
+ // E-chain with numeric arg (E200): log10 keeps E-arg
655
+ magStr = rAlpha + "E" + rArg
656
+ } else if (rLetter === "F" && rArg !== "") {
657
+ // F-chain: arg −1
658
+ magStr = rAlpha + "F" + commaFormat(Math.max(0, Number(rArg.replace(/,/g, "")) - 1))
659
+ }
660
+ // G and above: display unchanged
661
+ }
388
662
  return prefix + "E-" + magStr
389
663
  }
390
664
  // Format as aE-b (e.g., 0.0000000001 → 1.000E-10)
@@ -465,8 +739,18 @@ function format(num, precision=2, small=false) {
465
739
  if (r0[i] > 0) maxLevel = i
466
740
  }
467
741
 
468
- // ── Handle r0-only with maxLevel >= 23: convert to Aa notation ──
742
+ // ── Handle r0-only with maxLevel >= 23: Aa-diagonal (v2.0) ──
743
+ // The cascade collapses to αAaβ with (bottom, repeation, arrows) from
744
+ // polarizeFiniteChain (the structural climb), mantissa normalized into
745
+ // [1,10) carrying decades into β — identical to the finite-row display
746
+ // so format stays cross-config stable.
469
747
  if (maxLevel >= 23) {
748
+ let pol = polarizeFiniteChain(r0.slice(0), null)
749
+ if (pol && isFinite(pol.bottom) && pol.bottom > 0 && isFinite(pol.arrows)) {
750
+ let pair = aaDiagonalPair(pol)
751
+ return regularFormat(pair.mant, multiPrecision) + "Aa" + commaFormat(pair.beta)
752
+ }
753
+ // fallback: legacy base-9 coefficient form
470
754
  let coeff = r0[maxLevel]
471
755
  let pow9 = 9
472
756
  for (let i = maxLevel - 1; i >= 1; i--) {
@@ -537,17 +821,45 @@ function formatLayer(num, precision, precision2, precision3, precision4) {
537
821
  return innerStr + "ε" + commaFormat(num.layer)
538
822
  }
539
823
 
824
+ // Compact diagonal: the symbol row [1,0…0,1] with m=compactBase zeros is
825
+ // 10{ω^m}10 — a DIAGONAL letter (A + m a's) at argument 10. Diagonal
826
+ // letter combinations use the pure dlsdl α=2·5^f ∈ [2,10) convention:
827
+ // integer m anchors mantissa 2. For layers above ! the symbol of the
828
+ // layer below prefixes the same inner form:
829
+ // !Aa3 = 2.000Aaaa10, @Aa3 = !2.000Aaaa10.
830
+ // multiLetterLimit symbol carry: once the diagonal letter would be longer
831
+ // than the limit (m+1 letters) it switches to one more symbol and the Aa
832
+ // argument takes over the exponent — 10{ω^10}10 → !2.000Aa10, and layer 1
833
+ // on top stacks the symbols (no-symbol → ! → @ → # …):
834
+ // apix(3,10) = 10{ω^(ω^10)}10 → @2.000Aa10.
540
835
  if (canCompact && compactBase >= 2) {
541
- let bottom = Math.pow(10, compactBase - Math.floor(compactBase))
542
- let top = Math.floor(compactBase)
543
- if (top === compactBase) {
544
- // Exact integer, use compact form !Aa[r0[0]]
545
- return sym + regularFormat(bottom, precision4) + "Aa" + commaFormat(top)
836
+ let mInt = Math.floor(compactBase)
837
+ let frac = compactBase - mInt
838
+ let mant = 2 * Math.pow(5, frac)
839
+ let limit = Math.max(2, FORMAT_OPTIONS.multiLetterLimit | 0)
840
+ if (mInt + 1 > limit) {
841
+ let total = num.layer + 1
842
+ let body = regularFormat(mant, precision4) + "Aa" + formatR0Arg(mInt, precision4)
843
+ if (total <= SYMBOLS.length) return SYMBOLS[total - 1] + body
844
+ return body + "ε" + commaFormat(total)
546
845
  }
846
+ // Stored layer L (the parser de-layers small !Aa rows):
847
+ // L=0 (!Aa3) -> no prefix; L=1 (@Aa3) -> "!"; L=2 (#Aa3) -> "@!"
848
+ let prefixSyms = ""
849
+ for (let sl = num.layer; sl >= 1; sl--) prefixSyms += symbolName(sl)
850
+ return prefixSyms + regularFormat(mant, precision4) +
851
+ "A" + "a".repeat(mInt) + "10"
547
852
  }
548
853
 
549
- // For !, @, #, $, %, &, ~, <, >, ? symbols: prefix the symbol
854
+ // For !, @, #, $, %, &, ~, <, >, ? symbols: prefix the symbol. When the
855
+ // inner display itself had to carry (its letter exceeded multiLetterLimit)
856
+ // the symbols stack instead of doubling: layer 1 + inner "!…" → "@…".
550
857
  let innerStr = format(inner, precision, false)
858
+ if (innerStr.length > 1 && SYMBOLS.indexOf(innerStr[0]) >= 0) {
859
+ let total = num.layer + 1
860
+ if (total <= SYMBOLS.length) return SYMBOLS[total - 1] + innerStr.slice(1)
861
+ return innerStr.slice(1) + "ε" + commaFormat(total)
862
+ }
551
863
  return sym + innerStr
552
864
  }
553
865
 
@@ -570,6 +882,69 @@ function formatR0Arg(value, precision) {
570
882
  return regularFormat(m, precision) + "E" + commaFormat(e)
571
883
  }
572
884
 
885
+ // ─── Gamma-Arg Recovery ─────────────────────────────────────────
886
+ // Recover the real argument x with 10{level}x = v (the Γ-canonical argument of
887
+ // v at letter `level`), by bisection on the engine's own smooth arrow curve.
888
+ // Returns null when x exceeds the double-precision bisect range. The doubling
889
+ // guard runs to 200: E-notation arguments (hardy(1120)'s F-arg ≈ 4.4e13) need
890
+ // ~45 doublings before the bracket closes.
891
+ function gammaArgOf(v, level, iters) {
892
+ if (iters === undefined) iters = 60
893
+ let G = function (x) { return MetaNum(10).arrow(level)(x) }
894
+ // v must sit inside [Γ(1), Γ(HUGE)]: find integer hi with Γ(hi) >= v
895
+ let lo = 1, hi = 2
896
+ let guard = 0
897
+ while (G(hi).lt(v)) {
898
+ lo = hi
899
+ hi *= 2
900
+ if (++guard > 200) return null
901
+ }
902
+ // bisect the fractional part
903
+ for (let i = 0; i < iters; i++) {
904
+ let mid = (lo + hi) / 2
905
+ if (G(mid).lt(v)) lo = mid; else hi = mid
906
+ if (hi - lo < 1e-12) break
907
+ }
908
+ return lo
909
+ }
910
+
911
+ // Format a recovered Γ-argument x as the binary form αΓβ:
912
+ // α = 10^frac(x) (in [1, 10)), β = floor(x) (may itself carry E notation).
913
+ // Once β ≥ 10^sciThreshold the α only perturbs β by log10(α) < 1, far below
914
+ // the displayed precision: write the letter bare and let the canonical β
915
+ // carry the single α (hardy(1120) → "F4.398E13").
916
+ function formatGammaBinary(x, level, precision) {
917
+ let sciBound = Math.pow(10, FORMAT_OPTIONS.sciThreshold)
918
+ let beta = Math.floor(x + 1e-12)
919
+ let frac = x - beta
920
+ if (frac < 0) { frac += 1; beta -= 1 }
921
+ if (frac >= 1) { frac -= 1; beta += 1 }
922
+ if (beta >= sciBound) {
923
+ let alpha = Math.pow(10, frac)
924
+ return letterName(level) + formatR0Arg(beta + Math.log10(alpha), precision)
925
+ }
926
+ if (frac < 1e-11 || frac > 1 - 1e-11) {
927
+ // exact integer argument: bare letter + β
928
+ return letterName(level) + formatR0Arg(beta, precision)
929
+ }
930
+ let alpha = Math.pow(10, frac)
931
+ return regularFormat(alpha, precision) + letterName(level) + formatR0Arg(beta, precision)
932
+ }
933
+
934
+ // Distinct letter types in a display string: single-uppercase letters and
935
+ // Aa-style multi-letter tokens, ignoring an E inside a number ("7.626E12"
936
+ // sci notation, not an op) and !/@/# symbol prefixes.
937
+ function distinctLetterTypes(str) {
938
+ let stripped = String(str).replace(/(\d(?:\.\d+)?)E(\d[\d,]*)/g, "$1$2")
939
+ let tokens = stripped.match(/[A-Z][a-z]*/g) || []
940
+ let seen = {}
941
+ let out = []
942
+ for (let i = 0; i < tokens.length; i++) {
943
+ if (!seen[tokens[i]]) { seen[tokens[i]] = true; out.push(tokens[i]) }
944
+ }
945
+ return out
946
+ }
947
+
573
948
  // ─── Format r0 as Chain ────────────────────────────────────────
574
949
  // Formats r0 = [base, e, f, g, ...] as a dlsdl letter-notation chain.
575
950
  // Format pattern: outerLetters + α + lastLetter + β
@@ -591,8 +966,61 @@ function formatR0AsChain(r0, precision) {
591
966
  let count = r0[maxLevel]
592
967
  let effThreshold = Math.max(2, FORMAT_OPTIONS.repeatLetterThreshold)
593
968
 
594
- if (count <= effThreshold) {
595
- // Non-collapse: peel off one letter at maxLevel, recurse on inner
969
+ // Detect a descending run of count-1 letters (e.g. the VUTS…G chain that
970
+ // the engine produces for arrow(3,19,3) = 3{19}3). Such a chain is the
971
+ // diagonalized expansion of the top letter applied twice, so it must be
972
+ // compressed to the ΓαΓβ binary form instead of being spelled out.
973
+ let runLen = 0
974
+ if (count === 1) {
975
+ runLen = 1
976
+ while (maxLevel - runLen >= 2 && r0[maxLevel - runLen] === 1) runLen++
977
+ }
978
+
979
+ // ── ≤2-letter-types rule ──
980
+ // When the full structural spelling would use MORE than two letter
981
+ // types, spell only the top two levels' letters and compress everything
982
+ // below into the second letter's Binary-Canonical form
983
+ // "bottom Γ₂ repeation" — the polarize triple of the chain below the
984
+ // top letter (bisect fails there: the arg exceeds double range):
985
+ // 3{9}3 → "L2.376K2" (user: "L…K…")
986
+ // 3{10}3 → "M2.376L2" (user: "M(10^0.376)L2")
987
+ // 4{9}4 → "LLK3.550K3" (user: "LLK…K…")
988
+ // 5{9}5 → "LLLKK4.669K4" (user: "LLLKK…K…")
989
+ // A top count ≥ repeatLetterThreshold+1 carries to the next single
990
+ // letter instead (6{9}6 → "5.760M5" via the collapse paths below).
991
+ if (maxLevel >= 2 && maxLevel <= 22 && count <= effThreshold) {
992
+ let spellTypes = 0
993
+ for (let s = 2; s <= maxLevel; s++) {
994
+ if ((r0[s] || 0) > 0) spellTypes++
995
+ }
996
+ if (spellTypes > 2) {
997
+ let second = 0
998
+ for (let s = maxLevel - 1; s >= 2; s--) {
999
+ if (r0[s] > 0) { second = s; break }
1000
+ }
1001
+ if (second >= 2) {
1002
+ let c2 = r0[second]
1003
+ if (c2 <= effThreshold) {
1004
+ let restR0 = r0.slice(0)
1005
+ restR0[maxLevel] = 0
1006
+ let pol = polarizeFiniteChain(restR0, null)
1007
+ if (pol && pol.arrows === second && isFinite(pol.bottom) &&
1008
+ pol.bottom >= 1 && isFinite(pol.repeation)) {
1009
+ let prefix = letterName(maxLevel).repeat(count) +
1010
+ letterName(second).repeat(Math.max(c2 - 1, 0))
1011
+ return prefix + regularFormat(pol.bottom, precision) +
1012
+ letterName(second) + commaFormat(pol.repeation)
1013
+ }
1014
+ }
1015
+ }
1016
+ }
1017
+ }
1018
+
1019
+ if (count <= effThreshold && runLen <= effThreshold) {
1020
+ // Non-collapse: peel off one letter at maxLevel, recurse on inner.
1021
+ // Long descending count-1 runs (runLen > threshold) fall through to the
1022
+ // Γ-canonical collapse below (2-letter rule: the diagonal
1023
+ // chain formats at its bisect-exact letter, not spelled out VUTS…).
596
1024
  let innerR0 = r0.slice(0)
597
1025
  innerR0[maxLevel] = count - 1
598
1026
  if (innerR0[maxLevel] === 0) {
@@ -624,11 +1052,27 @@ function formatR0AsChain(r0, precision) {
624
1052
  if ((r0[i] || 0) !== 8) all8 = false
625
1053
  }
626
1054
 
1055
+ // sciThreshold boundary: once the top-level argument reaches 10^sciThreshold
1056
+ // the binary form αΓβ = Γ(β+log α) renders the argument in its own canonical
1057
+ // (αEβ) form and the outer letter is written bare — α must appear only once,
1058
+ // at the innermost value (2-letter rule: "G1.000E12", not "1.000G1.000E12")
1059
+ let sciBound = Math.pow(10, FORMAT_OPTIONS.sciThreshold)
1060
+
627
1061
  if (all8) {
628
1062
  // all-8s promotion: E^count(10^10) → F(count+2), F^count(F10) → G(count+2), etc.
629
1063
  let arg = count + 2
630
1064
  let level = maxLevel + 1
631
1065
  let letter = letterName(level)
1066
+ if (level >= 23) {
1067
+ // Promotion into a diagonal letter: the all-8s r0 is the exact
1068
+ // diagonal 10{L+1}10 = Aa(L+1), whose dlsdl binary form anchors
1069
+ // mantissa 2 (α=2·5^0): 10{23}10 -> "2.000Aa23".
1070
+ return regularFormat(2.0, precision) + "Aa" + commaFormat(maxLevel + 1)
1071
+ }
1072
+ if (arg >= sciBound) {
1073
+ // Γ(β) with β ≥ 10^sciThreshold: bare letter + canonical argument
1074
+ return letter + formatR0Arg(arg, precision)
1075
+ }
632
1076
  // Format: α + letter + β (always show α)
633
1077
  let alpha = Math.pow(10, arg - Math.floor(arg))
634
1078
  if (alpha < 1) alpha *= 10
@@ -637,22 +1081,150 @@ function formatR0AsChain(r0, precision) {
637
1081
  return alphaStr + letter + betaStr
638
1082
  }
639
1083
 
640
- // Non-all-8s promotion: fall back to metaPolarize for the polarized form
1084
+ // ── Γ-canonical recovery (2-letter rule) ──
1085
+ // Two rules, both driven by the engine's own smooth arrow curve:
1086
+ // (a) structural top count < threshold AND inner top < maxLevel: keep
1087
+ // the structural top letter Γᵀ bare and format the inner chain
1088
+ // recursively — at most 2 letter types appear (2-letter rule:
1089
+ // VαEβ → VαFβ → … → VαVβ, never a mixed "VFαEβ").
1090
+ // (b) otherwise: collapse to the single letter Γ = the HIGHEST level L
1091
+ // whose recovered argument x (bisect 10{L}x = v) is ≥ 2, in binary
1092
+ // form αΓβ (2-letter rule: hardy(4166) → "2.397G5", arrow(3,4.3,3) →
1093
+ // "1.285H8", arrow(3,4.1,3) → "G1.161G897").
1094
+ // Verified laws: n ops of level L on a Γ^{L+1}-structured value collapse
1095
+ // to ONE Γ^{L+1} with arg+n (F^4(G(1.3796)) = G(5.3796) — bisect-exact).
641
1096
  let r0Num = new MetaNum({ sign: 1, layer: 0, array: [r0.slice(0)] })
1097
+
1098
+ if (count < effThreshold && maxLevel >= 2 && runLen <= effThreshold) {
1099
+ // (a) structural top letter: peel ONE op of level maxLevel and format
1100
+ // the inner chain recursively (exact even when the argument itself is
1101
+ // letter-structured, e.g. arrow(3,4.1,3) = G(F^895-chain) →
1102
+ // "G" + format(F^895-chain) = "G1.161G897")
1103
+ let innerR0 = r0.slice(0)
1104
+ innerR0[maxLevel] = count - 1
1105
+ if (innerR0[maxLevel] === 0) {
1106
+ innerR0 = innerR0.slice(0, maxLevel)
1107
+ }
1108
+ let innerStr = formatR0AsChain(innerR0, precision)
1109
+ let hasLetter = /[A-Z]/.test(innerStr)
1110
+ if (!hasLetter) {
1111
+ let base = r0[0]
1112
+ let alpha = Math.pow(10, base - Math.floor(base))
1113
+ if (alpha < 1) alpha *= 10
1114
+ return regularFormat(alpha, precision) + letterName(maxLevel) + innerStr
1115
+ }
1116
+ // inner carries letters: bare outer letter (2-letter rule) — the
1117
+ // inner top must be LOWER (descending chain); ascending or same-level
1118
+ // chains fall through to collapse
1119
+ let innerTop = 0
1120
+ for (let it = innerR0.length - 1; it >= 1; it--) {
1121
+ if (innerR0[it] > 0) { innerTop = it; break }
1122
+ }
1123
+ if (innerTop < maxLevel) {
1124
+ // ≤2 letter types rule: count the distinct letter
1125
+ // types the peeled prefix + innerStr would use. If the inner chain
1126
+ // spells a THIRD type, collapse the inner to the binary form at
1127
+ // maxLevel-1 instead — VαEβ → VαFβ → … → VαVβ, never a mixed
1128
+ // "VFαEβ" (e.g. arrow(3,9,3) → "L2.376K2": the MLKJIHG F-chain
1129
+ // spells only L and K, the M..J part collapses into K's arg).
1130
+ let topLetter = letterName(maxLevel)
1131
+ let innerTypes = distinctLetterTypes(innerStr)
1132
+ if (innerTypes.length >= 2
1133
+ && innerTypes.indexOf(topLetter) < 0
1134
+ && innerTypes.indexOf(letterName(innerTop)) < 0) {
1135
+ // inner already uses its own 2 types: cannot keep the structural
1136
+ // letter — collapse below
1137
+ } else {
1138
+ let wouldTypes = {}
1139
+ wouldTypes[topLetter] = true
1140
+ for (let wt = 0; wt < innerTypes.length; wt++) wouldTypes[innerTypes[wt]] = true
1141
+ if (Object.keys(wouldTypes).length <= 2) {
1142
+ return letterName(maxLevel) + innerStr
1143
+ }
1144
+ // 3rd type would appear: collapse the inner chain to the binary
1145
+ // form at innerTop — arg recovered on the engine curve when
1146
+ // possible (level < 20), else structurally via refPolarize
1147
+ let innerNum = new MetaNum({ sign: 1, layer: 0, array: [innerR0.slice(0)] })
1148
+ let bx = (innerTop <= 19) ? gammaArgOf(innerNum, innerTop, 40) : null
1149
+ if (bx !== null && isFinite(bx) && bx >= 2) {
1150
+ return letterName(maxLevel) + formatGammaBinary(bx, innerTop, precision)
1151
+ }
1152
+ let pol2 = polarizeFiniteChain(innerR0, null)
1153
+ if (pol2 && pol2.arrows >= 2 && pol2.arrows <= 22) {
1154
+ let aStr = regularFormat(Math.max(pol2.bottom, 1.0001), precision)
1155
+ return letterName(maxLevel) + aStr + letterName(pol2.arrows) + commaFormat(pol2.repeation)
1156
+ }
1157
+ // polarize reached the multi-letter region: Aa-diagonal form
1158
+ if (pol2 && pol2.arrows >= 23) {
1159
+ let aStr = regularFormat(Math.max(pol2.bottom, 1.0001), precision)
1160
+ return letterName(maxLevel) + aStr + "Aa" + commaFormat(pol2.arrows)
1161
+ }
1162
+ }
1163
+ }
1164
+ }
1165
+
1166
+ // (b) collapse: the structural chain (n repeats of the top letter Γᵀ on
1167
+ // an inner value whose own canonical letter is Γᵀ⁺¹, e.g. F^895(G(2.06)))
1168
+ // compresses to ONE Γᵀ⁺¹ with arg+n — bisect at the STRUCTURAL next
1169
+ // letter first (maxLevel+1); if its arg falls below 2, search upward for
1170
+ // the highest level whose arg ≥ 2 (2-letter rule: hardy(4166) → "2.397G5",
1171
+ // arrow(3,4.3,3) → "1.285H8")
1172
+ let structuralNext = Math.min(maxLevel + 1, 22)
1173
+ let sx = gammaArgOf(r0Num, structuralNext, 40)
1174
+ if (sx !== null && isFinite(sx) && sx >= 2) {
1175
+ return formatGammaBinary(sx, structuralNext, precision)
1176
+ }
1177
+ for (let lv = 22; lv >= 2; lv--) {
1178
+ if (lv === structuralNext) continue
1179
+ let x = gammaArgOf(r0Num, lv, 40)
1180
+ if (x !== null && isFinite(x) && x >= 2) {
1181
+ return formatGammaBinary(x, lv, precision)
1182
+ }
1183
+ }
1184
+
1185
+ // Γ-arg not recoverable as a double (arg beyond bisect range): fall back
1186
+ // to metaPolarize for the polarized form
642
1187
  let pol = metaPolarize(r0.slice(0), r0Num)
643
1188
  let h = pol.height
644
1189
  let rep = pol.repeat || 1
645
1190
  let top = pol.top
646
1191
  let bottom = pol.bottom
647
1192
 
1193
+ if (runLen > effThreshold) {
1194
+ // The descending count-1 run is the diagonalized expansion of the top
1195
+ // letter applied twice: force the ΓαΓβ form (issues.md: arrow(3,19,3)
1196
+ // → "VαVβ", not "VUTS…G…")
1197
+ rep = 2
1198
+ h = maxLevel
1199
+ }
1200
+
648
1201
  // Convert repeated letters to next level when over threshold
649
1202
  if (rep > 1 && rep >= effThreshold) {
650
1203
  let newH = h + 1
651
1204
  let newTop = rep + 1
652
1205
  let newLetter = letterName(newH)
653
- // Format: α + letter + β (always show α)
654
- let alpha = bottom
655
- if (alpha < 1) alpha = Math.pow(10, alpha)
1206
+ if (newTop >= sciBound) {
1207
+ // bare letter + canonical argument (single α at the innermost value)
1208
+ return newLetter + formatR0Arg(newTop + Math.log10(bottom < 1 ? Math.pow(10, bottom) : bottom), precision)
1209
+ }
1210
+ // Format: α + letter + β. Canonical all-8s arrays (built from the
1211
+ // 10^10 base: G600, ZZ10, ...) carry no recoverable mantissa below
1212
+ // 10^10, so α = 1.000 by construction. Every other value recovers
1213
+ // its real α by stripping newTop slog's — the residue lands in
1214
+ // [1,10) and is exactly the α of the binary form αΓβ = Γ(β+log α)
1215
+ // (issues.md: hardy(4150) → "2.300G5", not "1.000G5")
1216
+ let alpha = 1.0
1217
+ let all8s = (r0[0] === 10000000000)
1218
+ for (let ai = 2; ai < pol.height && all8s; ai++) {
1219
+ if ((r0[ai] || 0) !== 8) all8s = false
1220
+ }
1221
+ if (!all8s) {
1222
+ try {
1223
+ let av = r0Num.clone()
1224
+ for (let si = 0; si < newTop && av.gt(MetaNum(1e15)); si++) av = av.slog()
1225
+ if (av.gte(1) && av.lt(10)) alpha = av.toNumber()
1226
+ } catch (e) { /* keep α = 1.0 */ }
1227
+ }
656
1228
  let alphaStr = regularFormat(alpha, precision)
657
1229
  let betaStr = formatR0Arg(newTop, precision)
658
1230
  return alphaStr + newLetter + betaStr
@@ -665,14 +1237,32 @@ function formatR0AsChain(r0, precision) {
665
1237
  // Repeated letters: outerLetters + α + lastLetter + β
666
1238
  let outerLetters = letter.repeat(rep - 1)
667
1239
  let alpha = bottom
668
- if (alpha < 1) alpha = Math.pow(10, alpha)
1240
+ let logAlpha = 0
1241
+ if (alpha < 1) {
1242
+ logAlpha = alpha
1243
+ alpha = Math.pow(10, alpha)
1244
+ } else {
1245
+ logAlpha = Math.log10(alpha)
1246
+ }
1247
+ let betaVal = top + logAlpha
1248
+ if (top >= sciBound) {
1249
+ // bare letters + canonical argument (single α at the innermost value)
1250
+ return outerLetters + letter + formatR0Arg(betaVal, precision)
1251
+ }
669
1252
  let alphaStr = regularFormat(alpha, precision)
670
1253
  let betaStr = formatR0Arg(top, precision)
671
1254
  return outerLetters + alphaStr + letter + betaStr
672
1255
  }
673
- // Single letter: α + letter + β
1256
+ // Single letter: α + letter + β — but once β ≥ 10^sciThreshold the
1257
+ // α only perturbs β by log10(α) < 1, far below the displayed
1258
+ // precision: write the letter bare and let β carry the single α
1259
+ // (issues.md: hardy(1120) → "F4.398E13", not "4.295F4.398E13")
674
1260
  let alpha = bottom
675
1261
  if (alpha < 1) alpha = Math.pow(10, alpha)
1262
+ let betaVal = top + Math.log10(alpha)
1263
+ if (top >= sciBound) {
1264
+ return letter + formatR0Arg(betaVal, precision)
1265
+ }
676
1266
  let alphaStr = regularFormat(alpha, precision)
677
1267
  let betaStr = formatR0Arg(top, precision)
678
1268
  return alphaStr + letter + betaStr
@@ -689,39 +1279,127 @@ function formatR0AsChain(r0, precision) {
689
1279
 
690
1280
  // ─── Format with Ordinal Rows ────────────────────────────────────
691
1281
 
692
- function formatOrdinal(num, precision, precision4) {
693
- let r0 = num.array[0]
694
- let ordRows = num.array.slice(1)
1282
+ // finite-row chain display (≤2-letter-types rule):
1283
+ // r0 cols + [count, level] rows form one hyperoperation cascade. Display:
1284
+ // top level ≥ 23 (Aa region): the αAaβ diagonal — (bottom, repeation,
1285
+ // arrows) from polarizeFiniteChain, mantissa normalized into [1,10)
1286
+ // carrying decades into β: 3{24}3 → "2.376Aa23", 3{100}3 → "2.376Aa99",
1287
+ // 10{10000}10 → "1.000Aa10,000" (bottom 1, repeation 10 carries).
1288
+ // top level ≤ 22 (single letters): the structural two-letter form
1289
+ // Γ_top×c₁ + Γ_second×(c₂−1) + bottom + Γ_second + repeation:
1290
+ // 3{22}3 → "Y2.376X2", 3{23}3 → "Z2.376Y2" (user: "Y…X…", "Z…Y…").
1291
+ // c₁ ≥ repeatLetterThreshold+1 carries to the next single letter.
1292
+ function formatFiniteRowChain(r0, finiteRows, precision) {
1293
+ function levelCount(lv) {
1294
+ if (lv < r0.length && (r0[lv] || 0) > 0) return r0[lv]
1295
+ for (let i = 0; i < finiteRows.length; i++) {
1296
+ if (finiteRows[i][1] === lv) return finiteRows[i][0]
1297
+ }
1298
+ return 0
1299
+ }
1300
+ let topLv = 0, c1 = 0
1301
+ for (let i = 1; i < r0.length; i++) {
1302
+ if ((r0[i] || 0) > 0) { topLv = i; c1 = r0[i] }
1303
+ }
1304
+ for (let i = 0; i < finiteRows.length; i++) {
1305
+ if (finiteRows[i][1] > topLv) { topLv = finiteRows[i][1]; c1 = finiteRows[i][0] }
1306
+ }
1307
+ if (topLv === 0) return formatR0Arg(r0[0], precision)
1308
+ let effThreshold = Math.max(2, FORMAT_OPTIONS.repeatLetterThreshold)
695
1309
 
696
- // Check if all rows are 2-element [count, value] (ω-level operations)
697
- let allTwoElement = true
698
- for (let i = 0; i < ordRows.length; i++) {
699
- if (ordRows[i].length !== 2) { allTwoElement = false; break }
1310
+ if (topLv >= 23) {
1311
+ let pol = polarizeFiniteChain(r0, finiteRows)
1312
+ if (pol && isFinite(pol.bottom) && pol.bottom > 0 && isFinite(pol.arrows)) {
1313
+ let pair = aaDiagonalPair(pol)
1314
+ return regularFormat(pair.mant, precision) + "Aa" + commaFormat(pair.beta)
1315
+ }
1316
+ // fallback: legacy coefficient form on the top row
1317
+ let lastRow = finiteRows[finiteRows.length - 1]
1318
+ if (lastRow) return lastRow[0] + "Aa" + commaFormat(lastRow[1])
1319
+ return formatR0AsChain(r0.slice(0), precision)
700
1320
  }
701
1321
 
702
- if (allTwoElement) {
703
- // 2-element format: [count, value] = count × ω^value
704
- let isTruncated = r0.length === 1 && r0[0] === 10
1322
+ // carry: c₁ ≥ threshold+1 top-letter repeats advance to the next letter
1323
+ if (c1 >= effThreshold + 1) {
1324
+ let lv = topLv + 1
1325
+ if (lv <= 19) {
1326
+ let num = new MetaNum({ sign: 1, layer: 0, array: [r0.slice(0)].concat(finiteRows.map(r => r.slice(0))) })
1327
+ let bx = gammaArgOf(num, lv, 40)
1328
+ if (bx !== null && isFinite(bx) && bx >= 2) return formatGammaBinary(bx, lv, precision)
1329
+ }
1330
+ let pol = polarizeFiniteChain(r0, finiteRows)
1331
+ if (pol && isFinite(pol.bottom) && pol.bottom > 0 && isFinite(pol.arrows)) {
1332
+ if (lv >= 23) {
1333
+ // carry lands on the Aa diagonal: convert the polarize triple
1334
+ // via the same linear mantissa/β rule as the topLv>=23 path
1335
+ // (10{23}10 -> 1.000Aa23, not "1.000Aa10")
1336
+ let pair = aaDiagonalPair(pol)
1337
+ return regularFormat(pair.mant, precision) + "Aa" + commaFormat(pair.beta)
1338
+ }
1339
+ if (pol.bottom >= 1) {
1340
+ return regularFormat(pol.bottom, precision) + letterName(lv) + commaFormat(pol.repeation)
1341
+ }
1342
+ }
1343
+ }
705
1344
 
706
- if (isTruncated) {
707
- // Truncated: show only the last row as count Aa value
708
- let lastRow = ordRows[ordRows.length - 1]
709
- return lastRow[0] + "Aa" + commaFormat(lastRow[1])
1345
+ // structural two-letter form (≤ 22: single-letter region)
1346
+ let secondLv = 0, c2 = 0
1347
+ for (let s = topLv - 1; s >= 1; s--) {
1348
+ let c = levelCount(s)
1349
+ if (c > 0) { secondLv = s; c2 = c; break }
1350
+ }
1351
+ if (secondLv >= 1 && c2 <= effThreshold) {
1352
+ let restR0 = r0.slice(0, Math.min(secondLv + 1, r0.length))
1353
+ let restRows = finiteRows.filter(r => r[1] <= secondLv)
1354
+ let pol = polarizeFiniteChain(restR0, restRows)
1355
+ if (pol && pol.arrows === secondLv && isFinite(pol.bottom) && pol.bottom >= 1) {
1356
+ let prefix = letterName(topLv).repeat(Math.min(c1, effThreshold)) +
1357
+ letterName(secondLv).repeat(Math.max(c2 - 1, 0))
1358
+ return prefix + regularFormat(pol.bottom, precision) + letterName(secondLv) + commaFormat(pol.repeation)
1359
+ }
1360
+ // low levels: engine-curve bisect at the second letter
1361
+ if (secondLv <= 19) {
1362
+ let num = new MetaNum({ sign: 1, layer: 0, array: [restR0].concat(restRows.map(r => r.slice(0))) })
1363
+ let bx = gammaArgOf(num, secondLv, 40)
1364
+ if (bx !== null && isFinite(bx) && bx >= 2) {
1365
+ let prefix = letterName(topLv).repeat(Math.min(c1, effThreshold))
1366
+ return prefix + formatGammaBinary(bx, secondLv, precision)
1367
+ }
710
1368
  }
1369
+ }
711
1370
 
712
- // Non-truncated: show all rows from last to first
713
- let tokens = []
714
- for (let i = ordRows.length - 1; i >= 0; i--) {
715
- let row = ordRows[i]
716
- let tok = row[0] > 1 ? row[0] + "Aa" + commaFormat(row[1]) : "Aa" + commaFormat(row[1])
717
- tokens.push(tok)
1371
+ // fallback: single binary at the top letter
1372
+ let polF = polarizeFiniteChain(r0, finiteRows)
1373
+ if (polF && isFinite(polF.bottom) && polF.bottom >= 1) {
1374
+ return regularFormat(polF.bottom, precision) + letterName(topLv) + commaFormat(polF.repeation)
1375
+ }
1376
+ return formatR0AsChain(r0.slice(0), precision)
1377
+ }
1378
+
1379
+ function formatOrdinal(num, precision, precision4) {
1380
+ let r0 = num.array[0]
1381
+ let ordRows = num.array.slice(1)
1382
+
1383
+ // v2.1 classification: Format-B finite rows [count, level] form the
1384
+ // hyperoperation cascade; Format-A ordinal rows [count, v…, diag] are the
1385
+ // ω-diagonalization markers displayed as letter prefixes.
1386
+ let finiteRows = []
1387
+ let ordinalRows = []
1388
+ for (let i = 0; i < ordRows.length; i++) {
1389
+ if (ordRows[i].length === 2) finiteRows.push(ordRows[i])
1390
+ else ordinalRows.push(ordRows[i])
1391
+ }
1392
+ if (finiteRows.length > 0 && ordinalRows.length === 0) {
1393
+ return formatFiniteRowChain(r0, finiteRows, precision4)
1394
+ }
1395
+ if (finiteRows.length > 0 && ordinalRows.length > 0) {
1396
+ let prefixLetter = getOrdinalLetter(ordinalRows)
1397
+ if (prefixLetter) {
1398
+ return prefixLetter + formatFiniteRowChain(r0, finiteRows, precision4)
718
1399
  }
719
- let base = r0[0]
720
- let bottom = Math.pow(10, base - Math.floor(base))
721
- let top = Math.floor(base)
722
- let baseStr = regularFormat(bottom, precision4)
723
- return tokens.join("") + baseStr
1400
+ // no letter recovered: fall through to the legacy machinery below
724
1401
  }
1402
+ ordRows = ordinalRows
725
1403
 
726
1404
  // Get the letter name from ordinal rows (3+ element format)
727
1405
  // ── Check for ω-level rows (diag=1, all vals=0) → J-like format ──
@@ -788,9 +1466,31 @@ function formatOrdinal(num, precision, precision4) {
788
1466
  return regularFormat(mantissa, precision4) + finalLetter + commaFormat(finalParam)
789
1467
  } else if (totalAaCount > 1) {
790
1468
  // Repeated Aa below threshold: use AaAa... prefix
791
- return letter.repeat(totalAaCount) + argStr
1469
+ let tokAa = letterTokenOf(baseHeight)
1470
+ if (tokAa.sym) {
1471
+ // multiLetterLimit symbol carry: the diagonal letter reads
1472
+ // as one more symbol with the chain as its argument
1473
+ return tokAa.sym + regularFormat(2, precision4) + tokAa.letter + argStr
1474
+ }
1475
+ // past the collapse threshold the repeats shift the argument and
1476
+ // advance the letter — a raw repeat would try to build a string
1477
+ // with `count` copies (Aaa repeated 1e9 times cannot be built)
1478
+ let repArgStr = argStr
1479
+ if (totalAaCount > 64) {
1480
+ let r0Bottom = Math.pow(10, r0[0] - Math.floor(r0[0]))
1481
+ let repParam = totalAaCount + 1
1482
+ let mantissa = Math.log10(Math.max(r0Bottom, 0.001)) +
1483
+ Math.log10(Math.max(repParam, 1)) * 0.4 + 1.6
1484
+ return regularFormat(mantissa, precision4) +
1485
+ letterName(baseHeight + 1) + commaFormat(repParam)
1486
+ }
1487
+ return letter.repeat(totalAaCount) + repArgStr
792
1488
  } else {
793
1489
  // Single Aa
1490
+ let tokAa = letterTokenOf(baseHeight)
1491
+ if (tokAa.sym) {
1492
+ return tokAa.sym + regularFormat(2, precision4) + tokAa.letter + argStr
1493
+ }
794
1494
  return letter + argStr
795
1495
  }
796
1496
  } else {
@@ -806,18 +1506,39 @@ function formatOrdinal(num, precision, precision4) {
806
1506
  let collapseAt = effThreshold + 1
807
1507
 
808
1508
  if (totalAaCount >= collapseAt) {
809
- // Collapse to next letter (Aa→Ab, Ab→Ac, etc.)
810
- let mantissa = Math.log10(Math.max(alpha, 0.001)) + totalAaCount
1509
+ // Collapse to next letter (Aa→Ab, Ab→Ac, etc.): the repeats
1510
+ // shift the argument (arg = count+1) and the mantissa keeps
1511
+ // the [1,10) convention (2.398Ab99 — the same α the GRAHAMS
1512
+ // branch above produces), never the raw count as α
1513
+ let arg = totalAaCount + 1
1514
+ let mantVal = Math.log10(Math.max(alpha, 0.001)) +
1515
+ Math.log10(Math.max(arg, 1)) * 0.4 + 1.6
811
1516
  let newHeight = baseHeight + 1
812
1517
  let newLetter = letterName(newHeight)
813
- return regularFormat(mantissa, precision4) + newLetter + betaStr
1518
+ return regularFormat(mantVal, precision4) + newLetter + formatR0Arg(arg, precision4)
814
1519
  } else if (totalAaCount > 1) {
815
1520
  // Repeated letters: outerLetters + α + lastLetter + β
816
1521
  let outerLetters = letter.repeat(totalAaCount - 1)
817
1522
  return outerLetters + alphaStr + letter + betaStr
818
1523
  } else {
819
- // Single letter: α + letter + β
820
- return alphaStr + letter + betaStr
1524
+ // Single DIAGONAL letter (the row ends in 'a': Aa, Aaa,
1525
+ // Ba, …): pure dlsdl convention α=2·5^f ∈ [2,10).
1526
+ // - symbol-de-layered row (base == #zeros, e.g. !Aa3): the
1527
+ // base is the LETTER INDEX, argument is fixed 10
1528
+ // (!Aa3 -> 2.000Aaaa10)
1529
+ // - ordinary letter application: base is the argument β
1530
+ // (Aa100 -> 2.000Aa100)
1531
+ let diagMant = 2 * Math.pow(5, base - Math.floor(base))
1532
+ let diagBeta = (base === aaZeroCount) ? 10 : top
1533
+ // multiLetterLimit symbol carry: A + aaZeroCount a's is
1534
+ // aaZeroCount+1 letters — past the limit the diagonal reads
1535
+ // as one more symbol with the Aa argument taking the exponent
1536
+ // (10{ω^10}10 → !2.000Aa10)
1537
+ if (aaZeroCount + 1 > Math.max(2, FORMAT_OPTIONS.multiLetterLimit | 0)) {
1538
+ return "!" + regularFormat(diagMant, precision4) +
1539
+ "Aa" + formatR0Arg(aaZeroCount, precision4)
1540
+ }
1541
+ return regularFormat(diagMant, precision4) + letter + commaFormat(diagBeta)
821
1542
  }
822
1543
  }
823
1544
  }
@@ -839,8 +1560,12 @@ function formatOrdinal(num, precision, precision4) {
839
1560
  // Calculate height for this letter
840
1561
  let height = 0
841
1562
  if (nVals === 1) {
842
- // 2-letter: Aa=23, Ab=24, ..., Az=48, Ba=49, ...
843
- height = 23 + (diag - 1) * 26 + vals[0]
1563
+ // 2-letter: Aa=23, Ab=24, ..., Az=48, Ba=49, ... A coefficient
1564
+ // above 25 (ω+81, ω+99, … from a ω*2 fundamental sequence) has no
1565
+ // letter in the grid — clamp down to the largest letter below it
1566
+ // rather than wrapping into a higher, wrong letter.
1567
+ let v0 = vals[0] > 25 ? 25 : vals[0]
1568
+ height = 23 + (diag - 1) * 26 + v0
844
1569
  } else {
845
1570
  // 3+ letters: need to calculate offset
846
1571
  // vals are stored in reverse letter order (e.g. "bc" -> [2,1]),
@@ -885,14 +1610,26 @@ function formatOrdinal(num, precision, precision4) {
885
1610
  // r0 has E/F/G levels: format as letter(s) + chain (e.g., Ab1.000E500)
886
1611
  let r0Str = formatR0AsChain(r0, precision4)
887
1612
  let letter = letterName(baseLetterHeight)
1613
+ let tokAdv = letterTokenOf(baseLetterHeight + 1)
1614
+ if (totalRepeat >= collapseAt && tokAdv.sym) {
1615
+ // Collapse to next letter — carried when it exceeds the limit;
1616
+ // the r0 chain stays as the β of the carried form
1617
+ return tokAdv.sym + regularFormat(tokAdv.mant, precision4) +
1618
+ tokAdv.letter + r0Str
1619
+ }
888
1620
  if (totalRepeat >= collapseAt) {
889
- // Collapse to next letter
1621
+ // Collapse to next letter — the repeat count shifts the
1622
+ // argument, so the mantissa keeps the [1,10) convention
890
1623
  let mantissa = Math.log10(Math.max(alpha, 0.001)) + totalRepeat
1624
+ let mantVal = Math.pow(10, mantissa - Math.floor(mantissa))
1625
+ if (mantVal < 1) mantVal *= 10
891
1626
  let newLetter = letterName(baseLetterHeight + 1)
892
- return regularFormat(mantissa, precision4) + newLetter + r0Str
1627
+ return regularFormat(mantVal, precision4) + newLetter + r0Str
893
1628
  } else if (totalRepeat > 1) {
894
- // Repeated letters + chain
895
- let outerLetters = letter.repeat(totalRepeat - 1)
1629
+ // Repeated letters + chain: one token per application, the
1630
+ // innermost one in front of the chain (Ba(Ba(1e16)) → the
1631
+ // stacked "BaBa1.000E16" — apea(apea(a)) must stay visible)
1632
+ let outerLetters = letter.repeat(totalRepeat)
896
1633
  return outerLetters + r0Str
897
1634
  } else {
898
1635
  // Single letter + chain
@@ -902,19 +1639,56 @@ function formatOrdinal(num, precision, precision4) {
902
1639
 
903
1640
  // Simple r0 (no E/F/G levels)
904
1641
  if (totalRepeat >= collapseAt) {
905
- // Advance to next letter with mantissa
1642
+ // Advance to next letter with mantissa — carried when the letter
1643
+ // exceeds multiLetterLimit (!αAaβ, repeats below display precision)
1644
+ let tokAdv = letterTokenOf(baseLetterHeight + 1)
1645
+ if (tokAdv.sym) {
1646
+ return emitLetterToken(tokAdv, regularFormat(tokAdv.mant, precision4), betaStr, precision4)
1647
+ }
906
1648
  let mantissa = Math.log10(Math.max(alpha, 0.001)) + totalRepeat
1649
+ // the repeat count shifts the argument, so the mantissa keeps the
1650
+ // [1,10) convention (the ω-level form recovers the base mantissa
1651
+ // the way the single-row Aa cascade does: 2.398Ab99)
1652
+ let mantVal = (baseLetterHeight === 23)
1653
+ ? Math.log10(Math.max(alpha, 0.001)) +
1654
+ Math.log10(Math.max(totalRepeat + 1, 1)) * 0.4 + 1.6
1655
+ : Math.pow(10, mantissa - Math.floor(mantissa))
1656
+ if (mantVal < 1) mantVal *= 10
907
1657
  let newHeight = baseLetterHeight + 1
908
1658
  let letter = letterName(newHeight)
909
- return regularFormat(mantissa, precision4) + letter + betaStr
1659
+ return regularFormat(mantVal, precision4) + letter +
1660
+ formatR0Arg(totalRepeat + 1, precision4)
910
1661
  } else if (totalRepeat > 1) {
911
1662
  // Repeated letters: outerLetters + α + lastLetter + β
912
1663
  let letter = letterName(baseLetterHeight)
1664
+ let tokRep = letterTokenOf(baseLetterHeight)
1665
+ if (tokRep.sym) {
1666
+ return emitLetterToken(tokRep, alphaStr, betaStr, precision4)
1667
+ }
913
1668
  let outerLetters = letter.repeat(totalRepeat - 1)
914
1669
  return outerLetters + alphaStr + letter + betaStr
915
1670
  } else {
916
- // Single letter: α + letter + β
1671
+ // Single letter. DIAGONAL letters (token ends in 'a': Aa, Ba,
1672
+ // Ca, Aaa, …) use the dlsdl [2,10) mantissa 2·5^f with β = base
1673
+ // argument (Ba1000 -> 2.000Ba1,000); successor letters (Ab, Bb,
1674
+ // … keep the ordinary [1,10) α/β form).
917
1675
  let letter = letterName(baseLetterHeight)
1676
+ // multiLetterLimit symbol carry for a single long letter: the
1677
+ // diagonal keeps its 2·5^f mantissa, the ordinary letters use the
1678
+ // definition digits (!αAaβ), and β becomes the ω-exponent
1679
+ let tokSingle = letterTokenOf(baseLetterHeight)
1680
+ if (tokSingle.sym) {
1681
+ if (letter.length >= 2 && letter[letter.length - 1] === "a") {
1682
+ let diagMant = 2 * Math.pow(5, base - Math.floor(base))
1683
+ return tokSingle.sym + regularFormat(diagMant, precision4) +
1684
+ tokSingle.letter + formatR0Arg(tokSingle.arg, precision4)
1685
+ }
1686
+ return emitLetterToken(tokSingle, alphaStr, betaStr, precision4)
1687
+ }
1688
+ if (letter.length >= 2 && letter[letter.length - 1] === "a") {
1689
+ let diagMant = 2 * Math.pow(5, base - Math.floor(base))
1690
+ return regularFormat(diagMant, precision4) + letter + betaStr
1691
+ }
918
1692
  return alphaStr + letter + betaStr
919
1693
  }
920
1694
  }
@@ -939,12 +1713,22 @@ function formatOrdinal(num, precision, precision4) {
939
1713
  let vals = row.slice(1, row.length - 1)
940
1714
  let nVals = vals.length
941
1715
  if (nVals === 1) {
942
- height = 23 + (diag - 1) * 26 + vals[0]
1716
+ // the letter grid holds ω·d+v with 0 ≤ v ≤ 25; a larger
1717
+ // coefficient (a ω*2 fundamental sequence produces ω+81,
1718
+ // ω+99, …) has no letter, so clamp it down to the largest
1719
+ // letter below the ordinal instead of wrapping into a
1720
+ // higher, wrong letter (ω+81 must not display as ω*4+3)
1721
+ let v0 = vals[0] > 25 ? 25 : vals[0]
1722
+ height = 23 + (diag - 1) * 26 + v0
943
1723
  } else {
1724
+ // vals are the CNF coefficients a1, a2, …, a(x-1) of
1725
+ // ord = ω^(x-1)·ax + … + ω·a2 + a1 — little-endian, the same
1726
+ // order letterName()/getOrdinalLetter() decode the letter:
1727
+ // the LAST val is the first lowercase letter of the token.
944
1728
  let offset = 0
945
1729
  for (let k = 2; k <= nVals; k++) offset += Math.pow(26, k)
946
1730
  let n = (diag - 1) * Math.pow(26, nVals)
947
- for (let j = 0; j < nVals; j++) n += vals[j] * Math.pow(26, nVals - 1 - j)
1731
+ for (let j = 0; j < nVals; j++) n += vals[j] * Math.pow(26, j)
948
1732
  height = 23 + offset + n
949
1733
  }
950
1734
  }
@@ -955,72 +1739,107 @@ function formatOrdinal(num, precision, precision4) {
955
1739
  letterCounts[height] = (letterCounts[height] || 0) + count
956
1740
  }
957
1741
 
958
- if (heights.length >= 2) {
1742
+ if (heights.length >= 1) {
959
1743
  // Sort heights ascending
960
1744
  heights.sort((a, b) => a - b)
961
1745
 
962
- // Collapse from lowest to highest
963
- let effThreshold = Math.max(2, FORMAT_OPTIONS.multiLetterRepeatThreshold)
964
- let collapseAt = effThreshold + 1
965
- let collapseInfo = {} // height -> collapsedFromCount
966
-
967
- for (let h of heights.slice()) {
968
- let count = letterCounts[h] || 0
969
- if (count >= collapseAt) {
970
- let nextH = h + 1
971
- if (!(nextH in letterCounts) && heights.indexOf(nextH) < 0) heights.push(nextH)
972
- letterCounts[nextH] = (letterCounts[nextH] || 0) + 1
973
- collapseInfo[nextH] = count
974
- delete letterCounts[h]
975
- }
1746
+ // README: at most TWO finite letter types survive — a longer
1747
+ // cascade keeps only its top two levels, every lower row
1748
+ // compresses into the second letter's argument (they are below
1749
+ // display precision, and folding them into its repeat COUNT would
1750
+ // overstate the level: 8 ω's + 8 (ω+1)'s ≠ 16 more (ω+2)'s).
1751
+ if (heights.length > 2) {
1752
+ let keepLo = heights[heights.length - 2]
1753
+ for (let i = 0; i < heights.length - 2; i++) delete letterCounts[heights[i]]
1754
+ heights = Object.keys(letterCounts).map(Number).sort((a, b) => a - b)
976
1755
  }
977
1756
 
978
- // Re-sort after collapse
979
- heights = Object.keys(letterCounts).map(Number).sort((a, b) => a - b)
980
-
981
1757
  if (heights.length >= 1) {
982
- // Compute r0 alpha
1758
+ // ── Display rules (README "format-metanum rules (v2.0)") ──
1759
+ // * the rows are a COMPOSITION: the top row is the outermost
1760
+ // operation, so it leads the display and the row below it
1761
+ // supplies the single α and β (at most TWO letter types —
1762
+ // every lower row compresses into that inner argument);
1763
+ // * "+count collapse": count >= multiLetterRepeatThreshold+1
1764
+ // repeats of a letter collapse to ONE next letter with
1765
+ // arg count+1 (F⁴(G(1.3796)) = G(5.3796));
1766
+ // * stacking the same function therefore nests the letter:
1767
+ // poea(10,poea(10,100)) = Ad(Ad(99)) → "Ad1.000Ad99";
1768
+ // * a combination longer than multiLetterLimit carries to the
1769
+ // symbol notation !αAaβ.
1770
+ let effTh = Math.max(2, FORMAT_OPTIONS.multiLetterRepeatThreshold)
1771
+ let collapseAt = effTh + 1
1772
+
983
1773
  let base = r0[0]
984
1774
  let r0Alpha = Math.pow(10, base - Math.floor(base))
985
1775
  if (r0Alpha < 1) r0Alpha *= 10
986
-
987
- // Find the innermost height (lowest) that has collapse info
988
- let innerHeight = heights[0] // lowest = innermost
989
- let collapseCount = collapseInfo[innerHeight] || 0
990
-
991
- let alphaStr, betaStr
992
- if (collapseCount > 0) {
993
- // Innermost letter collapsed: α and β come from the mantissa
994
- // α = 10^(fractional part) ∈ [1, 10), β = floor(mantissa) (integer)
995
- let mantissa = Math.log10(Math.max(r0Alpha, 0.001)) + collapseCount
996
- let alphaVal = Math.pow(10, mantissa - Math.floor(mantissa))
997
- if (alphaVal < 1) alphaVal *= 10
998
- alphaStr = regularFormat(alphaVal, precision4)
999
- betaStr = commaFormat(Math.floor(mantissa))
1000
- } else {
1001
- // No collapse at innermost: α and β come directly from r0[0]
1002
- alphaStr = regularFormat(r0Alpha, precision4)
1003
- betaStr = formatR0Arg(Math.floor(base), precision4)
1776
+ let frac = base - Math.floor(base)
1777
+ if (frac < 0) frac += 1
1778
+
1779
+ // α of a "+count collapse" (the ω-level form recovers the base
1780
+ // mantissa the way the single-row Aa cascade does: 2.398Ab99)
1781
+ let collapseMant = function (h, count) {
1782
+ let arg = count + 1
1783
+ if (h === 23) {
1784
+ return regularFormat(Math.log10(Math.max(r0Alpha, 0.001)) +
1785
+ Math.log10(Math.max(arg, 1)) * 0.4 + 1.6, precision4)
1786
+ }
1787
+ return regularFormat(r0Alpha, precision4)
1788
+ }
1789
+ // one collapsed level: α + next letter + (count+1)
1790
+ let collapseStr = function (h, count) {
1791
+ let tok = letterTokenOf(h + 1)
1792
+ if (tok.sym) {
1793
+ return tok.sym + regularFormat(tok.mant, precision4) +
1794
+ tok.letter + formatR0Arg(count + 1, precision4)
1795
+ }
1796
+ return collapseMant(h, count) + tok.letter + formatR0Arg(count + 1, precision4)
1004
1797
  }
1005
1798
 
1006
- // Build output: outerLetters + alphaStr + lastLetter + betaStr
1007
- // Descending order (highest first = outermost)
1008
- let descHeights = heights.slice().reverse()
1009
- let lastHeight = descHeights[descHeights.length - 1] // lowest = innermost
1010
- let lastLetter = letterName(lastHeight)
1011
- let lastCount = letterCounts[lastHeight] || 1
1012
-
1013
- let outerLetters = ""
1014
- for (let i = 0; i < descHeights.length - 1; i++) {
1015
- let h = descHeights[i]
1016
- let cnt = letterCounts[h] || 1
1017
- outerLetters += letterName(h).repeat(cnt)
1799
+ let hTop = heights[heights.length - 1]
1800
+ let cTop = letterCounts[hTop] || 1
1801
+ let hLow = heights.length >= 2 ? heights[heights.length - 2] : -1
1802
+ let cLow = hLow < 0 ? 0 : (letterCounts[hLow] || 1)
1803
+
1804
+ // (1) a single row level: collapse, or repeats with α + letter + β
1805
+ if (hLow < 0) {
1806
+ if (cTop >= collapseAt) return collapseStr(hTop, cTop)
1807
+ let tok = letterTokenOf(hTop)
1808
+ if (tok.sym) return emitLetterToken(tok, regularFormat(r0Alpha, precision4),
1809
+ formatR0Arg(Math.floor(base), precision4), precision4)
1810
+ let aStr = (tok.letter.length >= 2 && tok.letter[tok.letter.length - 1] === "a")
1811
+ ? regularFormat(2 * Math.pow(5, frac), precision4)
1812
+ : regularFormat(r0Alpha, precision4)
1813
+ return tok.letter.repeat(Math.max(cTop - 1, 0)) + aStr + tok.letter +
1814
+ formatR0Arg(Math.floor(base), precision4)
1018
1815
  }
1019
- if (lastCount > 1) {
1020
- outerLetters += lastLetter.repeat(lastCount - 1)
1816
+
1817
+ // (2) the inner (second) level carries the single α and β
1818
+ let lowTok = letterTokenOf(hLow)
1819
+ let innerStr
1820
+ if (cLow >= collapseAt) {
1821
+ innerStr = collapseStr(hLow, cLow)
1822
+ } else {
1823
+ let aStr = regularFormat(r0Alpha, precision4)
1824
+ innerStr = lowTok.letter.repeat(Math.max(cLow - 1, 0)) + aStr + lowTok.letter +
1825
+ formatR0Arg(Math.floor(base), precision4)
1021
1826
  }
1022
1827
 
1023
- return outerLetters + alphaStr + lastLetter + betaStr
1828
+ // (3) the top level leads: collapse when it repeats enough, else
1829
+ // one token per application (or the symbol-carried !αAaβ)
1830
+ if (cTop >= collapseAt) return collapseStr(hTop, cTop)
1831
+ let topTok = letterTokenOf(hTop)
1832
+ if (topTok.sym) {
1833
+ // multiLetterLimit carry: !αAaβ, β = the ω-exponent; the
1834
+ // inner level only survives when it collapses on its own
1835
+ if (cLow >= collapseAt) {
1836
+ return topTok.sym + regularFormat(topTok.mant, precision4) +
1837
+ topTok.letter + collapseStr(hLow, cLow)
1838
+ }
1839
+ return topTok.sym + regularFormat(topTok.mant, precision4) +
1840
+ topTok.letter + formatR0Arg(topTok.arg, precision4)
1841
+ }
1842
+ return topTok.letter.repeat(Math.min(cTop, effTh)) + innerStr
1024
1843
  }
1025
1844
  }
1026
1845
  }
@@ -1089,5 +1908,5 @@ function formatSmall(num, precision=2) {
1089
1908
  // ─── Exports ─────────────────────────────────────────────────────
1090
1909
 
1091
1910
  if (typeof module !== 'undefined' && module.exports) {
1092
- module.exports = { format, formatWhole, formatSmall, metaPolarize, letterName, symbolName, getOrdinalLetter, formatR0AsChain, FORMAT_OPTIONS }
1911
+ module.exports = { format, formatWhole, formatSmall, metaPolarize, letterName, symbolName, getOrdinalLetter, formatR0AsChain, FORMAT_OPTIONS, polarizeFiniteChain, gammaArgOf, formatGammaBinary }
1093
1912
  }