metanum 1.4.0 → 2.0.0

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)
710
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
+ }
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,19 @@ 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
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
+ }
791
1475
  return letter.repeat(totalAaCount) + argStr
792
1476
  } else {
793
1477
  // Single Aa
1478
+ let tokAa = letterTokenOf(baseHeight)
1479
+ if (tokAa.sym) {
1480
+ return tokAa.sym + regularFormat(2, precision4) + tokAa.letter + argStr
1481
+ }
794
1482
  return letter + argStr
795
1483
  }
796
1484
  } else {
@@ -816,8 +1504,24 @@ function formatOrdinal(num, precision, precision4) {
816
1504
  let outerLetters = letter.repeat(totalAaCount - 1)
817
1505
  return outerLetters + alphaStr + letter + betaStr
818
1506
  } else {
819
- // Single letter: α + letter + β
820
- return alphaStr + letter + betaStr
1507
+ // Single DIAGONAL letter (the row ends in 'a': Aa, Aaa,
1508
+ // Ba, …): pure dlsdl convention α=2·5^f ∈ [2,10).
1509
+ // - symbol-de-layered row (base == #zeros, e.g. !Aa3): the
1510
+ // base is the LETTER INDEX, argument is fixed 10
1511
+ // (!Aa3 -> 2.000Aaaa10)
1512
+ // - ordinary letter application: base is the argument β
1513
+ // (Aa100 -> 2.000Aa100)
1514
+ let diagMant = 2 * Math.pow(5, base - Math.floor(base))
1515
+ let diagBeta = (base === aaZeroCount) ? 10 : top
1516
+ // multiLetterLimit symbol carry: A + aaZeroCount a's is
1517
+ // aaZeroCount+1 letters — past the limit the diagonal reads
1518
+ // as one more symbol with the Aa argument taking the exponent
1519
+ // (10{ω^10}10 → !2.000Aa10)
1520
+ if (aaZeroCount + 1 > Math.max(2, FORMAT_OPTIONS.multiLetterLimit | 0)) {
1521
+ return "!" + regularFormat(diagMant, precision4) +
1522
+ "Aa" + formatR0Arg(aaZeroCount, precision4)
1523
+ }
1524
+ return regularFormat(diagMant, precision4) + letter + commaFormat(diagBeta)
821
1525
  }
822
1526
  }
823
1527
  }
