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.
4.4 KiB
| name | description | metadata | ||
|---|---|---|---|---|
| schunix-ttf-rasteriser | The TrueType rasteriser — why glyf and not CFF, and the composite-glyph trap that loses the letter "i". |
|
Built 2026-08-09. include/ttf.h + kernel/dev/ttf.c, the second font_ops
provider behind schunix-text-rendering. Host harness: tools/ttftest.c →
build/tools/ttftest font.ttf px "text" out.pgm.
Renders real JetBrains Mono at 32 px: correct advances, anti-aliased edges, the slashed zero, dots on i and j.
glyf, not CFF — and that was checked, not assumed
.ttf and .otf are both sfnt containers with the same table directory, cmap
and hmtx. Only the outlines differ: TrueType puts quadratic B-splines in
glyf; OpenType/PostScript puts cubic béziers in CFF as Type 2 charstrings,
a stack-based interpreted format and a separate project.
Both chosen fonts ship glyf and neither ships CFF — verified by dumping
JetBrains Mono's table directory before writing a line. So CFF buys nothing
today. ttf_open() rejects OTTO explicitly rather than failing later with an
empty glyph and no explanation.
The trap: composite glyphs are not an edge case
First working render produced "Schun x" — the i was missing. i is a
composite glyph: dotless-i plus the dot, assembled from two other glyphs at
offsets. Skipping composites (numberOfContours < 0) does not just lose
accented characters, it loses a letter from the project's own name.
Now assembled: parse the component list (flags, glyph index, dx/dy, optional scale), recurse, merge contours with the offset applied. Only the translation case is handled — components with a scale or 2×2 matrix are skipped, because applying a transform wrongly is worse than not applying one.
Other things that bite
- Every sfnt field is big-endian; read byte by byte, never cast. The file is not guaranteed aligned and a cast byte-swaps everything on x86.
- Glyph flags are run-length encoded (bit 3 = "next byte is a repeat count"). Missing that turns any glyph with a repeated flag into garbage.
- Coordinates are deltas in one of three encodings per axis, selected by two flag bits — including "same as previous", which occupies no bytes.
- TrueType omits the on-curve point between two consecutive off-curve points; it must be synthesised at the midpoint or curves render as corners.
hmtxstoresnumberOfHMetricsfull entries then bearings only — clamp the index.- cmap format 4's
idRangeOffsetis a byte offset from its own address, which is why that arithmetic looks the way it does.
Glyph cache (added the same day)
Direct-mapped over printable ASCII 32..126 into a 48 KB pool, filled linearly, never freed. No hashing, no eviction policy, no allocation — the contents are a pure function of (codepoint, size), so nothing ever goes stale. A size change drops the whole cache, which is the only event that invalidates it.
It fixes two things, not one: the obvious cost of re-rasterising every glyph,
and the contract — the single ttf_cov scratch buffer meant a glyph was valid
only until the next call, so a caller could not hold two at once. Cached glyphs
now point into the pool. Codepoints outside ASCII, and anything that arrives
after the pool is full, still render correctly through the scratch buffer under
the old contract.
tools/ttftest.c calls through fo->ops->glyph, not ttf_render, so the cache
is on the tested path rather than bypassed by the harness.
Still not done
- No font loaded from disk. The kernel compiles the rasteriser but no
.ttfis in the tree and nothing reads one. This is what blocks pointing the console at it. - Hinting ignored (
fpgm/prep/cvtskipped). Fine above ~16 px, blurry below. Real hinting is a bytecode interpreter — a project, not a fix. - No GSUB, so ligatures do not form: JetBrains Mono's
==renders as two glyphs. Correct, just not what the font can do. - CFF/OTF outlines. Requested 2026-08-09 for people who want a non-TrueType
font in the terminal. Not started, and it is not "more of the same": Type 2
charstrings are a stack-based bytecode to interpret — INDEX/DICT structures,
charset, local and global subrs with the biasing rule, ~40 operators, cubic
béziers. Roughly the size of everything else here, and a subtly wrong
interpreter draws plausible-looking wrong glyphs. It plugs in as a third
font_opsprovider, so nothing above it changes.