feat(reader-arc): frame-anchored compare recognizer (verb-free + polarity blocklist)
Increment 1 reader half. Re-anchor the two multiplicative-comparison frame
regexes on the closed-class frame ('<factor> as many/much <unit> as <REF>')
instead of the _comparison_anchor_verb() whitelist: the verb slot becomes a
verb-free bounded connector, 'as much' joins 'as many'. Admission is now
open-class for safe verbs (scored/wrote/caught/taught/…), gated by the frame.
One closed class is NOT frame-neutral — polarity-inverting/depletion verbs
(lose/spend/give/sell/win/donate/pay/eat/drop/lend): in a compare frame they
make the compared quantity a loss, so actor=factor×reference reads backwards
→ wrong>0. _COMPARE_POLARITY_BLOCK excludes exactly that set (the sanctioned
fallback; the frame is the gate for every other verb). Pins the wrong=0
guards in test_adr_0131_G2a + test_recognizer_comparative_inject.
Develop-on-tune / measure-once held. Footprint over 500 official cases: tune
byte-identical (0 divergences, wrong=0, PARSED=3); measure 1 divergence
(0441 'Zig wrote four times as many books as Flo') which advances
frame-refusal -> question-refusal (sanctioned delta) but does not solve — the
binding wall is the shared summation-question layer. X=15 measured delta=0.
Ruling per the funnel-conditioned escalation rule: mass is in shared layers,
so the next increment is shared-layer (summation-question reader), not a
practice escalation and not more compare frames. wrong=0 held on tune (261),
measure (239), smoke (176).
This commit is contained in:
parent
da663d1007
commit
c3aed13bbe
2 changed files with 125 additions and 6 deletions
|
|
@ -111,3 +111,71 @@ Compare is the **first slice of a much larger reader project**: the reader parse
|
|||
(3/261 tune), and "no injection" (75%) is a shared wall in front of every family. This is not scope
|
||||
creep — it is the arc seeing its true size. The increments stay small and measured *precisely because*
|
||||
the project is large.
|
||||
|
||||
---
|
||||
|
||||
## MEASURED OUTCOME — the reader half (frame-anchored recognizer)
|
||||
|
||||
### What shipped
|
||||
Per the construction constraint (anchor on the comparative FRAME, not verb-whitelist growth), the two
|
||||
multiplicative-frame regexes (`_COMPARE_MULT_ANCHOR_RE`, `_COMPARE_MULT_NTIMES_RE`) were re-anchored:
|
||||
the closed-class frame — `<factor> as many/much <unit> as <REF>` — is the gate, the verb slot became a
|
||||
verb-free bounded connector, and `as much` joined `as many`. `_comparison_anchor_verb()` (the whitelist)
|
||||
is left in place for the ADDITIVE / NESTED frames (out of scope). Both frame regexes remain `$`-anchored,
|
||||
so multi-clause / nested surfaces stay refused by construction.
|
||||
|
||||
### The polarity refutation (and the sanctioned fallback)
|
||||
Pure verb-free anchoring is **not** wrong=0-safe: a polarity-inverting / depletion verb inside the frame
|
||||
("Alice **lost** twice as many apples as Bob") makes the compared quantity a loss, so
|
||||
`actor = factor × reference` reads the relation backwards → wrong>0. The frame does NOT disambiguate this
|
||||
(pinned by `test_adr_0131_G2a…::test_polarity_inverting_verb_not_admitted`, `…spend…`,
|
||||
`test_recognizer_comparative_inject…[Alice lost…]`). Resolution = the fallback the ruling anticipated:
|
||||
frame-anchored **plus a closed-class polarity blocklist** (`_COMPARE_POLARITY_BLOCK`:
|
||||
lose/spend/use/give/sell/win/donate/pay/eat/drop/lend) the connector may not span. Admission stays
|
||||
open-class for every *safe* verb (scored/wrote/caught/taught/baked/grew/…); the blocklist is the one
|
||||
piece of verb knowledge wrong=0 genuinely requires — a small closed semantic class, not a whitelist.
|
||||
**The frame is the gate either way.**
|
||||
|
||||
### Footprint — provable, tiny, safe
|
||||
Develop-on-tune / measure-once discipline held. A new-vs-old frame-fire diff over all 500 official cases:
|
||||
|
||||
| Split | cases | divergent frame-fires | wrong | PARSED delta |
|
||||
| :--- | ---: | ---: | ---: | ---: |
|
||||
| tune | 261 | **0** (byte-identical histogram) | 0 | 0 |
|
||||
| measure | 239 | **1** — `0441` "Zig **wrote** four times as many books as Flo." | 0 | 0 |
|
||||
|
||||
`0441` is the exact archetype (verb-blocked-only, single-clause, frame-at-end). Post-change its frame
|
||||
fires correctly — and the case **still refuses**, one layer downstream: *"no admissible candidate for
|
||||
question: 'how many books did they write altogether?'"*. It advanced frame-refusal → **question-refusal**
|
||||
(the pre-registered sanctioned delta), confirming the frame now reads but the binding wall is the
|
||||
**shared summation-question** layer. Same wall killed `0228` ("…how many boxes in both warehouses
|
||||
combined?"). Smoke gate: **176 passed**.
|
||||
|
||||
### X = 15 — MISSED, delta = 0 (and why it's a shared-layer miss, not a family-exhaustion miss)
|
||||
The 38 tune compare-family cases, classified by their **actual** structural blocker (not the noisy
|
||||
surface histogram): 16 multiclause / nested (`…as Emily, who has…`, `…as Shawna, but 3 fewer…`),
|
||||
10 single-clause-frame-no-match (multi-word "the X Y" references, `than` vs `as`, decimal factors,
|
||||
unit-ellipsis "as much as", subject-is-comparison), 5 "times the number/amount of" (self-referential /
|
||||
possessive references), 5 "as ADJ as" (attribute register), 2 "double what / triple". End-to-end traces
|
||||
of the cleanest candidates (`0228`, `0274`, `0491`, `0165`, `0299`) show **every one dies at a
|
||||
non-compare layer**: summation-question reading, seeding-sentence injection (shared discrete-count),
|
||||
additive-compare, attribute registers, simultaneous equations. Even the cleanest compare-family frame
|
||||
extension (multi-word reference) converts **0** cases end-to-end — and introduces an actor-misbinding
|
||||
hazard — so it was reverted.
|
||||
|
||||
**Ruling per the pre-committed funnel-conditioned escalation rule:** the mass is dying in **shared
|
||||
layers**, not compare-specific logic ⇒ the next increment is a **shared-layer design increment**
|
||||
(highest-leverage: the summation-question reader — evidenced by `0228` + `0441` both dying there), NOT
|
||||
a practice-lane escalation, and NOT more compare-family frame surgery. X is re-applied *after* the
|
||||
shared layer lands.
|
||||
|
||||
### Triple report
|
||||
- **correct**: measure new-correct = **0**. The compiler tier solves the compare cases the reader can
|
||||
already reach (`0101` "double what", `0108` "times the number of", `0411`) — all **pre-existing**, not
|
||||
new from the frame-anchoring.
|
||||
- **refused-by-reason (traps broken out)**: polarity-inverting (lost/spent) → refused by
|
||||
`_COMPARE_POLARITY_BLOCK` (absent from the real splits; pinned by synthetic confusers). Family funnel:
|
||||
16 multiclause/nested, 10 frame-no-match, 5 number/amount-of, 5 as-ADJ, 2 double-what — all now
|
||||
refuse-and-record.
|
||||
- **wrong = 0**: held on tune (261) **and** measure (239) **and** smoke (176). No over-admission from
|
||||
the verb-free widening; no branch-disagreement regression (0 tune divergences).
|
||||
|
|
|
|||
|
|
@ -1217,25 +1217,76 @@ _COMPARE_ADDITIVE_RE: Final[re.Pattern[str]] = re.compile(
|
|||
rf"(?P<reference>{_COMPARE_REF})\s*\.?$"
|
||||
)
|
||||
|
||||
# ADR-0250 reader-arc increment 1 — FRAME-ANCHORED admission (verb-free).
|
||||
#
|
||||
# The multiplicative comparison FRAME — "<factor> as many/much <unit> as
|
||||
# <REF>" / "<N> times as many/much <unit> as <REF>" — is the closed-class
|
||||
# structure that carries the relation. The verb between the actor and the
|
||||
# frame is open-class ("scored", "caught", "taught", "has", ...) and does NO
|
||||
# disambiguating work the frame doesn't already do; growing a verb whitelist
|
||||
# only adds maintenance and trap surface ("scored twice" = event count,
|
||||
# "reads twice a day" = frequency — both verb-adjacent traps). So the
|
||||
# connector below admits ANY 0-3 word tokens in the verb slot and lets the
|
||||
# COMPLETE frame be the gate. This is safe by construction:
|
||||
# * both regexes remain ``$``-anchored, so the frame must end the clause —
|
||||
# multi-clause / nested surfaces ("…, but 3 fewer…", "6 more than three
|
||||
# times…") do NOT match and stay refused (a later layer / practice lane);
|
||||
# * the connector is word-only (``\w+``), so it cannot span a comma or
|
||||
# clause boundary — the actor stays the sentence-initial subject;
|
||||
# * the frequency/event-count traps lack the "as many/much <unit> as <REF>"
|
||||
# tail, so the frame refuses them WITHOUT any verb reasoning;
|
||||
# * the round-trip filter + branch-disagreement + compiler conservation
|
||||
# remain the wrong=0 net unchanged.
|
||||
# ``_comparison_anchor_verb()`` is intentionally left in place for the
|
||||
# ADDITIVE / NESTED frames (out of scope for this increment).
|
||||
#
|
||||
# ONE class of verb is NOT frame-neutral: polarity-inverting / depletion
|
||||
# verbs. In "A <verb> N as many X as REF", a verb like lose/spend/give/sell
|
||||
# makes the compared quantity a LOSS or transfer delta, not a possession —
|
||||
# so ``actor = factor × reference`` (what compare_multiplicative asserts)
|
||||
# reads the relation backwards → wrong>0. The frame does NOT disambiguate
|
||||
# this, so the connector must refuse to span any member of this closed set
|
||||
# (the exact set ``_comparison_anchor_verb`` documents as EXCLUDED). This is
|
||||
# the sanctioned fallback: the frame is still the gate for every OTHER verb;
|
||||
# this blocklist is the minimal, closed, semantic-class piece of verb
|
||||
# knowledge wrong=0 requires — NOT an open-class whitelist. Every non-listed
|
||||
# verb (scored/wrote/caught/taught/baked/grew/…) is admitted verb-free.
|
||||
_COMPARE_POLARITY_BLOCK: Final[str] = (
|
||||
r"lose|lost|loses|losing|"
|
||||
r"spend|spent|spends|spending|"
|
||||
r"use|used|uses|using|"
|
||||
r"give|gave|gives|giving|given|"
|
||||
r"sell|sold|sells|selling|"
|
||||
r"win|won|wins|winning|"
|
||||
r"donate|donated|donates|donating|"
|
||||
r"pay|paid|pays|paying|"
|
||||
r"eat|ate|eats|eating|eaten|"
|
||||
r"drop|dropped|drops|dropping|"
|
||||
r"lend|lent|lends|lending"
|
||||
)
|
||||
_COMPARE_FRAME_CONNECTOR: Final[str] = (
|
||||
rf"(?:(?!(?i:{_COMPARE_POLARITY_BLOCK})\b)\w+\s+){{0,3}}"
|
||||
)
|
||||
|
||||
# Multiplicative: anchor-as-value form ("twice"/"thrice"/"half"/"quarter"/
|
||||
# "third" carry the factor implicitly). "as many <unit>" required; unit
|
||||
# "third" carry the factor implicitly). "as many/much <unit>" required; unit
|
||||
# ellipsis ("twice as many as Bob") is deferred to keep wrong==0 strict.
|
||||
# "quarter" / "third" admit an optional article ("a quarter", "a third") —
|
||||
# the article is not a named group; matched_verb is the anchor word itself,
|
||||
# which is a substring of the source and registered in
|
||||
# COMPARE_MULTIPLICATIVE_ANCHORS, so round-trip checks pass.
|
||||
_COMPARE_MULT_ANCHOR_RE: Final[re.Pattern[str]] = re.compile(
|
||||
rf"^(?P<actor>{_ENTITY})\s+{_comparison_anchor_verb()}\s+"
|
||||
r"(?:a\s+)?(?P<anchor>twice|thrice|half|quarter|third)\s+as\s+many\s+"
|
||||
rf"^(?P<actor>{_ENTITY})\s+{_COMPARE_FRAME_CONNECTOR}"
|
||||
r"(?:a\s+)?(?P<anchor>twice|thrice|half|quarter|third)\s+as\s+(?:many|much)\s+"
|
||||
r"(?P<unit>\w+(?:\s+\w+)?)\s+as\s+"
|
||||
rf"(?P<reference>{_COMPARE_REF})\s*\.?$"
|
||||
)
|
||||
|
||||
# Multiplicative: explicit "N times as many <unit> as <REF>".
|
||||
# Multiplicative: explicit "N times as many/much <unit> as <REF>".
|
||||
# ADR-0131.G.2a — unit slot admits an optional second word ("jumping jacks").
|
||||
_COMPARE_MULT_NTIMES_RE: Final[re.Pattern[str]] = re.compile(
|
||||
rf"^(?P<actor>{_ENTITY})\s+{_comparison_anchor_verb()}\s+"
|
||||
rf"(?P<value>{_VALUE})\s+times\s+as\s+many\s+"
|
||||
rf"^(?P<actor>{_ENTITY})\s+{_COMPARE_FRAME_CONNECTOR}"
|
||||
rf"(?P<value>{_VALUE})\s+times\s+as\s+(?:many|much)\s+"
|
||||
r"(?P<unit>\w+(?:\s+\w+)?)\s+as\s+"
|
||||
rf"(?P<reference>{_COMPARE_REF})\s*\.?$"
|
||||
)
|
||||
|
|
|
|||
Loading…
Reference in a new issue