Sprint 4 Design Pack — GWP-246
Story Narrative
Section titled “Story Narrative”Three things every cartridge wants to draw eventually: a threat-level bar (how hot is this contract?), a progress bar (how far through this scan?), and a bordered box (panel chrome for any sub-region of the 80×25 grid). ADR-0005 §“Display Helpers (stdlib)” already lists all three with full signatures, range checks, and error tags — so this isn’t a design exercise; it’s a “ship what’s specified” sprint. Today, every cart hand-rolls its own version. icebreaker_cart.c and blackledger_cart.c already draw bordered boxes, with subtle inconsistencies in corner-glyph choice, title-padding behavior, and where the title sits inside the top edge. By the time we author 14 launch carts, those inconsistencies become 14 slightly-different box styles — exactly the kind of drift a stdlib exists to prevent. Implementing these three primitives in one PR, binding them to Lisp, migrating one in-tree cart as a sample, and citing them in the cart-authoring docs gives every future cart author one obvious right answer per drawing primitive.
Player-facing Semantics with Worked Example
Section titled “Player-facing Semantics with Worked Example”(draw-threat-bar level max-level col row)
Section titled “(draw-threat-bar level max-level col row)”Renders level filled blocks (■, U+25A0 — already in the KN-86 Code Page) followed by (max-level - level) empty blocks (□, U+25A1) starting at (col, row) in the 80×25 grid. Used for difficulty/heat/danger indicators where the meaningful range is small (1–16). Choosing block glyphs rather than [##.....] style: tighter, more terminal-like, fits the device aesthetic; still 1 cell per “tick” so a 5-of-8 threat reads as 5 filled + 3 empty = 8 cells wide.
(draw-progress-bar pct col row width)
Section titled “(draw-progress-bar pct col row width)”Continuous percentage rendered as a width-character bar at (col, row). Internally: leading [, width-2 interior cells, trailing ]. Interior fill is integer-rounded — pct * (width-2) / 100 filled cells of █ (U+2588), the rest · (U+00B7) or space (TBD — see edge cases). For pct = 100, the entire interior is filled. For widths < 4, the brackets eat the bar; behavior here is up to the engineer (clamp / no-op / error) — recommended: enforce width >= 4 before drawing interior, otherwise raise :invalid-width. ADR-0005 already says width: 1–32 chars so the clamp is in the spec; tighten the lower bound to 4 in the implementation.
(draw-bordered-box col row w h title)
Section titled “(draw-bordered-box col row w h title)”Outline-only box of width w × height h, top-left at (col, row), using CP437 box-drawing glyphs from font.c’s table:
┌── title ───┐ <- row r0 (top edge with optional centered title)│ │ <- rows r0+1 .. r0+h-2 (interior is left untouched — author draws into it)└────────────┘ <- row r0+h-1 (bottom edge)Title is centered within (col+1, col+w-2) (the interior of the top edge). If len(title) + 2 > w-2, truncate with single-character ellipsis (…, U+2026 — confirm presence in KN-86 Code Page; if absent, fall back to ..). Title is rendered with one space of padding on each side: ── title ──.
Worked example — icebreaker_cart.c migration:
Today (one of the box-drawing call sites):
text_putc(state, 0, 5, '+');for (uint8_t c = 1; c < 39; c++) text_putc(state, c, 5, '-');text_putc(state, 39, 5, '+');text_puts(state, 2, 5, " ICE LATTICE ");/* ... and the same dance for the other three sides */After:
(draw-bordered-box 0 5 40 12 "ICE LATTICE")One line, byte-identical output, identical to every other cart’s panel chrome.
Acceptance Criteria
Section titled “Acceptance Criteria”- C implementations land in
src/nosh_stdlib.c(additive):void stdlib_draw_threat_bar(uint8_t level, uint8_t max_level, uint8_t col, uint8_t row)void stdlib_draw_progress_bar(uint8_t pct, uint8_t col, uint8_t row, uint8_t width)void stdlib_draw_bordered_box(uint8_t col, uint8_t row, uint8_t w, uint8_t h, const char *title)
nosh_stdlib.hdeclares all three with the canonical signatures (additive).- CP437 box-drawing glyphs sourced from
font.c’s existing table — corners ┌┐└┘, horizontals ─, verticals │. Do NOT add or modify glyphs; iffont.cis missing any of these, file a sub-task and pause. - Title centering within
(col+1, col+w-2). Truncation with…when too long; fallback..if…is not in the code page. - Range validation matches ADR-0005:
draw-threat-bar: raises:out-of-rangeifmax_level > 16orcol + max_level > 80.draw-progress-bar::invalid-percentageifpct > 100;:invalid-widthifwidth > 32orcol + width > 80. Add: tighten lower bound towidth >= 4(raise:invalid-width) — leaves room for[..].draw-bordered-box::out-of-rangeifcol + w > 80orrow + h > 25;:invalid-sizeifw < 3orh < 2.
- Lisp bindings registered in
nosh_lisp_bridge.casdraw-threat-bar,draw-progress-bar,draw-bordered-box(kebab-case; conventional). - Test
tests/test_nosh_stdlib_helpers.cvalidates each helper against a mocked text buffer:draw-threat-bar: 5/8, 0/8, 8/8, off-screen edge case (raises error).draw-progress-bar: 0%, 50%, 100%, width=4 minimum, width=33 raises, pct=101 raises.draw-bordered-box: small (3×2) box, large box with long title (truncates), title-fits-exactly, off-screen raises.
- Cart author docs cite the helpers. Update
docs/software/cartridges/authoring/ui-patterns.mdwith a “Stdlib Display Helpers” subsection enumerating the three with one short example each. - Sample migration:
carts/icebreaker_cart.cmigrates one existing box-drawing call to usestdlib_draw_bordered_box. Pure C call (this cart is C-authored); demonstrates parity. Do NOT migrate other call sites or other carts in this PR — scope guard.
Edge Cases (≥3)
Section titled “Edge Cases (≥3)”- Title-bar width arithmetic. When
titleis exactlyw-4chars long (room for one space of padding either side, no ellipsis), it fits. Whentitleisw-3chars, padding-left becomes 1 and padding-right becomes 0 — does the function force symmetric padding (truncate one char) or accept asymmetry? Recommendation: accept asymmetry up to a 1-char delta (matches conventional terminal UI); document the rule. - Bordered box at exact grid edges. A
draw-bordered-box 0 0 80 25call sits flush with the cart’s content area boundary — but Row 0 is firmware territory and Row 24 is firmware territory. The helper must not clamp to “rows 1–23”; that’s the cart’s responsibility. The helper draws what’s asked. (Same posture as every other text-write primitive — caller decides where on the canvas.) Document this inui-patterns.md: bordered-box does not enforce the row-authority split. - Progress bar fill character. Spec choice between
·(middle dot, U+00B7) and(space) for unfilled interior. Recommendation: middle dot — gives the empty portion visible texture so the bar is readable even at 0%. Confirm the dot is in the KN-86 Code Page (checkfont.c). level > max_levelfor threat-bar. ADR-0005 doesn’t explicitly tag this. Two options: clamp silently tolevel = max_level, or raise:invalid-level. Recommendation: clamp silently — pragmatic for cart authors who compute level from gameplay state and may briefly overshoot during animations. Document the clamp in the contract row.
Cross-references (cart specs that consume)
Section titled “Cross-references (cart specs that consume)”These are the launch-cart specs that already plan to use threat/progress/box semantics — engineers shipping this PR should glance at them so they understand the consumer side:
docs/software/cartridges/modules/icebreaker.md— threat-level (threat-bar), ICE-break progress (progress-bar), lattice panel (bordered-box).docs/software/cartridges/modules/blackledger.md— fraud-trace progress, ledger-row panel chrome.docs/software/cartridges/modules/depthcharge.md— scan progress, intel-result panel.docs/software/cartridges/authoring/screen-design-rules.md— Row 0/1–23/24 layout contract; bordered-box authors must respect it.docs/software/cartridges/authoring/ui-patterns.md— gets the new “Stdlib Display Helpers” subsection.
Engineering Hand-off Notes
Section titled “Engineering Hand-off Notes”- Files (additive only):
kn86-emulator/src/nosh_stdlib.c— three new functions.kn86-emulator/src/nosh_stdlib.h— three new declarations.kn86-emulator/src/nosh_lisp_bridge.c— three bindings (additive).kn86-emulator/tests/test_nosh_stdlib_helpers.c— NEW test file. Add toCMakeLists.txt.kn86-emulator/carts/icebreaker_cart.c— migrate ONE box-drawing call (sample only).docs/software/cartridges/authoring/ui-patterns.md— new subsection.
- Do NOT touch:
font.c(glyph table),display.c. If a needed glyph is missing fromfont.c, file a sub-task and pause. - TDD via test-driven-development skill: test first (red on each helper), implement, refactor.
- Expected size: ~120 LOC stdlib + ~80 LOC bridge bindings + ~250 LOC test + ~10 LOC migration + ~40 lines doc. Single PR. ~2-day engineer-dispatch.
- Validation:
cd kn86-emulator/build && ctest -R stdlib_helperspasses; full ctest stays green; emulator boots andicebreaker_cartrenders identically to before migration (visual diff acceptable). - Spec hygiene: ADR-0005 is the contract; no spec values invented. Glyph choices reference the KN-86 Code Page in
docs/software/api-reference/grammars/character-set.md.
Open Questions for Josh
Section titled “Open Questions for Josh”- Progress-bar unfilled interior glyph: middle dot
·(recommended) or space? Affects readability at low percentages. level > max_levelfor threat-bar: silent clamp (recommended) or raise:invalid-level?- Title asymmetric padding tolerance: accept up to 1-char asymmetry (recommended) or always truncate to symmetric? Standard terminal UI accepts asymmetry; calling out for confirmation.