Purpose. A timeless, information-complete record of every gap between the original ncurses (C) and
ncurses-native(native Rust). Each gap is written as a diff: what ncurses does, what ncurses-native does (or cannot do), the gap class, severity, evidence, and what true 1:1 parity would require. Nothing is hidden. Where a surface is reconstructed, it is cited to an oracle court; where it diverges, the divergence is recorded; where it cannot be reconstructed in safe Rust, the boundary is named.This document was produced with an elite four-specialist research panel (C→Rust language/runtime semantics; ncurses feature surfaces incl. lifecycle, input, mouse, slk, wide-char, panel/menu/form; byte-level optimizer/doupdate; ABI/packaging/security/ecology/meta), cross-checked against the invisible-island.net man pages (ncurses 6.6), the Rust reference/nomicon, the ncurses CVE record, and this repo's clang C inventory vs
synRust inventory (the parity "compass") and oracle receipts.
Resolution status (the project's own vocabulary) distinguishes three different claims that outsiders routinely conflate — keep them separate:
- reimplemented / reconstructed = real Rust that emits/derives the bytes or
reconstructs the state, and is pinned by an oracle court (
full/partial/scaffold/divergent). - classified = accounted for with evidence but not reconstructed
(
deferred= proven to emit nothing now;n_a= no terminal-output contract). - gap = anything below byte-exact
full. 449 / 481 public functions are gaps of some degree (only 32 arefull). The complete per-function diff is the generated, freshness-gated companiondocs/generated/gap-ledger-functions.md.
The two halves of the ledger:
- Function-level gaps — generated from the parity model (see the companion
file). Every non-
fullfunction, its kind, counterpart, and court. - Class-level gaps — this document: the behavioural, semantic, byte-level, C→Rust language, ABI, packaging, security, ecology, and methodology gap classes a per-function table structurally cannot see.
Severity legend: S0 blocks "drop-in / full parity" in principle · S1
major behavioural/byte divergence · S2 edge-case / partial · S3
quality/meta. Status: open (not addressed) · recorded (court-pinned
divergence) · boundary (needs the unsafe ncurses-terminal crate) ·
partial (some of it reconstructed).
Project decision (the directive): we build all of it natively, creating what does not yet exist in native Rust — including the surfaces a per-function table can't see and including a native C-ABI surface so ncurses-native can be a real drop-in, not only a crate for Rust code. Nothing here is treated as "out of scope" or "impossible"; each is a native-build work item with a concrete path. The only thing we do not do is reintroduce C's memory-unsafety/UB footguns (MEM-04/05, UB-01/02/06): we keep the safe behaviour and record that as an intentional, documented divergence — that is strictly-better parity, not a gap.
-
STRUCT-01 · Ship a C shared object (
cdylib/staticlib). ✅ STARTED (tinfo slice drop-in-proven). ncurses shipslibncursesw.so.6/.a. native buildscrates/ncurses-cabiwithcrate-type=["cdylib","staticlib","lib"], producing reallibncurses_cabi.{so,a}. The low-level terminfo layer is wired and proven a drop-in:setupterm,tigetstr,tigetnum,tigetflag,tputs,putp,tgoto,tparmare#[no_mangle] extern "C"over the byte-exact core, with a thread-localcur_term(GLB-01) owning the returned strings. CourtNCURSES.CABIlinks one C program twice — againstlibncurses_cabi.aand the systemlibtinfo— and the stdout is byte-identical. The curses drawing path is also wired and proven, including multiple windows:initscr/endwin/refresh/doupdate,newwin/delwin,move/addstr/addch/mvaddstrand thew*family (wmove/waddstr/mvwaddstr/wattron/wattroff/wattrset/werase),wnoutrefresh/wrefresh,attron/attroff/attrset,start_color/init_pair— over heapWINDOWcell grids composited into the virtual screen (newscr) + the byte-exactdoupdate. CourtNCURSES.CURSESlinks a real curses C program (stdscr + anewwinsubwindow with bold/color) againstlibncurses_cabi.avslibncurseswand the screen-painting body is byte-identical. Interactive input is also wired:cbreak/raw/noecho/keypad/getchover thencurses-terminalraw-mode reader return the same keycodes as a real ncurses getch loop (courtNCURSES.CABI.GETCH) -- a complete input+output curses program runs on the native.so. Pads are wired too (newpad/pnoutrefresh/prefresh: a pad rectangle is composited ontonewscr, byte-exact in courtNCURSES.CURSES). A C header (crates/ncurses-cabi/include/tinfo.h) declares the tinfo symbols. Still open: the fullcurses.h(incl. the CPP macro API) and theESCDELAYtimer. S0→S1, in progress. -
STRUCT-02 · Provide the C ABI explicitly (not "impossible", just hand-built). Rust has no automatic stable ABI, but a C ABI is produced deliberately:
#[repr(C)]public structs (MEVENT✅ defined;cchar_tpending), opaque handle types forWINDOW/SCREEN, theextern "C"calling convention (✅ for the tinfo slice, proven byNCURSES.CABI), and a linker version script (.symver) to reproduce the soname/symbol-versioning nodes (NCURSES6_TIC_5.x,NCURSES_TINFO_5.x). The variadictparm(...)C ABI (ABI-VAR-01) is done (numeric); the version script is the next piece. S0→S1, in progress. -
STRUCT-03 · Build the whole API surface natively. Grow the denominator by reconstructing, in Rust: the macro API both as ergonomic methods (
win.getyx()) and as C macros in the generatedcurses.h; panel / menu / form as native modules/crates; the wide-char (ncursesw) API as a native module; the CLI tools (tput/tic/infocmp/clear/toe/tset/…) as native Rust binaries (tput/clear/infocmp/ticdone and byte-exact, see BLD-03;toe/tset/… remain); and the_nc_*de-facto-ABI behaviour via native internals (re-exported under the same names through the C-ABI crate where real programs depend on them). S0, open (native build). -
STRUCT-04 · Build full fidelity breadth natively. Multi-terminal output (terminfo-derived, every
TERM); the wide path. The terminfo reader is proven terminal-general: it parses the Linux console entry byte-identically to ncurses across 495/497 standard caps (courtNCURSES.TERMINFO.LINUX), the only two exceptions beingcols/lines(absent from the entry, resolved bysetuptermfrom the tty) -- so the low-level layer is not xterm-specific. Thetparm%-expression evaluator is proven terminal-general too (courtNCURSES.TPARM.LINUX: 78 of the Linux console's own parameterized caps --cup/setaf/sgr/ich/dch/... -- byte-exact). Themvcurbyte engine is also proven byte-exact for the Linux console (courtNCURSES.MVCUR.LINUX: all 23409 pairs), since linux shares xterm's cursor cap set + flags. The doupdate engine's plain path is now cap-driven (clear_screenbytes + ahas_repflag) and proven byte-exact for the Linux console too (courtNCURSES.DOUPDATE.LINUX: linux uses\e[H\e[Jand has norep, so identical runs emit literally) -- the second byte engine generalized for a terminal with differing caps. The color/attribute SGR engine (vidputs) is cap-driven too now (sgr/sgr0/rmacsoverridable; linux'ssgruses^N/^Oand always trails^O), proven on linux's colored/bold doupdate diffs (same court). Thencv(no_color_video) rule is modeled: while color is on, attributes the terminal can't combine with color are suppressed (linuxncv=18strips underline/dim), courted on linux (attr_underline_ncv). The ACS line-drawing path is courted too (acs_lineon both xterm\e(0/\e(Band linux^N/^O): linux'sacscis identity for the drawing chars, so onlysmacs/rmacsdiffer, already cap-driven. The recomputed-cost-model cap class is now done too: themvcurcost model is cap-parameterized (Caps) and proven byte-exact on vt100 -- a fundamentally different cap class with nohpa/vpaand$<N>padding oncup/cuf1/cuu1-- across all 23409 pairs (courtNCURSES.MVCUR.VT100). The vt100 costs (cup=33,cuf1/cuu1=13, parm caps=5,cub1=1) are derived from ncurses' normalized cost (chars + 5*padding_msat the 38400-baud pty) and make the engine step with the parameterizedcuf/cub/cuu/cudand\binstead of column/row addressing; vt100'sxonsuppresses padding NUL bytes. The doupdate engine is byte-exact on vt100 too (courtNCURSES.DOUPDATE.VT100, 24 plain scenarios): the TransformLine shaping is cap-driven (el/el1padded to 18/19 so spaces beatclr_eol; noech/parm_ich/parm_dch, so ncurses'can_idc-gated insert/delete detection is skipped and runs/shifts are painted as literal cells). Non-identityacscglyph remapping is now done too:A_ALTCHARSETcells are translated through the cap'sacscmap at emit time, so a terminal that renders line-drawing via remapped bytes (cygwin:q->0xC4CP437, smacs\e[11m/rmacs\e[10m) is byte-exact -- courtNCURSES.ACSproves three classes: xterm (identity acsc,\e(0/\e(B), screen (identity acsc,^N/^O), and cygwin (non-identity acsc + thecud1=\e[Bcost-model variant). Themvcurengine is now fully terminfo-driven (Caps::from_terminfo): every cursor cap's byte string and its ncurses sample-parameter cost (NormalizedCost,chars + 5*padding_ms) are derived from any loaded terminfo entry, and motion is emitted by expanding the actual cap viatparm(no hardcoded ANSI) -- so the engine drives an arbitrary terminal, not a fixed list. Proven byte-identical to the hand-built engine across the full 23409-pair grid for the committed xterm and linux fixtures (tests/mvcur_terminfo.rs), with the live xterm/vt100 matrices (23409 each) still byte-exact. A one-off sweep over the whole ncurses terminfo database (the canonicalterminfo.srccompiled withtic, ~2,900 entries incl. building-block fragments) measured the cursor engine byte-exact on ~383 real terminals across diverse cap classes (xterm family, vt100/vt220/vt320/vt420, linux, screen/tmux, rxvt/urxvt, cygwin/pcansi, sun, wsvt25, Eterm, dtterm, putty, konsole, kterm, hurd, mach, ansi, adm3a, …). Thedoupdate/Screen engine is now terminfo-driven too (Screen::from_terminfo): the cursor cost model,clear_screenbytes,reppresence,el/el1shaping costs (NormalizedCost),ech/ich/dchavailability, theacscglyph map, thesgr/sgr0/rmacsSGR engine, and thencvmask are all derived from the loaded entry -- so the screen optimizer drives an arbitrary terminal, not a hand-coded bundle. Proven byte-identical to the hardcoded xterm engine (first paint + diff) for the committed fixture (tests/doupdate_terminfo.rs); the same DB sweep measured the doupdate first-paint byte-exact on ~88% of real terminals (with clear-cap padding). Thetputspadding-byte model is now done (apply_padding): a terminal withoutxon(or a mandatory$<N/>) emitsfloor(N_ms * baud / 9000)pad bytes (padcap, else NUL) at the capture pty's 38400 baud; anxonterminal strips them. Grounded against real ncurses (ampex219cup $<5>-> 21 NULs, modgraph2cuu1 $<2/>-> 8 NULs) and pinned by the committedampex219fixture (cursor::tests::from_terminfo_padded_cup_emits_pad_bytes) -- both the mvcur emission and thedoupdateclearcarry padding now. Remaining open (the rest of the database): Theauto_left_margin/bwback-wrap tactic is now done (the dominant remaining mvcur class): a backspace at column 0 wraps to the previous line's last column, so a right-margin target (tx == cols-1) is reached via[cr] + cub1 + vertical(fy-1 -> ty)-- byte-exact vs real ncurses (court fixtureaixterm,tests/mvcur_terminfo.rs; aixterm/hft/ibm5151 went 0-diff), and it lifted the DB mvcur byte-exact rate from ~54% to ~63%. Two follow-on terminfo-derivation corrections lifted it further to ~74%: (a)cris not synthesized when absent -- ncurses disables the CR tactic for a terminal lackingcarriage_return(it reaches column 0 via home/cup), so the crate no longer defaultscrto\r; (b) the screen width is the runtime 80-col pty, not the terminfocols-- a 132-col entry (the-wwide variants) still runs in an 80-col window, andcolsdrives theNOT_LOCALthreshold, so reading terminfocolswrongly forcedcupwhere ncurses useshpa.cursor_to_ll(tactic 4) is now done too, lifting the rate to ~83%: thellcap jumps to the lower-left corner(LINES-1, 0)and a last-line target is reached viall + relative-- byte-exact on the Wyse family (court fixturewy350-wvb, ll =\x1e\x0b; wy350/wy120/wyse120/wyse160 went 0-diff). Atparmsave_charfix (%cof 0 emits0x80, not a NUL, matching ncurses -- binary cursor-address caps like\x1f%p1%c%p2%crely on it for row/col 0) lifted it to ~85%. Gating the bw back-wrap on!xenllifted it to ~89%: witheat_newline_glitchthe last column is a magic margin, so ncurses usescuprather than the back-wrap to reach it (fixing nsterm = macOS Terminal, hurd, Eterm, the prism/nsterm family; the bw-without-xenl aixterm still back-wraps --tests/mvcur_terminfo.rs). Three further grounded fixes closed the cup-less cluster and took the whole-database byte-exact rate to 99.84% (2558/2562 terminals checked): (a) theNOT_LOCALshort-circuit is gated oncupexisting -- inlib_mvcur.cthegoto nonlocalis nested inside theif (_address_cursor)block, so a cup-less terminal (pure-relative LCDs likeMtxOrb, Matrix-Orbital/pmcons/tek consoles) always runs the local optimizer instead of falling through to an absentcupand emitting nothing (pmcons); (b) acud1whose first byte is\nis rejected even with padding (\n$<5>), matching ncurses'*cursor_down != '\n'test, so a downward move falls through tocup/vpa(ncr160wy60pp); (c) the bw back-wrap (tactic 5) has no right-margin restriction -- it wraps to(fy-1, cols-1)and runs a fullrelative_movefrom that corner to any target column, reproducing\x08+cuu1+(cub1...|hpa) walks (apple2e,hp2626-ns). Each is pinned by a committed fixture test intests/mvcur_terminfo.rs. The measurement itself was corrected: the oracle's\x01\x01\x01segment mark collided with legitimate\x01value bytes in non-CSIcupoutput (column/row 1), under-counting matches; a high-byte (\xff\xfe\xfd+index) sentinel removed the collision and revealed the true rate. The remaining 4 are a genuine exotic tail: a malformed fixed-stringvpa(apollo), an Apple-II col-firstcup+ paddedcr(apple-80), and two Tektronix graphics terminals where real ncurses emits nothing (tek4113/-34).The doupdate engine got the same whole-database treatment, reaching 99.26% byte-exact first paint (2543/2562 checked; reproduce with
tools/db-coverage/sweep.py). The clearing path is now fully cap-driven, reproducing ncurses'ClearScreen4-way fallback (tty_update.c): (1)clear_screen; (2)clr_eos+ GoTo(0,0); (3) per-line GoTo(i,0)+clr_eol; (4) blank-overwrite when the terminal has no erase cap at all. The previous hardcoded\e[H\e[2Jfallback for aclear-less terminal -- and the hardcoded\e[r;cHinGoTofor an unknown cursor position -- were fake parity (bytes the terminal cannot understand); both are gone.GoTofrom an unknown position now drivesmvcurwith an unknown source, emitting the terminal's owncupor nothing (cup-less). Theel/el1/edbyte sequences and costs are reset to empty /ABSENT_COSTwhen the cap is absent, so the blank-overwrite fallbacks fire andClrBottom(gated onclr_eos) is skipped -- matching ncurses. This recovered the whole addressable no-clear cluster (avatar/avatar0/avatar1via clr_eol-per-line,newhp/newhpkeyboard/hp262xvia clr_eos,gt40/gt42/oldpc3/oldibmpc3/vanillavia blank-fill), pinned by committed fixtures (newhp,avatar,gt40intests/doupdate_terminfo.rs). A no-xonpaddedel/edstill emits its pad bytes in the incremental diff (ampex219'sed=\e[J$<50>-> 213 NUL pads).The incremental TransformLine diff path was then swept the same way (scene-A -> scene-B), going from ~95.5% to 98.95% byte-exact (2535/2562;
tools/db-coverage/sweep.py incremental) via three more grounded fixes: (i) abce(back_color_erase) terminal forces_el_costto 0 (lib_mvcur.c), biasing the trailing-blank clear towardclr_eoleven when the literalelis padded/expensive -- recovered arm100/scoansi/ms-vt100 (arm100fixture); (ii) theerase_chars/repeat_charrun-coalescing is now terminfo-derived (cap template +NormalizedCost+ emit viatparm), sorepemits the terminal's own cap -- concept's\Er%p1%c%p2%' '%+%c->\x1br &, not the ECMA\e[Nb-- recovering the whole concept/c100/c108 family (~25 terminals;conceptfixture); (iii) the leading-blanko_first > n_firstcase now setsfirstChar = nFirstCharunconditionally (matching ncurses TransformLine), dropping a spurious GoTo-to-column-0 -- recovered the hp264x block-mode family and others (hp2645fixture). The remaining ~1-2% incremental tail is the same cup-less degenerate family as first paint (cad68/beehive/otek411x/datapoint/ibm-pc: after a cup-less paint ncurses loses cursor tracking and streams the changed content unpositioned). The parameterizedich/dchshaping caps remain the CSI constants (gated byhas_idc).The attribute/SGR engine (
vid::Vid) was the next whole-database sweep, and the biggest single gap closed: 34% -> 91.5% byte-exact (tools/db-coverage/sweep.py attr). The crate had hardcoded xtermsgr/sgr0for every terminal; three grounded fixes (lib_vidattr.c / lib_mvcur.c): (i) the no-sgrindividual-mode-cap path -- 46% of the database has noset_attributes, so ncurses drives it withbold/smul/smso/rev/blink/dim/smacs/... and thermul/rmsoexit caps (used only when they differ fromsgr0,SGR0_TEST), resetting viasgr0when an attribute with no individual exit must turn off, then re-enabling everything (ti916-8-132fixture); (ii) padding is now applied to all attribute caps (vt100/vt220'ssgrends in$<2>, previously emitted as literal bytes); (iii)msgr-- a terminal without move_standout_mode resets to A_NORMAL before every cursor move and restores the attribute after (wyse520-wfixture). The remaining ~8.5% (218 terminals) is dominated by the magic-cookie glitch (xmc): 204 of the 218 attribute fails arexmcterminals (1980s tvi/wyse/adm/abm/concept clones where an attribute change physically occupiesxmcscreen cells). The mechanism is understood -- ncurses'_nc_do_xmc_glitch(tty_update.c) scans newscr for attribute turn-ons and, where there is no room for the cookie in adjacent blanks (e.g. attributed text at column 0, the whole sweep scenario), nukes the attribute;_xmc_triggers = _ok_attributes & XMC_CONFLICTselects which attrs glitch. A faithful display-level port of that pass (nuke/fit) was prototyped, but ncurses' exact byte stream for these terminals adds subtle glitch<->vidputs interactions (strayrmul/rmsoexit caps at span boundaries, and anxmc#0sub-case) that need ncurses trace tooling to reproduce byte-for-byte -- an honestly-bounded, near-zero-value tail (all obsolete terminals), so it is left documented rather than shipped non-byte-exact. The non-xmc remainder is the prism/P-series\x03-prefixed exotic encodings.The color engine (
_nc_do_color) was the next sweep: 56% -> 97.9% byte-exact on color-capable terminals (tools/db-coverage/sweep.py color). It was hardcoded to xterm (\e[3Xm/\e[4Xm/\e[39;49m); now terminfo-driven:setaf/setab(ANSI) orsetf/setbwithtoggled_colors(the SVr4 1<->4/3<->6 swap),orig_pair, all padded; gated oncolors#>0so a colorless terminal (vt100) emits no color. ncurses' ClearScreen color-init (_nc_do_color(SCREEN_PAIR, 0)before the clear) is reproduced for the default background, and -- the big lever -- a color terminal withoutbcecannot fast-clear (the erase wouldn't paint the bg), so ncurses setsfast_clear = FALSEand blank-fills; reproducing that required the fullPutCharLRbottom-right corner (cases:rmam/smamam-suppression,ich/smirinsert, or skip the cell) andamauto-margin-wrap cursor tracking. This recovered the mainstream color families (ansi/pcansi/aixterm/cygwin/sun), pinned bysun-color(setaf+blank-fill+corner),emots(setf+toggled),vt100(no color). The corner/am generalization also lifted first-paint doupdate to 99.45%. Theset_color_pair(scp) path (the DGcccd430-*family) and the no-msgr color reset are now handled; the remaining ~2% color tail is exotic: stateful%P/%gsetfcaps (qnx/ctrm) that need tparm static-variable state persisted across calls, and a few exotic positioners. The remaining ~1% first-paint tail is degenerate: unaddressable hardcopy/dumb terminals (dumb/ep4x/att53xx/megatek) that need exact auto-margin-wrap cursor modelling, hardware-tabviewdata, and the padded-clearhp9845/modgraph2. Hardware tabs (ht) were investigated for mvcur and are not a factor there (ncurses emits a tab in only ~2/250 sampled terminals on the capture pty), though they remain the cause of theviewdatadoupdate tail. The realdoupdateline-diff is done (OPT-02, plain + attr/color, byte-exact); single-width UTF-8 rendering is now byte-exact too (LOC-01, courtNCURSES.WIDECHAR✅). The version claim is now pinned to the build actually verified: the crate admits ncurses 6.4 (ADMITTED_NCURSES), and the init/teardown framing is captured live from that build and matches byte-for-byte (courtNCURSES.BYTE.FRAME.FULL✅, FRAME-01 resolved) — the earlier 6.4-oracle-vs-6.6-claim mismatch is gone. S1, in progress (multi-terminal + wide API/double-width remain).
Consequence for the rest of this ledger: the C-ABI / soname /
repr(C)/_nc_*/ variadic-ABI entries (ABI-VERS-01, ABI-PRIV-01, ABI-STRUCT-01, ABI-VAR-01) and the ecosystem drop-in items (ECO-01/03/04/07) are in-scope native targets delivered throughcrates/ncurses-cabi, not blockers.
- MEM-01 · Returned
char*into static/area buffers. ncurses:tigetstr,tparm/tiparm,keyname,unctrl,termname,longname,tgetstr,keyboundreturn pointers into internal static / per-TERMINALstorage, valid only until the next call that reuses the buffer. native: returns ownedString/Option<String>/&str. Gap: the "clobbered on next call" lifetime is unrepresentable in safe Rust except by tying the borrow to&mut self; callers that cache or compare the C pointer behave differently. S2, open. - MEM-02 ·
longname()clobbered bynewterm, not restored byset_term. ncurses: one static area, overwritten pernewterm, not saved across terminals. native (owned model): per-screen value — the clobber-and-not-restore behaviour disappears. S2, open. - MEM-03 · Three-state
(char*)-1/NULL/ valid. ncursestigetstrreturns(char*)-1for "not a string capability",NULLfor "absent/cancelled", else the string. native models this with a 3-variantTigetstrenum (good) — but the legacy termcaptgetstrand several others collapse toOption, losing the cancelled-vs-absent distinction unless explicitly modelled. S2, partial (the terminfo path is faithful; audit the others). - MEM-04 · Caller-frees vs
Drop; deletion ordering. ncurses:delwinmust precede deleting the parent (deleting a parent with live subwindows is UB);del_curtermon the livecur_termleaves all capability accessors dangling. native:Dropis automatic; the ordering UB and the dangling-cur_termfootgun become impossible. Gap: cleanup side effects (touch propagation on delete) and the disappearance of a whole UB class diverge from bug-for-bug behaviour. S2, open. - MEM-05 · UAF / double-free / dangling eliminated.
delwintwice, using aWINDOW*afterdelscreen, etc. are C UB; in Rust they are compile errors orNone. Gap: a security/robustness win that breaks bug-for-bug fidelity for any test/fuzzer that observed post-free garbage. S3, open (by design). - MEM-06 · Aliasing:
copywin/overlay/overwritewith overlap or self. ncurses: raw-pointer copies with overlap permitted. native:&mutaliasing is forbidden; self-copy/overlap needs interior mutability or index-based design. Current nativeoverlay/overwrite/copywinare classified deferred (no shared-cell model). S2, open. - MEM-07 · Pointer identity (
win == stdscr). ncurses code dispatches on pointer identity; Rust needsRc::ptr_eq/ an id field. There is nostdscrsingleton at all in native. S2, open. - MEM-08 · Opaque-but-poked structs. Real code reads
win->_maxx,win->_cury; native fields are private. Source ports that poke internals break. S2, open.
- INT-01 ·
chtype/attr_t/mmask_twidths & bit positions. ncurses packs char+color+attrs into a 32-bitchtype;--enable-ext-colorswidens the color field and moves the attribute bit boundaries;--enable-ext-mousewidensmmask_t. native fixesu32and matches the defaultA_*positions (verified:A_BOLD=0x200000,A_COLOR=0xff00, courtNCURSES.WINDOW.ATTR). Gap: the ext-colors/ext-mouse layouts (a second "6.6" ABI) are not modelled. S1, partial. - INT-02 ·
shortcolor-pair numbers (overflow at 32767) vsi32. ncurses has bothshort-based (init_pair/pair_content) andint-based extended (init_extended_pair) APIs precisely becauseshortoverflows; native usesi16/i32and silently fixes the overflow. S2, partial. - INT-03 · Signed overflow: C UB vs Rust panic/wrap. Hostile terminfo (giant
$<…>delays, huge%{…}constants) can trigger UB in C but a defined panic (debug) or wrap (release) in Rust — a determinism/fuzzing divergence. S2, open. - INT-04 ·
charsignedness & implicit conversions. Cchar-is-signed-or- unsigned is implementation-defined and affects high-bit (0x80–0xFF) chars built intochtypeviaaddch; Rustu8/i8is fixed → high-bit Latin-1 may map differently than on a given C platform. S2, open. - INT-05 ·
tparmvalue typing. ncurses caches per-format param-type analysis (int vs string per%p); nativetparmuses an int/stringParamenum (courtNCURSES.TPARM, 106 cases). Gap:tiparm's explicit typed API andtiscan_s/tiparm_sare not separately reconstructed. S2, partial.
- GLB-01 · The global set.
cur_term,SP,stdscr,curscr,newscr,LINES,COLS,COLORS,COLOR_PAIRS,ESCDELAY,TABSIZE,ttytype[],acs_map[],boolnames/numnames/strnames(+codes/fnames),ospeed,PC,UP,BC. native: owned-value model, no process globals. Gap: code reading/ writingLINES/COLS/ESCDELAYhas no target; the entire API shape differs. S0, open. - GLB-02 ·
--enable-reentrant: macros vs functions, write access. In the reentrant buildLINES/COLS/ESCDELAY/TABSIZE/SPbecome accessor functions and the only legal mutators areset_escdelay/set_tabsize; the default build exposes writable globals. A port can emulate only one. S1, open. - GLB-03 ·
_sp(screen-pointer) duality. Every screen function has anX_sp(SCREEN*, …)twin;new_prescr,ceiling_panel,ground_panelare_sp-only. native's owned-method model is the_spform, but must then layer the global-SPconvenience overloads. S1, partial (no global shim yet). - GLB-04 ·
tparmstatic%P/%g [a-z]vars: per-TERMINALsince 6.3, shared before. Interleavingtparmacross screens differs by version. native does not persist static vars across calls. S2, open. - GLB-05 ·
acs_map[]is terminal-dependent, andACS_*are macros over it.ACS_VLINE≡acs_map['x'], filled from terminfoacscat init — not a constant. native hardcodes the canonical lettersl q k x m j(correct only for terminals whoseacscmatches xterm's). S1, recorded (non-claim noted). - GLB-06 ·
LIFOmulti-screen teardown &new_prescr. Ifnewtermis called twice for the same terminal, the first SCREEN must be the lastendwin'd;new_prescrstores pre-init settings (use_env/ripoffline/slk_init) per screen. native models neither. S2, open.
- CON-01 · Thread-safety contract. Base ncurses is not thread-safe;
--enable-threadsaddsuse_window/use_screencallback mutexes (and even then "not enough"). Rust'sSend/Syncforces a different model entirely; nativeuse_window/use_screenare classified n_a. S1, open. - CON-02 ·
Send/Syncof handles. C passesWINDOW*/SCREEN*between threads freely (UB if concurrent, but compiles); Rust may forbid it at compile time. S2, open. - SIG-01 · SIGWINCH /
KEY_RESIZE/ auto-resize. ncurses installs a handler that flags a resize; the nextwgetch/doupdaterunsresizetermand updatesLINES/COLS, returningKEY_RESIZE(even without keypad). Impossible in pure-safe Rust (no signal API); needs the unsafencurses-terminalcrate orTIOCGWINSZpolling. S0, boundary. - SIG-02 · SIGTSTP/SIGCONT job control. Ctrl-Z/
fgmust save/restore tty modes (_nc_screen_resume); without signal hooks the terminal is left in raw mode on suspend. S1, boundary. - SIG-03 · Crash/exit cleanup (
endwinon death).Dropdoes not run onprocess::abort,exit, or signal kill; a panicking Rust TUI leaves the terminal in raw mode / no cursor unless a panic hook + signal handler is wired (unsafe/libc). S1, boundary. - CTL-01 · Varargs.
printw/scanw/mvprintw/vw_printw/vwprintw/tparmuse Cva_list/...; Rust has no stable variadics. nativeprintw*is a scaffold (formatting delegated);%n,%hhd,%'d(locale), andva_list-taking forms are not 1:1. S1, partial. - CTL-02 · Function-pointer callbacks.
ripoffline(line, int(*)(WINDOW*,int))(callback runs duringinitscr, may get a NULL window, max 5),tputs(s,n,int(*putc)(int)),vidputs/vid_attroutc,use_window/use_screencallbacks. native does not model the callback ABI / the putc output funnel. S1, open. - CTL-03 ·
setjmp/longjmp. longjmp over Rust frames is UB and skipsDrop; any C path using it must be restructured toResult/unwinding, changing cleanup timing. S3, open. - CTL-04 ·
atexit/_nc_freeallteardown ordering differs from RustDrop/atexit; valgrind-clean teardown and final flush parity. S3, open. - ERR-01 ·
ERR/OKint returns vsResult. Collapsing everyERR(which carries no reason) into one error type, or enriching it, both diverge; the exact conditions that yieldERR(off-screenwmove, NULL win) are under-tested. S2, open. - ERR-02 · Partial mutation on error.
waddstr/waddnstrmay write some chars then returnERRhaving already mutated the window;?/Resultcallers may assume atomicity. S2, open. - ERR-03 ·
setuptermtri-stateerrret.*errret=1ok /0unusable-or-generic /-1database not found, andNULLerrret → print message and exit the process. nativeTerminfo::loadreturns aResult, collapsing the tri-state and erasing the print-and-exit path. S1, partial. - ERR-04 ·
getchERR: would-block vs EOF vs error (three states) collapses badly intoOption/Resultunless modelled explicitly. S2, boundary.
- LOC-01 · Wide rendering reproduced for single-width UTF-8; the wide API
is still absent. The cell now stores a Rust
char(one single-width glyph per cell) anddoupdateemits each cell's UTF-8 encoding, with norepeat_charcoalescing for multibyte glyphs — matchingncursesw'sEmitRangebyte-for-byte (courtNCURSES.WIDECHAR: positioned mixed Latin/Greek/Cyrillic/symbol text and identical multibyte runs, byte-exact against reallibncurseswunder a UTF-8 locale). Thecabiaddstr/waddstr/mvaddstrdecode UTF-8 input into cells. The wide function surface is now wired in the C-ABI:cchar_twithsetcchar/getcchar, and theadd_wch/wadd_wch/mvadd_wch/mvwadd_wch+addwstr/addnwstr/waddwstr/waddnwstr/mvaddwstr/mvwaddwstrfamilies compile against the generatedcurses.hand draw byte-identically to systemlibncursesw(courtNCURSES.WIDECHAR.CABI: bold CJK, underlined fullwidth, Latin/CJK/Greek/Kana/ Hangul wide strings). Wide input is wired too:get_wch/wget_wchassemble a UTF-8 byte sequence into a codepoint (returningOK) and report a function key as itsKEY_*code (returningKEY_CODE_YES), byte-identical to libncursesw (courtNCURSES.WGET_WCH: UTF-8 single-/double-width chars interleaved with arrows/nav keys) -- closing the wide round-trip. Wide read-back is wired too:in_wch/win_wch/mvin_wch/mvwin_wchextract acchar_t(spacing wide char + attributes incl. color + pair, with the base glyph returned at a double-width glyph's padding column), byte-identical to libncursesw (courtNCURSES.WIN_WCH, withgetccharreturningA_ATTRIBUTESincl. color). Combining marks ✅ done (LOC-03): a cell carries up toCMAX=4 combining marks (cchar_t.chars[1..]) that attach to the preceding base and emit as UTF-8 right after it. Still absent / open:wunctrl, the*_wchstrbulk read, and the ext-colorcchar_tlayout. S1, partial/courted (single- + stable double-width I/O + read-back + combining marks, full wide cell API). - LOC-02 · narrow/wide is a compile-time fork in C; additive Cargo features
can't be both.
--enable-widecchangeschtype/addscchar_t;wchar_tis 32-bit on Linux / 16-bit on Windows; Rustcharis always a 32-bit USV and cannot hold unpaired surrogates or rawwchar_t. S1, open. - LOC-03 ·
cchar_tlayout &CCHARW_MAX=5grapheme cap. ✅ DONE (cell model + emission). ACellcarries 1 spacing glyph + up toCMAX=4 combining marks (cchar_t.chars[1..]); zero-width marks (is_zero_width, the combining-diacritical blocks) attach to the preceding base cell, advance no columns, and are emitted as UTF-8 right after the base -- byte-exact vslibncursesw(courtNCURSES.WIDECHAR,combining_acute/combining_word/combining_double/combining_mixed/combining_midword). Overflow pastCMAXis silently dropped (matching ncurses). The raw in-memorycchar_tstruct layout (an FFI concern, not byte output) stays unspecified by X/Open. S1, courted. - LOC-04 ·
wcwidth/ double-width / zero-width version skew — stable wide ranges reproduced. ncurses uses the host libcwcwidth(its Unicode version); a Rustunicode-widthcrate pins a different Unicode version → off-by-one column placement for new emoji/CJK. native now ships achar_widthcovering the rock-solid BMP wide ranges (CJK ideographs, Kana, Hangul, fullwidth forms — universally width-2 acrosswcwidthversions): a wide glyph fills a cell + a padding cell and advances two columns, emitted byte-exact vs the hostlibncursesw(courtNCURSES.WIDECHAR, cjk_basic / cjk_kana_hangul / cjk_fullwidth). The right-margin wide-glyph wrap is now reproduced too (cjk_cursor_wrap / cjk_glyph_wrap: filling the last two columns wraps the cursor to the next line, and a glyph that won't fit wraps whole — the OPT-02 auto-margin corner, now closed). Zero-width combining marks are now reproduced (LOC-03, courtNCURSES.WIDECHARcombining_*). Still open: version-skewed widths (emoji, recent CJK — deliberately treated as width-1). S1, partial/courted (stable double-width + wrap + combining); emoji width-skew open. - LOC-05 ·
setlocale/$LC_CTYPEdependence & non-UTF-8 locales (EUC-JP, ISO-2022) and incrementalmbrtowcdecoding of partial/invalid sequences. native is ASCII/UTF-8 only with no incremental decoder. S2, open. - LOC-06 ·
key_namevskeynameasymmetry (the widekey_namedoes NOT return function-key names);get_wchyieldswint_t/WEOF/KEY_*needing a union type. S2, open.
- TIM-01 ·
tputspadding / baudrate delay. Pad-char count fromospeed,affcnt,$<ms>,padding_baud_rate,xon,BSDpad; on slow/hardware terminals the pad bytes are part of the emitted stream. nativetputsstrips$<…>and emits no pad bytes (courtNCURSES.TPUTS, admitted for the normal- baud pty) — the baud/pad-char model is not reconstructed. S1, recorded. - TIM-02 ·
ESCDELAY(default 1000 ms, capped 30 s in 6.6),set_escdelay, theESCDELAYenv var — the ESC-vs-sequence disambiguation budget. Thencurses-terminalreader drains a multi-byte key within an inter-byte window (analogous toESCDELAY), so the keycode outcomes match ncurses: a lone ESC (or ESC not starting a known sequence) returns27then the next byte, while a buffered\eOAdecodes toKEY_UP— courted (esc_then_char/esc_then_textinNCURSES.INPUT/.LIVE/NCURSES.CABI.GETCH). Remaining: the exact configurable delay duration (fixed ~50 ms vs ncurses' default 1000 ms /$ESCDELAY/set_escdelay) — a timing value, not a keycode difference. S2, partial/courted. - TIM-03 ·
napms(caps at 30 s, auto-restarts on signal);delay_output(caps 30 000 ms, pads unlessnpc/NCURSES_NO_PADDING). native classifies these n_a (timing). S2, boundary. - IO-01 · termios raw/cbreak/noecho/nl/meta/keypad. ✅ PARTIAL (raw mode built).
cbreakclearsICANON;rawadditionally clearsISIG/IXON/…and is 8-bit clean;metatogglesCS8/CS7and emitssmm/rmm;keypad(TRUE)emitssmkx. Pure-safe Rust cannot set termios, so thencurses-terminalcrate (the quarantined unsafe boundary) now implementsRawMode(hand-declared LinuxtermiosFFI -- no libc dep -- a cfmakeraw-equivalent that restores the savedtermioson drop, even on panic). That backs the live nativegetch(courtNCURSES.INPUT.LIVE). Still open: thecbreak/noecho/metaindividual modes,smkxemission fromkeypad, and the three mode snapshots (IO-06). S0→S1, boundary/partial. - IO-02 ·
isatty/ tty-vs-pipe fallback, IO-03 · SP output buffer & flush points (flicker/atomicity), IO-04 · blocking/nonblocking + typeahead-cancels- refresh optimization, IO-05 ·newterm(type, FILE*, FILE*)arbitrary streams, IO-06 ·def_prog_mode/def_shell_mode/savetty— three distinct termios snapshots per screen (native models none). S1, boundary.
- ABI-MACRO-01 · the CPP macro API. ✅ PARTIAL (generated
curses.h). ~⅓ of the "API" is CPP macros with no symbol:getyx(win,y,x)assigns bare lvalues;getmaxyx/getbegyxlikewise;COLOR_PAIR/PAIR_NUMBER/ACS_*/A_*/KEY_*are macros. native shipscrates/ncurses-cabi/include/curses.hproviding them: theA_*/COLOR_*/KEY_*/ACS_*constants,COLOR_PAIR/PAIR_NUMBER, and thegetyx/getmaxyx/getbegyxstatement macros — implemented over accessor functions (getcury/getmaxy/… exported from the cabi) so theWINDOWstruct stays opaque (no struct-layout ABI exposed). A macro-API program compiles unmodified and draws byte-identically to the system<curses.h>/libncursesw(courtNCURSES.CURSES.HEADER, incl.getyx/getmaxyx/COLOR_PAIR/A_BOLD/ACS_VLINE/box). Still open: the full header breadth (getsyx/setsyx, the completemv*/mvw*macro matrix,wide/cchar_t). S0→S1, partial/courted. - ABI-VERS-01 · soname & symbol versioning. ✅ PARTIAL (soname + dynamic drop-in).
crates/ncurses-cabi/build.rssets SONAMElibtinfo.so.6and applies a version script (version.map) declaring theNCURSES6_TINFO_5.0/NCURSESW6_5.1nodes. CourtNCURSES.SONAMEconfirms the soname viareadelfand proves the.sois a working dynamic drop-in: a C program linked against it (placed aslibtinfo.so.6, resolved via rpath) produces byte-identical terminfo output to the systemlibtinfo.pkg-configtinfo.pc/ncursesw.pcare provided (crates/ncurses-cabi/pkgconfig/, validated). Still open: binding the exported symbols to the exact NCURSES version nodes so pre-existing versioned binaries (realtput) resolve against ours — rustc emits its own cdylib export version-script, so our symbols stay at the base version (the verdef nodes are emitted but unbound). S1–S2, partial. - ABI-PRIV-01 ·
_nc_*private-but-exported symbols relied on by tic/infocmp/tputand--with-termlib(_nc_tiparm,_nc_tparm_analyze,cur_term,use_env, …). Driving the real systemtputneeds these. S2, open. - ABI-STRUCT-01 · public struct layout (
MEVENT{short id;int x,y,z;mmask_t bstate}✅#[repr(C)];cchar_tpending). S2, open. - ABI-VAR-01 · variadic C ABI for
tparm(...). ✅ DONE (numeric). ncurses' classictparm(const char *, ...)reads up to ninelongparameters (va_arg(ap, long)). native exportstparmas a fixed nine-c_longextern "C"definition, which is call-compatible with variadic callers on register-based ABIs (e.g. x86-64 SysV: 32-bit int args are zero-extended into the 64-bit slots, and parameters past the highest%pthe cap references are ignored — exactly as ncurses ignores them). Verified byte-identical to the systemlibtinfoforcup/setaf/setabvia courtNCURSES.CABI. The rare string-valued (%s) caps andtiparmare not yet wired. S2, done (numeric). - ABI-NOTE-01 ·
abi_stableis the wrong tool for the C surface (evaluated, not adopted). Theabi_stablecrate provides a stable Rust↔Rust ABI (#[repr(C)]wrapper types,RVec/RString/RStr, trait- object vtables,#[sabi_trait], load-time layout checking) so two Rust crates compiled separately can interoperate across the otherwise-unstable Rust ABI. Our C-ABI target (STRUCT-01/02, ABI-STRUCT-01) is the opposite problem: we must match the C ABI that ncurses itself defines — exactMEVENT/cchar_tlayouts,chtype/mmask_twidths, thecurses.h/soname/.symvercontract, and the variadictparm.abi_stable's own representations (e.g.RVec) are not the C layouts agcc-built program expects, so it cannot stand in for theextern "C"#[repr(C)]+ version-script work; introducing it would add a dependency (against the dependency-free charter) without removing any of that work.ncurses-cabitherefore uses plain#[no_mangle] extern "C"+#[repr(C)].abi_stablecould be useful later for a plugin/driver split between native Rust crates (e.g. a dynamically-loaded terminal driver), which is a separate, optional concern. S2, evaluated/declined.
- BLD-01 · configure forks → behaviour/ABI (
--enable-widec,-ext-colors,-ext-mouse,-reentrant,-sp-funcs,-sigwinch,-tcap-names,-bsdpad,--with-termlib,--with-fallbacks). A single Rust build can't be all of them. S1, open. - BLD-02 · terminfo data is a separate dependency with two binary magics
(legacy
0o432/16-bit, extended0o1036/32-bit) and hashed-dir (BSD/macOS) vs letter-dir layouts; compiled-in--with-fallbacksentries. native reads both magics (courtNCURSES.TERMINFO.LOOKUP, the 32-bit format via xterm-256color inNCURSES.TERMINFO.ECOLOGY) and the letter-dir + hashed-dir paths, but has no compiled-in fallback (opposite failure mode: errors instead of working without a DB). S2, partial. - BLD-03 · the CLI suite IS ncurses to most users.
tic,infocmp,toe,tput,clear,tabs,reset,tset,captoinfo,infotocap— scripts depend ontput/clearexit codes & output, and oninfocmp | ticround-trip idempotence.tput/clearare done (native binaries,NCURSES.TPUTcourt).infocmpis done — 100.000% byte-exact for BOTH-1and-1 -xacross the whole 2,869-entry terminfo database (NCURSES.INFOCMPcourt) — the decompiler reconstructs_nc_tic_expandescaping (the caret-vs-octal length heuristic, the%-operator verbatim rule, the letter/\s/\,/\^escapes), power-of-two hex number formatting (colors#0x100), cancelled caps (name@),acscglyph sorting, and the full extended (user-defined,-x) section including cancelledname@extensions — the extended offset-table layout (value offsets + name offsets =2·es + eb + en, the header's string-count vs string-size fields, cancelled strings keeping a-2value slot but no value string) is now decoded byte-exactly.ticis done too — 100.000% byte-exact on BOTH theinfocmp -1 | ticandinfocmp -1 -x | tic -xround-trips across the whole database (NCURSES.TICcourt) — the inverse ofinfocmp, reconstructing the source parser (comma/^X/\X-aware splitting), the_nc_tic_expandun-escaper (\E/^X/\nnn/\0→0x80/\s, the%-operator verbatim rule), and the binary writer (header,|-names, bool bytes, even-offset numbers with 16-vs-32-bit magic selection, string offsets/table, cancelled-2, the SVr4 cutoff that drops ncurses-extension predefined caps without-x, and — with-x— the full extended section writer).infocmp -C(termcap source output) is in progress, not yet byte-exact (~16% across the DB, honestly bounded — no court yet): the foundation is correct — the curated emitted-code set, termcap escaping (:→\072, 0x7f→\177), the_nc_infotocapparameter reverse-map (%pN%d→%d,%2d→%2,%c→%.,%i,%'X'%+%c→%+X, reversed→%r, drop-on-untranslatable), trailing-padding→leading-delay, theis3→i2/rs2→rscode remaps, and the insert-mode synthesis (im/ei/icfrom smir/rmir/ich1). The remaining tail is the gnarly_nc_infotocaplegacy munging: the exact line-wrap rule (a width-60 break only reproduces ~50% of wraps — there is a further subtlety), the conditionalas/aeandrsdrops, theug-from-sgsynthesis, and a handful of value-translation edges (cm/te/me/ec). Measured limits of pure oracle-probing here: content-correctness (ignoring wrapping) tops out at ~25% and the wrap is provably not a greedy fixed-width fill (an exhaustive model×width×grouping sweep peaks at 46% exact). Unlikeinfocmp/tic— whose rules were finite and output-derivable —_nc_infotocapneeds either source reference or many more probing passes; flagged as the project's deepest clean-room wall.captoinfo/infotocapbuild on this. S1, in progress. - BLD-03b ·
toe(table of entries) is not cleanly clean-room byte-exact.toe -adedups files by the entry's primary name (1829 of 2869 files shown) and emitsprimary-name<TAB>longname, but its traversal order is neither alphabetical norreaddirorder (observed subdir orderb q E i w x j m …vsreaddir's3 b N q E …) — it depends on ncurses' hashed-directory / db iterator, which is filesystem- and build-specific. Byte-exacttoeoutput is therefore environment-fragile; deferred rather than faked. S1, open. - BLD-04 · companion libraries panel / menu / form (+
w) — ~200+ symbols, entire subsystems (overlapping-window stack; menu driver; field/form driver). Zero coverage. S0, open. - BLD-05 ·
.pc/*-configdiscovery. Mostly done;libtinfosplit open. The pkg-config filesncursesw.pc/tinfo.pcship undercrates/ncurses-cabi/pkgconfig/, and a nativencursesw6-config(crates/ncurses-tools) reproduces the system tool byte-for-byte across all 18 options —--version/--cflags/--libs/--abi-version/--mouse-version, the prefix/dir queries (incl. the--libdir-suppresses-when-empty quirk), and env-honoring--terminfo/--terminfo-dirs— plus the narrowncurses6-config(-lncurses) via program-name dispatch (courtNCURSES.CONFIG,admitted_match). Thelibtinfosplit is now done too: the standalonencurses-tinfo-cabicdylib is a true minimallibtinfo.so.6— sonamelibtinfo.so.6, exporting exactly the nine low-level terminfo symbols (setupterm, tigetstr/num/flag, tputs, putp, tgoto, tparm, curses_version) and no curses screen symbols, with a tinfo-only C program linked against it producing output byte-identical to the system libtinfo (courtNCURSES.LIBTINFO,admitted_match). This is the termlib bash/less/readline depend on. (It is a dedicated crate rather than a re-export because a cdylib depending onncurses-cabiwould re-export its curses symbols too — rustc exports a dependency's#[no_mangle]symbols.) S1, done.
- SEC-01 · The interesting ncurses CVEs are parser/trust bugs, not all spatial.
History clusters in terminfo/tic parsing: CVE-2017-10684/10685 (tic fmt-string /
stack overflow), CVE-2018-19211 (NULL deref
_nc_parse_entry), CVE-2019-17594/17595 (heap OOB in_nc_find_entry/convert_strings), CVE-2023-29491 (local privesc: setuid programs trusting$TERMINFO).#![forbid(unsafe_code)]kills the spatial-overflow class — but native has a terminfo parser (a fuzz target), and the trust-model CVE (env-controlled terminfo under setuid) is a policy bug Rust does not touch. native's loader trust policy for$TERMINFO/$TERMINFO_DIRSis undocumented. S1, open. - SEC-02 · Escape-sequence injection is unchanged by Rust. Writing untrusted
data emits attacker-controlled escapes (OSC title/clipboard-52, DCS,
\e[?…mode changes). Memory safety does nothing here; "safe Rust" must not imply "escape-safe". S1, open. - SEC-03 · DoS via crafted terminfo/tparm. Rust prevents OOB but a panic = DoS
and an unbounded
%-loop = hang;tparmis a parser on possibly-untrusted capability strings. S2, open. - SEC-04 · Format-string vulns vanish (compile-checked macros, no runtime
%n) — a win that also means an app can't accept a runtime format string even if it wanted to. S3, open (by design).
The whole point of curses is to emit the fewest bytes to update a screen. native reproduces the full
mvcurcursor optimizer byte-exactly (OPT-01 CLOSED) and a single-field repaint seed; the screen-diff (doupdate) is the largest remaining reconstructed-parity gap.
- OPT-01 ·
mvcurcost model. ✅ CLOSED (byte-exact). ncurses chooses CUP vs HPA vs repeatedcuf1vscr+motion vshomeviaonscreen_mvcur's tactic enumeration scored byrelative_movewith the static sample-parameter cost model precomputed in_nc_mvcur_init(hpa=vpa=5,cup=8,cuf1=cuu1=3,cub1=cr=1,home=3; costs are character counts because every term is scaled by the same_char_padding), with a strict-<tie-break so the lower-numbered tactic wins. The earlier "d=1→cuf1,d=2..7→HPA,d≥8→CUP" rule was the observable shadow of this cost model; the model is the real generator.cursor::mvcurnow reproduces it exactly for the publicmvcur(3)path (ovw=FALSE) — short forward moves usecuf1/hpa, never overwrite-with-spaces. Verified byte-exact for all 23409 pairs of a 153×153 sampled(from → to)grid captured from real libncurses 6.4 on an 80×24 xterm pty (courtNCURSES.MVCUR, fixturetests/fixtures/mvcur_matrix.txt, offline replaytests/mvcur_matrix.rs). The one residual is OPT-01a below. S1, resolved/byte-exact. - OPT-01a ·
mvcuroverwrite branch (ovw=TRUE). ✅ CLOSED.relative_movehas an overwrite path (lib_mvcur.c lines ~678–701) that, when ncurses knows the on-screen cells match the desired characters, advances the cursor by rewriting those characters instead of emitting motion (e.g. a forward move of one column over a known space is emitted as a literal" "rather thancuf1; a backward move becomes\r+ overwrite of the leading run). The publicmvcur(3)entrypoint passesovw=FALSE, so this never fires there (and the OPT-01 matrix is captured through it);doupdatecalls the optimizer withovw=TRUE. native now reproduces it:cursor::mvcur_ovw(from, to, want)threads awant(row,col)hook (theWANT_CHARdesired screen) into the forward-horizontal cost/emit, andupdate::Screen'sGoTosupplies it. This closed every residualNCURSES.DOUPDATEdivergence. S1, resolved. - OPT-02 ·
doupdate/TransformLine. ✅ PARTIAL (plain-text path byte-exact, 29/29). native reproduces the realtty_update.cmachinery —doupdate/ClrUpdate/TransformLine/PutRange/EmitRange/ClrToEOL/ClrBottomwith the exact static costs (_el_cost=3,_el1_cost=4,_ech_cost=5,_rep_cost=6,_inline_cost=5,_ich_cost=_dch_cost=5) — for the plain-text (A_NORMAL, no color) path, reusing the byte-exactmvcur/mvcur_ovwfor cursor motion (update::Screen). Verified byte-exact against real ncurses on all 29 scenarios of courtNCURSES.DOUPDATE: clear+paint, same-length overwrite, multi-line diff, scattered runs, the leading-blank GoTo-vs-emit reposition, trailingclr_eol,clr_bol,clr_eos-bottom,ich/dchinsert/delete shifting,echandreprun-coalescing, theovw=TRUEGoTo overwrite (OPT-01a), and the right-margin auto-wrap corner (auto_right_margin+eat_newline_glitch: filling the last column wraps the cursor to the next line, overflow wraps onto it, and a move from the magic margin forces an absolutecupsince relative motion is unreliable there). Scroll optimization ✅ done (single contiguous moved band,_nc_scroll_optimize/_nc_scrolln): reproduced byte-exact across three cases. (1) Whole-screen (top=0,bot=maxy): GoTo(bottom)+ind/indnup (\r\n/\r\E[NS), GoTo(top)+ri/rindown (\E[H\EM/\E[H\E[NT). (2) Bottom-anchored (bot=maxy,top>0): GoTo(top)+dl1/dlup, GoTo(top)+il1/ildown, with nocsr(deleting/inserting lines attopscrolls the region[top, maxy]). (3) Confined mid/top region (bot<maxy):csr(top,bot)(\E[<top+1>;<bot+1>r) — which homes the terminal cursor, so the following GoTo is an absolutecup— then theind/indn/ri/rinscroll, then acsrreset to the full screen (cursor invalidated again, so the park is absolute too). The region grows through matching rows (including blank-to-blank), so a shift with only blank rows below extends to the bottom and degrades to whole-screen / bottom-anchored; it is confined to acsrregion only when non-blank static content sits below the band (the_nc_hash_mapbehaviour for one band).curscris shifted in place over[top, bot]so the residual diff is empty. A non-blank line must actually move into view to scroll — content that merely becomes blank is cleared (the cost decision). Multi-region (_nc_hash_map) ✅ done: the optimizer scans top-to-bottom and emits each shift band as its own scroll, applying it tocurscr, so TransformLine then paints only the genuine edits — two independent bands, opposite-direction bands, and a band beside an unrelated edit are all reproduced byte-exact. Verified byte-exact against real ncurses on all 19 scenarios of courtNCURSES.SCROLL.OPTIMIZE(admitted_match): whole-screen up 1/2/3 + down 1/2, partial-content, the clear-not-scroll case, the partial-region band scrolls (region_mid_up1/up2/down1/down2,region_top_up1/down1,region_bot_up1/down1), and the multi-region cases (multi_two_bands_up1,multi_up_top_down_bot,multi_scroll_plus_edit). Grounded: the residual "fixup" bytes once read as intrinsic were a grounding-harness artifact (mvaddstr(row,"")is a no-op that left stale rows); with vacated rows actually cleared the scroll bytes are clean and deterministic, and the csr-region cursor invalidation was derived from the captured absolute-cupafter eachcsr. Still open: theidloverwrite-residual path — a single-line insert/delete that leaves new content in the vacated row (ncurses usesil/dland keeps the old line in itscurscrmodel, emitting a trailing residual); the crate abstains (the vacated row is non-blank) and redraws those rows instead. The bottom-right corner no-scroll write is now reproduced (courtNCURSES.STDSCR.DRAW): writing the last cell of the last row is bracketed byrmam(\e[?7l) /smam(\e[?7h) to suppress the auto-margin scroll, and the cursor stays on the last column (a known position, so the next move is relative, not an absolutecup). S1, partial/courted (scroll optimization — whole-screen, bottom-anchored, confined csr region, and multi-region_nc_hash_map— reproduced byte-exact; only theidloverwrite-residual path open). - OPT-03 · SGR/attribute emission strategy. ✅ PARTIAL (sgr + ANSI color byte-exact).
native reproduces ncurses's
vidputs(lib_vidattr.c) and_nc_do_color(lib_color.c) transition state machine invid::Vid, built on the byte-exacttparm(forset_attributes/sgr) and the colorPalette(pair_content): an attribute change emitstparm(sgr, …)(which resets color) and the pair is re-applied via_nc_do_color(opto the default pair, thensetaf/setab); a pure color change emits only the color transition; thePreviousAttr/SCREEN_ATTRSstate and thefix_pair0(nouse_default_colors) default white-on-black are tracked exactly. Wired intodoupdate'sUpdateAttrs(PutChar / clr_eol / clr_bol / clr_eos / ech / rep / cleanup) and themvcurovwoverwrite is gated to the current attribute. Verified byte-exact against real ncurses forA_BOLD/A_UNDERLINE/A_REVERSEand the ANSI color pairs across the attributedNCURSES.DOUPDATEscenarios (xterm hasmsgr, so motion needs no attr reset). The colored/attributed first paint (theClrUpdatepath, not just the diff) is now courted byte-exact too (courtNCURSES.DOUPDATE.COLOR_PAINT: bold/underline/reverse/color-pair text).ncvmasking is modeled (linux court).A_ALTCHARSET(line drawing) ✅ done: altcharset cells emit\e(0/\e(Bvia the sgrp9(andrmacson the A_NORMAL exit), thecoloronflag is tracked (start_color), andmvcur's turn-off-then-restore of altcharset around a move is reproduced — sobox/border/hline/vlineare byte-exact (courtNCURSES.CURSES, with and withoutstart_color). The colored background first paint (the ClearScreen color-init) is now reproduced too:ClearScreendoesUpdateAttrs(bkgd)beforeclear_screenso thebceclear fills with the background color, thenClrBottom/TransformLinepaint over it -- byte-exact (courtNCURSES.DOUPDATE.BG_PAINT). Still open: the individual on/off path (terminals withoutsgr), attribute coalescing edge cases, magic-cookie (xmc) handling, italics, anduse_default_colors/ext-color. S1, partial/courted. - OPT-04 · remaining output-shaping caps.
rep(repeat_char) andich/dchinsert/delete are reproduced (OPT-02). Colored backgrounds (wbkgd) ✅ done: theWindowcarries a background cell (bkgdset-- writes OR its attribute, clears take it), matched byte-exact (courtNCURSES.CURSES,wbkgdwindow).bce(back_color_erase) ✅ done: xterm hasbce, socan_clear_withnow treats a color-only blank as clearable (only the non-color attribute bits must be clear) -- clearing a colored region emits the background-color SGR thenclr_eol/clr_eos(the terminal's erase fills with it) rather than painting spaces. Verified byte-exact (courtNCURSES.DOUPDATE,bce_clrtoeol_colored_bkgd/bce_clrtoeol_two_lines). The auto-margin wrap (am/xenl) corner is reproduced (OPT-02). The colored first-paint ClearScreen color-init is now reproduced too (courtNCURSES.DOUPDATE.BG_PAINT:UpdateAttrs(bkgd)beforeclear_screen, thebcefill, thenclr_eos/clr_eolover the trailing/content rows). Still open: hardware tabs (ht/hts). S1, partial/courted. - OPT-05 ·
clearok/leaveok/immedok/idlok/idcokoutput effects.clearok✅ done:clearok(win, TRUE)(andclear/wclear) forces the nextdoupdateto clear + repaint from scratch (ClrUpdate), reproduced byte-exact (courtNCURSES.DOUPDATE,clearok_full_repaint).leaveok✅ done:leaveok(win, TRUE)suppresses the end-of-doupdatecursor park (the cursor is left wherever the last emit put it), reproduced byte-exact (courtNCURSES.DOUPDATE,loop). The scroll/csrmachinery these toggles gate -- including the multi-region_nc_hash_mapcase -- is now reproduced (OPT-02). Still deferred:immedok, andidlok/idcokfor theidloverwrite-residual line insert/delete path (OPT-02's one remaining open case). S1, partial.
- INP-01 · The key trie. ✅ PARTIAL (decoder byte-exact). ncurses builds a
per-SCREEN trie from
key_*caps +define_key;wgetchanswers exact-match→code, prefix-of-longer→keep waiting, no-match→emit bytes. native reconstructs the static queries (KeyMap::key_defined/has_key, courtsNCURSES.KEY.DEFINED/KEYNAME) and the runtime decode:KeyMap::decodemaps a fully-buffered byte stream to theKEY_*code sequencewgetchreturns (longest-match trie traversal), verified byte-for-byte against a real ncurses getch loop over a pty for arrows, F1–F12, navigation keys, and mixed text/keys (courtNCURSES.INPUT). The live read loop is also built: thencurses-terminalcrate'sRawMode+Keysdrives the decoder over a real raw tty, and the nativegetchbinary returns the same codes as a real ncursesgetchloop end-to-end (courtNCURSES.INPUT.LIVE). Still open: the timed wait for a lone trailing ESC (TIM-02). S1, partial/courted. - INP-02 · Two independent timers conflated risk. (a) per-window read wait
(
nodelay/timeoutms /wtimeout/halfdelaytenths-of-seconds); (b) intra- sequence ESC disambiguation (ESCDELAY/notimeout). native models (b) as a fixed inter-byte window (keycodes courted, TIM-02); the per-window read-wait timers (a) are not yet modeled. S1, boundary. - INP-03 ·
define_key/keyok/keyboundstack semantics.define_keypushes (LIFO) non-destructively;keybound(code,n)indexes the stack;keyoktoggling to the same state returns ERR (idempotent-fails); disabling a key that prefixes another must not break the longer key. native classifies these n_a. S2, open. - INP-04 · Echo is curses-level, not kernel-level. ncurses disables the
driver's echo and echoes into the window itself (tracking the cursor model);
erase/
KEY_LEFT/KEY_BACKSPACEediting. native (no tty) does not. S1, boundary. - INP-05 ·
keypademitssmkx; function keys decode only when keypad set;KEY_MOUSE/KEY_RESIZErules (mouse needs keypad; resize doesn't). S1, boundary. - INP-06 · Single per-SCREEN input FIFO + separate mouse queue;
ungetch/unget_wch/ungetmouseordering;flushinp;typeahead(fd). native: n_a. S2, boundary. - MOUSE-01 · Protocols & encoding. xterm modes 1000/1002/1003/1006 SGR
(no 223-col limit); legacy
\e[M+3 offset-by-32 bytes; negotiated viaXM. native reconstructs only the coordinate geometry (wenclose/wmouse_trafo, courtNCURSES.MOUSE.TRAFO); no SGR decoder, no mask negotiation. S1, partial. - MOUSE-02 · Click synthesis is ncurses' job, not the terminal's. press/
release→click/double/triple via
mouseinterval(default ~166 ms / 1/6 s); the terminal never sends "clicked".mousemaskreturns the reduced supported mask; coordinates are stdscr-relative. native: n_a. S1, boundary. - MOUSE-03 ·
has_mouseis runtime state (depends onmousemaskhaving enabled tracking), not a terminfo property — so it cannot be reconstructed from the entry. native classifies it n_a (recorded finding). S2, recorded. - INP-07 · Bracketed paste (
\e[?2004h,\e[200~/\e[201~) is opt-in via terminfo/define_key; there is no built-inKEY_PASTEpseudo-key. native: absent. S3, open. - INP-08 ·
keyname/unctrlC1 & meta forms. C1 bytes (128–159) shown with~prefix outside meta mode,M-form inside; nativekeynamehandles theM-/^X/KEY_*forms (courtNCURSES.KEYNAME, codes −1..600) but the C1~vsM-mode dependence andunctrlare not separately modelled. S2, partial.
- LIFE-01 ·
initscrisnewterm+ stream wiring, andinitscrexits the process on failure whilenewtermreturns NULL — an asymmetry aResult-only port erases. native: framing constants only (divergent). S1, recorded. - LIFE-02 ·
endwinis suspend, not destroy (resumed byrefresh/doupdatere-runningreset_prog_mode+smcup);delscreenis the separate teardown; andendwintwice without an intervening refresh isERR. native: divergent framing, no resume model. S1, recorded. - LIFE-03 ·
filter()mutates the loaded terminfo (LINES=1; suppressesclear/cud1/cud/cup/cuu1/cuu/vpa; suppressesedonly ifbce; rewriteshome←cr);nofilter()undoes it. native: n_a. S2, open. - LIFE-04 ·
use_env/use_tioctlsize-resolution precedence (terminfo → ioctl →$LINES/$COLUMNS, with the F/T vs T/T distinction). native: n_a. S2, open. - LIFE-05 ·
use_default_colors/assume_default_colorsand color-1requireorig_pair/orig_colors, called afterstart_color; a port using unsigned color indices can't represent-1. native: n_a (default-pair(7,0)only). S2, open. - LIFE-06 ·
slk_initformat codes (0=3-2-3 8×8, 1=4-4, 2=PC 4-4-4 12×5, 3/4 index-line) each change label count/width and shrink LINES; hardware soft labels (nlab/lh/lw/smln/rmln) vs the software line-steal path are entirely different. native reconstructs onlyslk_set/slk_labelstorage at the default 8×8/80-col width (courtNCURSES.SLK); formats 1–4, hardware labels, and the LINES coupling are gaps. S2, partial. - LIFE-07 ·
getsyx/setsyxmacros &leaveok((-1,-1)when leaveok; ripped-off top lines counted iny). native: macro surface absent. S3, open. - LIFE-08 ·
curses_version()third field is a YYYYMMDD date, not a semantic patch integer (string parsers mis-parse). native classifiescurses_versionn_a. S3, recorded.
- UB-01 · OOB window writes: C may return
ERRor compute a bad index; Rust bounds-checks (panic/Err) — fuzzing divergence. S2, open. - UB-02 · Hostile terminfo: malformed entries that C "worked" on (or
crashed/CVE'd on) become clean
Errin Rust — security win, fidelity loss. S2, open. - UB-03 ·
unctrl/control-char rendering &charsign-extension are platform/ locale-dependent in C, fixed in Rust — a reproducibility improvement that breaks bug-for-bug match. S3, open. - AL-01 · struct padding/alignment (
cchar_t,MEVENT,WINDOW) — only#[repr(C)]matches; anyonesizeof/memset-ing breaks. AL-02 ·wchar_twidth platform variance (4 B Linux/macOS, 2 B Windows) vs Rustchar4 B. S2, open. - MISC-01 ·
scr_dump/scr_restore/putwin/getwinfile format is a version-tagged ncurses-private binary; native classifies these n_a (file I/O, not terminal output) — so no C↔Rust saved-screen interop. S2, recorded. - MISC-02 · Environment surface:
$TERM,$LINES/$COLUMNS,$ESCDELAY,$TABSIZE,$NCURSES_NO_PADDING,$NCURSES_NO_UTF8_ACS,$BAUDRATE,$CC(command char),$NCURSES_TRACE,$TERMINFO/$TERMINFO_DIRS. native honours only the terminfo-DB path vars. S2, open. - MISC-03 ·
nl/nonloutput effect removed in ncurses 6.2 (input-only now) — a version-specific behaviour the admitted 6.4 build already reflects. S3, open.
- META-01 · "resolved" ≠ "reimplemented" ≠ "behaviour-verified". 481 resolved
but only 32
full; outsiders collapse "100% resolved across 31 groups" into "ncurses done". The status vocabulary is the antidote, but the headline invites the error. S3, recorded (README states the split). - META-02 · Denominator framing (see STRUCT-03): the true "full ncurses"
surface (macros,
_nc_*, panel/menu/form, tools, wide) is several × larger. S3, recorded. - META-03 · Freshness gating proves internal consistency, not external
fidelity.
cargo xtask checkverifies docs↔models↔symbols↔receipts; it cannot detect that a court captured one build while a claim names another (the claim is now pinned to the verified build, ncurses 6.4), or that a "match" is byte-equal only on a trivial case. Green CI ⇒ self-consistent, not ⇒ ncurses-accurate. S2, recorded. - META-04 · Oracle provenance/freshness decay. Captured facts are a snapshot of one ncurses build + distro patches + TERM + locale + tty settings, with no TTL vs upstream releases. Needs a freshness-of-oracle axis, not just doc-freshness. S2, open.
- META-05 · Clean-room provenance is asserted, not attested. The clean-room claim rests on process evidence (capture logs, no-source attestation) that should be preserved as an audit trail. S3, open.
- META-06 · Operational "why" / terminal folklore unported. ncurses encodes
decades of ecology — which terminals lie about caps, why
sgr0is emitted when, magic-cookie handling,xenl/auto-margin quirks, Linux-consoleacscspecials. A from-API reconstruction can resolve every function and miss every quirk. S2, open. - META-07 · The quirk/glitch corpus (
xmc,am/xenlcorner avoidance,bce,ich/smirinsert quirks,khome-vs-khvariants) lives in code paths, not the API list. S2, open.
- TOOL-01 ·
mvcurmatrix capture: NUL marker throughputpdoes not split. ✅ FIXED. The comprehensivemvcuroracle capture first usedputp("\0\0")markers to bracket per-call output through the SP buffer;tputs/putpswallow/short-circuit on NUL, so the segments did not separate (0 captured). Fix applied: a non-NUL printable marker — SOH\x01\x01\x01, whichtputspasses through unchanged — now brackets every call, and the whole 153×153 grid flushes in order atendwin. This unblocked OPT-01 (now byte-exact). One second-order artifact surfaced and is itself handled: the bulk run can interleave theendwinteardown (smcup+scroll- region+rmcup) into the final captured cell; the court (NCURSES.MVCUR) detects any segment containing\x1b[?1049and re-captures that pair in isolation, so the recorded oracle value is always authoritative. S2, fixed. - TOOL-02 · No timing/padding oracle. The pty harness records bytes, not
inter-byte delays;
tputspadding/baud effects (TIM-01) are unverifiable by a byte-equality court. S2, open. - TOOL-03 · No live-tty input / resize / wide-char oracles. Interactive
getch,KEY_RESIZE/resizeterm, and wide rendering have no output to compare (INP-, SIG-01, LOC-). S2, open. - TOOL-04 · No full-screen
doupdatebyte oracle. Courts are single-field; the min-byte diff engine (OPT-02), the behaviour that actually matters, is unmeasured. S1, open. - TOOL-05 · No multi-terminal byte-output oracle. Terminal ecology is verified for terminfo reads (9 terminals) and queries, but not for emitted bytes per TERM (the design goal). S1, open.
- TOOL-06 · No fuzz harness. The high-value targets (terminfo binary parser,
ticsource parser,tparmmini-language, input decoder) — exactly where ncurses CVEs live — are not fuzzed (cargo-fuzz/AFL). S2, open.
Refines §10/§5/§3 with the concrete terminfo-cap behaviours surfaced by the panel's output/ACS/attribute and color streams. Each is a distinct way the emitted bytes (or rendered cells) can diverge per terminal; native models none of these cap paths yet (it emits fixed xterm-shaped sequences).
- OPT-06 ·
set_attributes(sgr) coalescing algorithm. ✅ largely done (courted); xmc/italic tail open. Thevidputsengine: computeturn_off/turn_ondeltas; ifno_color_video>0stripncv-named attrs (done, OPT-08); then one of the emit paths —A_NORMAL→sgr0(or individualrmso/rmul/…), else the singletparm(sgr, …9 params)shot, else turn-off-before-turn-on via individual caps. native reconstructs this insrc/vid.rs(sgr-present path and ncurses' no-sgrindividual-cap branch withrmso/rmul/rmacsandmsgr), reaching 91.5% byte-exact across the whole terminfo DB (tools/db-coverage/sweep.py attr; courtsNCURSES.ATTR_SET,NCURSES.WINDOW.ATTR). The remaining tail is themagic_cookie_glitch(xmc) clamp tomax_attributes(OPT-12) andA_ITALIC(OPT-07), both still open. S1, partial/courted. - OPT-07 ·
A_ITALICis not insgr. Emitted viasitm/ritmseparately, andsgr0may reset italic — so italic must be re-asserted after anysgr0. native does not model italic. S2, open. - OPT-08 ·
no_color_video(ncv) bit-numbering. ✅ DONE (courted). A bitmask of attrs the terminal can't combine with color; bit positions follow SGR ordering, not thechtypeA_ bit numbers* — using the wrong numbering masks the wrong attribute. native translates the SGR-orderedncvmask tochtypeattrs (ncv_to_attr: 1→standout, 2→underline, 4→reverse, 8→blink, 16→dim, 32→bold, 64→invis, 128→protect, 256→altcharset) and strips those bits from the emitted SGR while a pair is active. Courted on the Linux console (ncv=18strips underline/dim,attr_underline_ncv). S1, done. - OPT-09 ·
bce(back-color-erase) explicit fill. ✅ DONE (courted). When!bce && color_on,clr_eol/clr_eos/scroll would erase to the terminal default bg, so ncurses must write explicit blanks in the active color. native models this: abceflag pluscan_clear_with()decide whether a cell is fast-clearable; when color is on andbceis absent the fast clears are disabled and ClearScreen blank-fills in the active color. Courted viaNCURSES.DOUPDATE.BG_PAINT/NCURSES.DOUPDATE.COLOR_PAINT(bothadmitted_match). S1, done. - OPT-10 ·
xenl/auto-margin bottom-right corner. ✅ DONE (courted). Writing the last cell of the last line would scroll; ncurses avoids it viasmam/rmamtoggling or write-at-col-2-then-ich1-shift, and models the deferred-wrap "cursor hang" (xenl). native reproduces this in thePutCharLRpath (smam/rmam/ichcaps +am/xenlmodelling) — byte-exact against real ncurses (courtNCURSES.DOUPDATE). S1, done. - OPT-11 ·
rep(repeat_char) run-length output. ✅ DONE (courted). Used only when a run exceeds_rep_cost, with count clamped near the right margin to avoid wrap. native'sEmitRangeemits the terminal's ownrepcap viatparm(rep, char, count)gated on_rep_cost = 6(alongsideerase_chars/echat_ech_cost = 5) — byte-exact for the plain-text path (courtNCURSES.DOUPDATE). S1, done (plain path). - OPT-12 ·
xmc(magic cookie glitch). Attribute changes occupy visible cells on HP-style terminals; ncurses limits simultaneous attrs tomax_attributesand accounts for consumed cells. native ignoresxmc. S2, open. - OPT-13 · Insert/delete-char output modes. ✅ largely done (courted). native's
TransformLinetakes the insert/delete-character path viaparm_ich/parm_dch, gated byInsCharCost/DelCharCostexactly as ncurses gates them (ICH_COST/DCH_COST = 5) — byte-exact for the plain-text path (courtNCURSES.DOUPDATE), beyond the window-state effect (NCURSES.WINDOW.STATE). Still open: thesmir/rmir-around-ich1andich1/dch1×n fallback emission for terminals lacking theparm_*caps, andidcokgating. S1, partial/courted. - OPT-14 · Hardware tabs.
ht/hts/it/cbtfor cursor motion; avoided onxt(destructive-tabs) terminals. native never uses tabs for motion. S2, open. - OPT-15 ·
acs_mapbuild +acscoverlay + A_ALTCHARSET. Mostly done & courted. The runtimeacs_map[]global is now exported (a TINFO symbol in bothlibncurseswand the standalonelibtinfo), andNCURSES_ACS(c)indexes it as in real ncurses. Verified against the oracle,acs_mapis the terminal-independent identity map withA_ALTCHARSETset —acs_map[c] == (A_ALTCHARSET | c); it does not overlayacsc(e.g. cygwin mapsa→0xb1 inacscyetacs_map['a']stays'a'|ALT). The per-terminalacscglyph translation happens at output time (set_acsc: identity base +acscoverlay), emitting eachA_ALTCHARSETglyph byte-exact incl. non-identity maps (cygwin→CP437) — courted (NCURSES.ACS/NCURSES.LIBTINFO,admitted_match; closes STRUCT-04). Still open: the emission-time ASCII fallback ('q'→'-','x'→'|', …) for terminals lackingsmacs/acsc, where ncurses drops alt-charset and writes the fallback char; native emits the glyph byte. S1, partial (extends GLB-05). - OPT-16 ·
smacs/rmacs/enacsemission &NCURSES_NO_UTF8_ACS. Partial (smacs/rmacs wrapping done & courted). native wraps alt-charset runs in the terminal'ssmacs/rmacs(e.g. screen^N/^O, cygwin\e[11m/\e[10m) — byte- exact, courted (NCURSES.ACS). Still open: sendingenacsonce at init, and the UTF-8-console / GNU-screen /NCURSES_NO_UTF8_ACSpath that bypasses alt-charset to write Unicode box-drawing code points (_nc_wacs). S1, partial. - OPT-17 ·
bkgdretroactive re-render vsbkgdset.bkgdsetaffects future writes only;bkgdimmediately rewrites every cell with color-match-vs-mismatch merge rules (code 0 treated as space; control bg chars rejected). native classifies bkgd deferred (no retroactive re-render modelled). S2, deferred. - OPT-18 ·
\n/cud1vsonlcrcursor tracking. When outputonlcris on,cud1="\n"also resets the column, so it can't be used as pure vertical motion; ncurses tracks tty output translation state. native does not model output\ntranslation. S2, open. - COLOR-DETAIL-01 ·
setf/setbvssetaf/setabordering swap.setaf/setabtake the ANSI ordinal directly;setf/setb(older HP/Tektronix) use blue=1/green=2/red=4 weighting — ncurses maps ANSI→setf (red 1→4, yellow 3→6, blue 4→1, cyan 6→3). native only knows thesetaf/setabpath; asetf-only terminal would get red↔blue / yellow↔cyan swapped. S1, open. - COLOR-DETAIL-02 · HLS
init_color/color_content. If thehlscap is set, the triple is Hue (0–360)/Lightness/Saturation, not RGB. native models RGB defaults only (courtNCURSES.COLOR.CONTENT); init_color is n_a (can_change=0). S2, open. - COLOR-DETAIL-03 · Direct/true color (
RGB,Tc). With theRGBuser-cap,setaf/setabtake packed 24-bit color; channel bit-widths come from booleanRGB((max_colors+2)/3per channel, blue/green lose bits if not a multiple of 3), numericRGB#n, or stringRGB=8/8/8;Tcis the older boolean. native has no direct-color path. S1, open. - COLOR-DETAIL-04 · 256-pair
chtypewall.COLOR_PAIR(n)packs the pair into 8 bits → pairs 0–255 only inchtype/attron;COLOR_PAIR(259)wraps to 4. Pairs >255 work only via thecchar_t/*_set(int) API. native'scolor_pairuses the 8-bit packing (courtNCURSES.COLOR.PAIR) but has no wide/_setpath for >255. S2, partial. - COLOR-DETAIL-05 · Per-screen
COLORS/COLOR_PAIRS/palette. Each SCREEN has its own color flag, palette, and pair table; theexternglobals alias the current screen's. native'sPaletteis an owned value (no global aliasing, no per-screenCOLORS). S2, open (extends GLB-01). - COLOR-DETAIL-06 ·
has_colorscapability gate. TRUE requiresmax_colorsANDmax_pairsAND one ofsetf/setb,setaf/setab, orscp— keying off onlysetafwrongly rejectssetf-only terminals. native'shas_colorskeys offcolors>0only. S2, partial. - COLOR-DETAIL-07 ·
op/ocfast reset & default-color env. ncurses emitsop(orig_pair) as the cheap "color off" fast path (distinct from "set pair 0"), requiresop+ocfor the default-color extensions, and honoursNCURSES_ASSUMED_COLORS(fg,bg) and interacts withCOLORFGBG. native models none of these. S2, open.
- DUMP-01 · scr_dump/restore/init/set + getwin/putwin format. Authoritative
spec is
scr_dump(5). The ncurses-6 magic is the 32-bit0x88888888— the four bytes\210\210\210\210immediately followed by ASCII"ncurses"+ a version string (e.g.6.0.20170415); the 16-bit0x8888form was rejected as collision-prone (historical SVr4/AIX/HP-UX magic was0435=\001\035, high-byte-first). The modern form is textual: WINDOW header fields written by name only if nonzero (_cury,_curx,_maxy,_maxx,_flags,_attrs,_idcok,_delay,_regbottom,_bkgrnd), then line-numbered row data with spaces as\sand renditions as{BOLD}/{REVERSE|C2}(C2=pair value). Subtleties:getwinsniffs and falls back to the legacy ncurses-5 binary, no-magic dump; the wide vs non-wide builds produce different dumps; a restored window is always top-level/pad, never a subwindow; cells persist the color-pair number, not resolved fg/bg (wrong colors if the reader'sinit_pairdiffers);scr_dumpdumps the virtual screen (newscr);scr_restoredoes not refresh (needsdoupdate);scr_init/scr_setreject the dump if the terminal was written since the dump or ifrmcup/nrrmcis present (so on xtermscr_initis usually rejected → full repaint), and resize (viawresize) rather than reject on size mismatch;scr_init(6.3+) checks dimensions. native classifies all of these n_a (file I/O) → no C↔Rust interop, none of the format/sniff/rejection logic. S2, recorded (extends MISC-01). - DUMP-02 ·
mcprintprinter routing. Prefersmc5p(counted, binary-safe, byte-count formatted viatparm), falls back tomc5+mc4(not transparent); returns bytes-sent (may be < len),ERRwithENODEV(no print caps) /ENOMEM. native: n_a. S3, open. - TRACE-01 ·
curses_trace(6.2+) vs deprecatedtrace().curses_trace(mask)returns the previous mask (and is a return-0 stub when trace is compiled out);trace(mask)returns void.TRACE_*flag values are exact and load-bearing (TRACE_ORDINARY=0x1Fis a 5-bit composite, not bit 5;TRACE_MAXIMUMis derived fromTRACE_SHIFT=13). The trace file is literally namedtracein cwd and tracing is refused if that file already exists. The_trace*/_nc_visbufhelpers use rotating static buffers. native classifies the whole trace group n_a. S3, open (extends META; trace is a debug side channel). - ENV-01 · Privileged-program env filtering. ncurses ignores exactly
{TERMINFO, TERMINFO_DIRS, TERMPATH, HOME}when privileged — defined as root orgeteuid()!=getuid()orgetegid()!=getgid()(setuid or setgid). This is the CVE-2023-29491 mitigation. native's loader has no such policy. S1, open (extends SEC-01). - ENV-02 ·
COLUMNS/LINESclamp at 512 and override the ioctl size by default;TABSIZEis NOT read from the environment (a NetBSD-vs-ncurses trap — a faithful port must not read$TABSIZE);ESCDELAYenv (capped 30 s, not AIX's numbers). S2, open. - ENV-03 · The
NCURSES_*behaviour switches.NCURSES_NO_PADDING(all but mandatory padding),NCURSES_NO_HARD_TABS(no hardware tabs in motion),NCURSES_NO_SETBUF(obsolete since 5.9 — modern ncurses owns its buffering, so interleaved stdio needsfflush),NCURSES_ASSUMED_COLORS(fg,bg),NCURSES_NO_MAGIC_COOKIE,NCURSES_NO_UTF8_ACS(tri-state: unset=auto special- caselinux/screen, nonzero=force Unicode ACS, zero=trust advertised ACS),NCURSES_GPM_TERMS,NCURSES_TRACE/BAUDRATE(debug builds),CC(cmdch, ignored unless exactly one char). native honours none of these. S2, open (extends MISC-02). - ENV-04 ·
TERMINFOinline & search precedence.$TERMINFOmay be a directory, a hashed.db, or an inline compiled entry prefixedhex:/b64:(used only if it matches$TERM); empty$TERMINFO_DIRSentries mean the compiled default dir; full order is internal-cache →$TERMINFO→$HOME/.terminfo→$TERMINFO_DIRS→ compiled default. native reads the directory/letter+hashed paths but not the inlinehex:/b64:form or the last-DB cache. S2, partial.
- ECO-01 · ABI 5 vs ABI 6 (and 7-in-progress). ABI 6 (default since 6.0,
2015) widened
cchar_t(>16 colors), restructuredmmask_tfor a 5th mouse button (NCURSES_MOUSE_VERSION1→2), made structs opaque (NCURSES_OPAQUE+ accessor functions), added the_spfunctions, and setchtype/mmask_t=uint32_t,--with-tparm-arg=intptr_t. The most dangerous divergence: the ABI-6 widec extended color pair is carried through thevoid *optsprototype arg (invisible at the signature — you can match the C signature and be wrong by ignoringopts). native targets neither ABI explicitly. S1, open. - ECO-02 · Build-variant tuple, not a single artifact.
--enable-widec(wsuffix, default-on 6.5),--with-pthread⇒--enable-reentrant(tsuffix; turnsLINES/COLS/ESCDELAY/TABSIZE/stdscr/cur_term/SPinto function- macros — must useset_escdelay()not assignment),--enable-ext-colors/-ext-mouse/-sp-funcs(default-on ABI 6),--enable-weak-symbols(drops thetsuffix, so suffix ≠ reentrancy). "Matches ncurses 6.6" is undefined without the full configure tuple. S1, open (extends BLD-01). - ECO-03 · tinfo/termlib & ticlib split.
--with-termlibputs the low-level layer (tigetstr/tputs/setupterm/set_curterm/tparm, terminfo reader incl. the 32-bit "v2" numeric format) inlibtinfo(w); on Debian/Fedora/Gentoosetupterm/tgetent/cur_termlive there, so-lncursesalone fails (needs-ltinfo). This is the most widely-depended-on layer (bash/less/readline). native is monolithic rlib with nolibtinfoalias. S1, open (extends BLD-05). - ECO-04 · pkg-config/
*-configdiscovery..pcfiles (ncursesw.pc,tinfo.pc, threaded/form/menu/panel/tic variants) andncursesw6-config(digit = ABI) that downstreamconfigure/CMake probe;--disable-overwriteputs headers underinclude/ncursesw/and the.pcCflagsmust point there. native produces none. S1, open (extends BLD-05). - ECO-05 · Version drift 6.0→6.6 (concrete NEWS). 6.0 textual screen dump +
output-buffering ownership + 5th mouse button; 6.1 extended colors stabilized
(
init_extended_*,reset_color_pairs,alloc_pair/find_pair/free_pair,RGB/direct color, opaqueTERMINAL+TERMTYPE232-bit numbers,optsextended pair, negative-pair errors); 6.2 SGR-1006 mouse modifiers,exit_curses/exit_terminfo,curses_trace,nl/nonllose output effect; 6.3 scr dimension check,KEY_EVENT/wgetch-events default-OFF; 6.4 bracketed paste (caps only), tic NUL rejection; 6.5tiparm_s/tiscan_s+ tparm provenance hardening,endwinre-call errors, widec+opaque default-ON; 6.6 conpty driver, separate mouse read/ write pointers,use=limit 32→40. native admits and is verified against 6.4 (the claim is now pinned to the build the courts run; FRAME-01 resolved). S1, recorded. - ECO-06 · Non-ncurses implementations. BSD curses (termcap, no pads/color,
obsolete), NetBSD curses (combining chars as a linked list vs ncurses' fixed
CCHARW_MAXarray; 16-bit numeric caps; different DB search;wunctrlsignature differs;assert()vsERR), macOS ships ncurses 5.7 (2008) (no extended/ direct colors, MOUSE_VERSION 1, 5.x dump/ABI — cross-checking against system curses on macOS misleads), PDCurses (no terminfo at all). "curses parity" is implementation-relative. native targets ncurses-6.x/Linux only. S2, open (extends G2). - ECO-07 · De-facto interop target. For maximum real-world compatibility the
target is
libncursesw.so.6: widec + ext-colors + ext-mouse + sp-funcs + opaque, ABI 6, 32-bitchtype/mmask_t, int-width extended pairs, v2 (32-bit) terminfo numbers, withncursesw.pc(+tinfo.pcalias). native is narrow, non-opaque-via- values, rlib-only — distance from this target is itself the headline gap. S0, recorded.
ncurses-terminalcrate (isolated unsafe tty) — closes IO-01..06, SIG-01..03, TIM-02/03, INP-02/04/05/06, ERR-04: real raw/cbreak/echo, getch, live initscr/endwin, napms, SIGWINCH. (Crate scaffolded; driver pending.)mvcurcost model ✅ + realdoupdate— OPT-01 and TOOL-01 are closed: the capture is fixed and the cost model is reverse-engineered and reproduced byte-exactly (courtNCURSES.MVCUR, all 23409 grid pairs). Remaining: OPT-01a (theovw=TRUEoverwrite branch) and OPT-02..05/TOOL-04 — build thecurscr/newscrdiff with hashmap line-move + scroll + insert/delete optimization, courted full-screen against ncurses.- Wide-char (ncursesw) layer — closes LOC-01..06:
cchar_t,add_wch/get_wch,wcwidthaccounting (pinning a Unicode version). - Companion libraries — panel (done: native
libpanelinncurses-cabi-- new_panel/del_panel/show/hide/top/bottom/move/replace/above/below/window/hidden/userptr/ update_panels, a z-ordered deck composited stdscr-ground + bottom-to-top onto newscr and rendered by the byte-exact doupdate; byte-identical to systemlibpanelw,NCURSES.PANELcourt) and menu (done: nativelibmenu-- new_item/new_menu/post_menu/menu_driver/ set_menu_win+sub/mark/fore/back/grey/format/current_item/...; post_menu fills the (sub)window cells -- mark in back + name/pad/desc in fore/grey/back -- and menu_driver redraws only the old+new current; byte-identical to systemlibmenuw,NCURSES.MENUcourt), and form (done: nativelibform-- new_field/new_form/post_form/form_driver data entry + O_BLANK first-edit blanking + REQ_FIELD navigation + REQ_DEL_PREV/DEL_CHAR/CLR editing; post_form draws each field's buffer padded into the (sub)window and form_driver edits the current field's buffer + parks the cursor; byte-identical to systemlibformw,NCURSES.FORMcourt). All three companion libraries are built natively (closes BLD-04 / STRUCT-03). - CLI tools —
tput/clearare done (native binaries incrates/ncurses-tools, byte-identical to the system tools incl. the extended-terminfoE3append,NCURSES.TPUTcourt);infocmpis done (100.000% byte-exact decompile for both-1and-1 -xacross the 2,869-entry terminfo DB,NCURSES.INFOCMPcourt);ticis done (100.000% byte-exact on both theinfocmp -1 | ticandinfocmp -1 -x | tic -xround-trips,NCURSES.TICcourt);toe/… remain (BLD-03). - Native API completeness — the macro API as Rust methods, panel/menu/form
modules, wide-char module, and the CLI tools as native binaries (closes
STRUCT-03); plus a global-state compatibility shim (thread-local
SP/cur_term,LINES/COLSaccessors, non-_spoverloads — GLB-01..03). - Native C-ABI surface (
crates/ncurses-cabi) —cdylib/staticlib+#[no_mangle] extern "C"wrappers +#[repr(C)]structs + generatedcurses.h.symverversion script +.pcfiles, so existing C programs can link/load it (closes STRUCT-01/02, ABI-*, ECO-01/03/04/07). Engineering, not impossible.
- Fuzzing + timing/multi-terminal/wide oracles (closes TOOL-02..06, SEC-03).
This ledger is the live map; the generated per-function companion is regenerated
and freshness-gated on every change (cargo xtask check).