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:
parent
cda5908660
commit
3182da3965
47 changed files with 2810 additions and 0 deletions
67
.claude/memory/schunix-console-tty.md
Normal file
67
.claude/memory/schunix-console-tty.md
Normal 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]].
|
||||
Loading…
Reference in a new issue