@@ -885,6 +1589,13 @@ function formatOrdinal(num, precision, precision4) {
885
1589
  // r0 has E/F/G levels: format as letter(s) + chain (e.g., Ab1.000E500)
886
1590
  let r0Str = formatR0AsChain(r0, precision4)
887
1591
  let letter = letterName(baseLetterHeight)
1592
+ let tokAdv = letterTokenOf(baseLetterHeight + 1)
1593
+ if (totalRepeat >= collapseAt && tokAdv.sym) {
1594
+ // Collapse to next letter — carried when it exceeds the limit;
1595
+ // the r0 chain stays as the β of the carried form
1596
+ return tokAdv.sym + regularFormat(tokAdv.mant, precision4) +
1597
+ tokAdv.letter + r0Str
1598
+ }
888
1599
  if (totalRepeat >= collapseAt) {
889
1600
  // Collapse to next letter
890
1601
  let mantissa = Math.log10(Math.max(alpha, 0.001)) + totalRepeat
@@ -902,7 +1613,12 @@ function formatOrdinal(num, precision, precision4) {
902
1613
 
903
1614
  // Simple r0 (no E/F/G levels)
904
1615
  if (totalRepeat >= collapseAt) {
905
- // Advance to next letter with mantissa
1616
+ // Advance to next letter with mantissa — carried when the letter
1617
+ // exceeds multiLetterLimit (!αAaβ, repeats below display precision)
1618
+ let tokAdv = letterTokenOf(baseLetterHeight + 1)
1619
+ if (tokAdv.sym) {
1620
+ return emitLetterToken(tokAdv, regularFormat(tokAdv.mant, precision4), betaStr, precision4)
1621
+ }
906
1622
  let mantissa = Math.log10(Math.max(alpha, 0.001)) + totalRepeat
907
1623
  let newHeight = baseLetterHeight + 1
908
1624
  let letter = letterName(newHeight)
@@ -910,11 +1626,34 @@ function formatOrdinal(num, precision, precision4) {
910
1626
  } else if (totalRepeat > 1) {
911
1627
  // Repeated letters: outerLetters + α + lastLetter + β
912
1628
  let letter = letterName(baseLetterHeight)
1629
+ let tokRep = letterTokenOf(baseLetterHeight)
1630
+ if (tokRep.sym) {
1631
+ return emitLetterToken(tokRep, alphaStr, betaStr, precision4)
1632
+ }
913
1633
  let outerLetters = letter.repeat(totalRepeat - 1)
914
1634
  return outerLetters + alphaStr + letter + betaStr
915
1635
  } else {
916
- // Single letter: α + letter + β
1636
+ // Single letter. DIAGONAL letters (token ends in 'a': Aa, Ba,
1637
+ // Ca, Aaa, …) use the dlsdl [2,10) mantissa 2·5^f with β = base
1638
+ // argument (Ba1000 -> 2.000Ba1,000); successor letters (Ab, Bb,
1639
+ // … keep the ordinary [1,10) α/β form).
917
1640
  let letter = letterName(baseLetterHeight)
1641
+ // multiLetterLimit symbol carry for a single long letter: the
1642
+ // diagonal keeps its 2·5^f mantissa, the ordinary letters use the
1643
+ // definition digits (!αAaβ), and β becomes the ω-exponent
1644
+ let tokSingle = letterTokenOf(baseLetterHeight)
1645
+ if (tokSingle.sym) {
1646
+ if (letter.length >= 2 && letter[letter.length - 1] === "a") {
1647
+ let diagMant = 2 * Math.pow(5, base - Math.floor(base))
1648
+ return tokSingle.sym + regularFormat(diagMant, precision4) +
1649
+ tokSingle.letter + formatR0Arg(tokSingle.arg, precision4)
1650
+ }
1651
+ return emitLetterToken(tokSingle, alphaStr, betaStr, precision4)
1652
+ }
1653
+ if (letter.length >= 2 && letter[letter.length - 1] === "a") {
1654
+ let diagMant = 2 * Math.pow(5, base - Math.floor(base))
1655
+ return regularFormat(diagMant, precision4) + letter + betaStr
1656
+ }
918
1657
  return alphaStr + letter + betaStr
919
1658
  }
920
1659
  }
@@ -959,6 +1698,18 @@ function formatOrdinal(num, precision, precision4) {
959
1698
  // Sort heights ascending
960
1699
  heights.sort((a, b) => a - b)
961
1700
 
1701
+ // README: at most TWO finite letter types survive — a longer
1702
+ // cascade keeps only its top two levels and compresses the rest
1703
+ // into the second letter's binary form (its repeat count).
1704
+ if (heights.length > 2) {
1705
+ let keepLo = heights[heights.length - 2]
1706
+ for (let i = 0; i < heights.length - 2; i++) {
1707
+ letterCounts[keepLo] = (letterCounts[keepLo] || 0) + (letterCounts[heights[i]] || 0)
1708
+ delete letterCounts[heights[i]]
1709
+ }
1710
+ heights = Object.keys(letterCounts).map(Number).sort((a, b) => a - b)
1711
+ }
1712
+
962
1713
  // Collapse from lowest to highest
963
1714
  let effThreshold = Math.max(2, FORMAT_OPTIONS.multiLetterRepeatThreshold)
964
1715
  let collapseAt = effThreshold + 1
@@ -979,48 +1730,73 @@ function formatOrdinal(num, precision, precision4) {
979
1730
  heights = Object.keys(letterCounts).map(Number).sort((a, b) => a - b)
980
1731
 
981
1732
  if (heights.length >= 1) {
982
- // Compute r0 alpha
1733
+ // ── Display rules (README "format-metanum rules (v2.0)") ──
1734
+ // * at most TWO finite letter types survive (VαEβ … VαVβ):
1735
+ // a longer cascade keeps its top two levels and compresses
1736
+ // the rest into the second letter's binary form;
1737
+ // * exactly one α, attached to the innermost (lowest) letter;
1738
+ // * a combination longer than multiLetterLimit carries to the
1739
+ // symbol notation !αAaβ instead of growing another letter.
1740
+ let effTh = Math.max(2, FORMAT_OPTIONS.multiLetterRepeatThreshold)
1741
+
1742
+ // (1) the single α / β pair
983
1743
  let base = r0[0]
984
1744
  let r0Alpha = Math.pow(10, base - Math.floor(base))
985
1745
  if (r0Alpha < 1) r0Alpha *= 10
1746
+ let frac = base - Math.floor(base)
1747
+ if (frac < 0) frac += 1
986
1748
 
987
- // Find the innermost height (lowest) that has collapse info
988
- let innerHeight = heights[0] // lowest = innermost
989
- let collapseCount = collapseInfo[innerHeight] || 0
1749
+ let collapseCount = 0
1750
+ for (let ci = 0; ci < heights.length; ci++) {
1751
+ collapseCount += collapseInfo[heights[ci]] || 0
1752
+ }
990
1753
 
991
1754
  let alphaStr, betaStr
992
1755
  if (collapseCount > 0) {
993
- // Innermost letter collapsed: α and β come from the mantissa
994
- // α = 10^(fractional part) ∈ [1, 10), β = floor(mantissa) (integer)
1756
+ // Collapsed repeats: α and β come from the mantissa
1757
+ // α = 10^(fractional part) ∈ [1, 10), β = floor(mantissa)
995
1758
  let mantissa = Math.log10(Math.max(r0Alpha, 0.001)) + collapseCount
996
1759
  let alphaVal = Math.pow(10, mantissa - Math.floor(mantissa))
997
1760
  if (alphaVal < 1) alphaVal *= 10
998
1761
  alphaStr = regularFormat(alphaVal, precision4)
999
1762
  betaStr = commaFormat(Math.floor(mantissa))
1000
1763
  } else {
1001
- // No collapse at innermost: α and β come directly from r0[0]
1002
1764
  alphaStr = regularFormat(r0Alpha, precision4)
1003
1765
  betaStr = formatR0Arg(Math.floor(base), precision4)
1004
1766
  }
1767
+ // Diagonal combinations (letters ending in 'a': Aa, Ba, Aaa, …)
1768
+ // use the pure dlsdl mantissa α = 2·5^f ∈ [2, 10) — but only
1769
+ // when the diagonal letter IS the value's canonical letter
1770
+ // (single-type display). Inside a cascade the α is the inner
1771
+ // value's own mantissa and keeps the ordinary [1,10) form
1772
+ // (AbAa600 → Ab1.000Aa600).
1773
+ let diagMant = function (tok) {
1774
+ if (collapseCount > 0) return alphaStr
1775
+ if (descHeights.length > 1) return alphaStr
1776
+ if (!(tok.letter.length >= 2 && tok.letter[tok.letter.length - 1] === "a")) return alphaStr
1777
+ return regularFormat(2 * Math.pow(5, frac), precision4)
1778
+ }
1005
1779
 
1006
- // Build output: outerLetters + alphaStr + lastLetter + betaStr
1007
- // Descending order (highest first = outermost)
1780
+ // (3) compose: descending order (highest first = outermost)
1008
1781
  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)
1782
+ let topTok = letterTokenOf(descHeights[0])
1783
+ if (topTok.sym) {
1784
+ // the top level needs the symbol notation — it dwarfs every
1785
+ // lower level, so the display is the single !αAaβ form
1786
+ return emitLetterToken(topTok, alphaStr, betaStr, precision4)
1018
1787
  }
1019
- if (lastCount > 1) {
1020
- outerLetters += lastLetter.repeat(lastCount - 1)
1788
+ let outerLetters = topTok.letter.repeat(
1789
+ Math.min(letterCounts[descHeights[0]] || 1, effTh))
1790
+ if (descHeights.length >= 2) {
1791
+ let loH = descHeights[1]
1792
+ let loTok = letterTokenOf(loH)
1793
+ let loCnt = letterCounts[loH] || 1
1794
+ if (loCnt > 1) {
1795
+ outerLetters += loTok.letter.repeat(Math.min(loCnt - 1, effTh))
1796
+ }
1797
+ return outerLetters + diagMant(loTok) + loTok.letter + betaStr
1021
1798
  }
1022
-
1023
- return outerLetters + alphaStr + lastLetter + betaStr
1799
+ return diagMant(topTok) + topTok.letter + betaStr
1024
1800
  }
1025
1801
  }
1026
1802
  }
@@ -1089,5 +1865,5 @@ function formatSmall(num, precision=2) {
1089
1865
  // ─── Exports ─────────────────────────────────────────────────────
1090
1866
 
1091
1867
  if (typeof module !== 'undefined' && module.exports) {
1092
- module.exports = { format, formatWhole, formatSmall, metaPolarize, letterName, symbolName, getOrdinalLetter, formatR0AsChain, FORMAT_OPTIONS }
1868
+ module.exports = { format, formatWhole, formatSmall, metaPolarize, letterName, symbolName, getOrdinalLetter, formatR0AsChain, FORMAT_OPTIONS, polarizeFiniteChain, gammaArgOf, formatGammaBinary }
1093
1869
  }