Carry project memory in the repo, with tools/memory-sync.sh

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.
This commit is contained in:
Nexoniarz 2026-08-09 14:27:22 +02:00
commit 3182da3965
47 changed files with 2810 additions and 0 deletions

View file

@ -0,0 +1,67 @@
---
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]].