ekp/postmortem/20260801-forbidden-run-emergency-boundary.md
2026-08-02 15:30:12 +08:00

81 lines
4.0 KiB
Markdown

# Forbidden-Run Emergency Boundary
Superseded note: this record explains the earlier rigid-atom repair. Neither
the fullest-prefix fallback nor the later atom-adjacency prohibition is
current. The accepted final-pass model is recorded in
`postmortem/20260802-final-pass-active-path-preservation.md`.
## Context
At 280px, the showcase paragraph before an overwide `ekp-no-break` atom
collapsed into one-glyph CJK lines. The buffer projector reproduced the
semantic plan exactly, so the visible failure was owned by core line breaking,
not redisplay, overlays, resize timing, or projection.
## What the trace showed
The strict pass correctly found no valid candidate where widths jumped from
an underfull CJK prefix to an overfull rigid atom. The emergency pass retained
only the first permitted candidate from each state. That rule guarantees
reachability for arbitrary hard input, but here it made the atom boundary
reachable only through repeated one-glyph emergency transitions.
Replacing that rule globally with the latest underfull candidate was also
wrong. It fixed the screenshot but changed emergency layouts for ordinary
Latin text: discretionary hyphens disappeared and existing DP-parameter
sensitivity changed. Three established regressions failed independently,
while the unmodified baseline passed them.
## Decision
Keep the original first-candidate emergency transition. In the emergency pass
only, remember the latest permitted underfull candidate. Use that additional
candidate only when one or more forbidden break positions are crossed and the
next permitted candidate is already overfull.
This assigns the exception to the event that creates it: a rigid or otherwise
forbidden run skipping the valid width range. The Elisp 1D path, looseness/
parshape path, and C engine implement the same state transition.
## Rejected alternatives
- Compensate in `ekp-buffer.el`: projection would diverge from the semantic
plan and the public string renderer would remain wrong.
- Break the protected atom: this violates the `ekp-no-break` contract.
- Replace every first-candidate emergency with the fullest underfull one: this
changes unrelated emergency layout, hyphenation, and cost sensitivity.
- Add resize debounce or cache invalidation: the failure is deterministic in
a fresh batch plan and is unrelated to time or stale state.
## Consequences
Rigid atoms remain intact, their ordinary prefix uses the fullest permitted
line, and source-clean projection is unchanged. Ordinary emergency behavior,
strict Knuth-Plass results, discretionary hyphenation, and DP parameter
sensitivity retain their previous semantics. The only additional DP state is
three fallback values plus a flag local to one candidate scan.
Automatic inline no-hyphen and explicit hard atoms are different contracts.
The later policy work keeps automatic inline code breakable at source spaces.
For explicit `ekp-no-break`, the ordinary boundary immediately before the
following hard atom is forbidden before DP; that adjacency rule prevents a
manual hard atom from stranding the preceding ordinary box. It is separate
from this postmortem's emergency forbidden-run fallback, which applies only
after a forbidden run skips the valid width range.
## Verification and rollback
The focused regression failed before the fix and passes in Elisp, C, and the
public buffer path. Normal, seeded-random, and per-test isolated 201-test
suites pass, as do 300 C/Elisp fuzz cases, warning-as-error Elisp compilation,
pinned package-lint, checkdoc, release/dictionary gates, and warning-clean
debug, ASan/UBSan, and portable C builds.
Reviewed 62.7-second evidence at
`/private/tmp/ekp-atom-gui-final-Beg8hb` covers nine checkpoints across
480→280→340→280 and returns PASS: the prefix stays on one screen line, the
atom remains intact, source is exact, overlays remain zero, the C engine is
active, and no black or transient vertical frame appears. Rollback is a
direct revert of the forbidden-run fallback and its tests; no API, schema,
cache, or stored-data migration is involved.