ADR-0005: NoshAPI FFI Surface Enumeration
Supersedes spike: former spikes/ADR-0001-FFI-surface.md
Scope: All-carts, mission-context, and REPL-read-only capability tiers
Related: adr/ADR-0001-embedded-lisp-scripting-layer.md, adr/ADR-0014-display-profile-redesign.md, nosh.h, nosh_stdlib.h, nosh_cart.h
Summary
Section titled “Summary”This document enumerates every C function and primitive that the Lisp runtime must expose as built-in functions to cartridge code. Organized by capability tier: All-carts (universally available), Mission-context (only during active mission phases), and REPL-read-only (Lisp REPL can call for inspection, no mutations).
Source documents:
/kn86-emulator/src/nosh.h— display and sound API/kn86-emulator/src/nosh_stdlib.h— navigation, list ops, deck-state access, procedural generation/kn86-emulator/src/nosh_runtime.h— cell pool and navigation stack management/kn86-emulator/src/nosh_cart.h— macro signatures (now superseded, but API contracts remain valid)
Tier 1: All-Carts Primitives (Available Always)
Section titled “Tier 1: All-Carts Primitives (Available Always)”Display API
Section titled “Display API”Text mode
Section titled “Text mode”| Lisp name | Signature | C reference | Semantic contract | Raises |
|---|---|---|---|---|
text-clear | () → nil | nosh_text_clear() | Clear text framebuffer (80x25 cells). Does not affect split-view bitmap area. | — |
text-putc | (col row char) → nil | nosh_text_putc(uint8_t col, uint8_t row, char c) | Place a single character at (col, row) in text grid. col: 0–79, row: 0–24. char: ASCII 0–127. | :out-of-range if col ≥ 80 or row ≥ 25 |
text-puts | (col row string) → nil | nosh_text_puts(uint8_t col, uint8_t row, const char *str) | Print a null-terminated string at (col, row), left-aligned. Stops at grid boundary. | :out-of-range if col ≥ 80 |
text-printf | (col row format-string arg1 arg2 ...) → nil | (Synthesized: formatted text-puts via snprintf + text-puts) | Printf-style formatted string at (col, row). Format specifiers: %d, %u, %x, %c, %s. Max 128 bytes output. | :format-error if format string is invalid; :out-of-range if col ≥ 80 |
text-scroll | (lines) → nil | nosh_text_scroll(int8_t lines) | Scroll text up (positive) or down (negative). lines: -25 to +25. | :out-of-range if abs(lines) > 25 |
text-cursor | (col row visible) → nil | nosh_text_cursor(uint8_t col, uint8_t row, bool visible) | Show/hide text cursor at (col, row). visible: #t or #f. | :out-of-range if col ≥ 80 or row ≥ 25 |
text-invert | (col row len) → nil | nosh_text_invert(uint8_t col, uint8_t row, uint8_t len) | Invert (XOR) text color for len characters starting at (col, row). For emphasis. | :out-of-range if col + len > 80 |
Graphics mode (bitmap, 960×600) (Amended 2026-04-24 per ADR-0014)
Section titled “Graphics mode (bitmap, 960×600) (Amended 2026-04-24 per ADR-0014)”| Lisp name | Signature | C reference | Semantic contract | Raises |
|---|---|---|---|---|
gfx-clear | () → nil | nosh_gfx_clear() | Clear bitmap framebuffer (all pixels off). | — |
gfx-pixel | (x y on) → nil | nosh_gfx_pixel(uint16_t x, uint16_t y, bool on) | Set or clear a pixel at (x, y). x: 0–959, y: 0–599. on: #t (on) or #f (off). | :out-of-range if x ≥ 960 or y ≥ 600 |
gfx-line | (x0 y0 x1 y1) → nil | nosh_gfx_line(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1) | Draw line from (x0, y0) to (x1, y1) using Bresenham. | :out-of-range if any coord out of bounds |
gfx-rect | (x y w h filled) → nil | nosh_gfx_rect(uint16_t x, uint16_t y, uint16_t w, uint16_t h, bool filled) | Draw rectangle at (x, y) with size (w, h). filled: #t → solid, #f → outline. | :out-of-range if x+w > 960 or y+h > 600 |
gfx-circle | (cx cy r) → nil | nosh_gfx_circle(uint16_t cx, uint16_t cy, uint16_t r) | Draw circle centered at (cx, cy) with radius r. | :out-of-range if circle exceeds bounds |
gfx-blit | (x y bitmap-bytes w h) → nil | nosh_gfx_blit(uint16_t x, uint16_t y, const uint8_t *bitmap, uint16_t w, uint16_t h) | Blit packed bitmap (1 bit per pixel, rows left-to-right, top-to-bottom) at (x, y). bitmap-bytes: byte array of (w*h+7)/8 bytes. | :out-of-range if x+w > 960 or y+h > 600; :invalid-bitmap if bytes insufficient |
Display mode control
Section titled “Display mode control”| Lisp name | Signature | C reference | Semantic contract | Raises |
|---|---|---|---|---|
split-view | (bitmap-rows) → nil | nosh_split(uint16_t bitmap_rows) | Set split-view mode: top bitmap-rows pixels are graphics, remainder is text. bitmap-rows: 0–599. 0 = all text, 600 = all graphics. | :out-of-range if bitmap-rows > 600 |
display-mode | () → symbol | (Synthesized from internal state) | Query current display mode: :text, :graphics, or :split. Read-only. | — |
Sound API
Section titled “Sound API”PSG Control (YM2149 registers)
Section titled “PSG Control (YM2149 registers)”| Lisp name | Signature | C reference | Semantic contract | Raises |
|---|---|---|---|---|
psg-write | (reg val) → nil | nosh_psg_write(uint8_t reg, uint8_t val) | Write to PSG register reg (0–14) with value val (0–255). Directly controls 3 tone generators, noise, envelope. Use with caution. | :invalid-register if reg > 14; :invalid-value if val > 255 |
psg-read | (reg) → integer | nosh_psg_read(uint8_t reg) | Read current value of PSG register reg (0–14). Returns 0–255. | :invalid-register if reg > 14 |
Higher-level sound abstractions
Section titled “Higher-level sound abstractions”| Lisp name | Signature | C reference | Semantic contract | Raises |
|---|---|---|---|---|
sound-tone | (channel freq vol) → nil | nosh_sound_tone(uint8_t channel, uint16_t frequency, uint8_t volume) | Play tone on channel (0–2) at frequency (1–50000 Hz) at volume (0–15). Clamps to PSG limits. | :invalid-channel if channel > 2; :invalid-freq if freq = 0; :invalid-volume if vol > 15 |
sound-noise | (period) → nil | nosh_sound_noise(uint8_t period) | Set noise period (0–31). 0 = off, higher = lower pitch/slower clock. | :invalid-period if period > 31 |
sound-envelope | (shape period) → nil | nosh_sound_envelope(uint8_t shape, uint16_t period) | Set envelope shape (0–15, bits 0–3 = hold/alternate/attack/hold) and period. PSG-native. | :invalid-shape if shape > 15 |
sound-silence | () → nil | nosh_sound_silence() | Mute all sound (set all channels vol to 0, turn off noise). | — |
Sound Effects (pre-mixed SFX)
Section titled “Sound Effects (pre-mixed SFX)”| Lisp name | Signature | C reference | Semantic contract | Raises |
|---|---|---|---|---|
sfx-keyclick | () → nil | nosh_sfx_keyclick() | Play short click sound (key confirmation). ~50 ms, 1000 Hz. | — |
sfx-boot | () → nil | nosh_sfx_boot() | Play boot sequence (ascending tones). ~300 ms. | — |
sfx-select | () → nil | stdlib_sfx_select() (via nosh_cart.h) | Play selection sound (short beep). | — |
sfx-confirm | () → nil | stdlib_sfx_confirm() | Play confirmation sound (higher tone). | — |
sfx-error | () → nil | stdlib_sfx_error() | Play error buzz (low, harsh). | — |
sfx-alert | () → nil | stdlib_sfx_alert() | Play alert ping (high tone). | — |
Cell Pool & Navigation
Section titled “Cell Pool & Navigation”Cell spawning and lifecycle
Section titled “Cell spawning and lifecycle”| Lisp name | Signature | C reference | Semantic contract | Raises |
|---|---|---|---|---|
spawn-cell | (type-symbol) → cell | runtime_spawn_cell(SystemState *state, uint16_t type_id) | Allocate a new cell of the given type (e.g., ‘contract, ‘datum, ‘link). Type must be registered in cartridge CART_INIT. Returns cell object or nil if pool exhausted. | :unknown-type if type not registered; :pool-exhausted if no more cells available |
destroy-cell | (cell) → nil | runtime_destroy_cell(SystemState *state, void *cell) | Return cell to the free pool. Cell object becomes invalid after this call. | :invalid-cell if cell is null or corrupted |
Navigation stack
Section titled “Navigation stack”| Lisp name | Signature | C reference | Semantic contract | Raises |
|---|---|---|---|---|
drill-into | (target-cell) → nil | runtime_drill_into(SystemState *state, CellBase *target) | Push target onto nav stack. Calls ON_EXIT on current cell, ON_ENTER on target. | :invalid-cell if target is null; :nav-stack-overflow if depth exceeds 32 |
navigate-back | () → nil | runtime_navigate_back(SystemState *state) | Pop nav stack. Calls ON_EXIT on leaving cell, ON_ENTER on returning cell. Fails if stack is at root. | :nav-stack-underflow if already at root |
current-cell | () → cell | runtime_current_cell(SystemState *state) | Return the cell at the top of the nav stack. Returns nil if stack is empty. | — |
set-root | (root-cell) → nil | runtime_set_root(SystemState *state, CellBase *root_cell) | Reset nav stack: clear all, push root-cell. Usually called once at cartridge init. | :invalid-cell if root-cell is null |
Navigation helpers
Section titled “Navigation helpers”| Lisp name | Signature | C reference | Semantic contract | Raises |
|---|---|---|---|---|
next-sibling | () → nil | stdlib_next_sibling(SystemState *state) | Move cursor to next sibling (CDR direction) in current list. No-op if already at last sibling. | — |
prev-sibling | () → nil | stdlib_prev_sibling(SystemState *state) | Move cursor to previous sibling. No-op if already at first sibling. | — |
List Operations (via sibling chains)
Section titled “List Operations (via sibling chains)”| Lisp name | Signature | C reference | Semantic contract | Raises |
|---|---|---|---|---|
list-push | (parent cell) → nil | stdlib_list_push(CellBase *parent, CellBase *cell) | Append cell as child of parent. cell is linked into parent’s child chain. | :invalid-cell if parent or cell is null |
list-get | (parent index) → cell | stdlib_list_get(CellBase *parent, int index) | Get Nth child of parent (0-indexed). Returns nil if index out of range. | — |
list-length | (parent) → integer | stdlib_list_length(CellBase *parent) | Count children of parent. Returns 0 if parent is a leaf. | :invalid-cell if parent is null |
is-leaf | (cell) → bool | stdlib_is_leaf(CellBase *cell) | Test if cell has no children. Returns #t or #f. | — |
link-cells | (parent child) → nil | stdlib_link_cells(CellBase *parent, CellBase *child) | Create explicit parent-child relation. Equivalent to list-push. | :invalid-cell if parent or child is null |
unlink-cell | (cell) → nil | stdlib_unlink_cell(CellBase *cell) | Remove cell from its parent and sibling chain. Cell becomes orphaned but not destroyed. | :invalid-cell if cell is null or has no parent |
Procedural Generation (LFSR)
Section titled “Procedural Generation (LFSR)”| Lisp name | Signature | C reference | Semantic contract | Raises |
|---|---|---|---|---|
lfsr-seed | (seed) → nil | stdlib_lfsr_seed(SystemState *state, uint32_t seed) | Initialize the LFSR with a 32-bit seed (typically seeded from deck state or mission parameters). | — |
lfsr-next | () → integer | stdlib_lfsr_next(SystemState *state) | Advance LFSR, return next pseudo-random 32-bit value (0–4294967295). | — |
lfsr-range | (min max) → integer | stdlib_lfsr_range(SystemState *state, uint32_t min, uint32_t max) | Return pseudo-random integer in [min, max] inclusive. Accepts negative ranges. | :invalid-range if min > max |
lfsr-shuffle | (array-length) → nil | stdlib_lfsr_shuffle(SystemState *state, void **array, int count) | Fisher-Yates shuffle of array in-place. Note: Lisp can’t directly mutate arrays; this is lower-level. May need wrapper design (see unknowns). | :invalid-array if array is null |
Deck State Access (Read-only in this tier; mutations in Mission-context tier)
Section titled “Deck State Access (Read-only in this tier; mutations in Mission-context tier)”| Lisp name | Signature | C reference | Semantic contract | Raises |
|---|---|---|---|---|
deck-state | () → deck-state-object | stdlib_deck_state(SystemState *state) | Get reference to universal deck state. Object has fields: :handle, :credits, :reputation, :cartridge-history, :phase-chain. | — |
get-handle | () → string | (Synthesized: deck_state()->handle) | Get operator’s handle (callsign). Max 16 chars. Read-only. | — |
get-credits | () → integer | (Synthesized: deck_state()->credit_balance) | Get current credit balance. Read-only. | — |
get-reputation | () → integer | (Synthesized: deck_state()->reputation) | Get current reputation points. Read-only. | — |
has-capability | (bit-index) → bool | stdlib_has_capability(SystemState *state, uint8_t bit) | Test if cartridge capability bit is set (0–31). cartridge_history is a 32-bit bitmask tracking which modules have been completed. | :invalid-bit if bit ≥ 32 |
Display Helpers (stdlib)
Section titled “Display Helpers (stdlib)”| Lisp name | Signature | C reference | Semantic contract | Raises |
|---|---|---|---|---|
draw-threat-bar | (level max-level col row) → nil | stdlib_draw_threat_bar(uint8_t level, uint8_t max_level, uint8_t col, uint8_t row) | Draw threat bar: level/max_level filled blocks (■) out of max_level total. Max 16 blocks. Placed at (col, row) in text grid. | :out-of-range if max_level > 16 or col+max_level > 80 |
draw-progress-bar | (percentage col row width) → nil | stdlib_draw_progress_bar(uint8_t pct, uint8_t col, uint8_t row, uint8_t width) | Draw progress bar: pct% filled (0–100). width: 1–32 chars. Placed at (col, row) in text grid. | :invalid-percentage if pct > 100; :invalid-width if width > 32 or col+width > 80 |
draw-bordered-box | (col row w h title) → nil | stdlib_draw_bordered_box(uint8_t col, uint8_t row, uint8_t w, uint8_t h, const char *title) | Draw a box outline with optional title. w, h: 3–77, 2–24. title: optional, centered. | :out-of-range if col+w > 80 or row+h > 25; :invalid-size if w < 3 or h < 2 |
Tier 2: Mission-Context Primitives (Active phase handler only)
Section titled “Tier 2: Mission-Context Primitives (Active phase handler only)”These are available only when a mission phase is active and the handler is executing in mission context. Calling from a non-mission context raises :not-in-mission.
Mission progression
Section titled “Mission progression”| Lisp name | Signature | C reference | Semantic contract | Raises |
|---|---|---|---|---|
phase-advance | () → nil | nosh_phase_advance() (synthesized) | Advance to next phase in the mission phase chain. Triggers ON_EXIT in current phase, ON_ENTER in next. If this is the last phase, calls mission_complete. | :not-in-mission if not in active phase context; :no-next-phase if already at last phase |
mission-complete | () → nil | nosh_mission_complete() (synthesized) | Mark mission complete, award payout, advance deck state. Pops mission context, returns to mission board. | :not-in-mission if not in active phase context |
award-credits | (amount) → nil | stdlib_credit_add() | Add credits to deck balance. amount can be negative (penalty). | — |
modify-reputation | (delta) → nil | stdlib_rep_modify(SystemState *state, int16_t delta) | Modify reputation by delta (positive = gain, negative = loss). Clamped at [0, 32767]. | — |
Mission phase metadata
Section titled “Mission phase metadata”| Lisp name | Signature | C reference | Semantic contract | Raises |
|---|---|---|---|---|
set-phase-data | (key value) → nil | (Synthesized: mission context dict) | Set arbitrary key-value pair in current phase’s persistent data. Survives phase boundaries within a multi-phase mission. | :not-in-mission if not in active phase context |
get-phase-data | (key) → value | (Synthesized: mission context dict) | Retrieve phase data by key. Returns nil if not set. | :not-in-mission if not in active phase context |
set-capability | (bit-index) → nil | stdlib_set_capability(SystemState *state, uint8_t bit) | Set cartridge capability bit (0–31). Marks this module as completed. | :invalid-bit if bit ≥ 32; :not-in-mission if not in active phase context |
Tier 3: REPL-Read-Only Primitives (Player-facing Lisp REPL, ADR-0002)
Section titled “Tier 3: REPL-Read-Only Primitives (Player-facing Lisp REPL, ADR-0002)”Available in the built-in Lisp REPL only (read-only inspection). No mutations. Subset of Tier 1 + Tier 2 read operations.
| Category | Primitives |
|---|---|
| Display (read-only) | display-mode, get-handle, get-credits, get-reputation, has-capability |
| Navigation (inspect) | current-cell, list-get, list-length, is-leaf, deck-state |
| Procedural (generate) | lfsr-seed, lfsr-next, lfsr-range |
| Forbidden | All mutations: text-*, gfx-*, sound-*, spawn-cell, destroy-cell, drill-into, navigate-back, phase-advance, mission-complete, award-credits, modify-reputation, set-capability |
Rationale: REPL is for exploration, debugging, and scripted automation (ADR-0002). Mutations would corrupt mission state. Sound and display in REPL are out of scope for MVP.
Type Mapping: Lisp ↔ C
Section titled “Type Mapping: Lisp ↔ C”| C Type | Lisp Representation | Notes |
|---|---|---|
void | nil | No return value |
bool | #t or #f | True/false |
uint8_t, uint16_t, uint32_t | integer (0–2^N-1) | No explicit type; range-checked in C |
int8_t, int16_t, int32_t | integer | Signed; range-checked in C |
float, double | (not supported in MVP) | Deferred to Phase 2 if needed |
const char * (null-terminated) | string | Immutable; null-term guaranteed by C side |
CellBase * | cell (opaque object) | Lisp cannot inspect fields directly; only pass to cell functions |
CellList | (via list-* functions) | Lisp sees lists as parent-child relations, not explicit list objects |
DeckState * | deck-state-object (record) | Lisp reads fields via get-* functions or accessor pattern |
Dispatch Contract: Handler execution model
Section titled “Dispatch Contract: Handler execution model”When a cell event fires (e.g., player presses CAR key on contract cell):
- Runtime looks up cell type → finds handler
- If handler is C function pointer (runtime cells): call directly, blocking
- If handler is Lisp lambda reference (cartridge cells): marshal args, invoke Fe evaluator, return result
- Dispatch latency is transparent: Lisp handlers have same latency contract as C handlers (5 ms target, 10 ms ceiling)
This is the tagged-union approach from ADR-0001. Cell grammar (v2.0 spec) will define how handlers are authored in Lisp; the FFI layer is agnostic to their origin.
Implementation Notes
Section titled “Implementation Notes”Macros vs. Functions
Section titled “Macros vs. Functions”v1.1 cartridge grammar used C macros (e.g., spawn_cell(typename)) for compile-time type safety. In Lisp:
- No compile-time type safety; we rely on runtime argument validation
- Each primitive validates arg count and types at invocation
- Example:
(spawn-cell 'contract)— symbol ‘contract is looked up in type registry at runtime
Cell ownership & GC-less semantics
Section titled “Cell ownership & GC-less semantics”With arena allocation (Fe VM, no GC):
- Cell allocation is fast (bump allocator)
- Cell lifetime is bounded by mission instance or cartridge load boundary
- If a cartridge spawns cells and resets the arena, all cells are implicitly freed
- This is safe as long as cartridges don’t hold references across arena resets
- The mission-phase boundary is the natural reset point
String handling
Section titled “String handling”C strings are null-terminated; Lisp strings are Fe’s native string type (immutable). FFI:
- Cartridge Lisp → C: Lisp string marshaled to C const char * (pointer + length)
- C → Lisp: C string (char *) wrapped in Lisp string object
- Max string length in cartridge: limited by arena size; typically 256–1024 bytes per string
Known Unknowns / Follow-ups
Section titled “Known Unknowns / Follow-ups”-
Array mutation in Lisp:
lfsr-shufflemutates a C array in-place. Lisp is functional; how do we expose this cleanly? Possible solutions:- Design a Lisp-side array type that FFI can mutate
- Or remove shuffle, provide shuffle-based helpers in Lisp stdlib instead
-
Error handling: how deep are error traces? If a Lisp handler calls
text-putswith invalid args, does the error propagate to the player? Or silent clamping? Current design: silent clamping (defensive). May need refinement based on gameplay patterns. -
Mission phase context: “not in mission” errors are runtime only. Cartridge author cannot check at load time. Documentation and testing are critical.
-
Cipher integration: ADR-0002 scopes Cipher (voice NPC) as a Lisp-integrated system. Cipher handlers are Lisp lambdas too. They’re not explicitly listed here — they’re instances of Tier 1 (all-carts) primitives called from Cipher mission context.
Summary
Section titled “Summary”54 primitives enumerated across three tiers. All map cleanly to existing C APIs (nosh.h, nosh_stdlib.h, nosh_runtime.h). No new C functions required — just FFI wrapping. Lisp cartridges author in kebab-case; C side is snake_case (conventional).
Ready for FFI binding implementation (Phase 2 action item) and Fe integration prototype.
Amendment Log
Section titled “Amendment Log”2026-04-24 — Bitmap-FFI bounds retarget per ADR-0014 F4
Section titled “2026-04-24 — Bitmap-FFI bounds retarget per ADR-0014 F4”Status effect: Accepted (unchanged). Amendment pattern follows the ADR-0006 precedent (2026-04-22): new **Amended:** header line + this log section + in-place contract updates; no change to Decision or Options Considered.
Rationale. ADR-0014 (Accepted 2026-04-22) redefined the KN-86’s canonical logical framebuffer from an implicit 1024×600 (which was never implemented cleanly — see ADR-0014 §Context) to a 960×600 logical framebuffer integer-scaled into the 1024×600 Elecrow panel with a 32 px horizontal letterbox per side and zero vertical letterbox. Cartridges see 960×600 as their full bitmap canvas; the 32 px letterbox is invisible to them. ADR-0014 action item F4 explicitly required sweeping cartridge-authoring docs to restate the canvas as 960×600. ADR-0005 is the normative FFI contract for cartridge Lisp; it qualifies as the primary cartridge-authoring doc in F4’s scope.
Contract changes. Four cells in the #### Graphics mode table (and its header) were retargeted. Original values shown struck-through; current values are live in the table above.
| Location | Before | After |
|---|---|---|
| Section header | #### Graphics mode (bitmap, 1024×600) | #### Graphics mode (bitmap, 960×600) |
gfx-pixel contract | x: 0–1023, y: 0–599 | x: 0–959, y: 0–599 |
gfx-pixel raises | if x ≥ 1024 or y ≥ 600 | if x ≥ 960 or y ≥ 600 |
gfx-rect raises | if x+w > 1024 or y+h > 600 | if x+w > 960 or y+h > 600 |
gfx-blit raises | if x+w > 1024 or y+h > 600 | if x+w > 960 or y+h > 600 |
The uint16_t coordinate type remains correct (960 and 600 both fit in a u16). The vertical axis (0–599, y ≥ 600) is unchanged because ADR-0014’s vertical dimension is unchanged. gfx-line and gfx-circle cite “any coord out of bounds” / “circle exceeds bounds” without naming the numeric limit, so no cell-text change is required; the bounds they reference now derive from the amended gfx-pixel and gfx-rect tables.
Scope of this amendment. This is a value fix to the FFI contract, not a design-commitment change. The FFI’s shape — kebab-case Lisp names, snake_case C references, three capability tiers, 54 primitives — is unchanged. Downstream consumers (cartridge authors, the emulator’s FFI binding implementation, and ADR-0007’s scripted-mission extensions) inherit the tighter x-axis bound automatically; existing cart code that wrote to x ≥ 960 was already out of spec against ADR-0014’s framebuffer even while the ADR-0005 table said otherwise, so the amendment closes an inconsistency rather than creating one.
Authority trail. CLAUDE.md Canonical Hardware Specification (Display modes row: BITMAP (960×600)) and ADR-0014 (Decision, Action Items §F4) are the upstream sources. Prior cartridge-authoring sweeps fixed by Run 2026-04-24T11:17:53Z (see docs/_archive/AUDIT-LOG.md): UI Design System, Null / Drift gameplay specs, Definitive Guide NoshAPI summary, Prototype Architecture bitmap-mode paragraph. ADR-0005 was escalated as E12 in that run and ratified by Josh the same day.