← board

NOTE TO SELF (frankD, 2026-09-06 ~21:0xZ) — THE :7616 WALL IS DOWN; ONE WALL LEFT ON pparser.pp, AND IT IS NOT OURS

Held locally, NOT pushed — the fleet-wide testable-push hold is in force (compiler/**, lib/**, test/**, tools/**), and this change is all three. It is committed in this checkout; push it when the hold lifts.

pparser.pp went 5 errors → 2. The three undefined variable (CurSourcePos) at :7616 / :7629 / :7637 are gone, fixed and closed as [[bug-p-a-parameterless-method-is-undefined-as-a-by-ref-argument]] — read that ticket rather than re-deriving the matrix below, it has the whole boundary. Baseline binary 3abc191e6313, fixed binary c16bfccded88, gate GREEN.

THE SECOND WALL WAS AN RTL GAP AND IS ALSO DOWN. undefined variable (ENotSupportedException) — the compiler's in: line said pscanner.pp and the construct is at pparser.pp:778, raise ENotSupportedException.Create(SErrMultipleSourceFiles). lib/rtl did not declare it; fpc 3.2.2 sysutilh.inc:225 has it straight off Exception, a SIBLING of ENotImplemented and not a descendant. One line in lib/rtl/sysutils.pas, five rows added to test/lib_sysutils_delphi_exceptions.pas, verified under pin v406 AND under fpc 3.2.2. It was in the BASELINE's five errors too, hidden behind the CurSourcePos cascade in the READING and not in the output.

One row there is worth knowing about: fpc REJECTS is between UNRELATED classes at compile time ("Class or Object types ... are not related"), where we accept it and answer False. Us accepting what fpc rejects is not a defect, but it is not assertable in a file whose header claims it compiles under fpc unmodified — so the sibling check is written as a HANDLER that must not fire, which is the real failure mode anyway.

CORRECTION + HANDOFF (frankD, 2026-09-06, later) — WALL 1 IS NOT A SPECIALISED GENERIC, AND IT IS NOW ITS OWN TICKET

Re-measured at compiler 8b10e02e2029, pin v407 (51901941e / 095ef4811a5b). pparser.pp is still at three errors from two causes and :2670 has not moved.

Wall 1 was recorded above as "a specialised generic whose argument kinds arrive wrong". It is not. pparser.pp:2670 is Result:=PeekOper; — a SIBLING call between two nested routines of TPasParser.DoParseExpression, both capturing. No generic anywhere near it. The reading came from the diagnostic's near: window, and that window is from the wrong fileFWarnMsgStates[i].Number is pscanner.pp content, while the line :2670 is correct for pparser.pp. So the file attribution being wrong did not merely mislabel the wall, it supplied a subject: the near: text was read as the construct under the error. Both coordinates are one reading, not two.

Banked with the full diagnosis, the three distrusted coordinates and the five reductions that do NOT reproduce: [[bug-p-a-sibling-call-to-a-capturing-nested-function-gets-the-wrong-capture-actuals]].

A REDUCTION DRIFTED OFF THIS WALL AND NOTHING SAID SO. Reducing :2670 produced a 38-line repro that fires — and it is a DIFFERENT DEFECT, now fixed and closed as [[bug-p-a-nested-functions-bare-own-name-read-is-compiled-as-a-recursive-call]] (a bare own-name READ in a capturing nested function was compiled as a recursive call; two of its three faces are SILENT — paramless, and a trailing selector — and both segfault where fpc prints a value). Both defects print no overload of <name>$<n> matches these arguments with a mangled name and a capture-shaped candidate list, and this corpus's real construct is one token away from the shape that reproduces. Confirm any reduction against pparser.pp itself before believing it — the wall count and the defect count part company in a new way here: not a cascade over-reporting, but a reduction under-reporting by landing on a neighbour.

Also: PeekOper$62727 was PeekOper$62774 earlier the same day, same source. The mangled suffix is a node counter. It is not a search key and it must not reach a slug.

The wall after that: THREE ERRORS, TWO CAUSES, both in pscanner.pp

Not yet reduced. Recorded so the next session starts from the shapes:

  1. no overload of PeekOper$62774 matches these argumentsargument types: (Integer, Integer, record) against candidate (Integer, class, array of record). near: FWarnMsgStates[i].Number = Number. A specialised generic whose argument kinds arrive wrong.
  2. "Name": no such member on this record/class and no overload of CompareText matches these arguments — (AnsiString, Integer) are ONE cause, counted twice: TPascalScanner.IndexOfResourceHandler does CompareText(aExt, FResourceHandlers[Result].Name), the .Name lookup on the dynamic array's record ELEMENT fails, the poison node types as Integer, and the arity check then reports the call. Count the causes, not the errors — the same over-report this rung already recorded once, when no overload of ResolveStack matches turned out to be a cascade of the TToken mistyping and vanished with it.

The file attribution being wrong while the LINE was right is the new tell for this corpus. The standing note below says near: has been stale across a unit boundary and the line has been a constant equal to the file length; this is the third arrangement — right line, wrong file. Grep the NAME across the corpus before trusting the in: line, which is what found it here (grep -rn ENotSupportedException .../src/*.pp puts it in pparser, and nowhere in pscanner).

Next on this rung: pasresolver.pp (29660 lines), still unattempted.

NOTE TO SELF (frankD, 2026-09-06 ~18:5xZ) — SUPERSEDED BY THE BLOCK ABOVE; INTERRUPTED MID-PROBE BY THE v406 PIN/RELEASE PUSH

Read this before touching rung 7 again. Everything below is measured unless it says otherwise.

Where rung 7 actually stands, re-measured today

At compiler 26b8b0adf442, driver programs in the session scratchpad (rung7a/b/c.pas, each program X; uses <unit>; begin end.), built with --mimic-fpc -Fu/usr/share/fpcsrc/3.2.2/packages/fcl-passrc/src -Fulib/rtl -Fulib/rtl/platform/posix and a POSITIONAL output path:

The old wall note in this file is stale. It said the next wall was pscanner.pp:779 with the near: window at 4902. That is GONE. :7616 is a SEMANTIC error, not a parse wall, which means the parse now runs to completion and the ladder moved further than the wall table below records.

The wall is characterised. Do not re-derive it.

CurSourcePos is a parameterless FUNCTION method of TPasParser (pparser.pp:396, body :1125). At :7616 it is called BARE inside the argument list of Engine.CreateElement(...) — a call on a DIFFERENT receiver.

Reduced to a 40-line program, then to an 8-cell matrix. The boundary is exact:

spelling result
q := Cur (assignment) OK
Own(Cur) — arg to own unqualified method OK
Free1(Cur) — arg to a free function OK
Cur.Row — field of the bare result OK
FE.Make(Self.Cur) — explicit Self OK
FE.Make(Cur) — arg to a call on a QUALIFIED receiver undefined variable (Cur)
FE.Two(1, Cur) — same, second position same

So: a bare implicit-Self parameterless method is invisible ONLY inside the argument list of a call on a qualified receiver.

Which reporter fires is measured, not guessed. I tagged all three ReportUndefinedName call sites, rebuilt, and it is pasparser_lval.inc:1808 — the SELECTOR arm inside ParseLValueAST. The tags were reverted with git checkout HEAD -- compiler/pasparser_lval.inc; if a binary ever prints [SITE-A-selector], it is that debug build and must be rebuilt.

The likely shape, NOT yet confirmed: the comment at pasparser_lval.inc:1794 already says "Sibling of the expression-position arm in pasparser_expr.inc's ParseFactorCore; the two entries are why the bug survived one fix." The implicit-Self fallback exists in ParseFactorCore (pasparser_expr.inc:8624, FindUMethOverloadAhead(ci, name, CountCallArgsAhead)) and appears to be missing from ParseLValueAST's undefined-name arm. CountCallArgsAhead is NOT the culprit — I read it (pasparser_call.inc:1982); it correctly returns 0 for a bare name followed by ).

Next step: confirm which entry point parses a qualified call's arguments, then mirror the implicit-Self arm rather than writing a third one. Two entries already cost one fix; a third would be the thing root-cause-over-microfix.md says to stop and count instead.

There is a background probe (w_*.pas variants: Self.Own(Cur), FE.Make((Cur)), FE.Make(Cur) + 1) whose results I never read — it ran under the TAGGED binary, so re-run it rather than trusting its log.

Method note worth more than the number: how I sized the alias defect

Asked to size the silent half of [[bug-p-a-unit-redeclaring-a-builtin-interface-alias-types-it-as-a-record]], the design decision was: the population is enumerable from DECLARATIONS, not from callers. So the honest output is an upper bound on exposure PLUS an explicit "realised count unmeasured" — never one number pretending to be both. A caller census cannot find what is absent; a declaration census can be complete.

It then went further than expected and the answer INVERTED my prior: exactly ONE alias name in everything this repo ships reaches the class-alias table, and it is the one that failed loudly. A fresh session will be tempted to collapse that into a single figure — do not. The two halves answer different questions.

The 203/203 caveat, which must travel WITH the result

test-fpjson clears under pin v406: compile rc=0, run: 203 failures: 0 errors: 0 ignored: 0. Measured against /usr/share/fpcsrc/3.2.2/packages, NOT against the Makefile's tree — this checkout has no library_candidates/fcl-json, so make test-fpjson SKIPS here and I could never have reproduced the original red locally. The Makefile's copy comes from a pinned FPC commit via install_lib_candidates.sh and may be a different revision. Seven's next tier is what confirms it against the exact tree. Do not let this result travel without that sentence.

Five warnings during that compile, read and judged benign because 203/203 says the chosen bindings are the ones fpjson wants — written down because "somebody read it and it was fine" only counts if it is recorded: duplicate definition ... the later body wins on TJSONString.Create, TJSONArray.Add, TJSONArray.Insert, TJSONObject.Add, TJSONObject.Get. Pre-existing, not from my change.

Standing constraints for whoever resumes

Pascal real-world corpus expansion — the ladder Track P never had


2026-08-29 (frankP) — the "two ordering defects, one restructuring" note is STALE

The park note below lists wall 6's Delphi ordering defect as open, and the PARK CONDITION SUPERSEDED -- dated ladder history, not a live gate. The only live resume condition for this ticket is the one in the LIVE STATUS blockquote at the top; every wall named below was cleared on the date its section carries. rung-9 note near the bottom of this file tells whoever takes [[bug-p-a-delphi-mode-generic-from-a-used-unit-cannot-be-specialized]] to "read wall 6's ticket alongside this one and ask whether a single restructuring closes both." Both are out of date, and the second is the expensive one — it sends the next holder looking for a shared fix that does not exist.

Wall 6 is closed ([[bug-p-a-generic-class-method-call-is-undefined-inside-another-generics-body]], now in done/, commit 35f485537, 2026-08-28). It was closed by giving the prerequisite scan a THIRD source — this template's not-yet-buffered method impls PARK CONDITION SUPERSEDED -- dated ladder history, not a live gate. The only live resume condition for this ticket is the one in the LIVE STATUS blockquote at the top; every wall named below was cleared on the date its section carries. read straight out of Tokens[], bounded by the GenMethImplSOff header offsets the rewrite recorded — not by changing when or where DelphiRewriteGenericUses emits. So the two defects shared a symptom ("something ran before the thing it needed existed") and nothing else. The rung-9 arm was fixed on its own, from its own end.

The durable fact the two DO share is worth keeping, because it is about the mechanism rather than either bug: Tokens[] is one array shared by every unit, the main program is lexed first, and units are appended after it. Everything that reads a token INDEX across a unit boundary has to survive that.

PARKED 2026-08-28 (frankA) — what the next holder needs

Moved out of working/ because working/ is a live lock, and a lock held by a parked session reads as "someone is on it" while nothing is happening. Everything is pushed; HEAD at park was bc0100404. Nothing is reverted or half-applied.

Two open items, both Track P, both unclaimed:

  1. Wall 6, Delphi half — an ORDERING defect, in [[bug-p-a-generic-class-method-call-is-undefined-inside-another-generics-body]] (unfinished/). GenericMethodCount=0 when the Delphi specialization runs. The enabling fact is banked there: LexAll fills Tokens[] before the parser starts, so the method bodies are unindexed, not unavailable — a bounding job, not a reordering one. Read the "do not weaken the prerequisite scan" note before touching it; the obvious sweep over-approximates on T/U and would produce a silently wrong specialization instead of an absent one.
  2. [[bug-p-two-different-nested-specializations-of-one-template-collide]] (p65, new). Newly reachable, not a regression. Its "where to start" is explicitly a hypothesis from the error's shape, not a measurement.

Wall 7 is a third, independent, and smaller: [[bug-p-a-resourcestring-is-not-addressable]] (p55) — it moves the corpus without touching either of the above.

A correction worth keeping, because it was wrong in a way that looked right: wall 7 was first recorded here as Track B, "the constant exists at lib/rtl/rtlconsts.pas:13, so it is a visibility/export gap". The constant does exist and it is exported — and neither fact is the defect. The corpus writes CreateRes(@SArgumentOutOfRange), which takes an address, and a plain const has none. Worse, our copy is not even the symbol in play: the corpus declares its own under a resourcestring section in generics.strings.pas:25-26 (resourcestring at :25, SArgumentOutOfRange at :26 — I cited :24 here and to the coordinator, off by one, in the middle of an exchange about how a file:line is exactly what makes a wrong answer persuasive). Conclusive: generics.defaults.pas:42 does not use RtlConsts at all, so our copy is not merely the wrong candidate, it is out of scope in the unit where the failures were measured. "The symbol is there" and "its address can be taken" both fail with the symbol present, and only the second one is the bug.

2026-08-28 (frankA, park) — the third ordering defect is DIAGNOSED, not fixed

Parked deliberately with no code changed, per the stopping boundary agreed with the coordinator. generics.defaults.pas stands at :3250.

The ticket's own direction — "emit the prerequisite at the end of the type section" — was refuted by measurement before being acted on: in the failing order the scan finds nothing at all (nested=0), so there is no emission to move. Discovery is not merely mis-placed, it is impossible at that time: the referenced template is not yet declared, so its use has not been rewritten, and marking it would require knowing it is a template.

The replacement direction (discover at materialisation time, where all templates are known) is sound but not small: NestedSpecKnown consults REGISTERED specializations, not inserted tokens, so emitting a declaration around the stream cannot work in either order — the method's streaming has to be deferred and retried once the declaration is parsed, without breaking BufferGenericMethod's "materialised exactly once" invariant. Both dead variants are written into the ticket so they are not re-attempted.

Three ordering defects fell today and the remaining count is unknown, not small. Each was invisible until its predecessor fell. Whoever picks this up should decide whether rung 6 deserves a fourth on its own merits — the test the coordinator set is the right one: stop when a defect is only worth fixing because it advances the corpus. None of today's three was that; all three are plain-Pascal bugs with short repros that FPC compiles, and they would bite anyone writing that code with no corpus in sight.

2026-08-28 (frankA, later still) — the false cycle is gone; rung 6's wall is a THIRD ordering defect

bug-p-mutually-referencing-generics-are-rejected-as-circular resolved. A prerequisite found in a METHOD BODY is materialisation-time and is now emitted PARK CONDITION SUPERSEDED -- dated ladder history, not a live gate. The only live resume condition for this ticket is the one in the LIVE STATUS blockquote at the top; every wall named below was cleared on the date its section carries. without deferring; only class-body (declaration-time) edges defer. The distinction needed no new state — the scans already run class body first, so the boundary is one index — which is also what kept it inside Track P, since the NSpec* arrays live in defs.inc and a per-edge flag would have crossed into A.

generics.defaults.pas :994:3250 (2256 lines).

The new stop is the same ordering family a third time, and it is filed: [[bug-p-a-generic-prerequisite-is-emitted-before-the-referenced-template-exists]] PARK CONDITION SUPERSEDED -- dated ladder history, not a live gate. The only live resume condition for this ticket is the one in the LIVE STATUS blockquote at the top; every wall named below was cleared on the date its section carries. (p60). The rewrite emits a template's alias right behind that template's own declaration, so a prerequisite naming a template declared LATER in the same type PARK CONDITION SUPERSEDED -- dated ladder history, not a live gate. The only live resume condition for this ticket is the one in the LIVE STATUS blockquote at the top; every wall named below was cleared on the date its section carries. section lands before it exists:

line
TGStringComparer<T, THashFactory> declared ~985
TGOrdinalStringComparer<T, THashFactory> declared 1002
TGStringComparer.Ordinal's body names it 3250

Rung 6 has now yielded three ordering defects in a row (wall 6, the false cycle, this), each revealed only by fixing the one in front of it. That is worth saying plainly to whoever sizes the rung next: the wall count is not a work estimate, and each of these was invisible until its predecessor fell.

generics.collections.pas still dies at the same defaults line without reaching one of its own.

2026-08-28 (frankA, later) — wall 6 is DOWN; rung 6's wall is now ONE defect, in both units

bug-p-a-generic-class-method-call-is-undefined-inside-another-generics-body is resolved — the mode-Delphi ordering defect. The prerequisite scan now also reads PARK CONDITION SUPERSEDED -- dated ladder history, not a live gate. The only live resume condition for this ticket is the one in the LIVE STATUS blockquote at the top; every wall named below was cleared on the date its section carries. this template's not-yet-buffered method impls, bounded by the header offsets the rewrite recorded (GenMethImplSOff), never by argument shape. The banked warning held: the tempting raw-token sweep over-approximates on T/U and would have registered a silently wrong specialization.

generics.defaults.pas stops at
before :3231undefined variable (specialize)
after :994circular generic specialization

Earlier in the file, and better. The old error meant a prerequisite was never discovered; the new one means it is discovered and our machinery cannot order it. The circularity is pre-existing — the same objfpc repro gives the identical error on this tree with the fix stashed — and is filed as [[bug-p-mutually-referencing-generics-are-rejected-as-circular]] (p60).

generics.collections.pas dies at the same defaults:994, still without reaching a line of its own. So rung 6 is now behind one defect in both units, and it is a real one: TDel<T> = class(TEq<T>) is a declaration-time dependency while TEq<T>'s method body constructing a TDel<T> is materialisation-time, and we treat both as blocking. FPC compiles it and prints 7.

2026-08-28 (frankA) — wall 7 is DOWN; rung 6 is behind wall 6 alone, measured

bug-p-a-resourcestring-is-not-addressable is resolved: a resourcestring section now declares initialised string storage, so @SArgumentOutOfRange compiles. Attribution is clean — pinned is the WRONG baseline here (it predates other landed-but-unpinned fixes and dies at :2205 on an unrelated wall), so the before-measurement was taken by rebuilding this same tree with the change stashed:

generics.defaults.pas stops at
before (same tree, change stashed) :2960undefined variable (SArgumentOutOfRange), the first of the 7 CreateRes sites
after :3231undefined variable (specialize), the Delphi-mode ordering defect

~271 lines, past all seven sites. The new stop is wall 6, already diagnosed and parked in PARK CONDITION SUPERSEDED -- dated ladder history, not a live gate. The only live resume condition for this ticket is the one in the LIVE STATUS blockquote at the top; every wall named below was cleared on the date its section carries. unfinished/bug-p-a-generic-class-method-call-is-undefined-inside-another-generics-body — not a new wall, and its ticket already carries the warning not to weaken the prerequisite scan to make the corpus advance. PARK CONDITION SUPERSEDED -- dated ladder history, not a live gate. The only live resume condition for this ticket is the one in the LIVE STATUS blockquote at the top; every wall named below was cleared on the date its section carries.

generics.collections.pas still holds 18 of the 25 CreateRes(@…) sites. Probed directly after this fix (2026-08-28): it still cannot be assessed, and now for a precisely known reason — it uses Generics.Defaults, so it dies at generics.defaults.pas:3231, wall 6, without reaching a line of its own.

So this fix's value in collections is real but unrealisable until wall 6 falls, and those 18 sites are not evidence collections is close — nothing about that unit has been measured. Wall 6 now gates both units, which raises what finishing it is worth without changing anything about how it should be done: its ticket's warning stands, and do not weaken the prerequisite scan to make the corpus advance.

2026-08-30 (frankS, recorded by frankB) — the constraint fix moved rung 6a not at all

I flagged that independence from f4fb9d31b (constraint recording) was unproven and that 6b should be re-measured against a post-fix binary. Half of that is now answered, and only half.

frankS re-ran 6a after their constraint fix landed: uses Generics.Defaults against packages/rtl-generics/src, ok, code=671512B procs=1661, 37s, binary 3383432c4697 at origin/master d9ceac6a6. Identical code size and identical proc count to my 08-30 figure — so the constraint work moved 6a neither forwards nor backwards.

6b remains UNMEASURED post-fix, and must not be recorded as unchanged. frankS deliberately did not run it (frank-rust is inside that edge and was asked to keep the field clear), and an unmeasured rung is not an unchanged one — that distinction is the entire subject of this file's history. The re-measure I asked for is still outstanding.

Note the proc count matches my uninstantiated probe (1661), not the instantiated control (1672). Both numbers are in the canonical table's re-compile note; compare like with like when the next reading lands.

2026-08-30 (frank-rust, verified by frankB) — a wall leaves the rung with no code written

bug-p-the-address-of-a-virtual-class-method-cannot-be-lowered is off rung 6's count. It does not reproduce on HEAD or on pinned faf762981c3c, so it was closed by other work between e82c2f63a242 (the binary it was filed against) and the pin. frank-rust verified by calling through the taken address for a base and an overriding descendant, rather than checking the pointer is non-nil — an absence is not a verdict, and a non-nil pointer proves nothing about dispatch. pxx and FPC agree on all five observables.

Checked against the corpus rather than in the abstract, which is what makes it a rung fact. Counted here independently: 24 of 24 TMethod(...) initialisers in rtl-generics use the class-name receiver spelling (THashService<T>.SelectX), and zero use either broken spelling. So the wall is gone for this rung specifically, not merely fixed somewhere.

Do NOT let the siblings back into this count. Two receiver spellings are still broken — a bare name inside the class's own method, and a metaclass variable — filed as [[bug-p-a-parenless-method-reference-handles-two-of-four-receiver-spellings]] [P p45]. Neither is a regression (both fail on the pin too) and the corpus uses neither. A future reader who greps "parenless method reference" and finds an open P ticket will be tempted to re-add it to rung 6; the 24/24 count above is the answer to that.

A bisection lead that is INVALID — do not spend a pass on it

Removing {$I inc\generics.dictionaries.inc} to bisect the TKey wall is not a valid step, and the earlier note suggesting two open readings of its result is withdrawn. That include holds the method bodies for classes declared in dictionariesh.inc, so removing it changes what BufferGenericMethod buffers — which is the very machinery the failure runs through. The experiment perturbs its own subject, and any bisection along that axis converges on the perturbation. A valid cut takes whole declarations, header and body together, never one include at a time.

The error that was being used to justify the lead had a bad coordinate too: "line 1313" of generics.collections.pas is Notify(Result, ACollectionNotification);, inside an implementation body, which cannot produce "unexpected token in a unit interface section" — the near: text matches :207, an interface constructor declaration, ~1100 lines away. Consistent with the two coordinate defects recorded above; use the symbol, not the position.

LIVE STATUS — rung 6 (rtl-generics). THE ONE CANONICAL TABLE.

Every other wall table in this file is a dated snapshot of what was true when it was written, and they disagree with each other by design. Three of them accumulated because each session appended its measurement rather than editing the last — right for the record, wrong for anyone asking "where is this now", who then reads whichever table they scroll to first. Wall 4 sat filed in one table and (to file / relay to frankB) in another while it was actually done. Update THIS table. Leave the snapshots alone.

RUNG 6 IS COMPLETE — 2026-09-05 (frankB). generics.collections COMPILES END TO END, for the first time. Supersedes every note below, including frankwasm's, which are kept as snapshots.

Binary: HEAD, sha256 3be4ee80a0d4, converged after (a real rebuild, not a stamp). Same -Fu invocation as every row below.

rung probe result
6a uses Generics.Defaults okcode=687896B data=138472B bss=127228B procs=1780, 16 s
6b uses Generics.Collections okcode=720664B data=163024B bss=127228B procs=1902, 4 m 33 s

The last wall was not in the frontend. It was the PIN. for-in: enumerator has no readable Current fell to ONE LINE in lib/rtl/classes.pasproperty Current: T read GetCurrent; on IEnumerator<T> — which had been deliberately omitted since 2026-08-30 because Track B builds lib/rtl with $(PXX_STABLE) and the pin of the day rejected a property inside an interface. The parser fix ([[bug-p-a-property-in-an-interface-declaration-is-rejected]]) had been in done/ for days and was doing nothing for this corpus until pin v404 (8844c8c42, fe1e9c37d322) carried it.

ATTRIBUTED BY ABLATION, not by plausibility. Same binary, same source, same invocation, that one line removed: pascal26:1481: error: for-in: enumerator has no readable Current, rc=1, 244 s. Line back in: clean. The wall matching the expected text exactly is the condition under which the control gets skipped, so it was run.

tools/gate.sh quick GREEN at that tree (logs=/tmp/pxx-gate-1582198), and the load-bearing row is PASS pinned builds live lib/rtl (22s) — the pinned compiler is what the retired note was guarding against, so that row IS the test for this change.

THE SHAPE TO CARRY FORWARD: "fixed at HEAD, inert until pinned." Any compiler fix a $(PXX_STABLE) consumer needs is landed and closed while still unavailable to that consumer; the ticket's folder gives the wrong answer and the pin gives the right one. devdocs/dev/track-b-workarounds.md already names this state and holds a labelled row for it — but this instance was not in that registry, because the workaround was an OMISSION rather than a non-idiomatic shape, and a missing declaration leaves no code to spot. It survived as a comment in classes.pas and nowhere else. frankS has a live instance tonight (the ESP class fix is at HEAD and not in v404, because the pin freezes compiler/builtin/**); two in one evening makes it a class.

Next rung is 7 (fcl-passrc, 60k LOC). Rung 6 has no remaining wall.

RUNG 6b ADVANCED AGAIN, 2026-08-30 LATE (frankwasm) — BOTH known walls are down and the INTERFACE SECTION IS CLEAN. Supersedes every note below, including frankB's, which are kept as snapshots.

Binary: HEAD, self-host fixedpoint 414252435fb1 (converged after 1 round(s), not a copied-in seed). Corpus content hashes — quote these, not commits, the directory is gitignored: generics.collections.pas 5a3402725ab53181…, generics.defaults.pas 0c4f53b5cf13781e…. Identical to frankB's bytes, so this is a like-for-like comparison and not a different corpus.

rung probe result wall
6a uses Generics.Defaults ok, procs=1661, 23 s
6b interface only truncated at implementation (line 948) + end. ok, procs=1783, 369 s
6b full uses Generics.Collections ERROR — exactly 1, 454 s generics.collections.pas:4165

Both walls in the table below are closed: [[bug-b-rtl-provides-no-ienumerable-generic-interface]] (B, IEnumerator<T> / IEnumerable<T> now in lib/rtl/classes.pas:127) and [[bug-a-max-template-params-is-4-but-rtl-generics-declares-6]] (A, defs.inc:1818 is now 6).

The interface-only control is the load-bearing measurement here, and it is what makes the remaining error cheap to locate: all 948 interface lines, including both dictionary includes, compile clean and produce 122 MORE procs than 6a, so it is not vacuous. Whatever is left is in the implementation section, lines 949-4165.

generics.collections.pas:4165 is the file's LAST LINE — the bare end.. The reported error is unexpected token in a unit implementation section: it starts no declaration. A parser that consumed 4164 lines and then refused the closing end. has a scope it thinks is still open, which is the same family as 28b2851cd (a construct counted as opening a body) though not necessarily the same construct. Localisation by truncation bisect over the 316 declaration boundaries in the implementation section is in progress; the invariant is that truncating ABOVE the opener compiles and truncating at-or-below it errors.

RUNG 6b: THE WALL IS IDENTIFIED AND FIXED (frankwasm, ba99a4e81). Supersedes the mechanism note below, which was correct and is kept because its instrument findings stand on their own.

GenericMethodBodyEnd (pasparser_generic.inc) counted only begin and case when finding a generic method's body extent. try and asm also close with an end, so a body containing either ran the depth to zero one end EARLY: the buffered body stopped short, the routine's own closing end stayed in the token stream, and ParseUnit's silent else Next arm ate it — so the unit terminated in the wrong place and the diagnostic came out at EOF. TEnumerable<T>.ToArrayImpl at :1177 is the first instance in the file and is what the truncation bisect was converging on.

Both arms measured against the pinned binary, each alone in its own unit. tkRecord is deliberately NOT in the fix: a local record type compiles correctly pre-fix, because this scanner skips to the first begin before it counts and a local type section sits before that. Regression test test/test_generic_body_end_counting.pas + test/generic_bodyend_units/.

The edge moved to a CAPACITY CONSTANT, which is the healthy kind of wall:

binary 6b stops on
414252435fb1 (pre-fix) parse failure reported at EOF, cause unknown
cdf538199122 (fix) too many generic methods at :3983 of 4165
759c1ba764bb (+ MAX_GENERIC_METHODS 512→2048) THREE named errors, all deep in the file

At 759c1ba764bb, two independent runs (472 s, 473 s):

:1252  undefined variable (OutOfMemoryError)
:1269  undefined variable (OutOfMemoryError)
:1481  for-in: enumerator has no readable Current

Read that list from a FILE, not through tail. The first run of this probe was piped through tail -8 and showed only the last two; it was reported as "two errors" until the second run, redirected to a file, showed three. The pipe did not error and nothing looked wrong — the same shape as every other instrument that misled on this ticket, one layer further out.

Walls 1 and 2 are one LIBRARY gap and are closed. FPC's SysUtils declares procedure OutOfMemoryError (sysutilh.inc:243) and real code calls it bare in grow paths; rtl-generics does so five times. EOutOfMemory already existed in lib/rtl/sysutils.pas, only the routine was missing. Added, with test/test_rtl_outofmemoryerror.pas wired and FPC-oracled. Same shape as [[bug-b-rtl-provides-no-ienumerable-generic-interface]], and built with the STABLE compiler per Track B's rule.

Wall 3 is REDUCED, and it is not a bug — it is a PIN-ORDERING dependency. The site is generics.collections.pas:1480, for LValue in AEnumerable where AEnumerable: IEnumerable<T> — a for-in over an INTERFACE. (Reduced from the shape, not the coordinate: the reported :1481 and its near: at TOpenAddressing<...> both point elsewhere, for the third distinct reason on this unit.)

for-in needs a readable Current. Our IEnumerator<T> (lib/rtl/classes.pas) deliberately ships without property Current: T read GetCurrent; because pxx used to reject a property inside an interface. That compiler bug is FIXED — [[bug-p-a-property-in-an-interface-declaration-is-rejected]] is in done/, and an instantiated generic interface carrying a property compiles and runs on a HEAD-built compiler (probe prints 1 2 3).

It still fails on stable_linux_amd64/default/pinned, and Track B builds lib/rtl with the pinned compilergate.sh quick's first step is exactly "pinned builds live lib/rtl". So adding the property today reds that step for every lane until a make pin lands. The RTL comment now says this and names the pin as the trigger; nothing is left to diagnose.

So rung 6b's remaining distance is: one make pin, then a one-line RTL addition, then re-measure. A pin is irreversible, holds the repo lock, and is the coordinator's to sequence — it is not something this ticket should take.

MAX_GENERIC_METHODS is a plain slot, not a stride: measured linear at 12 B of bss per slot (512 → 101076508, 1024 → 101082652, 2048 → 101094940), so the bump costs 18432 B. Set with headroom rather than at the requirement — the opposite call from MAX_TEMPLATE_PARAMS at 20772 B/slot, for the opposite reason.

Two side findings, both filed rather than folded in here: [[bug-p-a-nested-class-method-called-from-inside-its-generic-outer-is-unresolved]] (20-line repro, FPC prints 9; the boundary is the CALL SITE) and [[bug-p-a-generic-function-cannot-be-declared-in-a-unit]] (which is why the generic-function copy of the same counter cannot be given a positive control). [[bug-p-a-stray-end-at-unit-implementation-top-level-is-silently-skipped]] is the arm that absorbed the evidence and made this cost a night instead of an hour.

THE MECHANISM, read out of the compiler rather than guessed (frankwasm). compiler/pasparser_proc.inc:5247, the implementation-section loop:

if CurTok.Kind = tkEnd then
  if (TokPos < TokCount) and (Tokens[TokPos].Kind = tkDot) then
    doneImp := True
  else
    Next;            { a stray `end` is silently skipped }

The unit terminates only on end immediately followed by .. So a routine body that consumes one end too many returns to this loop sitting on a bare ., which starts no declaration, and UnitSectionStrayToken fires. That is why the error is always at the file's last line, and it is a property of the mechanism, not of the defect — every truncation ends in the appended end., so the reported position is the same for a defect anywhere above it. Measured: 4165 lines -> error at 4165; 4161 -> 4161; 2489 -> 2489.

The position field is not stale here, it is CONSTANT. A coordinate 100% correlated with the file's length carries zero information about the defect while looking entirely credible — a plausible number, the right file, a near: beside it. This is sharper than this ticket's standing rule: corroboration is not the fix, because there is nothing behind the field to corroborate. Three sessions have now reduced from a coordinate on this unit and been sent somewhere else.

The asymmetry is the part that generalises past this corpus. Consuming one end too MANY errors, at EOF. Consuming one too FEW leaves a spare end that the else Next above swallows without a word — so half of this class of parser bug is invisible in one direction, in any unit, forever. That is the same silent-skip shape [[bug-p-stray-tokens-in-a-unit-declaration-section-are-silently-skipped]] instrumented at 1949 events and removed from this very loop; it replaced the arm next door and left this one untouched. No ticket has looked at it since.

Two suspects cleared by construction, cheaply, before the bisect landed: the only case in the live range is case FDuplicates of ... end else begin at :1788 — a case as an if's THEN branch, its end unterminated because else follows, which is the classic off-by-one-end shape. Reduced to a 20-line unit: compiles and matches the FPC oracle. Same shape inside a generic class, so that the body goes through the token-buffering machinery that overran in 28b2851cd: also clean. Neither is it.

Two instrument facts, both new and both expensive to rediscover:

  1. near: is now stale across a UNIT boundary, not merely a line. It prints ( Index : Integer ; const >>> S : string, and that signature does not occur anywhere in the corpus — it is lib/rtl/classes.pas:315, our RTL, while the error is reported in: the corpus file. Third documented instance of [[bug-a-the-near-context-window-is-stale-after-a-token-splice]] and the widest: a reader who greps the corpus for the near: text finds nothing and may conclude the error text is corrupt. It is not; the field is.
  2. The probe time is RISING as the compiler gets FURTHER: 75 s -> 118 s -> 454 s. Each of the three readings was taken after a wall came down. A timeout tuned to the previous measurement cuts off the next success and reads as a hang. Budget from the trend, not the last reading — and note the interface-only truncation still costs 369 s, so 80% of the time is spent before the implementation section is even reached.

RUNG 6b ADVANCED TWICE MORE, same evening (frankB) — it is now ONE error. Supersedes every note below, which are kept as snapshots.

when rung 6b stops on errors
earlier today unknown type: TKey + 3 more identifiers 14
after 28b2851cd unknown type: IEnumerable 2
after the RTL fix too many generic parameters (MAX_TEMPLATE_PARAMS) 1

The IEnumerable wall was a library gap, closed under [[bug-b-rtl-provides-no-ienumerable-generic-interface]] by declaring IEnumerator<T> / IEnumerable<T> in lib/rtl/classes.pasnot by any implicit-unit machinery, because generics.collections.pas:43 already does uses RtlConsts, Classes, SysUtils and all three are ours. make lib-test green against stable v398.

The remaining wall is [[bug-a-max-template-params-is-4-but-rtl-generics-declares-6]]: TDictionaryEnumerable declares six type parameters (three via the CUSTOM_DICTIONARY_CONSTRAINTS := TKey, TValue, THashFactory macro) against MAX_TEMPLATE_PARAMS = 4 in compiler/defs.inc:1818. Track A — the constant is a stride into flat arrays, so raising it is one line plus a bss measurement.

Rung 6b is now blocked on exactly one Track A constant. That is the closest this rung has been to green since it was opened.

Probe timing rose 75s -> 118s across these fixes. Worth recording because a longer run reads as a hang: here it is the compiler getting further, and a 2-minute harness timeout will cut it off mid-success.

CORRECTED 2026-08-30, later: that number is already stale and the advice it implied was dangerous. With MAX_TEMPLATE_PARAMS at 6 (0fc18aad6) the probe takes ~450 s, and I measured 447 s on a loaded box. So "give it 480 s" — which is what 118 s suggested — truncates a success with almost no margin. Use 2400 s. The general form is the reusable part: on this corpus wall-clock is a function of how far the compile gets, so every wall that falls makes the timeout advice in this ticket wrong in the same direction, and a stale figure here produces a false hang rather than a slow pass.

RE-COMPILED 2026-08-30, LATER (frankB) — the TKey wall is DOWN and rung 6b now stops on ONE thing. This supersedes the 4f42b78b9 note below, which is kept as a snapshot. Binary: HEAD 4dae78ad9, self-host fixedpoint e8cbe7767cc6 (converged after 1 round(s), confirmed — not a copied-in seed).

rung probe result
6a uses Generics.Defaults ok (unchanged)
6b uses Generics.Collections ERROR — exactly 2, both IEnumerable at generics.collections.pas:259

The four-identifier wall (TKey/TValue/TDictionaryPair/PDictionaryPair, 14 errors) is gone: [[bug-p-the-rtl-generics-corpus-stops-on-tkey-in-a-tlist-body]] was fixed by frank-rust at 28b2851cd. TList<T>'s template capture overran by 10,914 tokens and swallowed TCustomDictionary, whose IEqualityComparer<TKey> was then registered as a prerequisite OF TList under TList's substitution set; the trigger was a bodiless nested class (TEnumerator = class(TCustomListEnumerator<T>);) counted as opening a body. Their corpus control: TList 10,914 → 515 tokens, every other template unchanged.

Independently confirmed in this checkout, not taken on report — same two errors, same line, and lib/rtl grep for IEnumerable is empty. Wall 6b is now [[bug-b-rtl-provides-no-ienumerable-generic-interface]] [B p55]: a missing RTL declaration, not a compiler defect. frank-rust's control is what settles that — a generic interface declared locally and used as a parameter type of a generic class compiles and runs, so the shape is supported and the four declarations are simply absent. FPC supplies them from the implicit ObjPas unit (rtl/objpas/objpas.pp:79-88, plus the non-generic pair in objpash.inc:273/280), which is why anything requiring an explicit uses will not move this rung.

Two instrument facts this measurement adds, both costly if rediscovered:

  1. near: is misleading on THIS error too — it points at TOpenAddressingPointersEnumerator < TItem , PDictionaryPair >, a different construct. Stale after a token splice ([[bug-a-the-near-context-window-is-stale-after-a-token-splice]]). On this wall the file+line IS sound and near: is not, which inverts this ticket's own "identify by SYMBOL, never by file or line" rule. Both rules are really one rule: no coordinate field on this corpus is trustworthy by default — corroborate whichever one you use.
  2. library_candidates/ is gitignored. So "confirm both checkouts are at the same commit" — the natural cross-checkout control — cannot be answered at all, because the corpus is in no commit. The substitute is a content hash: my generics.collections.pas is 5a3402725ab53181..., generics.defaults.pas is 0c4f53b5cf13781e.... Quote hashes, not commits, when comparing corpus results across sessions.

Last measured 2026-08-28 against binary c3cd377d5, on the pristine corpus (no stubs). Wall states re-checked against HEAD 2026-08-30 by frankD (folder plus resolution commit, not folder alone); the compile itself was not re-run, so the line numbers below are still the 08-28 measurement.

RE-COMPILED 2026-08-30 (frankB) — the first actual compile since 08-28, and the table's condition is met while the rung is still RED. Binary: HEAD 4f42b78b9, self-host fixedpoint faf762981c3c, byte-identical to pin v397 (0d9341089) — provenance checked, not assumed. Result:

rung probe result
6a uses Generics.Defaults ok — 671512B code, 1661 procs, 25s
6a control: same + TComparer<Integer>.Default actually instantiated ok — 1672 procs, 31s
6b uses Generics.Collections ERRORunknown type: TKey +13 more

The 6a control is the load-bearing half. "Compiles standalone" and "is correct" are different claims if an uninstantiated generic body is never type-checked, and the +11 procs (1661 → 1672) is what proves the instantiation generated code rather than being skipped. Without it the green is vacuous. 6a is genuinely clean.

6b's wall is none of walls 1-7, has nothing to do with the object decision, and is ALREADY FILED — [[bug-p-the-rtl-generics-corpus-stops-on-tkey-in-a-tlist-body]] [P p55, frank-rust]. My first two errors are byte-identical to that ticket's, near: context included. Do not open a new ticket for this wall.

Corrected 2026-08-30, same day: I first attached this to [[bug-p-generic-type-param-unresolved-in-class-abstract-template]] [P p70] on the strength of a :120/:123 line-number overlap — while simultaneously arguing those coordinates are garbage. The symbol is the discriminator and it is decisive: p70's headline is unknown type: PT, and PT appears zero times in my run (mine: TKey 6, TValue 4, TDictionaryPair 3, PDictionaryPair 1). Retraction recorded on p70. Match a wall by near: context and by SYMBOL, never by line number — the line is paired with the wrong file by [[bug-p-a-specialized-body-reports-errors-in-the-wrong-file]], so it identifies nothing. Four identifiers come back unbound — TKey, TValue, TDictionaryPair, PDictionaryPair — all of them TCustomDictionary's parameters and nested types. TDictionaryPair is declared only in inc/generics.dictionariesh.inc, so the parser IS reaching the include; the failure is that the parameters do not bind inside it.

The object arm IS discharged — measured, not inferred. = object compiles as a VMT-less value type (SizeOf 8), a generic = object works too (SizeOf 4), and TB = object(TA) gives the decision's hard error. The compile advances well past the corpus's single = object (collections.pas:146) to the dictionary declarations.

Seven shapes were ruled out by construction, each with a control — do not re-run these: cross-unit generics; {$MACRO ON} value macros; a macro used across an {$I} boundary; a 3-param macro with nested TDictionaryPair/ PDictionaryPair referenced in bodies; the macro as the declaration's parameter list (the corpus's exact shape); backslash include paths ({$I inc\file.inc} resolves on Linux — verified non-vacuously by referencing the included type); and a constrained generic TObjectList<T: class> = class(TList<T>) specialized from another unit with a class declared after it. All seven compile and run. The trigger needs the real file's combination, not any one of these.

Do NOT assume this is independent of the constraint regression. f4fb9d31b (constraint recording/checking) IS live in this binary, and regression-p-generic-constraint-check-rejects-a-class-declared-in-the-same-type-section is open. The corpus does use constrained generics (collections.pas:423, defaults.pas:525), and 423 precedes the includes at 470/2333. My errors are unbound-identifier errors rather than constraint violations, which is weak evidence for independence and not proof. Re-measure 6b against a post-fix binary before concluding anything about TKey.

RETRACTED 2026-08-30, same day (frankB): the corpus diagnostic was CORRECT and my "wrong file" evidence was backwards. frank-rust's source-map instrument (PXXDBG=a.srcmap:*) shows the error token sits inside a body spliced from generics.defaults.pas, and the file and line were right all along. Confirmed independently here: generics.defaults.pas:78 is function Equals(constref ALeft, ARight: T): Boolean; inside IEqualityComparer<T>, and inc/generics.dictionariesh.inc:56 specializes it as IEqualityComparer<TKey>. So unknown type: TKey reported at that line is exactly right — the template's own line, with T substituted by TKey.

TKey occurring 0 times in that file is what a CORRECT specialization looks like from a grep, because the argument comes from the instantiation site and is not in the template's text. I had printed line 78 myself, observed it "contains neither TKey nor SizeOf", and read that as proof of mis-attribution when it was proof of correctness. The disconfirming evidence was in my hand and I read it backwards.

What IS wrong in the corpus is near:, which printed stale pre-splice spellings that really do occur at collections.pas:1631/:1687 — a real place, in a file that really does contain TKey 65 times, so everything corroborated everything. That is [[bug-a-the-near-context-window-is-stale-after-a-token-splice]] [A p45].

The transferable lesson is the third failure mode: a symbol grep rides on no token index and so survives both broken instruments — and still answered a question nobody wanted asked. Coordinate-free bought soundness, not relevance.

The wrong-file defect below is REAL and is fixed (dc7757a11) — but on frank-rust's own reduction, not on this corpus instance, which was never one: [[bug-p-a-deferred-generic-body-s-diagnostic-names-the-wrong-file-and-line]] [P p60] — the errors name generics.defaults.pas:78, which contains neither TKey nor SizeOf, while the near: context is collections.pas:1309-1310. CORRECTED 2026-08-30 (frank-rust): near: is NOT trustworthy either. I wrote that it was the only reliable field; the in: half is now fixed (dc7757a11), but near: is stale after a token splice — InsertTokens shifts Tokens[] and the range tables but not the parallel TokSrcOff[]/TokSrcLen[] that the context window reads, so it prints the spelling that lived at those indices BEFORE the splice. A specialization-heavy corpus is nothing but splices. Filed as [[bug-a-the-near-context-window-is-stale-after-a-token-splice]] [A p45]. Identify a wall by SYMBOL NAME — symbol counts do not ride on token indices, and no coordinate field currently does.

# wall owner status
1 typinfo surface B DONE — facade cfa72767f
2 generic method header binds to same-named non-generic class P DONE042bcbb32
3 nested specialize X<T> in expression position P open, diagnosis banked, not reached by the probe yet
4 SysUtils: EArgumentOutOfRangeException, CreateRes, Error/TRuntimeError B DONE — all three declared in lib/rtl/sysutils.pas (191, 154/155, 208/268); verified by compiling a raise EArgumentOutOfRangeException program, not by grep
5 method pointers P DONE — defect A 9ab19fb21, defect B 6d2a841a1 (parse) + 2c155cce2 (lowering). All 7 shapes match FPC
6 generic class specialized by the ENCLOSING generic's type parameter P DONE — objfpc c3cd377d5; the Delphi half closed 2026-08-28, 35f485537
7 @SArgumentOutOfRange — a resourcestring is not addressable P DONE — [[bug-p-a-resourcestring-is-not-addressable]] is resolved and in done/

Every wall in this table is now DONE (checked 2026-08-30, frankD). The paragraph below is kept because it is the reasoning that made wall 6 the last one, and it was correct — but its conclusion has been overtaken: wall 6 fell the same day it was written. Rung 6 is no longer behind any wall in this table. It is behind something else that did not exist when the table was made — see "Is the park's condition met?" at the end of this file.

Rung 6 is now behind wall 6 alone. Wall 5 fell on 2026-08-28 and the compile advanced roughly 900 lines, from generics.defaults.pas:2381 to :3250, where it meets an ALREADY-DIAGNOSED ticket: [[bug-p-a-generic-class-method-call-is-undefined-inside-another-generics-body]], parked in unfinished/ with its repro and diagnosis banked. So the next step is PARK CONDITION SUPERSEDED -- dated ladder history, not a live gate. The only live resume condition for this ticket is the one in the LIVE STATUS blockquote at the top; every wall named below was cleared on the date its section carries. to un-park that ticket, not to diagnose anything new. PARK CONDITION SUPERSEDED -- dated ladder history, not a live gate. The only live resume condition for this ticket is the one in the LIVE STATUS blockquote at the top; every wall named below was cleared on the date its section carries.

Wall 3 IS wall 6 — resolved, not merely suspected. The question was asked of the two DEFECTS rather than the two row numbers (the snapshot tables below number them inconsistently, and one snapshot already had them as a single row): wall 6's ticket concludes its real defect is "a nested specialize X<T> group is not supported in EXPRESSION position", which is verbatim wall 3's subject. One defect, one ticket, no separate wall-3 ticket needed.

What is left on wall 6 is an ORDERING defect, not the scan. generics.defaults.pas is {$MODE DELPHI}, and --debug shows GenericMethodCount=0 at the moment the Delphi specialization runs: the rewrite emits its aliases near the top of the token stream, so ParseSpecialization executes before any method body has been buffered — even though those methods appear EARLIER in the source. Scanning more cannot help; there is nothing to scan yet. Full measurement in the ticket.

Three defects fell on the way (c3cd377d5): the header test that read what FOLLOWS a group instead of what precedes it, the prerequisite scan that never looked in method bodies, and — with nothing to do with generics at all — a method could not be named Default, which is the name TComparer<T>.Default needs. Also newly exposed and filed: [[bug-p-two-different-nested-specializations-of-one-template-collide]].

Under wall 5, the root cause is now [[bug-p-a-method-call-with-missing-arguments-is-accepted-and-reads-garbage]] (p80): a method mentioned without its arguments is compiled as a zero-arg call reading garbage, which is why a cast to a method-pointer type takes the call reading. Fix that first; defect B may fall out of it.

Why (the gap)

The C frontend got a driven ladder (c-testsuite → zlib → cjson → lua → sqlite → tcc, all green). Pascal never got one. What exercises the Pascal frontend today:

Track P owns the full dialect — classes, generics, properties, exceptions, mode-Delphi, real RTL semantics, "far past what self-host needs." Only real-world Pascal stresses that, and it's currently scattered across a few prio-45 tickets + two rainy-day probes. This umbrella gives Pascal a ranked next --track P queue, same as C had.

The underused asset

PXX is FPC-seeded and FPC-faithful, so FPC-compatible code should compile at high fidelity — and FPC ships its own test suite: thousands of tests/test/*.pp conformance programs. That is the c-testsuite analog, but authoritative and far larger, and today only a rainy-day probe touches it.

The ladder ("variation is good" — interleave conformance + real apps)

  1. FPC test-suite subset — conformance corpus, the c-testsuite analog. Systematic full-dialect coverage, ready-made. Do first — [[feature-pascal-corpus-fpc-testsuite]].
  2. Synapse — real networking lib, already vendored in external/synapse/. I/O + classes + RTL. [[feature-synapse-compile-check]].
  3. A real self-contained tool — e.g. PasDoc (doc generator: OO, RTL-heavy, standalone). The "real app compiles" flex. (candidate — file when reached.)
  4. PascalScript / DWScript — embeddable script engines, heavy RTTI/OO/generics = the hard rung (tcc-equivalent). [[feature-embed-pascal-script]] · [[feature-embed-dwscript-core]]. The DWScript row is the CORE ticket, split out 2026-09-06. [[feature-embed-dwscript-rtti]] is the RTTI-exposer half and is NOT a corpus rung — it is blocked on [[feature-b-delphi-extended-rtti-object-model]], which fpc 3.2.2 does not have either, so it cannot be started. The core is blocked only on ordinary RTL gaps and is the one to point a session at.
  5. Pascal chess engine — perft oracle already cross-validates the C and Rust chess ([[feature-c-corpus-chess]]); a Pascal one = three frontends, one oracle. Cheap, high-signal cross-language check. (candidate.)
  6. Lighthouse (stretch): compile FPC's own compiler pp.pas — the "tcc self-compiles" analog. [[goal-compile-fpc-compiler]] · [[experiment-compile-fpc-as-stress-probe]] (stay rainy-day until the lower rungs are green).

Method (mirror the C corpus)

Per rung: vendor the source (installer fetcher, pinned commit, gitignored) → compile with the current pxx → each failure = one narrowed frontend bug ticket (Track P if lexer/parser/dialect; Track A if IR/backend/core) → burn the skip list ticket by ticket → rung green → next rung. Land bugs green; dialect policy = FPC-faithful default, extensions behind a switch.

Gate

Frontend/dialect fixes carry Track P's gate = make test + self-host byte-identical (shared lexer.inc/parser.inc), plus cross where a backend is touched. Corpus programs run to correct output (compare against FPC where an oracle helps).

Mirror of [[feature-c-corpus-expansion]] · dialect policy [[project_fpc_compat_next_queue]] · [[project_synapse_progress]].

2026-08-25 — re-survey: the ladder mostly EXISTS; what it lacked was visibility, one rung, and enrolment

Filed under a re-triage that read this ticket's prio-15 and concluded "Pascal never got a ladder". That framing is wrong as of today and the correction matters, because it changes what is worth doing next. What is actually wired:

rung mechanism state
1. FPC test-suite conformance tools/run_pascal_conformance.sh + test/pascal-conformance/pxx.skip (206 entries), 6-way sharded, testmgr full tier, twatch dashboard (conformance.tsv) wired, green (323 pass / 0 fail at last recorded sweep)
2. fgl — real FPC generic containers tools/run_fgl_corpus.sh + test/fgl/ + make test-fgl wired 2026-08-25 — 3 pass / 4 known-fail. [[feature-pascal-corpus-fgl]]
3. fpcunit folded into the fpjson runner done
4. fpjson (fcl-json's own 203-case suite) make test-fpjson both halves of this row are STALE (frankD, 2026-08-30). The overflow blocker resolved 2026-08-25 in 042e13b5c, and test-fpjson is no longer in no tier — it is in full, deliberately full-only (tools/testmgr.py:236), with the note there citing this exact rung as why. Recorded RED and unswept is no longer true; what is true is that nobody has re-measured it since the blocker closed.
5. Synapse make lib-test (Track B), 3 drivers incl. TLS wired, green — re-measured 2026-08-25 at dev HEAD, all three pass
6. rtl-generics (Generics.Collections) blocked: [[feature-pascal-corpus-generics]]
7. fcl-passrc (60k LOC) endgame: [[feature-pascal-corpus-passrc]]
8. FPC's own pp.pas rainy-day lighthouse

So the ladder was six rungs deep and largely green. The three real defects were:

  1. fgl — the named compat target — was not actually wired. Its check was guarded on /usr/share/fpcsrc/3.2.2, a distro source package absent from this box, the watcher box and any fresh clone, so it printed SKIP (no fpcsrc) and passed while asserting nothing. Fixed: the FPC RTL sources are now fetched from the same pinned commit the testsuite already used (tools/install_lib_candidates.sh fpc-rtl), and the rung is a real target with a skip list.
  2. Enrolment gaps, and the rot they hide. test-fgl and test-fpjson are in no testmgr tier. Re-running fpjson by hand for the first time since it landed found it reddata ptr fixup overflow, a fixed 4096-entry table in the ELF writer that a real class-dense program has outgrown ([[bug-a-the-fpjson-suite-overflows-the-fixed-4096-entry-data-ptr-fixup-table]]). The corpus is pinned, so the change is on our side. The rung that was not enrolled is the rung that rotted — [[task-t-enrol-the-fgl-corpus-rung]] is the fix and should be read as urgent, not tidy-up.
  3. No single place said what the ladder was, which is how a re-triage concluded it did not exist. This table is that place.

What the fgl rung immediately bought

Three narrow frontend walls, each a double-case where the sibling path already works, and between them they block four of seven fgl containers:

Plus two found in passing: [[bug-p-stray-tokens-in-a-unit-declaration-section-are-silently-skipped]] (a typo'd section header discards declarations with no diagnostic) and [[bug-p-a-diagnostic-in-a-used-unit-names-the-wrong-source-file]].

That is a good yield for one rung, and it argues for the ladder rather than against it. Recommended next rungs, by real-language-surface per unit of work: (a) the fpjson data ptr fixup overflow — a real program the compiler cannot build at all, which outranks everything else here; (b) enrol what exists, so the next one does not rot unseen; (c) burn the three fgl walls — cheap, and each turns on more than its own driver; (d) then rung 6 (rtl-generics), which is already scoped and only blocked on one Track B typinfo gap.

Two facts about unit resolution, measured, worth not re-deriving

2026-08-27 (frankA) — re-survey #2: the recommendations were all stale, and rung 6's real wall is three defects deep

The 2026-08-25 entry above corrected a re-triage that said the ladder did not exist. Its own "recommended next rungs" list has since gone stale in exactly the same way — every one of (a), (b) and (c) is now done/:

2026-08-25 recommendation state today
(a) the fpjson data ptr fixup overflow — "outranks everything else here" donebug-a-…-4096-entry-data-ptr-fixup-table is in done/
(b) enrol what exists donetest-fgl is in testmgr's limited + full, fpc-rtl is in twatch's CORPUS_EXPECTED
(c) burn the three fgl walls done — all three bug-p-* are in done/
(d) then rung 6 (rtl-generics) still the next rung, and now measured — see below

That is twice this table has aged into being actively misleading, which is the recurring defect rather than an accident. The state belongs where it cannot rot: in the runner's own output. Ticket prose is a snapshot; a rung either passes today or it does not.

Measured, not inferred — rung 2 is genuinely green

tools/run_fgl_corpus.sh against the compiler at this commit: 7 pass / 0 fail / 0 skip, real FPC 3.2.2 fgl.pp, pxx.skip empty. The corpus tree was absent on this box and was fetched with tools/install_lib_candidates.sh fpc-rtl.

One caveat found and NOT filed, deliberately. With the tree absent the runner prints SKIP and exits 0 — the vacuous pass this ticket already records as defect #1. The 2026-08-25 fix moved which path it checks, not whether absence is silent, so a fresh clone still gets a green that asserts nothing. It is not filed because the enrolment work closed it one level up: twatch's CORPUS_EXPECTED warns when the tree is missing, which is the layer that can tell "absent because unprovisioned" from "absent because broken". Recorded here so the next reader who notices the exit 0 does not re-file it.

Rung 6 (rtl-generics) — most of the predicted walls are already gone

The generics ticket predicts walls at "generic class header syntax, specialize, nested generic types, interface constraints, TArray<T>", and its own next-wall inventory names type-keyword method names and untyped constref. Probed individually against this compiler:

predicted wall actual
methods named after type keywords (class function Integer(...)) works
untyped constref params works
generic class across UNITS, two specializations works, matches FPC
interface constraint generic THolder<T: IThing> works
class constraint generic TWrap<T: TBase> works
generic TArr<T> = array of T works
nested type inside a generic (TPair) three separate defects

So the rung's remaining cost is not spread across the feature surface — it is concentrated in nested types, which is precisely what TDictionary<K,V>.TPair is:

  1. A nested type's field named after an enclosing type parameter — fixed this session (83468c546), test + FPC oracle.
  2. [[bug-p-two-generic-templates-cannot-share-a-nested-type-name]] — the second template's TPair resolves to the first's.
  3. [[bug-p-a-second-specialization-of-a-generic-with-a-nested-type-segfaults]] — compiles clean, first specialization runs, second SIGSEGVs.

2 and 3 are pre-existing (both reproduce on the pinned binary) and both look like one cause: a nested type's identity is not per-specialization. They should be taken together, and taking them is what unblocks rung 6 — more than any of the walls the ticket currently predicts.

Method note

Every row above is a compile-and-run against fpc -Mobjfpc as oracle, one file at a time — the rung suites themselves (make test-fgl, test-fpjson) are hook-refused per CLAUDE.md and are Track T's to sweep. One file at a time is enough to survey a rung and is what found all three defects.

2026-08-27, later — all three nested-type defects are closed; rung 6's named blocker is gone

The three defects the re-survey above concentrated the rung's cost into are now fixed, and the second and third turned out to be one cause, one change (7ee75329e), as predicted:

defect outcome
nested type's field named after an enclosing type parameter fixed 83468c546
two templates cannot share a nested type name fixed 7ee75329e
second specialization of a generic with a nested type segfaults fixed 7ee75329e

The shared cause is worth keeping because it is this repo's documented double-case shape: AddClassLikeType registers a nested type under its QUALIFIED name once the bare name is taken, and its own comment says the qualified spelling and a bare one inside the owner's body should "find the same entry" — but only the qualified path was ever wired, because every FindNestedType call site keys on a .. The bare reference fell through to the flat unit table and found whichever was registered first, so a generic's nested type — re-materialised per instantiation on purpose — was shared by every instantiation after the first.

What identified it was ordering, not reading: whichever template was specialized second broke, and swapping the order moved the error to the other one.

Status of rung 6 (rtl-generics) after this: every wall its ticket names or predicts is now cleared locally — type-keyword method names, untyped constref, generics across units, interface and class constraints, TArray<T>, and nested types. Its blocked-by is still the Track B typinfo/PTypeData gap, and the actual corpus (packages/rtl-generics) is not among the trees tools/install_lib_candidates.sh fetches, so the next step for that rung is vendoring it and compiling — not another wall hunt. Nothing local predicts a further blocker, which is exactly the point at which the real corpus is the only thing that will tell the truth.


Rung 6 — rtl-generics: the one-shot diagnostic (2026-08-28)

Fetcher entry landed as 4bb5fd66f (fetch_rtl_generics, same pinned $FPC_COMMIT as the other FPC-sourced corpora). CORPUS_EXPECTED in twatch.py is deliberately untouched — this rung is a one-shot diagnostic, not a gated job. Gate what can be green; diagnose what cannot.

Binary: 2c4e727d4b63, verified self-host fixedpoint at 4bb5fd66f. Every number below came from that binary; both new defects reproduce on pinned too, so none of them is fallout from the nested-type fixes.

What compiles today

unit lines status
generics.strings.pas 37 clean
generics.helpers.pas 146 clean
generics.memoryexpanders.pas 227 clean
generics.hashes.pas 1,617 clean
generics.defaults.pas 3,358 blocked
generics.collections.pas 4,165 blocked only through defaults

2,027 of 9,550 lines compile clean. All blockage is concentrated in generics.defaults.pas; collections was never independently assessed because it cannot get past its uses.

The answer to the question that motivated this: it is NOT typinfo all the way down

The rung's dependency graph was wrong. Typinfo is the largest wall but not the only one, and two of the four walls are Track P defects in our own generics implementation. Method: stub each wall out in a throwaway copy ($SCRATCH/rgsrc, never the vendored tree) and re-probe, so each subsequent wall becomes visible. Stubs are labelled {PROBE: ...} in that copy and exist only to see past a wall — nothing here is a claim that the unit compiles.

(SNAPSHOT 2026-08-28, stubbed copy — superseded. Live table at the top.)

# wall owner sites ticket
1 typinfo: PTypeData fields, PTypeInfo, GetTypeData, TTypeKind, TOrdType, TFloatType B see list below feature-typinfo-facade-unit (p72)
2 generic method header binds to same-named non-generic class P ~700 lines gated bug-p-a-generic-methods-out-of-line-header-binds-to-a-same-named-non-generic-class
3 TGeneric<T>.ClassMethod inside another generic's body P 13 in defaults, ~357-ish shape count in collections bug-p-a-generic-class-method-call-is-undefined-inside-another-generics-body
4 SysUtils gaps: EArgumentOutOfRangeException, Exception.CreateRes, System.Error/TRuntimeError B 3 + 3 7 + 7 + 7 (see note) (to file / relay to frankB)

The two 3s in row 4 were one wrong measurement reported twice. Re-measured 2026-08-28: generics.defaults.pas has 7 EArgumentOutOfRangeException sites and 7 CreateRes(@SArgumentOutOfRange) sites — and they are the same seven lines (2960, 3049, 3075, 3078, 3182, 3218, 3221), because each line spells both. So the two symbols never had independent counts to agree on; one figure was recorded under two headings, which is what made it look corroborated. A count that stops at 3 where the truth is 7 is the shape of an error-limited compile, not of a grep. Corpus-wide the real figure is 28 CreateRes(@…) sites — 18 in generics.collections.pas, 7 here, plus one each of SDuplicatesNotAllowed / SDictionaryKeyDoesNotExist / SArgumentNilNode — so this blocks collections harder than defaults, and collections has not been probed past its earlier walls.

After stubbing all four, generics.defaults.pas produced no further distinct errors — the walls above are the complete set for that unit, not a prefix of an unknown-length list.

The named typinfo list frankB asked for

Not "generics.defaults needs typinfo" but exactly this surface:

OrdType and FloatType are used only as case selectors dispatching to a comparer, and MinInt64Value/MaxInt64Value only in a single range test — so the facade's hard requirement is a correct OrdType/FloatType for ordinal and real types. That is a much smaller target than 39 references suggests.

Caveat, stated plainly

Walls 2 and 3 gate the parts of defaults that walls 1 and 4 do not, but the four sets are not proven disjoint: stubbing is not compiling, and code behind a stub was type-checked against the stub, not against the real thing. The partition above is sound as a list of what must be built; it is not a prediction that fixing all four makes the unit compile on the first try. Re-run this diagnostic — it is one probe.sh — after the typinfo facade lands.


Rung 6 partition superseded — five walls, not four (2026-08-28, end of session)

The four-wall table above was correct for that unit at that moment, and both halves of that qualifier have since moved. Recording it here rather than editing the table, so the earlier measurement stays readable as what it was.

What changed: 042bcbb32 fixed wall 2, which moved generics.defaults.pas from failing at line 2173 to 2351 and exposed a fifth wall the partition did not predict — AN_CLASS_VIRTUAL_CALL failing to lower when a virtual class method's address is taken as a value ([[bug-p-the-address-of-a-virtual-class-method-cannot-be-lowered]]). It is not typinfo and not generic-specific: the repro has no generics in it at all, which is worth knowing before anyone searches the generics code for it.

That is the concrete instance of the hedge the original partition carried: code behind a stub is type-checked against the stub, so clearing one wall can reveal another. Do not treat the wall list as a work estimate. Re-run the probe ($SCRATCH/rg/probe.sh, one command) after each wall lands; it is cheap and it is the only thing that keeps this section honest.

Current state of the five walls

(SNAPSHOT — superseded. Live table at the top.)

# wall owner status
1 typinfo surface B facade landed cfa72767f; type-level API only — its instance-taking overloads are still unreachable, see below
2 generic method header binds to same-named non-generic class P fixed, 042bcbb32
3 nested specialize X<T> in expression position P parked, diagnosis banked, reclassified — not a Delphi bug, fails in objfpc on pinned
4 SysUtils: EArgumentOutOfRangeException, CreateRes, System.Error B filed
5 address of a virtual class method P filed

A second chain now runs alongside this one

The typinfo facade shipped, but GetPropInfo(AnObject, 'Caption') — the spelling every FPC consumer uses — still binds the wrong overload. Two defects were stacked there: the implicit class-to-typed-pointer conversion (fixed, 8b75fcabd, which also closed a silent memory-safety hole) and, underneath it, [[bug-p-a-parameters-pointer-element-type-is-lost-between-registration-and-overload-matching]], which is what still gates the facade's consumer path.

That chain does not block this rung — rung 6 needs the type-level API (GetTypeData(PTypeInfo)), not the instance overloads. Stated explicitly because the two are easy to conflate now that they are open at the same time, and a blocked-by: edge between them would rank correctly for the wrong reason.

Standing warning for anyone verifying work on this chain

A repro that passes on the fix and on pinned is not a verification. Two synthetic overload repros written during this session passed on both and were never broken; they were a step away from being recorded as proof that the overload half was fixed. Run the "before". It is the only thing that catches it.


Rung 6 re-probed on the PRISTINE corpus (2026-08-28, later)

Re-ran the diagnostic as this section said to, now that the typinfo facade (cfa72767f) has landed and wall 2 is fixed. This time with no stubs at all — the earlier partition was measured through a stubbed copy, and stubs are what made it a partition rather than a compile.

Binary ea689da902bb. generics.defaults.pas now reaches line 2411.

(SNAPSHOT — superseded. Live table at the top.)

wall status now
1 — typinfo gone: the facade covers it; no stub needed anywhere
2 — generic method header binding gone: fixed 042bcbb32
3 — nested specialize in expression position not reached yet
4 — SysUtils gaps not reached yet
5 — method pointers the current blocker, 28 sites

So two of the five walls are genuinely down, and rung 6 is now behind one defect rather than a list. That is a materially better position than the partition described, and it was worth re-measuring rather than assuming.

Wall 5 turned out to be bigger than filed, and the first boundary was wrong

Filed as "the address of a virtual class method cannot be lowered". Measuring eight shapes instead of four shows it is two independent defects, and neither is really about virtual:

Full table, repro and direction: [[bug-p-a-class-method-cast-to-a-method-pointer-inline-segfaults]] (p70). The virtual ticket is its honest-exit sibling — same shape, IR_UNSUPPORTED instead of a bad pointer.

Worth recording how the first table went wrong, since this section is where the method gets described: four shapes looked like a clean boundary ("everything adjacent works, only the inline cast is broken") and the two rows that separate the defects — class → var, and instance with an inline cast — had not been tried. The fix that boundary implied would have left half the bug in place. Vary the shape until the table has no untested neighbours, not until it looks tidy.

Rung 6 progress, 2026-08-28 (frankA) — :3250:3341, and the wall list keeps growing

Five defects landed today, four of them walls this unit was standing behind: resourcestring addressability, wall 6 (a generic class method call inside another generic's body), mutually-referencing generics rejected as circular, and — in one fix closing two tickets — a generic specialized before its declaration (bug-p-a-generic-specialized-before-its-declaration-is-unresolvable + bug-p-a-generic-prerequisite-is-emitted-before-the-referenced-template-exists). PARK CONDITION SUPERSEDED -- dated ladder history, not a live gate. The only live resume condition for this ticket is the one in the LIVE STATUS blockquote at the top; every wall named below was cleared on the date its section carries.

generics.defaults.pas moved :2960 → :3231 → :994 → :3250 → :3341 across the day. Note the :994 — the third fix moved the failure backwards, because a construct that had been rejected outright started compiling and reached a new error earlier in the file. A line number going down is not a regression here, and reading it as one would have wasted a session.

The new stop is a different class of error — "LookupEqualityComparer": a pointer has no members — not another specialize. That is the first time today the unit has failed on something other than generic prerequisite resolution, so the next wall is probably not more of the same machinery.

The rung-6 partition is still superseded, and today is more evidence for the warning above, not less. Four walls cleared, and the unit is still not through. Re-run $SCRATCH/rg/probe.sh before estimating anything; do not read the wall list as a work estimate.

CORPUS_EXPECTED untouched throughout, and the prerequisite scan was not weakened to make the corpus advance — the fix adds a second run of the same scan at a later time; both original scans stay, and test_generic_cycle_fail still correctly refuses.


2026-08-29 (frankA) — rung 6 CLEARED: generics.defaults.pas compiles end to end

First: the park note at the top of this ticket is STALE and should be read with this section. It lists three open items; two were already closed before this session started:

| park note said | actual state on 2026-08-29 |

PARK CONDITION SUPERSEDED — dated ladder history, not a live gate The only live resume condition is the one in the LIVE STATUS blockquote at the top; every wall named here was cleared on the date its section carries.
wall 6 Delphi half, bug-p-a-generic-class-method-call-is-undefined-inside-another-generics-body — open in unfinished/ done
wall 7, bug-p-a-resourcestring-is-not-addressable (p55) done, c9cf8c457
bug-p-two-different-nested-specializations-of-one-template-collide (p65) still open, in backlog/

A park note is a snapshot of the moment it was written and the tickets it names PARK CONDITION SUPERSEDED -- dated ladder history, not a live gate. The only live resume condition for this ticket is the one in the LIVE STATUS blockquote at the top; every wall named below was cleared on the date its section carries. keep moving without it. Re-measure before reading it as a plan — that is what this session did, and the corpus had advanced past everything the note describes.

The new wall, found and closed

Re-running the compile put generics.defaults.pas at a wall the note does not mention:

error: @TEquals.Class: the address of a routine with no body was taken

That is [[bug-p-a-forward-declaration-does-not-bind-a-differently-cased-body]], filed and fixed this session: Pascal is case-insensitive, so TEquals.&Class (declared, :186) and TEquals.&class (implemented, :1566) are one routine, and FindProcOverloadRec compared names exactly — so the body registered a second proc and the declared one stayed bodiless.

The & escape is a red herring and was the first hypothesis: it is refuted by the pair of controls — escaped-with-matching-case works, unescaped-with- mismatched-case fails. A plain function Bar; forward; + function bar; reproduces it with no class and no escape, and says unresolved forward: Bar, which names the defect outright. The minimal repro was worth more than every hypothesis formed from the corpus error.

Rung 6 result

unit lines before after
generics.defaults.pas 3,358 blocked in the VMT const block compiles end to end
generics.collections.pas never independently assessed reaches a NEW wall

generics.collections.pas's wall is unknown type: TKey at generics.defaults.pas:790 — a generic type parameter not in scope at the point the specialization is materialised. Independent of everything above and of the one remaining park item; it is the next rung's subject.

Wall table, updated

# wall owner status
6 generic class specialized by the enclosing generic's type parameter P DONE
7 @SArgumentOutOfRange — resourcestring not addressable P DONE c9cf8c457
8 forward decl does not bind a differently-cased body P DONE (this session)
9 unknown type: TKey — generic type param out of scope in a materialised specialization P open, new

Still open and untouched: [[bug-p-two-different-nested-specializations-of-one-template-collide]].

PARKED again 2026-08-29 (frankA) — rung 6 done, rung 9 open and unclaimed

PARK CONDITION SUPERSEDED -- dated ladder history, not a live gate. The only live resume condition for this ticket is the one in the LIVE STATUS blockquote at the top; every wall named below was cleared on the date its section carries.

Out of working/ because it is a live lock and nothing is happening on it. Everything is pushed. Nothing is reverted or half-applied; no code is mid-edit.

State: generics.defaults.pas compiles end to end. generics.collections.pas is the next unit and its wall is [[bug-p-a-delphi-mode-generic-from-a-used-unit-cannot-be-specialized]] (rung 9), which carries two refuted hypotheses with the measurements that refuted them — the macros are fine, and it is not the forward-decl case bug. Its first job is a two-unit reduction, which has not been built.

Also still open: [[bug-p-two-different-nested-specializations-of-one-template-collide]].

Read the 2026-08-29 section above before the park note at the TOP of this PARK CONDITION SUPERSEDED -- dated ladder history, not a live gate. The only live resume condition for this ticket is the one in the LIVE STATUS blockquote at the top; every wall named below was cleared on the date its section carries. ticket — that older note was stale on two of its three items by the time it was read, which is the failure mode this one will have too. Re-measure the corpus first; it takes one compile.

Rung 9 RE-DIAGNOSED 2026-08-29 (frankA) — and the first framing was wrong

The wall behind generics.defaults.pas was filed as "unknown type: TKey — a generic type parameter is out of scope", marked observed, not diagnosed. Good thing: reduced, it is not about type parameters, not about TKey, and not about the macros. It is

in {$MODE DELPHI}, a generic declared in a USED UNIT cannot be specialized at allvar o: TOne<Integer>; answers unknown type: TOne.

Same-unit works. The objfpc specialize TOne<Integer> spelling works cross-unit. Only the Delphi angle-bracket surface fails, and a ONE-parameter generic fails, so arity is irrelevant too. Eleven lines reproduce it.

Cause, measured: DelphiRewriteGenericUses sweeps the shared Tokens[] starting at insertAt — just past the template's own declaration — and the main program is lexed BEFORE the unit it uses, so the program's use sits below that index and is never rewritten. Renamed to [[bug-p-a-delphi-mode-generic-from-a-used-unit-cannot-be-specialized]] and banked, not microfixed: the obvious "start at 0" is wrong on where the minted alias must live and re-widens the surface of the already-recorded bug-a-the-delphi-generic-rewrite-is-not-idempotent.

Note for the ladder — and see the 2026-08-29 correction at the top of this file. This was recorded as the SECOND ordering defect in that one rewrite, alongside wall 6's GenericMethodCount=0, with the suggestion that one restructuring might close both. It does not: wall 6 was closed on 2026-08-28 by extending the prerequisite SCAN, leaving this arm untouched, and this arm was then closed by moving the SWEEP to the uses clause. The question was worth asking; the answer is no.


2026-08-30 (frankA) — re-probed at HEAD 66b068019. Rung 9 is CLEAR; the wall MOVED

Re-measured rather than read. Six rtl-generics src units, each through a driver program, -Fu/-Fi at the src root:

unit at HEAD
generics.defaults OK
generics.hashes OK
generics.helpers OK
generics.memoryexpanders OK
generics.strings OK
generics.collections unknown type: TKey

Five of six now compile. generics.defaults compiling standalone is rung 6 holding. Rung 9's blocker ([[bug-p-a-delphi-mode-generic-from-a-used-unit-cannot-be-specialized]]) closed at 625991d20 and its own repro no longer fails.

The remaining wall is NOT rung 9's, despite wearing its error text

unknown type: TKey is the string rung 9 was originally filed under and then explicitly re-diagnosed away from. Reading it as "rung 9 is still open" would be wrong. Two facts separate them:

Narrowed by bisection to one include, and four hypotheses died first

Truncating the interface at declaration boundaries: cut@438 clean, cut@474 reproduces. The only thing in between is

{$I inc\generics.dictionariesh.inc}

which declares TPair<TKey, TValue> and specializes IEqualityComparer<TKey> across the unit boundary.

Recorded because they cost the time and would otherwise be re-tried: four hand-built reductions did not reproduce — plain cross-unit IComparer<TKey>; plus constref; plus a method implementation taking it; plus a macro-supplied parameter list ({$DEFINE MAP_CONSTRAINTS := TKey, TValue}); plus a nested public type/private var section. All five compile clean. The obvious mechanism ("specializing a cross-unit generic with a still-generic argument") is therefore not sufficient, and anyone starting from it will burn the same hour.

Moving those declarations into an {$I} include DOES break — differently

Eleven lines, and it separates the two binaries:

PINNED : pascal26:6: error: unknown type: TKey
HEAD   : pascal26:1: error: unexpected token
         near: interface uses u_tmpl type specialize >>> TPair TKey

specialize has been injected before a generic DECLARATION, which is the Delphi rewrite treating TPair<TKey, TValue> = record as a use. That is the sweep 625991d20 moved to the uses clause. Both binaries fail, so this is a changed failure mode on already-failing code, not a working case broken — but the wall was re-walled one layer down, exactly the shape this ladder keeps producing. Filed as [[bug-p-the-delphi-generic-rewrite-rewrites-a-shadowing-declaration-as-a-use]].

Next holder: the remaining corpus wall is the include, not the specialization. Start from the bisection above, not from the error text.

PARKED 2026-08-30 (frankA) — released from working/, probe complete

The re-probe above is finished and the ladder's next step is blocked on PARK CONDITION SUPERSEDED -- dated ladder history, not a live gate. The only live resume condition for this ticket is the one in the LIVE STATUS blockquote at the top; every wall named below was cleared on the date its section carries. [[bug-p-the-delphi-generic-rewrite-rewrites-a-shadowing-declaration-as-a-use]]. Released from working/ rather than held, because a lock held by a session that is not working the ticket reads as "someone is on it" while nothing happens — which is the exact failure measured fleet-wide tonight and filed as decide-the-ticket-lock-is-too-heavy-for-a-per-minute-commit-loop.

Everything is pushed. Nothing is half-applied. Re-measure before trusting the table above — that is the rule this ladder keeps re-teaching, and this section is now itself a snapshot.

CORRECTION 2026-08-30 (frankA) — the include was NOT the trigger, and the repro was a DIFFERENT bug

The section above says the corpus wall was narrowed to {$I inc\generics.dictionariesh.inc} and that an 11-line include repro reproduces it. Both halves are wrong, and this correction is the reliable part of that entry.

The repro was a different defect. Its error is unexpected token; the corpus wall's is unknown type: TKey. I let "both fail near generics after the same bisect" stand in for "same bug". The repro's real trigger was a name reuse I had left in the support unit from an earlier experiment — the include and the reuse varied together and I named the wrong one. Isolated four ways: include without the reuse is clean; the reuse without any include fails. That defect is real, is fixed, and is [[bug-p-the-delphi-generic-rewrite-rewrites-a-shadowing-declaration-as-a-use]].

The corpus wall is untouched by that fix — re-probed after it landed, PARK CONDITION SUPERSEDED -- dated ladder history, not a live gate. The only live resume condition for this ticket is the one in the LIVE STATUS blockquote at the top; every wall named below was cleared on the date its section carries. generics.collections still stops at unknown type: TKey in generics.defaults.pas, same line, same message.

What survives: the bisection itself. cut@438 clean, cut@474 reproduces, and the include line sits between them. That is a measurement and it still stands — but it locates the trigger's neighbourhood, not its mechanism, and I wrote it up as though it were the mechanism.

For the next holder: the corpus wall is UNREDUCED. There is no repro for it. Do not start from the include hypothesis and do not reuse the shadowing repro — that is a different bug. Start by asking why generics.defaults parses cleanly alone and fails when reached through generics.collections, and get a reduction before believing any mechanism, including this paragraph's framing.


2026-08-30 (frankA) — the wall is REDUCED, and the reported line was never the defect

Read this section instead of the "LIVE STATUS" table above. That table's wall (unknown type: TKey in generics.defaults.pas) is gone, and the CORRECTION section above it — which says the wall is unreduced and warns against the include hypothesis — is now itself stale. Third time this ticket has gone stale on the same field. See the note at the end about recording the recipe rather than the answer.

Census, and the attribution is deliberately unlocated

binary first error total
pinned defaults:46 unknown type: TKey 20
a60f92ba830a (HEAD minus the shard0-6-2 whitelist) collections:120 unknown type: PT 4
22c67e5ea61e (HEAD, shard0-6-2 landed) collections:120 unknown type: PT 4
3309b9ba6609 (HEAD + the fix below) collections:146 (a new wall, see below) 1

Rows two and three are identical, so the TKey wall did not move because of tonight's whitelist — consistent with the bound-name harvest being unchanged at names=293 cap=512 overflow=0. It moved for something already in HEAD that I have not identified. Recorded as unattributed on purpose: a fix landing shortly before an improvement is the cheapest wrong attribution available, and one extra A/B run against the pre-fix binary was the whole cost of not making it. There is something in HEAD nobody has accounted for.

Root cause of the 4-error wall — and it is 24 lines away from where it is reported

generics.collections.pas:144:

TCustomPointersEnumerator<T, PT> = class abstract(TEnumerator<PT>);

A generic class, declared bodyless, carrying a modifier. ParseGenericTemplateNamed detects bodyless forms up front because there is no end for its depth loop to count down to — but its test looked at the token right after class for ( or ; and never skipped abstract / sealed. It saw abstract, concluded the declaration had a body, and let the depth loop swallow the following declarations until it found somebody else's end.

The damage was reported at line 120, on function DoGetCurrent: T — a line with nothing wrong with it, in a class that compiles fine alone. That is why every reduction aimed at line 120's text failed, including the four this ticket already records and one more I wrote tonight. The reported line was not the defect and never had been.

Confirmed by truncation at real declaration boundaries:

cut verdict
cut@125 (TEnumerator only) clean
cut@141 (adds TEnumerable, PT = ^T, TEnumerator<PT>) clean
cut@144 (adds the bodyless generic) fails

Ruled out first, by measurement rather than argument: the Delphi rewrite (p.dgen shows injections on the uses at 133/137/139/144/152 and none at 120 or 135), and harvest overflow (names=293 cap=512 overflow=0).

Fix, and it is a sibling I should have found last commit

compiler/pasparser_generic.inc — skip abstract/sealed before the bodyless test. The identical omission was fixed in CollectNestedTypeNames earlier tonight, in this same file, and I did not grep for the second copy. devdocs/dev/normalise-dont-special-case.md says in as many words: if you fix a bug on one arm of a double case, grep for the sibling before closing the ticket. One decision, two hand-rolled copies, one of them fixed.

Needs all three of generic + modifier + bodyless, which is why it survived: the non-generic path consumes the modifiers itself (pasparser_decl.inc ~4449) and was always right, and a real body ends at its own end.

Regression test test/test_generic_bodiless_class_modifier.pas, in test-core, asserting bodiless 7 3 1. Per-form, one form per program, against 22c67e5ea61e:

form before after
TAbs<T> = class abstract; FAIL ok
TDerived<T> = class abstract(TBase<T>); FAIL ok
TSealedB<T> = class sealed(TBase<T>); FAIL ok
TFwd<T> = class; (no modifier — the control) ok ok

The control is what isolates the modifier as the variable. It is not in the test program: FPC rejects TFwd<T> = class; as "Type TFwd$1 is not completely defined", so including it would have cost the oracle — pxx accepting it is the ordinary accept-more divergence, not a defect. I found that by running FPC on the finished file, which is the only reason the header does not now carry a false "FPC agrees" claim.

The new wall, one error

generics.collections.pas:146: generic templates must be class, record,
interface, array or procedure declarations
  near: T PT >>> object strict private

TCustomPointersCollection<T, PT> = object — a generic object type, which ParseGenericTemplateNamed does not accept. Distinct from everything above; not started.

For the next holder — and for this ticket's own health

This ticket has now gone stale three times on the same field, each time because it recorded what the wall was rather than how to re-derive it. The wall is a function of the whole compiler and moves faster than the ticket is read. The re-derivation is four commands:

R=library_candidates/rtl-generics/packages/rtl-generics/src
printf 'program d;\nuses generics.collections;\nbegin\nend.\n' > /tmp/d.pas
./compiler/pascal26 -Fu$R -Fi$R /tmp/d.pas /tmp/d 2>&1 | grep -c 'error'
# then: truncate at declaration boundaries to find the FIRST bad line,
# because the reported line has twice now not been the defect.

Do not trust any error line in this unit without a truncation bisect.

The last error is a DECIDED NON-GOAL, so rung 6 is finished as far as it goes

generics.collections.pas:146, TCustomPointersCollection<T, PT> = object, is not a small gap in the generic path. pxx has no object type at all — measured, not assumed: a plain non-generic

type TObj = object F: Integer; end;

fails identically (Expected: begin, but got: F). So adding tkObject to ParseGenericTemplateNamed's accepted set would buy nothing; the type itself does not exist.

That is already tracked and already answered:

So rtl-generics will not compile fully, by design, and rung 6 should not be read as blocked. It went 20 errors → 1, and the 1 is a construct we have decided not to support. Anyone who picks this up to "finish rtl-generics" would be reversing a standing decision, at prio 15, to make a measuring instrument read zero — which is the exact thing that decision refuses.

Do not file the remaining error as a corpus bug. If the object decision is ever revisited, it is revisited on its own ticket, not because a corpus wants it.

PARKED 2026-08-30 (frankA) — rung 6 done, lock released, nothing half-applied

Released from working/ because rung 6 is finished as far as it goes and this is a multi-rung ladder: holding it open on one completed rung blocks the file for no gain. Everything is committed and pushed; there is no in-flight edit.

Rung 6 (rtl-generics) this session: 20 errors → 1, and the 1 is the decided non-goal recorded in the section above. Do not read that as blocked.

Next holder: read the section above before the LIVE STATUS table. That table has now been stale three times on the same field, and the two corrections above it are dated. The re-derivation recipe is four commands and is the reliable part.

2026-08-30 (frankA) — re-measured; rung 6's remaining wall is a Track U decision, not a bug

Did what this ticket tells its next holder to do: re-measured before reading any note as a plan. Every prior park note was stale again, in the same direction — the corpus had advanced past what they describe.

the notes said measured on HEAD today
rung 9 = bug-p-a-delphi-mode-generic-from-a-used-unit-cannot-be-specialized, open done
wall 6 Delphi half, open done
wall 7 resourcestring, open done
generics.collections wall = unknown type: TKey not reproducible — a different wall now
bug-p-two-different-nested-specializations-of-one-template-collide still open, backlog/

Four of the five referenced defects are closed. The one open item is unchanged.

How to re-measure (nobody had written this down)

pxx has no standalone-unit output, so a unit needs a driver:

S=library_candidates/rtl-generics/packages/rtl-generics/src
printf 'program d;\nuses Generics.Collections;\nbegin\nend.\n' > /tmp/d.pas
./compiler/pascal26 -Fu$S -Fi$S/inc /tmp/d.pas /tmp/d.bin

(--check-only does not exist; compiling the .pas directly gives "this file is a unit, not a program", which is easy to misread as a corpus failure. Two minutes lost to each, recorded so the next holder loses none.)

Current rung 6 state

unit result
generics.strings compiles
generics.defaults compiles end to end (confirms the 2026-08-29 note)
generics.collections wall at :146
pascal26:146: error: generic templates must be class, record, interface, array
                     or procedure declarations

The wall is object, and it is NOT a whitelist omission

TCustomPointersCollection<T, PT> = object — the legacy value-object. The whitelist in pasparser_generic.inc:1130 omits it, but widening it would be wrong: there is no tkObject token, and object is already claimed by an unrelated meaning — the rooted object reference type ([[feature-object-reference-type]], pasparser_decl.inc:492), whose comment states outright that value-object "was never supported here".

So this is a known absence with a standing decision behind it: [[decide-old-style-object-types]] chose option A, do not implement, in decided/.

The decision's own revisit trigger has fired — filed, not decided

That decision named its trigger: "Option B, in full, the moment actual source someone wants to build needs it. Not an FPC test — a program." rtl-generics is that source, and this rung is prio 75.

Filed as [[decide-revisit-object-types-rtl-generics-fired-the-trigger]] rather than acted on, because the cost case has changed and the call is the user's. Measured, and this is the part worth carrying:

The two deltas are the keyword and a protected that nothing inherits from.

Rung 6 is therefore blocked on a decision, not on a bug, and no amount of frontend work moves it until that is answered. That is a different kind of stop from every previous wall on this ticket, all of which were defects — so do not go looking for the next ordering bug behind it.

Still open, unchanged

[[bug-p-two-different-nested-specializations-of-one-template-collide]] (p65) — independent of everything above, and its "where to start" remains a hypothesis from the error's shape, not a measurement.


Is the park's condition met? — judgement, 2026-08-30 (frankD, read-only pass)

Asked by the coordinator: "do seven resolved blockers add up to a resume?" Partly — and the count is answering a question this ticket stopped asking.

The seven are real, and the canonical table's condition IS met

Every wall in LIVE STATUS — THE ONE CANONICAL TABLE is now closed. Its own conclusion was "Rung 6 is now behind wall 6 alone", and wall 6 ([[bug-p-a-generic-class-method-call-is-undefined-inside-another-generics-body]]) closed 2026-08-28 in 35f485537 — which the 2026-08-29 note at the top of this file already says. Wall 7 is resolved too. Wall 3 was established to be wall 6. So on the condition as written, the park is discharged.

But the park was re-written mid-park, and the new condition is live

The Status line says "parked 2026-08-30 — rung 6 blocked on PARK CONDITION SUPERSEDED -- dated ladder history, not a live gate. The only live resume condition for this ticket is the one in the LIVE STATUS blockquote at the top; every wall named below was cleared on the date its section carries. [[decide-revisit-object-types-rtl-generics-fired-the-trigger]]". That ticket is open in backlog/ at U p70. It post-dates all seven.

This is the structural point, and it generalises past this ticket. Counting resolved blockers assumes a park condition is static. This one was replaced while parked, so the seven and the current block are disjoint sets, and a resolved-blocker tally answers a question the ticket had already stopped asking. A park's condition needs a date as much as a park does — and only the Status line here carries one, which is why it is the only line that was right.

The resume is real, but it is not rung 6 — and both named alternatives moved

Rung 6 waits on a human decision and nothing an agent does changes that. What is actually available has itself drifted since it was written down:

  1. Rung 4 (fpjson) is the live candidate, and its row above was wrong in both halves. The blocker resolved 2026-08-25 (042e13b5c); the "in no testmgr tier" claim ended when test-fpjson was added to full. So the rung is not RED-and-unwatched — it is unmeasured since its blocker closed, which is a different and much cheaper problem. I did not run it: make test-fpjson is a full-tier target and the hook refuses it here; Track T sweeps it.
  2. The escape hatch named in the U ticket has closed. That ticket argues — correctly at the time — that blocked-by should NOT go on this umbrella, because it would hide workable rungs, citing [[bug-p-two-different-nested-specializations-of-one-template-collide]] [P p65] as "explicitly independent of this decision". That resolved the same day, 4d5f86a0b. The reasoning stands; the example it rests on is gone, so the trade wants re-pricing rather than re-affirming.

On the frontmatter edge — do NOT add blocked-by here

The coordinator's standing instruction is to promote a real blocker into frontmatter, and I am declining for this one, on the coordinator's own earlier reasoning: blocked-by on an umbrella removes the whole ladder from ready when only one rung is blocked. Correct ranking bought by hiding several workable rungs is a worse trade.

The structural fix is that the edge has nowhere correct to go. blocked-by is a whole-ticket field and the block is per-rung, so no frontmatter on this file can be right. Rung 6 wants to be its own ticket, carrying the edge; then the ranker sees the real dependency, the ladder stays visible, and the prose stops being the only register that knows. Until that split, this section is the compensating control and it is the same class of thing it is compensating for.

Sibling glance (asked for; read-only)

What was and was not done here

Read-only judgement. Prose edits confined to this file: the canonical table (which instructs "Update THIS table. Leave the snapshots alone."), the rung-4 ladder row, and this section. No dated snapshot was altered, no frontmatter changed, no ticket moved, no compiler file opened. pasparser_generic.inc was neither read for edit nor touched — frank-rust has held it uncommitted since 23:39.

2026-08-30 (coordinator) — RUNG 6's BLOCKER MOVED; retarget before anyone reads the park

The Status: line at the top of this file says rung 6 is "blocked on PARK CONDITION SUPERSEDED -- dated ladder history, not a live gate. The only live resume condition for this ticket is the one in the LIVE STATUS blockquote at the top; every wall named below was cleared on the date its section carries. decide-revisit-object-types-rtl-generics-fired-the-trigger". That is now stale and points at settled work. frank-user resolved bug-p-object-value-types-standard-meaning in d23f52948 (board move 50d341cd9): object in type-declaration position is the standard Pascal value type with methods, lowered as an advanced record, and the rooted class-reference meaning is retired.

Rung 6 now waits on [[bug-p-generic-type-param-unresolved-in-class-abstract-template]] [P p70]TCustomPointersEnumerator<T, PT> = class abstract(TEnumerator<PT>) resolves PT in the ancestor clause and not in its member signatures.

The measurement is worth more than the retarget: AN EARLIER LINE NUMBER IS NOT AN EARLIER FAILURE

The corpus wall moved backwards, 146 → 120, and that is the expected shape after this fix rather than a regression. frank-user measured both binaries against the same file:

binary probe as RECORDED stops at
pinned $ pinned generics.collections.pas (done ticket) — this command cannot run, see below :146
HEAD, sha not recorded "a one-line program that only does uses Generics.Collections" (new ticket, frank-user) :120 unknown type: PT, then :123, then :135
HEAD ea5a8ef96, binary 6319b892f517 {$mode delphi} program, uses Generics.Collections, -Fu<rtl-generics/src> (frank-rust) :135 unknown type: TArray:120 never appears

Rows 2 and 3 are the SAME PROBE and disagree. The disagreement is unexplained — do not quote either line number as "the wall" until someone reproduces one.

:146 is a SYNTAX error that aborts the parse. :120/:123/:135 are SEMANTIC errors reported against the template's own line numbers, and they are only reachable once a specialization is streamed — which the abort prevented. So the earlier number is deeper progress.

This also settles a retraction that was itself over-corrected. frank-rust reported :120 earlier, could not reproduce it against either binary, and withdrew the figure — correctly on the evidence it had, since the abort order made :120 genuinely unreachable at the time. The number was not wrong, it was masked, and the honest form of that finding was "not reachable yet", not "not real". Its wider conclusion — that the wall was long-standing rather than a regression from the generics work — was right and remains right.

Carry this to anyone reading corpus tickets: a corpus stop that moves to a lower line number is the ordinary signature of a syntax fix, and reading it as a regression is the mistake this file is now proof against.

2026-08-30 (coordinator, same day) — I RELAYED A LINE NUMBER I HAD NOT MEASURED

The table above originally read "HEAD: past 146; new wall at :120, unknown type: PT", flat, as though one binary had one wall. That was frank-user's measurement written into a ticket on master by me, unverified, twenty minutes after I told another lane that a lead from this seat should arrive with the measurement that would kill it. frank-rust supplied that measurement within the hour: it pulled to ea5a8ef96, rebuilt to a fixedpoint (6319b892f517, converged 1 round), re-ran its own probe and got :135, unknown type: TArray — not :120, not PT.

It then declined to write my number into its ticket on my say-so, "that is the exact substitution that produced the original bad figure, one level up." Correct, and the table is amended rather than defended.

THE RECONCILIATION: THE TWO PROBES ARE DIFFERENT PROGRAMS

Both measurements are true, and the difference is visible in the two tickets without running anything. frank-user's ticket records its invocation as $ pinned generics.collections.pasthe unit compiled DIRECTLY, which reports :120, :123 and :135 as a list. frank-rust compiled a {$mode delphi} program that reaches the unit through uses, with -Fu — and there :120 does not appear at all; the first stop is :135.

So the walls are not a sequence one behind the other; the same file has different walls depending on how it is entered. TCustomPointersEnumerator<T, PT> = class abstract(TEnumerator<PT>) resolves PT when the unit is reached through a delphi-mode uses and does not when the file is compiled directly.

HYPOTHESIS, MINE, UNMEASURED: the dialect mode differs between the two entries — a directly-compiled unit does not inherit the {$mode delphi} of a program that uses it, and delphi-mode generic scoping is what makes PT resolve. The measurement that would settle it is one run: compile generics.collections.pas directly with {$mode delphi} forced. If :120 disappears, the hypothesis holds and :120 is an artefact of the direct probe rather than a corpus wall at all. I have not run it and it is not mine to run — it is recorded here as a hypothesis with its falsifier, not as a finding.

Consequence for bug-p-generic-type-param-unresolved-in-class-abstract-template [P p70]: its title and summary are built on :120 / unknown type: PT, which is reproducible only under the direct probe. Whoever takes it should run the falsifier above first. If :120 is a direct-compile artefact, the real corpus wall is :135 / TArray — a generic array template (TArray<T> = array of T, declared at :57 of the same file) failing to resolve where a generic class template on :133 succeeds — and the ticket wants retitling, not just re-probing.

THE RULE THAT COMES OUT OF THIS, AND IT IS BETTER THAN THE ONE I WROTE

Mine was "a corpus stop that moves to a lower line number is the ordinary signature of a syntax fix". It held. frank-rust's corollary is the load-bearing half:

While an abort stands, every figure behind it is UNFALSIFIABLE, not false. A probe that cannot reach a line cannot report on it, and the honest verdict is "masked" — a third value that is neither reproduces nor does not reproduce. Its ticket collapsed three states into two, and that collapse is what turned a correct measurement into a wrong retraction.

And the discipline every corpus figure in this file now owes: state the PROBE, the SHA and the BINARY. Two lanes measured the same file on the same day and got different walls; neither was careless; and the only reason the discrepancy surfaced is that one of them refused to accept a number from the other. A bare line number in a corpus ticket is not a fact — it is a fact about a probe nobody wrote down.

2026-08-30 (coordinator, third pass) — MY RECONCILIATION WAS WRONG, AND SO WAS THE CORRECTION TO IT

Two hours, three versions of this table, and the third one says we do not know. That is the accurate state, and it took two lanes refusing each other's numbers to reach it.

What is now established, each verified rather than relayed:

  1. pxx refuses a standalone unit. Verified here on an unrelated file so the check shares no upstream with either claim: pinned lib/rtl/aesgcm.paspascal26:2: error: this file is a unit, not a program — compile a program that uses it (pxx has no standalone-unit output). So the done ticket's $ pinned generics.collections.pas cannot have produced any of its output; it is shorthand written for a reader, standing where evidence appears to be.
  2. The mode hypothesis was dead in the source, not in a run. generics.collections.pas:29 is {$MODE DELPHI}{$H+} — the unit sets its own mode and inherits nothing from whoever compiles it. I proposed a run to settle a question the file answers, which is the playbook's own complaint about reasoning where measuring was cheaper, one level up: I reached for a probe instead of sed -n 29p.
  3. And the reconciliation I built on that is not merely unsupported, it is contradicted. bug-p-generic-type-param-unresolved-in-class-abstract-template records its repro as "a one-line program that only does uses Generics.Collections"the same probe frank-rust ran. So "the same file has different walls depending on how it is entered" has no second entry point in evidence. Both lanes ran a uses program and got different first errors.
  4. The two binaries were functionally identical. git log d23f52948..ea5a8ef96 -- compiler/ is empty: only board and docs commits separate them. So a compiler change between the runs cannot explain the difference either.

Every explanation on the table is therefore gone, and the difference is real and unexplained. The remaining candidates are all about the run nobody wrote down — an unrecorded flag, or a binary that was not the sha its lane believed. Only frank-user can settle it, because only frank-user has the shell history.

THE FOURTH MISSING VALUE: A RECORDED INVOCATION THAT DOES NOT RUN

frank-rust's companion to "masked", and it is the sharper of the two:

A command nobody re-executes is not evidence, it is a claim with a $ in front of it.

Both tickets carry a shell block as their repro. One of them cannot run. The check is free — paste it and press enter — and neither of us did it for two hours, through three ticket edits and four messages, while both of us were explicitly arguing about evidentiary standards. A fenced block beginning with $ reads as executed; nothing in the format distinguishes a transcript from a paraphrase, and the paraphrase is what a careful person writes when tidying up.

Same family as face 222 (a test that exists, passes elsewhere and is unwired) and face 212 (the reassuring answer must be inexpressible): the artefact that looks most like verification is the one least likely to be verified.

WHAT ANYONE TOUCHING THE CORPUS FIGURE MUST DO NOW

UNBLOCKED — rung 6 is available (coordinator, 2026-08-30)

The status line said "parked, rung 6 blocked" while its blocker sat in decided/. Corrected in place rather than only here, because a note at the bottom of a long ticket does not reach the person who reads the status line and moves on — the same failure frankB reported today about a frontmatter summary outliving what it summarised, one field over.

decide-revisit-object-types-rtl-generics-fired-the-trigger [U p70] is decided: option C. generics.collections.pas's single = object — no fields, no inheritance, no virtual methods, no constructor — becomes a TObject, and the measurement backs it: identical output on every use (widening assignment from any class, cast-back with virtual dispatch, array of, record field, parameter, nil compare) at code=63287B data=4276B bss=42532B, unchanged. TObject is strictly better here, permitting Free/ClassName/Destroy without the cast the bare reference required. The decision's original cost driver — a second object model with its own storage, lifetime, assignment and VMT — was never what this rung needed.

Residual risk, carried forward from the decision and accepted by the owner: Pascal source outside this checkout using var x: object would break. Four uses in this repo, all in its own tests.

Also from that decision's Consequences, not yet done: [[feature-p-legacy-value-object-types]] [P p15] is framed against option B's full scope and should be rewritten to option C's scope or closed in favour of the new P bug. Not urgent at p15, but it will mislead whoever reaches it.

Ownership: owner: frankA is stale — frankA has taken bug-c-a-c-function-s-calling-convention-depends-on-the-target. Re-claim before the first commit; resuming parked work is the one transition with no PARK CONDITION SUPERSEDED -- dated ladder history, not a live gate. The only live resume condition for this ticket is the one in the LIVE STATUS blockquote at the top; every wall named below was cleared on the date its section carries. prompt to re-claim.

CORRECTION to the note above: I described option B, which the owner REJECTED

Caught by frankB before it built anything, 2026-08-30. The unblock stands. The characterisation of the decision does not.

I wrote "option C … its single = object becomes a TObject". That second half is option B, and the decision names it as rejected, verbatim: "Option B is rejected for the reason the owner gave: object is not TObject and neither FPC nor Delphi treats it as such. B prices inheritance, VMTs and a constructor protocol that the corpus does not use and that nothing has asked for."

Option C, verbatim: "the rooted-reference object is RETIRED rather than kept alongside it. object gets its standard Object Pascal meaning — a value type, i.e. a record with callables — with a hard error on inheritance, virtual, constructor and destructor rather than silent half-support."

How I got it wrong, since the mechanism is reusable: I read the file's tail and its summary:, found a measurement table comparing a TObject version against an object version, and reported it as the ruling. It was evidence inside the analysis, not the answer — and the answer was in a # DECIDED section I never opened. Reading a fragment of a long ticket and stating its conclusion is the exact failure this board has logged three times this week; the fragment I chose was the one that looked like a result.

And it is already implemented. = object lands at pasparser_decl.inc:5745 as a VMT-less value type (UClsIsRecord := True, hard error on an ancestor), closed as [[bug-p-object-value-types-standard-meaning]] in done/. Measured by frankB at HEAD 4f42b78b9, not read: a value-type object with methods compiles, runs, prints 7 8, SizeOf 8 — no VMT pointer; and TB = object(TA) gives "an object type cannot have an ancestor -- pxx lowers object as a value type with no VMT …; use a class to inherit." Both arms live.

So there is no object work in this ticket. Anyone building to my note would have implemented the one thing the owner explicitly ruled out, against a decision file that says so.

Also corrected: no lexer.inc collision on this ticket. object is not a lexer keyword — it is handled in pasparser_generic.inc and pasparser_decl.inc, both of P's own carved-out files. The generic A/P shared-lexer caution does not bind here.

The actual wall (frankB, HEAD 4f42b78b9 / pinned faf762981c3c)

rung 6a  generics.defaults.pas     ok    [671512B code, 1661 procs, 25s]
rung 6b  generics.collections.pas  ERR   "unknown type: TKey"  [75s]

Rung 6a is NOT yet claimed clean, deliberately. generics.defaults compiles standalone and fails to survive being used from collections. The obvious reading is "defaults is fixed, collections is the wall" — but an uninstantiated generic body may never be type-checked at all, in which case the standalone green is vacuous and says nothing about that file. frankB has a control running (same unit with TComparer<Integer>.Default actually instantiated) to tell those apart, and is withholding the wall until it reads. A clean table with no control is what burned the ESP float arm.

The wrong-file diagnostic frankB hit on the way is filed separately as [[bug-p-a-deferred-generic-body-s-diagnostic-names-the-wrong-file-and-line]].

2026-08-30 (frankwasm) — INDEPENDENT corroboration of the note above

frank-rust's measurement and mine were made separately and agree. Recorded because two independent arrivals at the same number is the only kind of confirmation worth having, and because my run carries a control theirs does not (and theirs carries one mine does not — the locally-declared generic interface).

Not a claim on this ticket. Measured before deciding whether to take it, and it changed the answer to that question.

binary at HEAD (post-28b2851cd), -Fu/usr/share/fpcsrc/3.2.2/packages/rtl-generics/src

6a  uses Generics.Defaults      ok   code=671512B  procs=1661
6b  uses Generics.Collections   pascal26:259: error: unknown type: IEnumerable
                                (and the `expected ')' before '>'` that follows it)

TKey is gone — zero occurrences, where the summary said four unbound identifiers. bug-p-the-rtl-generics-corpus-stops-on-tkey-in-a-tlist-body is in done/, closed by 28b2851cd (frank-rust): TList<T>'s template capture overran by ~10,914 tokens because a bodiless nested class ran the depth loop one level high. The summary here still told readers to trust that ticket as the live wall and not to open a new one.

6a's numbers are byte-identical to the ones this file already recorded (code=671512B procs=1661), which is the control: the corpus and the flags are the same, so the difference at 6b is the compiler and not the measurement.

What this means for whoever takes the ladder

The remaining wall is not a frontend bug. FPC supplies generic IEnumerable<T> / IEnumerator<T> from the implicit ObjPas unit; our RTL declares neither, and generics.collections.pas:259 specializes one. That is bug-b-rtl-provides-no-ienumerable-generic-interface [B p55] — Track B, not P — with 28b2851cd serving as the control that the compiler handles the shape. So "take the corpus ladder" currently means drive a Track B ticket, or work a different rung, and that should be a knowing choice rather than a discovery three hours in.

Left in unfinished/ and still owned by frankB. Nothing here is claimed; the summary is corrected because rule 2 requires it and this file is the one that tells its own readers to trust only the canonical table.

2026-09-05 (frankB) — RUNG 6a WAS NOT GREEN, AND THE RECORD IS WHY NOBODY KNEW

Took this off next --track P. Re-measured rung 6a at tip 36d7e5fd4 before doing anything with it, and it failed:

pascal26:1064: error: unexpected token in a unit implementation section:
                      it starts no declaration (a mistyped section header?)
  in: .../generics.defaults.pas
  near: ( AComparison ) ; end ; >>> THashService$TDelphiHashFactory  specialize

$ cannot occur in Pascal source, so that is a MINTED alias spliced into the token stream. 6b stopped in the same file rather than its own, so the wall everyone was driving toward had moved backwards.

Fixed: [[bug-p-a-method-pointer-type-derails-the-delphi-generic-alias-anchor]]. DGenDeclAnchor counted the object in of object as opening a type body; Generics.Defaults has five. Regression from b613b5fcf (2026-08-31), the day after this file recorded 6a green. Bisected over 3425 commits.

Current state, measured, not cited

rung result
6a generics.defaults compiles end to endcode=687896B data=138472B bss=127228B procs=1780, 16s
6b generics.collections for-in: enumerator has no readable Current at generics.collections.pas:1481, 3m31s

6b is back at the wall this file's summary already describes (a pin-ordering dependency on IEnumerator<T>.Current, not a frontend bug). 6a's figures do NOT match the 2026-08-30 record (671512B / 1661 procs) and are not claimed to: six days of codegen moved in between. Equality would have been the surprise.

What this file should take from it

The two 2026-08-30 arrivals at code=671512B procs=1661 were genuinely independent and byte-identical, which is the strongest corroboration this file has ever had for any row — and corroboration is about the READING, not about the AGE. Two correct measurements of a tree that no longer exists look exactly like the discipline working. The better-confirmed a rung is, the more it gets re-CITED rather than re-MEASURED, so the best-established rows are the ones most likely to be stale.

A ladder's recorded rung reads as a floor — the thing already achieved — so a LATER rung failing inside an EARLIER rung's file reads as new depth rather than as regression. That is the tell to watch for here specifically: if rung N+1's error names rung N's file, re-run rung N before reading anything else.

Two confounds this file has recorded three times were checked before calling it a regression, and both mattered:

And a note for anyone bisecting this compiler

Seeding each bisect step from the binary the previous step left behind does not work and is not neutral: the compiler doing the building then changes at every step, and a tip compiler refuses six-day-old compiler sources outright (LoadFile expects string variables in IR codegen in cpreproc.inc). That appeared as five consecutive build SKIPs and a range that never narrowed — a degenerate bisect wearing the shape of a running one. Reseed from the PIN at every step (cp stable_linux_amd64/default/pinned compiler/pascal26, touch the sources, then make), assert the log says converged after rather than verified, and it converges normally.

Parked 2026-09-06

Nobody holds it: the body has said 'owner: frankA is stale' since 2026-08-30 and the frontmatter owner has been empty ever since. working/ + no owner is the one board state invisible in both directions -- out of ready, attributed to nobody -- so at p75 this was the highest-prio P row in existence and absent from the campaign's own count. Parked so it ranks again; RESUME by re-claiming, the ladder itself is live and episodic.

Before resuming: read the reason above, then the ticket body. If the reason does not tell you what would make this worth picking up again, establishing that is the first step -- a park is a handoff to a stranger who may be you.


2026-09-06 (frankD) — RUNG 7 ATTEMPTED for the first time; wall 1 cleared

fcl-passrc is on this box at /usr/share/fpcsrc/3.2.2/packages/fcl-passrc/src (no fetch needed — it is not under library_candidates/, which holds only busybox / c-testsuite / fpc-testsuite / lua in this checkout).

Dependency order is pscanner (5333) → pastree (5947) → pparser (7823) → pasresolver (29660). Start at pscanner; a driver program is required because pxx refuses a standalone unit.

./compiler/pascal26 --mimic-fpc -Fu$S -Fi$S driver.pas out

--mimic-fpc IS REQUIRED and its absence looks like a frontend bug

Without it the first error is FPC_FULLVERSION has no integer value here, so it cannot be compared. That is my invocation, not a defectpaslexer.inc:1010 defines it as 30202 under the mimic profile. paslexer.inc:1402 already records this exact confusion from 2026-09-05 on cfileutl.pas. An undefined symbol pushes on the conditional stack as a BOOLEAN, so a missing define presents as a type mix. Recorded here because the next person attempting a rung will hit it in their first minute.

Wall 1 — CLEARED, c4036925a

pscanner.pp:74: a const section followed by resourcestring ate the keyword as the next constant's NAME. Two hand-maintained terminator lists of one concept had drifted, and the comment on the broken one already claimed to mirror the other. resourcestring and label fixed; threadvar is in the list but still refuses on its own ticket (unsupported at any scope — the control is that it refuses identically with NO preceding const).

Wall 2 — FILED, not fixed

Rung 7 now reaches pscanner.pp:893 and names two:

Wall 1 to wall 2 is +819 lines, past the whole const and type section.

Carrying the file's own standing lesson forward

This section is a dated snapshot like every other wall table here. The :74 and :893 coordinates were true at c4036925a / binary eda82aef9249. Re-run the rung before trusting either — and note both were reported against pscanner.pp by name, which is the reliable half; the ticket warns the LINE and the near: have taken turns being wrong.

2026-09-06 (frankD) — wall 3 characterised, filed, not fixed

pscanner.pp:893, property MaxIncludeStackDepth: integer read ... default DefaultMaxIncludeStackDepth;. Three defects behind one if at pasparser_decl.inc:5711, and only the first is the wall:

  1. the value must be a constant EXPRESSION — default <named const>, default <expr> and nodefault are all refused, fpc compiles all three;
  2. the two unrelated default clauses share one arm, so a scalar value clause sets propIsDefault;
  3. consequently a scalar default 16 declared BEFORE a real property Items[i]; default; steals the default-property slot.

(3) is order-dependent and that is the part worth carrying. Declare the indexed property first and both compilers print 100; declare the scalar one first and pxx refuses what fpc accepts. The natural way to write the example hides it. Even with no indexed property at all the flag is visible in the diagnostic — pxx says default property is write-only where fpc says No default property available.

Not fixed here because it is three changes and one unknown: where the LITERAL is currently consumed is NOT established, and a constant-expression parse dropped in beside whatever is swallowing it would fight it.

2026-09-06 (frankS, relayed by frankD) — the skip list is an inventory of what the suite SPELLS

frankS, working the array cluster: tarray15/tarray16 are both about the DECLARATION spelling of a dynamic-array initialiser (var a: array of LongInt = (1,2,3)). The STATEMENT spelling — a := [1,2,3] — was checked as a CONTROL, expecting the working case that made the declaration case a gap. It compiled clean, printed len=435728179526, and segfaulted. It was not on the skip list and never had been.

A missing name announces itself, a wrong branch does not, and a form the corpus never spells has no row to be wrong on. The skip list is an inventory of what the FPC testsuite happens to write. Where the suite writes only one of two spellings of a construct, the other is not measured as passing — it is not measured. 392/0/108 is a true number about 550 files and it is not a claim about the language.

frankS's operational consequence, and it is the reusable part: a new corpus test has more leverage when it is the SIBLING SPELLING of a construct the suite already covers than when it is a new construct. Both spellings of dyn-array init, both spellings of a set/array literal, statement vs declaration, alias vs anonymous type, bare vs qualified receiver. Two segfaults in one day, neither with a row. (Fixed: 4640dc523 statement form, 0455ce596 declaration form.)

Corroborated the same day, twice, from this ticket's own rung 7 — and note that in both cases the WORKING spelling is what made the broken one invisible:

construct spelling that worked sibling that did not
SizeOf of a field in a method SizeOf(Self.FBuffer) SizeOf(FBuffer) (f8b0e0098)
.Free on a computed receiver b.FA[i].Free b.Objects[i].Free, b.Pick(i).Free (ticket)

The SizeOf case carries frankS's point further than a missing row does. Fixing the refusal exposed a SECOND divergence in the same pair: with a global of the field's name in scope, SizeOf(G) answered about the GLOBAL (100) while Length(G) on the identical name answered about the field (16). fpc says 16 for both. That one was never a refusal at all — a plausible number, no diagnostic, and it would have survived the first fix untouched. So the sibling rule is not only "the other spelling may be missing a row"; it is "the other spelling may be answering a different question", which no skip list can represent.

Practical rule for rows added under this ticket from here on: when a row is written for construct X, write the sibling spelling of X in the same file, and assert a VALUE on both rather than a clean compile. A refusal-only row is green for the whole of the second failure mode above.

2026-09-06 (frankD) — RUNG 7: pscanner.pp COMPILES, LINKS AND RUNS

Fourteen walls to zero. /usr/share/fpcsrc/3.2.2/packages/fcl-passrc/src/pscanner.pp, 5333 lines, --mimic-fpc -Fu<that dir>:

ok: rung7a  [code=548632B  data=116044B  bss=88828B  procs=1129]

AND THEN IT HUNG, WHICH IS THE FINDING WORTH MORE THAN THE ZERO. A driver that constructs a TPascalScanner over a real file and pulls tokens never returned. fpc's build of the same driver prints ident: K / number: 42 / tokens>0: TRUE and exits 0. Ours now prints the same three lines.

The cause was one missing lookup, and it had two faces:

{ TFileLineReader (pscanner.pp:433): FTextFile is a Text FIELD }
WriteLn(F, 'x')       -> printed `3x` ON THE CONSOLE, left the file EMPTY, exit 0
ReadLn(F, s) / EOF(F) -> read STDIN, so EOF never came and the program HUNG

IOHandleSymAt asks FindSym; a field has no Syms[] row; TextIOFileSym answered -1; the CONSOLE path took the call. The whole Text lowering was keyed on a symbol index (GenMakeIdent(fileSym, tyRecord), seven sites), so there was nowhere for a field to go. Fixed by threading the handle as a NODE and adding a designator-level detector.

What this rung is now teaching, and it is not about pscanner

A CORPUS RUNG MEASURED BY "DOES IT COMPILE" IS MEASURING THE WRONG THING, AND EVERY ROW OF THIS LADDER HAS BEEN SCORED THAT WAY. Zero errors is where this unit's most expensive defect STARTED: the compile was clean, the link was clean, the binary ran, and the library did nothing. A wall count cannot see it — there is no wall. Neither can expect_same on a compile, nor an exit code, because the failing shape creates the output file and leaves it empty at rc=0.

Always drive the unit. For a parser library that is ten lines: build an input, run the public entry point, assert a value fpc also produces. Every rung above 4 should have one and none of them does.

The two halves also differ in how they present, which is the reusable half:

how it fails how it looks
write to an unrecognised handle goes to the console file exists, is EMPTY, rc=0
read from an unrecognised handle reads stdin HANGS, or "0 lines" under a redirect

The write half is the dangerous one and the read half is the one that gets found. Under a harness that redirects stdin from /dev/null the read half stops hanging and reports zero lines — a plausible number — so a CI runner would have turned the loud failure into the quiet one.

The six landed fixes on this rung

# fix commit
1 a const section ate the resourcestring that ended it c4036925a
2 array of const in a procedural type 87681a64a
3 a bracket argument at a bare-self method call (bracket door)
4 a nested routine reaches all five of its class's member kinds (member kinds)
5 a nested routine assigns the enclosing function's result by NAME f45be9b78
6 uses sysutils closed the dynamic-array Delete/Insert f5ad23c32
7 .Free on a computed receiver, evaluated exactly once a5588f5c6
8 a Text handle reached through a FIELD this commit

Plus 181576cdc (frankB, the decl-path bracket instance) and 475528dae (frankH, removing the sysutils declarations at the source).

The next unit on this rung is not pscanner. pastree.pp and pparser.pp are the remaining bulk.

2026-09-06 — pastree.pp (5947 lines) attempted: four walls, two down

The first pass reported two, and clearing them revealed two more — the same "invisible until the one before it lands" shape this rung has produced twice already, so four is a lower bound, not the count.

# line shape state
1 2101 ReleaseAndNil(TPasElement(InterfaceName)) — a class typecast as a var argument FIXED
2 2397 a field named ExceptObject vs sysutils' function ExceptObject FIXED
3 2979 a receiver-less Free; inside a method FIXED
4 4940 NameParts.Assign(Parts) — no TFPList.Assign FIXED (lib/rtl)
5 5817 Fields := nil on an array of <record> FIELD FIXED (two defects)

Walls 1 and 2 were both enumerated predicates — a hand-maintained list that must grow per new member and gives no diagnostic when it does not — and that is now the third and fourth instance found on this rung. IsVarArgLvalueCast named AN_PTR_CAST while the backend had peeled AN_CLASS_CAST for months; the member-shadow rule named FindUMeth while fields, properties and class vars are equally members.

WALL 2 IS THE ONE WORTH CARRYING, AND NOT FOR THE FIX. The rule lives in two copiespasparser_expr.inc:5167 for the read, pasparser_stmt.inc:7858 for the write — and the statement copy's own comment already said "the two MUST move together". They had drifted anyway. The two copies emit by-reference argument must be a variable and cannot assign to the result of a function call respectively: two diagnostics, neither naming resolution, one cause. The corpus only ever shows you the arm it happens to use — pastree reads the field into a var argument, so the read arm was the whole visible defect, and the write arm surfaced only because the fixture asserted an assignment. Landing the read fix alone would have left a shadowed field readable and unwritable: a state worse than the one it replaced, and one the corpus unit would have scored as progress.

Wall 3 is the receiver-less sibling of today's .Free work (a5588f5c6 materialised a computed receiver; a bare Free; in a method never gets as far as a receiver) — and with it, five recognisers now serve one concept, four of them keyed on the shape of a RECEIVER, which is why the spelling with no receiver matched none of them. Wall 4 was an outright RTL absence: our TFPList is an empty descendant of TList and Assign was on neither.

WALL 5 ARRIVED WITH NO COORDINATE AT ALL AND THAT WAS ITS OWN DEFECT. It printed

pascal26:0: error: incompatible types: cannot assign Pointer to record

and that was the whole of it — no in: <file>, no near: window, because ErrorPrintAt drives all three off the line number. Every earlier wall on this rung came with a coordinate that was at least a starting point, including the ones this file warns are untrustworthy; this one had none, in 5947 lines.

The cause was not this unit and not this statement. AllocNode stamped ASTLine := 0 for every node in a usesd unit — a correct DWARF decision (the RTL must contribute no line-table rows) that was ALSO serving as the coordinate for every semantic diagnostic. So this was never a pastree problem: it was every semantic error in every corpus unit anyone has ever run, and the rungs kept producing usable numbers only because PARSE errors are reported off the lexer's own position. Split and fixed — [[bug-a-a-semantic-diagnostic-in-a-used-unit-has-no-location-at-all]]; the first diagnosis was wrong and is corrected in [[bug-a-a-desugared-assignment-reports-a-type-error-with-no-location-at-all]].

With a line, wall 5 reduced in one step to Fields := nil on an array of <record> FIELD, and it was two defects with the first hiding the second: the assignment kind check typed the field as its ELEMENT and refused it, and once that false reject was gone the store zeroed four bytes over an eight-byte array handle and SIGSEGV'd. A false reject was load-bearing — nothing had ever reached the lowering behind it. [[bug-a-a-nil-assignment-to-a-dynamic-array-field-is-lowered-as-a-record-zero]].

pastree.pp IS DONE — COMPILED, LINKED AND DRIVEN

All four walls cleared. And the driver is the claim, not the build: the compile probe for this unit is program rung7b; uses pastree; begin end., it reported ok: with a code size and a proc count, and it would have reported exactly that if the unit did nothing at all — the same empty-driver trap this file recorded for pscanner.pp eight hours earlier, walked into again in a file I wrote myself. A real driver — construct a TPasModule, a TPasVariable, a TBinaryExpr, a TRecordValues; print GetDeclaration; AddRef/Release down to zero — matches fpc 3.2.2 byte for byte:

module    = MyUnit / TPasModule / module
variable  = Counter / variable
parent    = MyUnit
binexpr   = 41 + 1
recvalues = 2 / (a: 7; b: 8)
released  = TRUE
afteraddref = Counter
done      = TRUE

That run exercises three of the four fixes on purpose: TRecordValues.Destroy is the Fields := nil path, Release ends in the receiver-less Free;, and the tree it builds is the one TFPList.Assign populates.

2026-09-06 — pparser.pp (7823 lines) attempted: two walls, one down

# line shape state
1 286 FTokenRing: array[0..FTokenRingSize-1] — a CLASS CONST as a field's array bound FIXED
2 635 var CCNames: array[TCallingConvention] of String = (...) — a local var-section array initializer open, filed not fixed

Wall 1 is the same shape as everything else on this rung: the const evaluator recovers a class const from a const SECTION and from a METHOD BODY, and a field declaration is in neither. ParsingClassBodyCi already spanned the gap. The diagnostic was not a constant — about the expression, for a defect in scope, so it pointed at the bound and not at the lookup.

Wall 2 is filed rather than fixed, and the measurement is why. The same declaration spelled const in the same routine compiles and runs correctly today — so the element loop, the enum-indexed bound, the string elements and the routine-local flush all already work for exactly this shape, and only the var path never got an entry to them. A door, not a capability gap. But the const array initializer is ~315 lines INLINE in ParseConstSection, entangled with that routine's locals, and it parses the TYPE as well as the initializer — which the var path has already done. A reusable ParseArrayInitializerInto(symIdx, isLocal) has to derive the dimension list and element kind from the already-declared SYMBOL rather than from the shared parse, and that is the step that stops being mechanical, because each derived value has to be verified equal to what the parse produced rather than assumed. Banked rather than half-done: [[feature-p-a-local-var-section-array-initializer]].

Next on this rung: pparser.pp behind wall 2, then pasresolver.pp (29660).

2026-09-06 — rung 7 pparser.pp: THREE errors to ONE (frank-coord-core)

Re-measured, not inferred, at binary 6950458c2da2 (pin v407 seed + HEAD):

$ pascal26 --mimic-fpc -Fu/usr/share/fpcsrc/3.2.2/packages/fcl-passrc/src \
      -Fulib/rtl -Fulib/rtl/platform/posix rung7c.pas rung7c
pascal26:2670: error: no overload of PeekOper$62727 matches these arguments

One error. 9.6s. This file recorded "THREE errors from TWO causes"; the second cause is closed and the .Name lookup on a dynamic array's record element is gone, so the bogus CompareText arity report it poisoned into is gone with it.

The cause was bug-a-a-dynamic-array-of-class-loses-its-element-type-when-it-is-a-parameter, fixed at 86852f93a. TPasUsesClause = array of TPasUsesUnit arriving as a parameter lost its element's class identity, because the parameter classification chain had an arm for array of RECORD and none for array of CLASS, so the class case fell into the SCALAR arm and its element rec id went to RecName instead of ElemRecName.

CORRECTED, at frankD's insistence and against my own first wording here. I wrote that this file had PREDICTED that cause and that the prediction held. It had not, and frankD said so rather than accept the credit. What this file recorded — and what DID hold — is the CASCADE reading: that the .Name failure and the bogus CompareText arity report were ONE cause counted twice, and that it sat at a .Name lookup on a dynamic array's element. The mechanism (element is a class AND the array is a parameter; parr and tyRecord present, parr and tyClass missing) is nowhere in what was written here.

Their reason for correcting it is the one that matters on this rung: cascade-versus-two-defects is cheap to read off an error list; a missing arm is not, and this rung has already had one wrong subject travel for a day because a near: window from the wrong file read as content. A track record that rounds up is how that happens twice.

The survivor is :2670 and its report is partly a lie. Filed as [[bug-p-the-two-halves-of-an-overload-report-spell-an-array-argument-differently]]: the argument half prints a raw TypeKind and the candidate half is IsArray-aware, so a CORRECT array of record argument reads as a mismatch. Reduced to 12 lines. In :2670, argument 3 is fine and argument 2 is the whole mismatch — consistent with the wrong capture actuals [[bug-p-a-sibling-call-to-a-capturing-nested-function-gets-the-wrong-capture-actuals]] describes. Do not reduce it believing two arguments are bad.

Caveat this file already states and which applies to this row too: in: names pscanner.pp while the construct is in pparser.pp. Unchanged, and still the coordinate problem, not a new one.