Claude Code keeps per-project memory at <home>/.claude/projects/<key>/,
where <key> is the project's absolute path with every separator and the
drive colon turned into a dash. That makes it both rename-fragile and
machine-specific, and this project has hit each of those once:
- D:\DEV\Schunix -> D:\DEV\SchOS changed the key, and 43 memories were
invisible until moved across by hand.
- Development happens from Windows and from Linux, and each looks in a
different place for the same checkout.
Committing .claude/memory/ fixes both -- git carries it across renames
and between hosts -- and `import` puts it wherever the local machine
expects. .claude/settings.local.json stays ignored: those are per-machine
permissions.
The colon maps to a dash rather than being dropped. Deleting it gives
D-DEV-SchOS, which matches nothing and silently finds zero memories; that
was the first attempt. On Git Bash the key comes from `cygpath -w`,
because Claude Code is a Windows program and sees D:\DEV\SchOS, not
/d/DEV/SchOS.
67 lines
3.5 KiB
Markdown
67 lines
3.5 KiB
Markdown
---
|
|
name: schunix-console-tty
|
|
description: "The console has an ANSI parser, real termios and UTF-8; the font is CP437 not Latin-1, and five bugs that looked like broken commands"
|
|
metadata:
|
|
node_type: memory
|
|
type: project
|
|
originSessionId: a665e14b-86bd-4b41-a228-51377f3e9225
|
|
modified: 2026-08-04T21:10:44.835Z
|
|
---
|
|
|
|
**`kernel/core/console.c` was rewritten on 2026-08-04**: ANSI escape parser,
|
|
real termios, signals, and UTF-8.
|
|
|
|
**The serial line and the framebuffer are treated differently on purpose.**
|
|
Escapes are passed through to serial verbatim -- a real terminal emulator is
|
|
on the other end and understands them better than we ever will -- and
|
|
*interpreted* only for the framebuffer. So a serial transcript can never tell
|
|
you whether the escape parser works; it holds the raw bytes either way. Use
|
|
`pwsh -File tools/ttyshot.ps1`, which drives colour/cursor/erase/UTF-8 and
|
|
screendumps the result.
|
|
|
|
**The font is CP437, not Latin-1.** `include/font.h`'s per-glyph comments
|
|
label index N with Unicode codepoint N, which is fiction above 127: index 0xE9
|
|
is annotated "e-acute" and draws a capital theta. Trusting it turned "café"
|
|
into "caf" + theta. `console.c` maps Unicode through `latin1_to_cp437[]` plus
|
|
a small table of box-drawing characters. **Verify glyphs by rendering the
|
|
bitmap, never by reading the comment.**
|
|
|
|
`font_data` was also declared `[128][16]` while containing 256 glyphs, so the
|
|
compiler silently discarded the entire upper half -- the wall of "excess
|
|
elements in array initializer" warnings was that.
|
|
|
|
**Five bugs that all presented as broken commands, none of which were:**
|
|
|
|
- `ls /bin | wc -l` said 48 instead of 240 -- `sys_ioctl` answered *every*
|
|
unrecognised request with 0, so `TIOCGWINSZ` on a pipe "succeeded" and every
|
|
program thought pipes were terminals. It returns `-ENOTTY` now. isatty() is
|
|
built on this call, so the blast radius was everything.
|
|
- `yes | head -2` failed with "Invalid argument" -- `readv`/`writev` capped
|
|
iovcnt at 16 (POSIX's minimum) and toybox's `yes` builds 256. Now
|
|
`UIO_MAXIOV` = 1024, Linux's value.
|
|
- `cat big | head -c 4` killed the shell at `rip 0x1` -- **SIG_IGN is handler
|
|
value 1**, and `deliver_signals()` only skipped 0 (SIG_DFL), so it jumped to
|
|
address 1. Nothing sent SIGPIPE until pipes learned to, at which point every
|
|
shell that ignores SIGPIPE died.
|
|
- Pipes returned a bare `-1` with no signal on a closed read end; now SIGPIPE
|
|
plus `-EPIPE`.
|
|
- Ctrl-C did `proc->killed = 1`, and `proc` in an interrupt handler is
|
|
whatever was running on that CPU -- almost never the foreground job. It
|
|
sends a real SIGINT to `tty_fg_pgrp` now.
|
|
|
|
**Control characters must never reach the glyph path.** `vga_putc` had no tab
|
|
case, so `'\t'` fell through to `vga_draw_char(9)` -- CP437 glyph 9 is a
|
|
hollow circle, so pressing Tab printed a circle. The font has a picture for
|
|
every control code and none of them is what the character means. Tab now
|
|
advances to the next eight-column stop (erasing as it goes, so it overwrites
|
|
rather than skipping); everything else below 0x20 is dropped.
|
|
|
|
termios is real: TCGETS returns what was last set, TCSETS is honoured, and the
|
|
console implements ICANON, ECHO, ECHOE, ECHONL, ISIG, ICRNL, INLCR, IGNCR,
|
|
ISTRIP and VMIN. Raw mode returns bytes as they arrive rather than waiting for
|
|
a newline -- that is what zsh and any line editor need. EOF is a flag, not a
|
|
`^D` byte stuffed into the buffer.
|
|
|
|
Check with `pwsh -File tools/bootcheck.ps1 -Tty` (six assertions, including a
|
|
real Ctrl-C against a running `sleep`). See
|
|
[[schunix-verify-by-driving-shell]] and [[schunix-tty-fg-pgrp-coupling]].
|