···11+# AC Native OS Boot Speed Optimization Report
22+33+Analysis of the full boot path from kernel handoff to running JS piece, with concrete proposals for reducing the ~9-second typical boot time.
44+55+Source files analyzed:
66+- `fedac/native/initramfs/init`
77+- `fedac/native/src/ac-native.c` (main boot sequence, `try_mount_log`, `draw_startup_fade`, `draw_boot_status`, `load_boot_visual_config`)
88+- `fedac/native/src/audio.c` (`audio_init`)
99+- `fedac/native/src/wifi.c` (`wifi_init`)
1010+- `fedac/native/src/tts.c` (`tts_init`, `tts_precache`)
1111+- `fedac/native/src/input.c` (`input_init`)
1212+- `fedac/native/kernel/config-minimal`
1313+1414+---
1515+1616+## 1. Current Boot Timeline
1717+1818+Typical boot from kernel to piece running: **~9 seconds** (when audio hardware cooperates). Measured by the `boot_start`/`boot_end` monotonic clock in `main()` (ac-native.c:1774, 2101-2105).
1919+2020+| Phase | Location | Duration | Blocking? | Notes |
2121+|---|---|---|---|---|
2222+| Kernel decompress + hw init | kernel/BIOS | ~1-2s | Yes | LZ4 initramfs, built-in driver probe |
2323+| Init script | `initramfs/init` | ~1-2s | Yes | mounts, zram, dmesg dump (1s sleep) |
2424+| PID 1 setup + DRI wait | ac-native.c:1778-1802 | 0-1s | Yes | Polls `/dev/dri/card0` every 10ms for 1s |
2525+| Display init (DRM) | ac-native.c:1860 | <100ms | Yes | Opens card, finds CRTC, creates dumb buffers |
2626+| Framebuffer + font init | ac-native.c:1883-1888 | <10ms | Yes | Software render context |
2727+| **USB mount wait** | ac-native.c:266-273 | **0-2s** | **Yes** | Polls `/dev/sda1` every 20ms for 2s |
2828+| Config load | ac-native.c:542-620 | <1ms | Yes | File read + JSON parse |
2929+| **Audio init** | audio.c:616-928 | **0-18s** | **Yes** | Sound card poll + ALSA retries (see below) |
3030+| TTS init | tts.c:125-158 | ~50ms | Yes | Flite voice registration |
3131+| **TTS precache** | tts.c:204-222 | **~500ms-1s** | **Yes** | 41 Flite renders (a-z, 0-9, 5 keys) |
3232+| Boot beep | audio.c:1407-1413 | ~200ms | Yes | Two-tone E5/B5 with 80ms gap |
3333+| **Boot animation** | ac-native.c:1288-1700 | **0-5s** | **Skippable** | 300 frames at 60fps, key-skippable after 1s |
3434+| Input init | input.c:315+ | <100ms | Yes | evdev scan + hidraw |
3535+| **WiFi init** | wifi.c:653-758 | **0-3s** | **Yes** | Interface poll every 100ms for 3s |
3636+| WiFi autoconnect | wifi.c (after init) | ~async | Partly | Starts connection, returns |
3737+| JS init | js-bindings.c:1637+ | ~100-200ms | Yes | QuickJS context + API registration |
3838+| Config re-read + piece resolve | ac-native.c:2016-2077 | <10ms | Yes | |
3939+| Piece load + boot() | ac-native.c:2080-2144 | ~100ms | Yes | Module eval + boot() call |
4040+| TTS wait + melody | ac-native.c:2128-2134 | **~700ms** | **Yes** | 300ms TTS drain + 400ms melody ring-out |
4141+4242+### Audio Init Breakdown (the big variable)
4343+4444+Audio initialization at `audio.c:616-928` has two stacked wait loops:
4545+4646+**Loop 1 -- Sound card detection** (audio.c:665-670):
4747+- Polls for `/dev/snd/pcmC{0,1,2}D0p` every 20ms
4848+- 400 iterations = **8 seconds max**
4949+- On fast boots with working HDA: exits in <500ms
5050+- On slow codec probe: hits full 8s timeout
5151+5252+**Loop 2 -- ALSA device open** (audio.c:722-743):
5353+- 5 retry attempts, each trying 11 device names
5454+- 2-second `usleep()` between retries
5555+- **10 seconds additional worst case** (5 retries x 2s)
5656+5757+**Worst case total: 8s + 10s = 18 seconds for audio alone.**
5858+5959+Typical case (hardware ready quickly): <1s.
6060+6161+---
6262+6363+## 2. Polling vs. Event-Driven Detection
6464+6565+The current code uses busy-polling for all hardware detection. Every poll loop wastes CPU cycles spinning on `usleep()` and `access()` calls, and more importantly, it cannot react faster than its poll interval.
6666+6767+### 2.1 Current Polling Inventory
6868+6969+| Subsystem | File | What it polls | Interval | Max wait | Location |
7070+|---|---|---|---|---|---|
7171+| Sound card | audio.c | `/dev/snd/pcmC*D0p` | 20ms | 8s | Line 665-670 |
7272+| USB block device | ac-native.c | `/dev/sda1`, `/dev/sdb1` | 20ms | 2s | Line 270-273 |
7373+| WiFi interface | wifi.c | `detect_iface()` via sysfs | 100ms | 3s | Line 736-743 |
7474+| DRI device | ac-native.c | `/dev/dri/card0`, `/dev/fb0` | 10ms | 1s | Line 246 (PID 1 path) |
7575+7676+### 2.2 Proposed Alternatives
7777+7878+**inotify on `/dev/snd/`** (for audio card detection):
7979+- Create an `inotify` watch on `/dev/snd/` with `IN_CREATE` mask.
8080+- Block on `read()` with a timeout (via `poll()` or `select()` with 8s timeout).
8181+- When a `pcmC*D0p` node appears, immediately break out.
8282+- Reacts within milliseconds of device node creation instead of up to 20ms poll granularity.
8383+- Implementation: ~30 lines of C replacing the 6-line poll loop.
8484+8585+```c
8686+// Pseudocode
8787+int ifd = inotify_init1(IN_NONBLOCK);
8888+inotify_add_watch(ifd, "/dev/snd", IN_CREATE);
8989+struct pollfd pfd = { .fd = ifd, .events = POLLIN };
9090+while (!card_found) {
9191+ // Check if already present
9292+ if (access("/dev/snd/pcmC0D0p", F_OK) == 0) { card_found = 1; break; }
9393+ int ret = poll(&pfd, 1, 8000); // 8s timeout
9494+ if (ret > 0) { /* read events, check name */ }
9595+ else break; // timeout
9696+}
9797+close(ifd);
9898+```
9999+100100+**inotify on `/dev/` for USB block devices**:
101101+- Same pattern as above, watching `/dev/` for `IN_CREATE` events matching `sd[a-d]1`.
102102+- Replaces the 2s poll in `try_mount_log()`.
103103+104104+**Netlink socket for WiFi interface detection**:
105105+- Open a `NETLINK_ROUTE` socket, subscribe to `RTNLGRP_LINK` group.
106106+- Listen for `RTM_NEWLINK` messages with `ifi_flags` containing `IFF_UP`.
107107+- Eliminates the 3s poll loop in `wifi_init()` and reacts to interface creation instantly.
108108+- More complex (~80 lines) but the canonical Linux approach. Already used by NetworkManager, iwd, etc.
109109+110110+**Why not udev?**
111111+- AC Native OS runs no udevd (no service manager at all). Starting udevd would add its own startup cost.
112112+- A minimal `libudev` monitor is possible without udevd (it reads the same netlink/inotify events), but adds a library dependency.
113113+- The inotify/netlink approaches above are lower-level but dependency-free, which matches the system's philosophy.
114114+115115+---
116116+117117+## 3. Parallelization Opportunities
118118+119119+Currently, every boot phase in `main()` is fully sequential. The execution order is:
120120+121121+```
122122+try_mount_log() -> load_boot_visual_config() -> audio_init() -> tts_init() ->
123123+tts_precache() -> boot_beep -> draw_startup_fade() [5s animation] ->
124124+input_init() -> wifi_init() -> wifi_autoconnect() -> js_init() -> piece load
125125+```
126126+127127+Several of these have no data dependencies on each other.
128128+129129+### 3.1 Dependency Graph
130130+131131+```
132132+ display_init
133133+ |
134134+ framebuffer + font
135135+ |
136136+ +----------+----------+
137137+ | | |
138138+ try_mount_log (parallel) (parallel)
139139+ |
140140+ load_config
141141+ |
142142+ +---------+---------+
143143+ | | |
144144+audio_init wifi_init input_init <-- all independent of each other
145145+ |
146146+ tts_init
147147+ |
148148+ tts_precache
149149+ |
150150+boot_animation <-- needs: display, audio (for beep), tts (for greeting)
151151+ |
152152+ js_init <-- needs: graph, input, audio, wifi, tts
153153+ |
154154+ piece_load
155155+```
156156+157157+### 3.2 Concrete Parallelization Plan
158158+159159+**Phase A: Start audio + wifi + input concurrently**
160160+161161+These three subsystems have zero shared state during init:
162162+- `audio_init()` touches ALSA devices only
163163+- `wifi_init()` touches wireless sysfs/netlink only
164164+- `input_init()` touches evdev/hidraw only
165165+166166+Spawn threads for audio and wifi, run input on the main thread (it is fast):
167167+168168+```c
169169+pthread_t audio_thread, wifi_thread;
170170+pthread_create(&audio_thread, NULL, audio_init_threaded, NULL);
171171+if (!wifi_disabled) pthread_create(&wifi_thread, NULL, wifi_init_threaded, NULL);
172172+input = input_init(display->width, display->height, pixel_scale);
173173+pthread_join(&audio_thread, (void**)&audio);
174174+if (!wifi_disabled) pthread_join(&wifi_thread, (void**)&wifi);
175175+```
176176+177177+**Estimated savings: 2-3 seconds typical** (wifi 3s wait runs during audio 8s wait instead of after).
178178+179179+**Phase B: Overlap boot animation with hardware waits**
180180+181181+The boot animation (`draw_startup_fade`, 5 seconds) is currently run *after* audio/TTS init. But the animation only needs:
182182+- Display (already initialized)
183183+- Audio (for boot beep at start, TTS greeting at frame 10)
184184+185185+Strategy: start the animation immediately after display init. Audio can still be initializing in a background thread. The boot beep and TTS greeting are delayed until audio is ready (a simple flag check per frame).
186186+187187+```c
188188+// Start audio init in background
189189+pthread_create(&audio_thread, NULL, audio_init_threaded, NULL);
190190+// Start animation immediately
191191+// Inside animation loop: if (audio_ready && !beep_played) { audio_boot_beep(); beep_played=1; }
192192+```
193193+194194+**Estimated savings: up to 5 seconds** -- the animation runs *during* audio init instead of after, and the 5s animation cost is fully overlapped.
195195+196196+**Phase C: Defer TTS precache to after piece starts**
197197+198198+`tts_precache()` renders 41 utterances (~500ms-1s). This only matters when the user starts typing, which happens well after boot. Defer it to a background thread launched after `js_call_boot()`.
199199+200200+```c
201201+js_call_boot(rt);
202202+// Precache TTS in background -- user won't type for several seconds
203203+pthread_create(&tts_precache_thread, NULL, (void*)tts_precache, tts);
204204+```
205205+206206+**Estimated savings: ~500ms-1s.**
207207+208208+**Phase D: Move JS init earlier**
209209+210210+`js_init()` has no dependency on wifi or input -- it only needs the `ACGraph` pointer (and NULL is acceptable for input/audio/wifi, which can be set after). The QuickJS runtime creation and API registration could be done in parallel with hardware waits.
211211+212212+However, `js_init()` currently takes the pointers at creation time. Refactoring to allow late-binding of subsystem pointers would require changes to `js-bindings.c`. Lower priority.
213213+214214+---
215215+216216+## 4. Specific Optimizations
217217+218218+### Optimization 1: Replace audio polling with inotify
219219+220220+**Current cost:** Up to 8s polling at 20ms intervals (400 iterations).
221221+**Proposed:** inotify watch on `/dev/snd/` with `IN_CREATE`, blocking `poll()` with 8s timeout.
222222+**Expected savings:** Reacts within ~1ms of device node creation instead of up to 20ms. On fast boots where the card appears at 200ms, saves no time. On slow boots where it appears at 7.5s, saves ~20ms max. The real win is cleaner code and less CPU wake-up churn.
223223+**Risk:** Low. inotify is a stable Linux API (since 2.6.13). Fallback: if inotify_init fails, fall back to current poll loop.
224224+**Estimated time savings:** 0-20ms typical (marginal), but enables Phase B parallelization cleanly.
225225+226226+### Optimization 2: Smarter ALSA retry strategy
227227+228228+**Current cost:** 5 retries x 2s sleep = up to 10s additional on top of 8s card wait.
229229+**Problem:** The 2s sleep between retries is a fixed delay. If the codec probes 100ms after the last attempt, we waste 1.9s.
230230+231231+**Proposed approach -- inotify + exponential backoff:**
232232+1. After the card wait loop finds a PCM device node, do NOT sleep-retry on `snd_pcm_open()` failure.
233233+2. Instead, use inotify on `/dev/snd/` to detect NEW device nodes appearing (codec re-probe creates additional nodes).
234234+3. When a new node appears, immediately retry all devices.
235235+4. If no new nodes appear within 500ms, try again with exponential backoff: 200ms, 400ms, 800ms, 1600ms (4 retries, 3s total instead of 10s).
236236+237237+**Alternative -- probe via sysfs:**
238238+Before trying `snd_pcm_open()`, check `/proc/asound/card0/codec#0` for a `Codec:` line. If present, the codec is probed. If not, wait for it via inotify on `/proc/asound/card0/`.
239239+240240+**Estimated time savings:** 0-7s on pathological hardware. Reduces worst-case audio init from 18s to ~11s (8s card wait + 3s smarter retry).
241241+**Risk:** Medium. ALSA timing edge cases vary by hardware. Must keep the fallback timeout.
242242+243243+### Optimization 3: Parallelize audio + wifi + boot animation
244244+245245+**Current cost:** Sequential: audio (0-18s) + animation (5s) + wifi (3s) = up to 26s serial.
246246+**Proposed:** Run audio_init and wifi_init in background threads while the boot animation plays on the main thread. See Section 3.2, Phases A and B.
247247+248248+**Estimated time savings:** 3-8s typical.
249249+- WiFi 3s overlapped with audio: saves 3s.
250250+- Animation 5s overlapped with audio: saves up to 5s.
251251+- Total overlap with audio: audio takes 0-18s, animation+wifi take 5-8s. If audio finishes in <5s (typical), the animation is the gating factor. If audio takes >5s (rare), audio is the gating factor but we saved the animation's 5s by running it concurrently.
252252+253253+**Risk:** Medium. `audio_init()` writes to `audio->pcm`, `audio->audio_status`, etc. The boot animation currently reads `audio` only for `audio_boot_beep()` and TTS. These calls would need to be guarded with an `audio_ready` flag or atomic. `wifi_init()` is already self-contained (returns an opaque struct) and safe to thread.
254254+255255+### Optimization 4: Defer TTS precache
256256+257257+**Current cost:** ~500ms-1s blocking on main thread before animation starts.
258258+**Proposed:** Move `tts_precache()` to a background thread launched after `js_call_boot()`.
259259+**Estimated time savings:** 500ms-1s.
260260+**Risk:** Low. The only consumer of cached TTS is `tts_speak_cached()`, called on keypress. If a keypress arrives before precache finishes, the cache entry will be NULL and `tts_speak_cached()` already handles this (falls back to nothing). Could add a fallback to `tts_speak()` (live render) for uncached entries during the precache window.
261261+262262+### Optimization 5: Eliminate init script dmesg dump sleep
263263+264264+**Current cost:** 1 second (`sleep 1` at init:69).
265265+**Problem:** The init script does `cat /dev/kmsg > /mnt/dmesg.log &` then `sleep 1; kill $PID`. This is a debugging aid that costs 1 second on every boot.
266266+**Proposed:** Two options:
267267+1. Remove entirely and rely on ac-native's USB log (which already captures kernel messages).
268268+2. Replace with a bounded read: `timeout 0.2 cat /dev/kmsg > /mnt/dmesg.log 2>/dev/null` (busybox `timeout` if available, or just skip).
269269+3. Move to background: let ac-native copy `/dev/kmsg` to USB asynchronously after boot completes.
270270+271271+**Estimated time savings:** 1s.
272272+**Risk:** Low. Loss of kernel boot log on USB is acceptable since ac-native logs diagnostic info anyway.
273273+274274+### Optimization 6: Reduce boot animation to 3 seconds
275275+276276+**Current cost:** 300 frames at 60fps = 5 seconds.
277277+**Observation:** The animation was designed for 3s (`180 frames` in the comment at line 1381) but was extended to 5s (`total_frames = 300` at line 1384) to accommodate TTS greeting. If TTS greeting runs in a background thread (it already does -- `tts_speak` is async), and we overlap the animation with hardware init (Optimization 3), the extra 2s are unnecessary padding.
278278+**Proposed:** Reduce `total_frames` back to 180 (3 seconds). Or better: make the animation end when all hardware init threads join, with a 3s minimum.
279279+**Estimated time savings:** 0-2s (only saves time when animation is the gating factor).
280280+**Risk:** Low. TTS greeting may get cut short if it exceeds 3s, but the melody at the end serves as the "ready" signal anyway.
281281+282282+### Optimization 7: Kernel config -- skip unnecessary subsystem probes
283283+284284+**Current state:** All audio (HDA, SOF) and WiFi (iwlwifi) drivers are built-in (`=y`), not modules. This means they probe at kernel boot time, which is correct for fast startup -- no `modprobe` overhead.
285285+286286+**Potential wins:**
287287+- `CONFIG_SND_HDA_CODEC_HDMI` is already disabled, good.
288288+- Ensure only needed HDA codecs are enabled. Currently only `CONFIG_SND_HDA_CODEC_REALTEK=y` and `CONFIG_SND_HDA_GENERIC=y`. This is already well-tuned.
289289+- Consider disabling `CONFIG_SND_PROC_FS=y` and `CONFIG_SND_VERBOSE_PROCFS=y` -- saves a few ms of procfs setup. But useful for debugging; probably not worth it.
290290+- `CONFIG_SND_CTL_LED=y` can be disabled if no LED indicators are needed -- tiny savings.
291291+292292+**Estimated time savings:** <100ms. The kernel config is already lean for the target hardware.
293293+**Risk:** Very low for cosmetic changes. Disabling needed codecs would break audio.
294294+295295+### Optimization 8: Initramfs compression -- uncompressed for NVMe
296296+297297+**Current state:** LZ4 compression (`CONFIG_INITRAMFS_COMPRESSION_LZ4=y`).
298298+**Analysis:** LZ4 is already the fastest decompression option Linux supports. On NVMe (>3 GB/s read), decompression CPU cost may actually exceed the I/O savings from smaller size. However:
299299+- The initramfs contains firmware blobs, the ac-native binary, Flite voice data, pieces, and shared libraries -- likely 50-150MB uncompressed.
300300+- LZ4 decompresses at ~4 GB/s on modern CPUs, so a 100MB initramfs decompresses in ~25ms.
301301+- Uncompressed would save those 25ms but increase read time from NVMe by ~20ms (assuming 2:1 compression ratio, 50MB extra at 3 GB/s).
302302+- Net difference: essentially zero.
303303+304304+For USB boot (USB 3.0 at ~400 MB/s), LZ4 is clearly better -- the smaller size saves more I/O time than the decompression costs.
305305+306306+**Estimated time savings:** ~0ms (LZ4 is already optimal).
307307+**Risk:** Switching to uncompressed increases kernel image size, which matters for EFI partition space and OTA upload time.
308308+309309+### Optimization 9: Eliminate post-boot delays
310310+311311+**Current cost:** After piece loads, there are explicit waits:
312312+- `usleep(300000)` -- 300ms TTS ring buffer drain (ac-native.c:2131)
313313+- `usleep(400000)` -- 400ms melody ring-out (ac-native.c:2134)
314314+315315+**Proposed:** The ring buffer drain and melody can continue playing after `js_call_boot()`. The audio thread is independent. Move `js_call_boot()` before the melody/drain wait:
316316+317317+```c
318318+// Current:
319319+tts_wait(); usleep(300000);
320320+audio_ready_melody(); usleep(400000);
321321+audio_prewarm();
322322+js_call_boot();
323323+324324+// Proposed:
325325+audio_prewarm();
326326+js_call_boot(); // piece starts running immediately
327327+tts_wait(); // TTS finishes in background (audio thread handles playback)
328328+audio_ready_melody(); // plays over the already-running piece
329329+```
330330+331331+**Estimated time savings:** 700ms.
332332+**Risk:** Low. The piece's first `paint()` frame will render while the melody is still playing, which is fine -- it creates a seamless transition. The `audio_prewarm()` must happen before boot to avoid first-note latency, but it is nearly instant (50ms at 0.001 volume).
333333+334334+### Optimization 10: USB mount -- skip poll when block device already exists
335335+336336+**Current cost:** `try_mount_log()` always waits up to 2s, even when booted from NVMe where `/dev/sda1` may never appear.
337337+**Proposed:** Check once. If no USB block device exists and we detect NVMe (`/dev/nvme0n1p1`), skip the 2s wait and go directly to NVMe mount.
338338+339339+```c
340340+// Quick check -- is a block device already present?
341341+int found_quick = (access("/dev/sda1", F_OK) == 0 || access("/dev/sdb1", F_OK) == 0);
342342+if (!found_quick && access("/dev/nvme0n1p1", F_OK) == 0) {
343343+ // NVMe boot, USB probably won't appear. Mount NVMe ESP directly.
344344+ goto mount_phase;
345345+}
346346+// Only poll if nothing found and no NVMe fallback
347347+for (int w = 0; w < 100; w++) { ... }
348348+```
349349+350350+**Estimated time savings:** 0-2s (saves the full 2s on NVMe-installed boots where no USB is plugged in).
351351+**Risk:** Low. If a USB drive is being slow to enumerate on an NVMe system, we miss it -- but we still mount NVMe ESP for config.json, which is the critical need.
352352+353353+---
354354+355355+## 5. Combined Impact Estimate
356356+357357+| Optimization | Time Saved (typical) | Time Saved (worst case) | Risk |
358358+|---|---|---|---|
359359+| 1. inotify for audio detection | ~0ms | ~20ms | Low |
360360+| 2. Smarter ALSA retry | ~0ms | ~7s | Medium |
361361+| 3. Parallel audio + wifi + animation | **3-5s** | **8s** | Medium |
362362+| 4. Defer TTS precache | **500ms-1s** | **1s** | Low |
363363+| 5. Eliminate init dmesg sleep | **1s** | **1s** | Low |
364364+| 6. Shorter boot animation | **0-2s** | **2s** | Low |
365365+| 7. Kernel config tuning | <100ms | <100ms | Very low |
366366+| 8. Initramfs compression | ~0ms | ~0ms | N/A |
367367+| 9. Eliminate post-boot delays | **700ms** | **700ms** | Low |
368368+| 10. Skip USB poll on NVMe | **0-2s** | **2s** | Low |
369369+370370+### Projected Boot Times
371371+372372+**Current typical:** ~9s (kernel to piece running)
373373+374374+**After low-risk optimizations (4, 5, 6, 9, 10):**
375375+- Savings: ~1s (init dmesg) + ~700ms (post-boot delays) + ~750ms (TTS precache) + ~1s (shorter animation, NVMe skip) = **~3.5s**
376376+- **Projected: ~5.5s**
377377+378378+**After all optimizations including parallelization (1-10):**
379379+- Audio, WiFi, and animation all run concurrently. The gating factor becomes `max(audio_init, 3s_animation)`.
380380+- Typical audio init: <1s. Animation: 3s. WiFi: overlapped.
381381+- **Projected: ~3-4s** (kernel to piece running)
382382+383383+**Worst-case (bad audio hardware):**
384384+- Current: ~27s (18s audio + 5s animation + 3s wifi + 1s init)
385385+- After: ~13s (11s audio with smarter retry, overlapped with everything else)
386386+387387+---
388388+389389+## 6. Implementation Priority
390390+391391+Recommended implementation order, balancing impact vs. effort:
392392+393393+1. **Eliminate init dmesg sleep** (5 min, saves 1s) -- change `sleep 1` to `sleep 0.1` or remove entirely
394394+2. **Eliminate post-boot delays** (10 min, saves 700ms) -- reorder `js_call_boot()` before melody
395395+3. **Defer TTS precache** (15 min, saves ~750ms) -- spawn background thread after boot
396396+4. **Skip USB poll on NVMe** (20 min, saves 0-2s) -- quick-check before poll loop
397397+5. **Shorten animation to 3s** (5 min, saves 0-2s) -- change `total_frames = 300` to `180`
398398+6. **Parallel audio + wifi init** (2 hours, saves 3-5s) -- thread spawning + ready flags
399399+7. **Overlap animation with audio init** (4 hours, saves additional 0-3s) -- requires animation to handle audio-not-ready state
400400+8. **inotify for device detection** (2 hours, saves marginal) -- cleaner code, enables further async work
401401+9. **Smarter ALSA retry** (3 hours, saves 0-7s worst case) -- inotify + exponential backoff
402402+403403+Items 1-5 are low-risk, high-value, and can be done independently in an afternoon. Items 6-9 require more careful testing across hardware variants.
404404+405405+---
406406+407407+## 7. Risk Assessment Summary
408408+409409+| Risk Level | Optimizations | Gotchas |
410410+|---|---|---|
411411+| **Very Low** | 7 (kernel config), 8 (compression) | Negligible impact; don't bother unless chasing <100ms |
412412+| **Low** | 4 (defer TTS), 5 (init sleep), 6 (shorter anim), 9 (post-boot delays), 10 (NVMe skip) | TTS precache race: keypress before cache ready (already handled by NULL check). NVMe skip: misses slow USB enumeration (acceptable). |
413413+| **Medium** | 1 (inotify audio), 2 (smarter retry), 3 (parallel init), overlap animation | Thread safety: `audio_init()` return value must be visible to main thread (use `pthread_join` or atomic flag). WiFi thread must not conflict with audio's sysfs reads. Boot animation must handle `audio == NULL` during early frames. ALSA retry timing: some codecs need the full 2s between probes; shorter retry could miss a working window. |
414414+| **High** | Removing the 8s card wait entirely | Some hardware genuinely takes 5-7s for HDA codec probe. Removing the timeout would break those machines. Always keep a generous maximum. |
415415+416416+### Hardware-Specific Concerns
417417+418418+- **ThinkPad HDA codecs** (Realtek ALC257): Known to have variable probe times. The 5-retry ALSA loop was added specifically for machines where the codec appears in `/dev/snd/` but `snd_pcm_open()` fails for another 2-4 seconds.
419419+- **SOF audio** (Intel Sound Open Firmware): Not currently targeted but present in firmware bundles. If future hardware needs SOF, its probe time is typically longer (3-5s for DSP firmware load).
420420+- **USB audio**: If an external USB audio device is the primary output, the 8s wait for PCM nodes may be insufficient (USB enumeration + audio class driver). This is an edge case.
421421+- **NVMe vs. USB boot**: NVMe boots are faster for everything except situations where config.json is on USB. The USB poll skip (Optimization 10) must not break USB-only boots.
+521
reports/ac-native-init-analysis.md
···11+# AC Native OS — Initialization Analysis
22+33+Internal technical analysis of the full boot path from EFI firmware to running JS piece.
44+55+Source files analyzed:
66+- `fedac/native/initramfs/init` (81 lines)
77+- `fedac/native/src/ac-native.c` (~2300+ lines, focus on `main()` and boot functions)
88+- `fedac/native/src/audio.c` (1497 lines)
99+- `fedac/native/scripts/build-and-flash.sh` (956 lines)
1010+- `fedac/native/ac-os` (build orchestrator)
1111+- `fedac/native/Makefile`
1212+1313+---
1414+1515+## 1. EFI Boot
1616+1717+The system boots via UEFI from a GPT-partitioned drive with a single EFI System Partition (ESP), FAT32-formatted, labeled `AC-NATIVE`.
1818+1919+**Partition layout** (build-and-flash.sh:876-879):
2020+```
2121+label: gpt
2222+type=C12A7328-F81F-11D2-BA4B-00A0C93EC93B, size=512M
2323+```
2424+2525+**Boot chain**: The kernel (`vmlinuz`) is a standard Linux bzImage with the initramfs CPIO archive (LZ4-compressed) embedded directly via `CONFIG_INITRAMFS_SOURCE`. This means the kernel and initramfs are a single file.
2626+2727+Two boot layouts are supported:
2828+1. **Direct EFI stub** — `EFI/BOOT/BOOTX64.EFI` is the kernel itself (default for USB flash).
2929+2. **Splash chainloader** — If `fedac/native/bootloader/splash.efi` exists, it is placed at `BOOTX64.EFI` and the kernel goes to `EFI/BOOT/KERNEL.EFI`. The splash EFI displays a logo while the kernel loads (build-and-flash.sh:900-908).
3030+3. **systemd-boot layout** — Detected during install-to-disk: kernel at `EFI/Linux/vmlinuz-ac-native` with separate loader configs.
3131+3232+The kernel version defaults to 6.14.2 (build-and-flash.sh:36). It is compiled with Intel WiFi firmware (`iwlwifi-*`) built in via `CONFIG_EXTRA_FIRMWARE`, i915 GPU drivers, HDA audio, and all necessary subsystems for the target ThinkPad-class hardware.
3333+3434+---
3535+3636+## 2. Init Script (`/init` — PID 1)
3737+3838+The init script is a minimal POSIX shell script (`#!/bin/sh`) that runs as PID 1 inside the initramfs. It never uses systemd, udev, or any service manager.
3939+4040+### Filesystem Mounts (lines 4-13)
4141+4242+Mounts are performed in order with error suppression:
4343+4444+| Mount Point | Type | Notes |
4545+|---|---|---|
4646+| `/proc` | proc | |
4747+| `/sys` | sysfs | |
4848+| `/dev` | devtmpfs | Kernel-populated device nodes |
4949+| `/dev/pts` | devpts | PTY support (ptmxmode=0666) |
5050+| `/dev/shm` | tmpfs | Shared memory |
5151+| `/tmp` | tmpfs | Temporary storage |
5252+| `/run` | tmpfs | Runtime data |
5353+| `/sys/kernel/tracing` | tracefs | Performance tracing |
5454+| `/sys/kernel/debug` | debugfs | Debug interfaces |
5555+5656+### Credential Restoration (lines 15-34)
5757+5858+The build system bakes Claude Code credentials into the initramfs at `/claude-creds.json` and `/claude-state.json`. Because `/tmp` is mounted as tmpfs (hiding the initramfs's own `/tmp`), the init script copies these from the initramfs root:
5959+6060+- `/claude-creds.json` -> `/tmp/.claude/.credentials.json`
6161+- `/claude-state.json` -> `/tmp/.claude.json`
6262+- Seeds `/tmp/.claude/settings.json` with default permissions (all tools allowed, `installMethod: "native"`, auto-updates off)
6363+- Copies `/device-claude.md` -> `/tmp/ac/CLAUDE.md` (working dir for Claude Code sessions)
6464+6565+### Logging (line 37)
6666+6767+An `ilog()` function writes to `/tmp/ac-init.log` (tmpfs). This log is later copied to USB by `ac-native`'s `try_mount_log()` for post-mortem debugging.
6868+6969+### zram Swap (line 42)
7070+7171+Creates 1GB compressed RAM swap via zram:
7272+```
7373+modprobe zram && echo 1G > /sys/block/zram0/disksize && mkswap /dev/zram0 && swapon /dev/zram0
7474+```
7575+7676+This effectively doubles usable memory for Firefox/GTK browser sessions.
7777+7878+### Networking (line 45)
7979+8080+Brings up loopback interface (`ip link set lo up`) for localhost connections (needed for Claude OAuth callback server).
8181+8282+### Environment (lines 47-51)
8383+8484+Sets `PATH`, `SSL_CERT_FILE`, `CURL_CA_BUNDLE`, `SSL_CERT_DIR` for curl/OpenSSL trust in the initramfs environment.
8585+8686+### System Setup (lines 53-59)
8787+8888+- Creates minimal `/etc/group` and `/etc/passwd` (root only) for seatd compatibility.
8989+- Sets all CPU cores to `performance` governor.
9090+9191+### Kernel Log Dump (lines 62-77)
9292+9393+Before launching ac-native, the init script attempts to dump kernel boot logs to USB for debugging:
9494+- Scans `/dev/sda1`, `/dev/sdb1`, `/dev/sdc1` for block devices
9595+- Mounts the first available one at `/mnt`
9696+- Reads `/dev/kmsg` (non-blocking, 1-second read window) and writes to `/mnt/dmesg.log`
9797+- Copies `/tmp/ac-init.log` to `/mnt/init.log`
9898+9999+### Exec (line 80)
100100+101101+```sh
102102+exec /ac-native /piece.mjs
103103+```
104104+105105+The init script replaces itself with the ac-native binary (PID 1 preserved). The default piece is `/piece.mjs` (baked into initramfs at build time, defaults to `prompt.mjs`).
106106+107107+---
108108+109109+## 3. ac-native Main Boot Sequence
110110+111111+The `main()` function begins at line 1773 of `ac-native.c`. The binary records a monotonic clock timestamp at entry (`boot_start`) and reports total boot time at the end.
112112+113113+### 3.1 PID 1 Detection and Filesystem Setup (lines 1777-1802)
114114+115115+Two code paths based on PID:
116116+117117+**PID 1 (direct DRM boot — no cage):**
118118+- Calls `mount_minimal_fs()` (line 217) which mounts proc, sysfs, devtmpfs, devpts, shm
119119+- Creates 1GB zram swap (again — safe if already done by init, skips silently)
120120+- Brings up loopback
121121+- **Waits up to 1 second** for `/dev/dri/card0` or `/dev/fb0` to appear (line 246, 100 iterations x 10ms)
122122+- Sets all CPUs to performance governor
123123+124124+**Non-PID-1 (under cage compositor):**
125125+- Filesystems already mounted by init/parent
126126+- Opens `cage-child.log` on USB or falls back to tmpfs
127127+128128+Both paths set `PATH` and SSL environment variables.
129129+130130+### 3.2 Signal Handling (lines 1804-1814)
131131+132132+- `SIGINT`, `SIGTERM` -> graceful shutdown (`running = 0`)
133133+- `SIGUSR1` -> DRM master release (for browser handoff)
134134+- `SIGUSR2` -> reboot request
135135+- `SIGSEGV`, `SIGBUS`, `SIGABRT`, `SIGFPE` -> crash report to `/mnt/crash.json` with machine ID and timestamp, then re-raise
136136+137137+### 3.3 Display Initialization (lines 1834-1880)
138138+139139+Two display backends, selected at runtime:
140140+141141+**Wayland path** (when `WAYLAND_DISPLAY` env var is set — running under cage):
142142+- Calls `wayland_display_init()` which connects to the cage compositor
143143+- Creates a minimal `ACDisplay` struct with width/height from Wayland surface
144144+- On failure: exits with code 1 (cage will restart or fall through to DRM)
145145+146146+**DRM path** (direct framebuffer — default for PID 1 boot):
147147+- Calls `drm_init()` which opens `/dev/dri/card0`, finds a connected CRTC, and sets up double-buffered dumb buffers
148148+- Fatal if no display found — powers off after 5 seconds if PID 1
149149+150150+**Framebuffer creation** (line 1869):
151151+- Software framebuffer at 1/3 display resolution: `fb_create(display->width / 3, display->height / 3)`
152152+- Default `pixel_scale = 3` (3x nearest-neighbor upscale on present)
153153+154154+### 3.4 Graphics and Font Init (lines 1883-1888)
155155+156156+- `graph_init()` — software rendering context bound to framebuffer
157157+- `font_init()` — loads built-in bitmap font
158158+- Creates separate cursor overlay framebuffer (prevents KidLisp effects from smearing cursor)
159159+160160+### 3.5 USB/Disk Mount and Log Setup (line 1894)
161161+162162+`try_mount_log()` (defined at line 266):
163163+164164+1. **Waits up to 2 seconds** for USB block devices (`/dev/sda1`, `/dev/sdb1`) to appear (100 iterations x 20ms)
165165+2. **Two-pass mount strategy:**
166166+ - Pass 0: Try removable devices first (USB install source)
167167+ - Pass 1: Fall back to internal ESP (for disk-installed boots)
168168+3. Scans: `sda1, sdb1, sdc1, sdd1, nvme0n1p1, nvme1n1p1`
169169+4. Mounts as VFAT at `/mnt`
170170+5. Opens `/mnt/ac-native.log` in append mode
171171+6. Dumps init log (`/tmp/ac-init.log`) and cage stderr to `/mnt/init.log`
172172+7. Logs all available block devices with removable status
173173+174174+### 3.6 Config Loading (line 1897)
175175+176176+`load_boot_visual_config()` (defined at line 542):
177177+178178+Reads `/mnt/config.json` (USB/HD) or falls back to `/default-config.json` (baked in initramfs).
179179+180180+Extracts:
181181+- **`handle`** — sets `AC_HANDLE` env var, generates time-of-day greeting as boot title (e.g., "good morning @jeffrey")
182182+- **`colors`** — per-character RGB colors for the boot title text, from the `handle-colors` API
183183+- **`wifi`** — boolean, defaults to true; when false, sets `wifi_disabled = 1`
184184+- **`claudeCreds`** — JSON object written to `/tmp/.claude/.credentials.json`
185185+- **`claudeState`** — JSON object written to `/tmp/.claude.json`
186186+- **`voice`** — "off" disables keystroke TTS
187187+188188+The config supports an `AC_IDENTITY_BLOCK_V1` header line (used by `ac-usb` flash tool).
189189+190190+### 3.7 Audio Init (line 1900)
191191+192192+`audio_init()` (defined at audio.c:616):
193193+194194+This is the most timing-sensitive subsystem. The function:
195195+196196+1. **Allocates state** — `ACAudio` struct with defaults: BPM 120, sample rate 192kHz (default target), reverb buffers, 10-second sample buffer at 48kHz, TTS ring buffer (5 seconds at output rate).
197197+198198+2. **Seeds default sample** — Generates a 550ms synthesized one-shot (240Hz with wobble and exponential decay) so sample-mode is playable before first mic recording (audio.c:587-614).
199199+200200+3. **Waits for sound card** — Polls for `/dev/snd/pcmC{0,1,2}D0p` for up to **8 seconds** (400 iterations x 20ms) at audio.c:665-670. This is the critical wait for HDA codec probe completion.
201201+202202+4. **Diagnostics** — On timeout, distinguishes between:
203203+ - HDA controller present but codec not probed (`/dev/snd/controlC0` exists but no PCM device)
204204+ - No hardware at all
205205+ Dumps `/proc/asound/cards` and `/dev/snd/` contents to `/mnt/ac-audio.log`.
206206+207207+5. **ALSA device open** — Tries 11 device names with **5 retry attempts** (2-second sleep between retries, audio.c:722-743):
208208+ ```
209209+ hw:0,0 hw:1,0 hw:0,1 hw:1,1 hw:0,2 hw:0,3 hw:1,2 hw:1,3
210210+ plughw:0,0 plughw:1,0 default
211211+ ```
212212+ Total worst case: 5 attempts x 2s sleep = 10 additional seconds on top of the 8s card wait.
213213+214214+6. **ALSA configuration** (audio.c:755-801):
215215+ - Format: S16_LE interleaved, 2 channels
216216+ - Rate: Requests 192kHz (AUDIO_SAMPLE_RATE), accepts whatever the hardware provides via `set_rate_near`
217217+ - Period size: `AUDIO_PERIOD_SIZE` (negotiated)
218218+ - Buffer: 6 periods for underrun tolerance
219219+ - Logs actual vs. requested rate (common mismatch on some codecs)
220220+221221+7. **Mixer setup** (audio.c:809-875):
222222+ - Opens ALSA mixer for the detected card
223223+ - Unmutes **every** playback switch found
224224+ - Sets **all** playback volumes to maximum
225225+ - Enables all capture switches and sets capture volume to 90% of max
226226+ - Attempts HDA codec verb injection for ALC257 (Speaker node 0x14, HP Out node 0x21)
227227+228228+8. **HDMI audio** (audio.c:882-920) — Best-effort secondary output:
229229+ - Tries: `hdmi:0,0` through `hdmi:1,1`, `plughw:0,3`, `plughw:0,7`, `plughw:0,8`, `plughw:1,3`, `plughw:1,7`
230230+ - Opens non-blocking, 48kHz, 512-frame period
231231+ - Calculates downsample ratio from primary rate
232232+233233+9. **Audio thread** (audio.c:924) — Starts the real-time audio thread at SCHED_FIFO priority 50.
234234+235235+**Audio is treated as optional** — if no ALSA device is found after all retries, `audio->pcm` is NULL and the struct is returned with a status message. The rest of the system continues without sound.
236236+237237+### 3.8 TTS Init and Precache (lines 1901, 1928-1929)
238238+239239+In DRM boot path only:
240240+241241+- `tts_init(audio)` (tts.c:125) — Initializes Flite with two voices:
242242+ - `cmu_us_slt` (female, primary)
243243+ - `cmu_us_kal` (male, secondary)
244244+ - Both set to 0.9x duration stretch (slightly faster speech)
245245+ - Starts a background TTS thread
246246+247247+- `tts_precache(tts)` (tts.c:204) — Pre-renders all lowercase letters (a-z) and digits (0-9) as cached TTS waveforms. This eliminates latency on keystroke speech during typing.
248248+249249+### 3.9 Boot Beep (line 1927)
250250+251251+`audio_boot_beep(audio)` (audio.c:1407-1413):
252252+- Two-tone "doo-dah": E5 (660Hz, 120ms) then B5 (990Hz, 150ms) with 80ms gap
253253+- Stereo-panned slightly left then right
254254+255255+### 3.10 Boot Animation — Startup Fade (line 1937)
256256+257257+`draw_startup_fade()` (defined at line 1288):
258258+259259+**300 frames at 60fps = 5-second animation.**
260260+261261+1. **Immediate solid frame** — White (daytime 7am-6pm LA time) or black (nighttime) to hide kernel console text.
262262+2. **Version check** — Compares `AC_GIT_HASH-AC_BUILD_TS` against `/mnt/booted-version`. Marks as "FRESH" if different. Appends to `/mnt/boot-history.log`.
263263+3. **Machine ID** — Reads `/mnt/.machine-id` or generates `notepat-XXXXXXXX` (random hex from `/dev/urandom`).
264264+4. **TTS greeting** (frame 10) — Speaks time-of-day greeting with handle name and build name: "good morning jeffrey. enjoy Los Angeles! fuzzy-meadow."
265265+5. **Visual elements:**
266266+ - Fade from solid color to time-of-day themed background (cream/warm white for day, deep blue/sunset purple for night)
267267+ - Centered title with per-character handle colors (animated pulse)
268268+ - Version panel (top-right): build name, git hash, build timestamp, "FRESH" badge
269269+ - "enjoy Los Angeles!" subtitle (appears after frame 130)
270270+ - Auth badges (bottom-left): pixel-art crab (Claude token present) and octocat (GitHub PAT present)
271271+ - Shrinking time bar at bottom
272272+ - "W: install to disk" hint (only when booting from removable USB)
273273+6. **Input handling** — Opens all `/dev/input/event*` devices non-blocking:
274274+ - First 60 frames (1 second): all key presses are drained/ignored
275275+ - After frame 60: `W` key triggers install-to-disk flow, any other key skips animation
276276+277277+### 3.11 Input Init (line 1944)
278278+279279+`input_init()` (input.c:315):
280280+281281+- Scans all `/dev/input/event*` devices
282282+- Opens each with `O_RDONLY | O_NONBLOCK | O_CLOEXEC`
283283+- Checks event bits: accepts devices with EV_KEY, EV_ABS, EV_REL, or EV_SW
284284+- Detects tablet mode (EV_SW + SW_TABLET_MODE) with sysfs fallback for ThinkPad ACPI
285285+- Identifies NuPhy keyboards by vendor ID — marks them for hidraw analog handling instead of evdev
286286+- Calls `hidraw_scan()` for analog keyboard support via HID raw protocol
287287+288288+### 3.12 Install-to-Disk Flow (lines 1967-1981)
289289+290290+Triggered only when user pressed `W` during boot animation and confirmed with `Y`:
291291+292292+`auto_install_to_hd()` (line 713):
293293+1. Identifies source (removable USB with kernel at `EFI/BOOT/BOOTX64.EFI`)
294294+2. Finds target non-removable block device (NVMe or SATA)
295295+3. Creates GPT with EFI System Partition via `sfdisk`
296296+4. Formats FAT32, copies kernel and config.json
297297+5. Supports both direct EFI stub and systemd-boot layouts
298298+6. Displays animated progress during install
299299+7. On success: prompts reboot (with TTS "rebooting", shutdown sound, 600ms delay)
300300+301301+### 3.13 WiFi Init (line 1987)
302302+303303+`wifi_init()` (wifi.c:653):
304304+305305+1. Checks for `iw` binary existence
306306+2. Logs diagnostic info: network interfaces, firmware files, kernel wireless messages from `/dev/kmsg`, PCI device enumeration
307307+3. **Waits up to 3 seconds** for wireless interface (30 attempts x 100ms)
308308+4. If found, stores interface name (e.g., `wlan0`)
309309+5. `wifi_autoconnect()` called immediately after — connects to saved/preset network
310310+311311+WiFi is skipped entirely if `wifi_disabled` is set from config.json.
312312+313313+### 3.14 Secondary HDMI Display (lines 1997-2000)
314314+315315+`drm_init_secondary()` — attempts to find a second connected DRM output for HDMI mirroring/extension.
316316+317317+### 3.15 JS Runtime Init (line 2004)
318318+319319+`js_init()` (js-bindings.c:1637):
320320+321321+1. Allocates `ACRuntime` struct with pointers to all subsystems (graph, input, audio, wifi, tts)
322322+2. Creates WebSocket and UDP client handles
323323+3. Initializes 3D camera
324324+4. Creates QuickJS-ng runtime and context
325325+5. Registers `Form` and `Painting` JS classes
326326+6. Sets up module loader (`JS_SetModuleLoaderFunc`)
327327+7. Registers the full native API surface:
328328+ - **Graphics chain API**: box, line, circle, plot, write, scroll, blur, zoom, contrast, spin, qr, form, ink
329329+ - **Top-level graphics**: wipe, ink, line, box, circle, qr, plot, write, scroll, blur, zoom, contrast, spin
330330+ - **console**: log, warn, error
331331+ - **performance**: now()
332332+8. Evaluates init JS code (Button, Box classes)
333333+9. Evaluates browser API stubs for KidLisp compatibility
334334+335335+### 3.16 Config Application to Runtime (lines 2016-2077)
336336+337337+After JS init, re-reads `/mnt/config.json` to populate runtime fields:
338338+- `rt->handle` and `rt->piece` (boot piece override)
339339+- Voice config ("off" disables keystroke TTS)
340340+- Claude token -> `/claude-token`
341341+- GitHub PAT -> `/github-pat`
342342+- Piece aliases: "claude" and "cc" resolve to "terminal" with param "claude"
343343+- Resolves piece path: `/pieces/{name}.mjs`
344344+345345+### 3.17 Piece Load and Boot (lines 2080-2144)
346346+347347+1. `js_load_piece(rt, piece_path)` — Evaluates the `.mjs` file, extracts `boot`, `paint`, `act`, `sim`, `leave`, `beat` function references
348348+2. Logs boot time: `"Booted in %.1fms"` (line 2105)
349349+3. Logs subsystem status: audio, backlight, input devices, NuPhy analog status, JS function availability
350350+4. Waits for TTS to finish greeting + 300ms drain + plays ready melody (C5-E5-G5 ascending triad) + 400ms ring-out
351351+5. `audio_prewarm()` — plays near-silent 440Hz note (50ms, 0.001 volume) to fill ALSA buffers
352352+6. Drains queued input events from boot animation
353353+7. `js_call_boot(rt)` — Calls the piece's `boot()` function
354354+8. `perf_init()` — Initializes performance logger (30-second CSV chunks to `/mnt/perf/`)
355355+356356+### 3.18 DRM-to-Cage Transition (lines 2149-2280)
357357+358358+After boot completes in DRM mode (compiled with `USE_WAYLAND`), the system attempts to transition to a Wayland session for browser popup support:
359359+360360+1. Destroys audio (cage child will re-open ALSA)
361361+2. Releases DRM master
362362+3. **Forks:**
363363+ - **Child**: Sets env vars (`WLR_RENDERER=pixman`, `WLR_BACKENDS=drm`), creates XDG runtime dir, starts `seatd -g root`, waits up to 3s for `/run/seatd.sock`, then `execlp("cage", "cage", "-s", "--", "/ac-native", "/piece.mjs")`
364364+ - **Parent**: Waits for cage to exit, copies cage stderr to USB log
365365+366366+This means ac-native boots twice: once in DRM for fast startup, then re-execs under cage for full Wayland capabilities.
367367+368368+---
369369+370370+## 4. Timing
371371+372372+Based on log data and code analysis, approximate timing from kernel handoff:
373373+374374+| Phase | Duration | Cumulative | Notes |
375375+|---|---|---|---|
376376+| Kernel + initramfs decompress | ~1-2s | ~1-2s | LZ4 decompression, driver probe |
377377+| Init script (mounts, zram, dmesg dump) | ~1-2s | ~2-4s | dmesg dump takes ~1s (sleep 1) |
378378+| Display wait | 0-1s | ~2-5s | Up to 1s for /dev/dri/card0 |
379379+| USB mount + log setup | 0-2s | ~2-7s | Up to 2s waiting for USB block devs |
380380+| Config load | <1ms | — | Simple file read + JSON parse |
381381+| Audio init | 0-18s | ~2-25s | **Highly variable** — see Known Issues |
382382+| TTS init + precache | ~0.5-1s | — | 36 Flite renders (a-z, 0-9) |
383383+| Boot animation | 0-5s | — | Skippable with any keypress |
384384+| Input init | <100ms | — | evdev + hidraw scan |
385385+| WiFi init | 0-3s | — | Interface detection wait |
386386+| JS init + piece load | ~100-200ms | — | QuickJS context + module eval |
387387+| **Total (typical, audio found quickly)** | | **~9s** | From kernel to piece running |
388388+389389+The `ac_log` at line 2105 reports `"Booted in %.1fms"` measuring from `main()` entry to piece load completion. With well-behaved audio hardware (card appears quickly, first ALSA device opens on attempt 0), total time from kernel to playable piece is approximately 9 seconds.
390390+391391+---
392392+393393+## 5. Known Issues
394394+395395+### 5.1 ALSA Race Condition (HDA Codec Probe Timing)
396396+397397+**The primary boot-time variability comes from audio initialization.**
398398+399399+On fast NVMe boots, the HDA codec may not be fully probed when ac-native first tries to open ALSA. The code handles this with a two-layer retry:
400400+401401+1. **Card wait loop** (audio.c:665-670): Polls for `/dev/snd/pcmC{0,1,2}D0p` for 8 seconds. If timeout occurs and `/dev/snd/controlC0` exists, it means the HDA controller initialized but the codec hasn't finished probing — this is logged as `"HDA ctrl ok, codec not probed"`.
402402+403403+2. **ALSA device open retries** (audio.c:722-743): Even after a PCM device node appears, `snd_pcm_open()` can fail if the codec isn't fully ready. The code retries 5 times with 2-second intervals across 11 different device names.
404404+405405+**Worst case**: 8s card wait + 5 retries x 2s = 18 seconds for audio init alone. On machines where audio hardware is ready quickly, this completes in under 1 second.
406406+407407+The diagnostic output is written to `/mnt/ac-audio.log` (separate from the main log) with full `/proc/asound/cards` and `/dev/snd/` directory listings.
408408+409409+### 5.2 Missing Shared Libraries
410410+411411+The build system uses dynamic linking by default (gcc, not musl-gcc). This means all shared libraries must be correctly bundled into the initramfs. The build script (build-and-flash.sh) handles this with:
412412+413413+1. Per-binary `ldd` scanning for direct dependencies
414414+2. A two-pass transitive dependency resolver (lines 740-769) that scans all ELF files in the initramfs and copies missing `.so` files
415415+416416+Historical issues:
417417+- Libraries missed by single-pass `ldd` (transitive deps of deps)
418418+- Unversioned `.so` symlinks needed for `dlopen()` but not created
419419+- Mesa GPU stack requiring `libexpat`, `libffi`, `libxkbcommon` that aren't direct deps of ac-native
420420+421421+The build now generates an initramfs manifest (`build/initramfs-manifest.txt`, copied into the image as `/manifest.txt`) for build parity verification.
422422+423423+### 5.3 Firmware Requirements
424424+425425+Required firmware blobs (bundled in initramfs at `/lib/firmware/`):
426426+427427+| Firmware | Purpose |
428428+|---|---|
429429+| `iwlwifi-9260-th-b0-jf-b0-*.ucode` | Intel WiFi 9260 |
430430+| `iwlwifi-cc-a0-*.ucode` | Intel WiFi AX200 |
431431+| `iwlwifi-QuZ-a0-hr-b0-*.ucode` | Intel WiFi AX201 |
432432+| `iwlwifi-QuZ-a0-jf-b0-*.ucode` | Intel WiFi AX201 (variant) |
433433+| `regulatory.db` + `.p7s` | Wireless regulatory database |
434434+| `i915/*` | Intel GPU display firmware (all generations) |
435435+| `intel/sof/*`, `intel/sof-ipc4/*` | Intel SOF audio firmware |
436436+| `intel/sof-tplg/*`, `intel/sof-ipc4-tplg/*` | SOF audio topology |
437437+438438+The kernel config also embeds a subset of WiFi firmware via `CONFIG_EXTRA_FIRMWARE` for early availability before the initramfs mounts firmware. The build script decompresses `.xz` and `.zst` compressed firmware from the host system.
439439+440440+### 5.4 Wayland/Cage Transition Complexity
441441+442442+The DRM-to-cage transition (Section 3.18) is inherently fragile:
443443+- Audio must be destroyed and re-opened by the cage child
444444+- DRM master must be released before cage can take it
445445+- seatd must start successfully within 3 seconds
446446+- The ac-native binary effectively boots twice (DRM then Wayland)
447447+- If cage fails, the parent logs the error but the system stays on the DRM frame (no automatic fallback to DRM-only mode with continued operation)
448448+449449+### 5.5 Clock Accuracy
450450+451451+Time-of-day greetings and themed backgrounds depend on `get_la_hour()` which computes LA time. Since the system has no RTC battery on some hardware and NTP hasn't run yet at boot, the kernel time may be epoch (1970). The code accepts this gracefully — it just shows the wrong greeting.
452452+453453+---
454454+455455+## 6. Build Pipeline
456456+457457+### Local builds (`ac-os build`)
458458+459459+Orchestrated by `fedac/native/ac-os`:
460460+461461+1. **Binary build** (`build_binary()`):
462462+ - Runs `make CC=gcc` (or musl-gcc if available with linux/input.h)
463463+ - Passes `BUILD_TS` env var for timestamp embedding
464464+ - **Verifies** the binary contains the current git hash via `strings` check
465465+ - Builds BPF trace tools if Makefile exists
466466+467467+2. **Initramfs build** (`build_initramfs()`):
468468+ - Runs `build-and-flash.sh --skip-kernel --skip-binary` under sudo
469469+ - **KidLisp bundling** done as user (npx unavailable under sudo): `npx esbuild` bundles `kidlisp.mjs` as IIFE
470470+ - Copies fresh binary into initramfs
471471+ - **Claude Code native binary**: copies from `~/.local/share/claude/versions/` (latest), bakes OAuth token from `~/.claude/.credentials.json`, bakes GitHub PAT from `gh auth token`
472472+ - Bakes credentials into initramfs root files
473473+474474+3. **Kernel build**: Runs `build-and-flash.sh` for kernel compilation with embedded initramfs
475475+476476+### Makefile version stamps (Makefile:10-15)
477477+478478+```makefile
479479+GIT_HASH := $(shell git rev-parse --short HEAD)
480480+BUILD_TS := $(shell date -u '+%Y-%m-%dT%H:%M')
481481+BUILD_NAME := $(shell scripts/build-name.sh --bump)
482482+```
483483+484484+These are compiled into the binary via `-DAC_GIT_HASH`, `-DAC_BUILD_TS`, `-DAC_BUILD_NAME` and used for:
485485+- Boot screen version panel
486486+- TTS greeting (build name spoken aloud)
487487+- `/mnt/booted-version` comparison for "FRESH" detection
488488+- `/mnt/boot-history.log` entries
489489+490490+**Critical note** (Makefile:123 comment): Make's dependency tracking doesn't detect changes to `AC_BUILD_NAME` or `AC_GIT_HASH` because they're not source file changes. The Makefile forces a rebuild of the main object file to prevent stale version strings.
491491+492492+### Oven builds vs. local builds
493493+494494+The primary differences:
495495+496496+| Aspect | Local (`ac-os`) | Oven (CI/Docker) |
497497+|---|---|---|
498498+| Compiler | gcc (dynamic) or musl-gcc (static) | gcc (dynamic) |
499499+| Claude binary | From `~/.local/share/claude/versions/` | Downloaded from GCS (`claude-code-releases`) |
500500+| Claude credentials | From `~/.claude/.credentials.json` | Baked by `ac-usb flash` injection |
501501+| GitHub PAT | From `gh auth token` | Baked by flash injection |
502502+| KidLisp bundle | `npx esbuild` (user space) | Not available under sudo |
503503+| Firmware | Host `/lib/firmware/` (all generations) | Host `/lib/firmware/` (Docker image) |
504504+| i915 blobs | All from `/lib/firmware/i915/` | All from Docker image |
505505+| SOF audio | All from host | All from Docker image |
506506+| Manifest | Generated and embedded | Generated and embedded |
507507+| Build name | `scripts/build-name.sh --bump` | Same (increments per build) |
508508+509509+Both produce the same artifact structure: a single `vmlinuz` with embedded LZ4 initramfs CPIO. The build parity is verified via the initramfs manifest file.
510510+511511+### Flash pipeline
512512+513513+`ac-os flash` extends the build with USB write:
514514+1. Full rebuild (binary + initramfs + kernel)
515515+2. GPT partition table with 512MB ESP
516516+3. FAT32 format labeled `AC-NATIVE`
517517+4. Kernel copied to `EFI/BOOT/BOOTX64.EFI` (or splash chainloader + `KERNEL.EFI`)
518518+5. `config.json` with handle + colors from `handle-colors` API
519519+6. Credentials injected (Claude token, GitHub PAT, Claude settings)
520520+521521+`ac-os upload` always rebuilds first (kernel embeds git hash at compile time), then uploads to OTA release endpoint.
+418
reports/ac-native-size-analysis.md
···11+# AC Native OS Size Analysis
22+33+**Date:** 2026-03-19
44+**Build:** native-notepat-latest (kernel 6.14.2)
55+**Source:** Live data from `oven.aesthetic.computer:/opt/oven/native-cache/`
66+77+---
88+99+## 1. Size Summary
1010+1111+| Metric | Size |
1212+|--------|------|
1313+| **vmlinuz (compressed, shipped)** | **271 MB** (284,120,064 bytes) |
1414+| Initramfs uncompressed (cpio) | 781 MB |
1515+| Initramfs compressed (cpio.lz4) | 306 MB (320,097,197 bytes) |
1616+| Initramfs root (on disk) | 783 MB |
1717+| Kernel overhead (vmlinuz - initramfs) | ~-35 MB (kernel re-compresses with gzip) |
1818+| vmlinux (uncompressed ELF) | 357 MB |
1919+| Manifest entries | 1,893 files |
2020+2121+**Compression pipeline:** The initramfs is first compressed with LZ4 (781 MB -> 306 MB, 2.55x ratio). The kernel then wraps everything with gzip (`CONFIG_KERNEL_GZIP=y`), yielding a final vmlinuz of 271 MB. The effective compression ratio from initramfs root to vmlinuz is **2.89x**.
2222+2323+---
2424+2525+## 2. Component Breakdown
2626+2727+### 2.1 Top-Level Directory Sizes
2828+2929+| Directory | Size | % of 783 MB |
3030+|-----------|------|-------------|
3131+| `bin/` | 234 MB | 29.9% |
3232+| `lib/` (firmware + x86_64 libs) | 275 MB | 35.1% |
3333+| `lib64/` (shared libraries + DRI) | 263 MB | 33.6% |
3434+| `usr/` (ALSA, XKB, sbin) | 8.4 MB | 1.1% |
3535+| `pieces/` | 616 KB | 0.1% |
3636+| `etc/` | 684 KB | 0.1% |
3737+| Other (dev, proc, sys, scripts, sbin, var) | <100 KB | ~0% |
3838+3939+**Critical finding:** `lib/x86_64-linux-gnu/` and `lib64/` contain **214 MB of duplicated libraries** (same files, different inodes -- not hardlinks or symlinks). This is the single largest waste.
4040+4141+### 2.2 Detailed Component Breakdown
4242+4343+#### Core Binary
4444+4545+| Component | Size | Notes |
4646+|-----------|------|-------|
4747+| `ac-native` | 1.3 MB | The AC piece runner (dynamically linked, C + QuickJS) |
4848+4949+#### Claude Code
5050+5151+| Component | Size | Notes |
5252+|-----------|------|-------|
5353+| `bin/claude` | **226 MB** | Bun SEA binary, **not stripped**, 1,061 symbols exposed |
5454+5555+This single file accounts for **28.9%** of the uncompressed initramfs and is the #1 largest item.
5656+5757+#### JavaScript Runtime
5858+5959+| Component | Size | Notes |
6060+|-----------|------|-------|
6161+| `pieces/` (34 .mjs files) | 616 KB | clock.mjs (284K) is largest |
6262+| `jslib/` | 4 KB | KidLisp bundle (empty -- bundled by ac-os step) |
6363+6464+#### Shared Libraries (lib64/) -- Top 15
6565+6666+| Library | Size | Purpose | Duplicate in lib/x86_64? |
6767+|---------|------|---------|--------------------------|
6868+| libLLVM.so.20.1 | **137 MB** | LLVM backend for Mesa gallium | YES (137 MB) |
6969+| libgallium-25.2.8.so | **42 MB** | Mesa gallium GPU driver (iris/i915/swrast) | No (lib64 only) |
7070+| libicudata.so.74 | **30 MB** | ICU Unicode data tables | YES (30 MB) |
7171+| libcrypto.so.3 | 5.1 MB | OpenSSL crypto | YES |
7272+| libflite_cmu_us_slt.so.1 | 4.0 MB | Flite TTS female voice (slt) | YES |
7373+| libflite_cmu_us_slt.so.2.2 | 4.0 MB | Same voice, version duplicate in lib64 | No |
7474+| libstdc++.so.6 | 2.5 MB | C++ standard library | YES |
7575+| libicuuc.so.74 | 2.1 MB | ICU Unicode common | YES |
7676+| libc.so.6 | 2.1 MB | glibc | YES |
7777+| libgnutls.so.30 | 2.0 MB | GnuTLS | YES |
7878+| libxml2.so.2 | 1.9 MB | XML parser | YES |
7979+| libunistring.so.5 | 1.7 MB | Unicode string handling | YES |
8080+| libp11-kit.so.0 | 1.7 MB | PKCS#11 module loader | YES |
8181+| libflite_cmu_us_kal.so.1 | 1.4 MB | Flite TTS male voice (kal) | YES |
8282+| libglib-2.0.so.0 | 1.3 MB | GLib | YES |
8383+8484+Total files: 121 shared libraries in lib64/ (50 are symlinks).
8585+8686+#### Library Duplication Detail
8787+8888+The build script copies libs to `lib64/` for most binaries, but `ac-native` libs go to `lib/x86_64-linux-gnu/` (via canonical path resolution). The result is two full copies:
8989+9090+| Path | Size | Role |
9191+|------|------|------|
9292+| `lib64/` | 263 MB | Used by claude, cage, busybox, git, curl, etc. |
9393+| `lib/x86_64-linux-gnu/` | 217 MB | Used by ac-native (canonical Debian paths) |
9494+| **Overlap** | **214 MB** | Same content, different inodes |
9595+9696+#### Firmware
9797+9898+| Category | Size | Files | Notes |
9999+|----------|------|-------|-------|
100100+| **i915 GPU** | **27 MB** | 128 blobs | All Intel GPU generations |
101101+| **Intel SOF audio** | **31 MB** | ~180+ files | 4 directories (sof, sof-ipc4, sof-tplg, sof-ipc4-tplg) |
102102+| WiFi (iwlwifi) | 2.0 MB | 4 ucodes | 9260, AX200, AX201 |
103103+| Regulatory DB | 12 KB | 2 files | |
104104+| **Total firmware** | **59 MB** | ~314 files | |
105105+106106+**i915 by generation:**
107107+108108+| Generation | Size | Blobs | Target hardware |
109109+|------------|------|-------|-----------------|
110110+| tgl (Tiger Lake) | 4.3 MB | 15 | 11th gen |
111111+| icl (Ice Lake) | 3.0 MB | 10 | 10th gen |
112112+| dg1/dg2 (Arc) | 4.6 MB | 16 | Discrete GPU |
113113+| skl (Skylake) | 2.0 MB | 17 | 6th gen |
114114+| kbl (Kaby Lake) | 1.9 MB | 13 | 7th/8th gen |
115115+| bxt (Broxton) | 1.9 MB | 13 | Atom |
116116+| ehl (Elkhart Lake) | 2.1 MB | 6 | Embedded |
117117+| mtl (Meteor Lake) | 2.1 MB | 5 | 14th gen (current target) |
118118+| adlp (Alder Lake) | 1.7 MB | 10 | 12th gen |
119119+| glk (Gemini Lake) | 1.6 MB | 9 | Atom |
120120+| cml (Comet Lake) | 1.2 MB | 6 | 10th gen |
121121+| Other (rkl, cnl, xe3lpd, xe2lpd, bmg, adls) | <0.3 MB | 6 | Various |
122122+123123+**Intel SOF audio breakdown:**
124124+125125+| Directory | Size | Notes |
126126+|-----------|------|-------|
127127+| sof-tplg/ (legacy topologies) | 9.7 MB | Old IPC format |
128128+| sof-ipc4/ (new firmware) | 7.7 MB | 11 platform dirs |
129129+| sof/ (legacy firmware) | 6.7 MB | Old IPC format |
130130+| sof-ipc4-tplg/ (new topologies) | 6.4 MB | 129 topology files |
131131+132132+SOF IPC4 platforms: adl, adl-n, adl-s, arl, arl-s, lnl, mtl, rpl, rpl-s, tgl, tgl-h
133133+134134+#### Mesa GPU Stack
135135+136136+| Component | Size | Notes |
137137+|-----------|------|-------|
138138+| libLLVM.so.20.1 | 137 MB | JIT compiler for gallium shaders |
139139+| libgallium-25.2.8.so | 42 MB | Monolithic driver (iris + i915 + swrast) |
140140+| lib64/dri/ (driver stubs) | 120 KB | 1 real file + 4 symlinks |
141141+| EGL/GBM/GLES libs | ~2 MB | Symlinks into lib64/ |
142142+| **Total Mesa (deduplicated)** | **~181 MB** | libLLVM + gallium + supporting libs |
143143+144144+**Dependency chain:** `dri/iris_dri.so` -> `libdril_dri.so` -> `libgallium` -> `libLLVM.so.20.1` -> `libicudata.so.74`
145145+146146+#### System Tools
147147+148148+| Binary | Size | Notes |
149149+|--------|------|-------|
150150+| busybox | 2.1 MB | 45+ applet symlinks (sh, find, cp, mv, etc.) |
151151+| git | 3.9 MB | For Claude Code git operations |
152152+| awk (gawk) | 724 KB | GNU awk |
153153+| ip | 756 KB | Network config |
154154+| ac-trace | 412 KB | BPF/ftrace diagnostic tool |
155155+| iw | 308 KB | WiFi scanner |
156156+| curl | 292 KB | HTTP client |
157157+| grep | 184 KB | Pattern search |
158158+| dropbear | 165 KB | SSH daemon |
159159+| sed | 112 KB | Stream editor |
160160+| sfdisk | 108 KB | Partition tool (USB flash) |
161161+| cage | 64 KB | Wayland kiosk compositor |
162162+| seatd | 48 KB | Seat daemon for wlroots |
163163+| mkfs.vfat | 52 KB | FAT32 formatter |
164164+| dropbearkey | 47 KB | SSH key generator |
165165+| jq | 32 KB | JSON processor |
166166+| Other (head, cut, sleep, etc.) | ~200 KB | Small coreutils |
167167+168168+#### Flite TTS
169169+170170+| Component | Size | Copies | Notes |
171171+|-----------|------|--------|-------|
172172+| libflite_cmu_us_slt (female voice) | 4.0 MB | 3 copies (lib64 .so.1, .so.2.2, lib/x86_64) | **12 MB total** |
173173+| libflite_cmu_us_kal (male voice) | 1.4 MB | 3 copies | **4.2 MB total** |
174174+| libflite_cmulex (lexicon) | 600 KB | 3 copies | 1.8 MB total |
175175+| libflite.so (core) | 228 KB | 3 copies | 684 KB total |
176176+| libflite_usenglish.so | 168 KB | 3 copies | 504 KB total |
177177+| **Total Flite (all copies)** | | | **~19 MB** |
178178+179179+The .so.1 and .so.2.2 files in lib64/ are also duplicates (different inodes, same content).
180180+181181+#### Audio Config
182182+183183+| Component | Size | Notes |
184184+|-----------|------|-------|
185185+| ALSA ucm2/ | 2.4 MB | Use Case Manager configs |
186186+| ALSA topology/ | 364 KB | |
187187+| ALSA cards/ | 272 KB | |
188188+| ALSA pcm/ + ctl/ + conf | ~90 KB | |
189189+| **Total ALSA** | **3.1 MB** | |
190190+191191+#### XKB Keyboard Data
192192+193193+| Component | Size | Notes |
194194+|-----------|------|-------|
195195+| symbols/ | 2.5 MB | All keyboard layouts worldwide |
196196+| rules/ | 804 KB | Layout selection rules |
197197+| geometry/ | 404 KB | Physical keyboard geometry |
198198+| keycodes/ | 148 KB | |
199199+| compat/ + types/ | 152 KB | |
200200+| **Total XKB** | **4.0 MB** | |
201201+202202+#### Other
203203+204204+| Component | Size | Notes |
205205+|-----------|------|-------|
206206+| CA certificates | 444 KB | /etc/pki/ |
207207+| SSL certs (duplicate CA) | 224 KB | /etc/ssl/ |
208208+| init script | 2.9 KB | Boot script |
209209+| scripts/ | 4 KB | upload-log.sh |
210210+| dhclient-script | 4 KB | |
211211+| git-credential-ac | 4 KB | PAT helper |
212212+213213+---
214214+215215+## 3. Shrink Opportunities
216216+217217+### 3.1 Library Deduplication (lib64/ vs lib/x86_64-linux-gnu/)
218218+219219+**Current waste: 214 MB of exact duplicates**
220220+221221+The build script (`build-and-flash.sh`) copies libs in two ways:
222222+1. Per-binary `ldd` copies go to `lib64/`
223223+2. ac-native's canonical-path copy goes to `lib/x86_64-linux-gnu/`
224224+225225+**Fix:** Replace `lib/x86_64-linux-gnu/` with a symlink to `lib64/` (or vice versa). The transitive dependency resolver (lines 740-769 of build-and-flash.sh) already copies everything to lib64/, so the x86_64-linux-gnu directory can be a symlink.
226226+227227+**Estimated savings:** 214 MB uncompressed, ~84 MB compressed
228228+**Difficulty:** Low (single line change in build script)
229229+**Risk:** Low (just a symlink, all paths still resolve)
230230+231231+### 3.2 Flite TTS Version Duplicates in lib64/
232232+233233+Both `.so.1` and `.so.2.2` exist as separate files with identical content (different inodes).
234234+235235+**Fix:** Make `.so.1` a symlink to `.so.2.2` (or vice versa).
236236+237237+**Estimated savings:** 6.4 MB (slt 4.0 MB + kal 1.4 MB + cmulex 600 KB + core 228 KB + usenglish 168 KB)
238238+**Difficulty:** Low
239239+**Risk:** Low
240240+241241+### 3.3 Claude Code Binary (226 MB)
242242+243243+The binary is a Bun Single Executable Application (SEA). It is **not stripped** (1,061 symbols, `file` reports "not stripped"). However, the build script contains a warning: "Do NOT strip -- Claude Code is a Bun SEA binary and stripping destroys the embedded JS blob."
244244+245245+**Options:**
246246+1. **UPX compression:** UPX can compress ELF binaries ~50-70%. The binary would self-extract at startup (adds ~1-2 seconds boot time). UPX is not currently installed on the oven. Bun SEA binaries may or may not survive UPX -- needs testing.
247247+ - **Potential savings:** 100-150 MB
248248+ - **Difficulty:** Medium (needs testing)
249249+ - **Risk:** Medium (may break Bun SEA resource extraction)
250250+251251+2. **Lazy load from USB:** Instead of embedding Claude in the initramfs, keep it on the FAT32 USB partition and mount + exec at runtime. The initramfs would ship without Claude, loading it on demand.
252252+ - **Potential savings:** 226 MB from initramfs (but still on USB)
253253+ - **Difficulty:** Medium (requires init script changes + mount logic)
254254+ - **Risk:** Low (USB is always present at boot)
255255+256256+3. **Download on first use:** Only fetch the Claude binary when the user first invokes it (requires network).
257257+ - **Potential savings:** 226 MB
258258+ - **Difficulty:** Medium
259259+ - **Risk:** High (requires network, slow first launch)
260260+261261+4. **Wait for smaller Claude builds:** Anthropic may release a more compact Claude Code binary in the future.
262262+263263+### 3.4 libLLVM.so.20.1 (137 MB)
264264+265265+This is the LLVM JIT compiler, required by Mesa gallium for shader compilation. The dependency chain is: `iris_dri.so` -> `libgallium` -> `libLLVM`.
266266+267267+**Options:**
268268+1. **Build Mesa without LLVM:** Mesa can be built with `-Dllvm=disabled`. This removes software shader compilation (swrast) and some gallium optimizations, but the Intel iris driver can still work using Intel's proprietary shader compiler path (i965 backend, not gallium). However, the Ubuntu-shipped Mesa packages always include LLVM.
269269+ - **Potential savings:** 137 MB (+ 30 MB libicudata which is only needed by libLLVM)
270270+ - **Difficulty:** High (custom Mesa build required)
271271+ - **Risk:** Medium (may degrade GPU performance or break some features)
272272+273273+2. **Strip LLVM:** The shipped libLLVM may contain debug sections or unnecessary targets. A custom LLVM build targeting only x86_64 backend would be significantly smaller (~30-50 MB).
274274+ - **Potential savings:** 90-110 MB
275275+ - **Difficulty:** High
276276+ - **Risk:** Low (if built correctly)
277277+278278+3. **Use i965 DRI driver instead of iris/gallium:** For older Intel hardware, the classic i965 driver doesn't need LLVM. But for modern Intel (Gen 12+), iris is required.
279279+ - Not recommended for current target hardware.
280280+281281+### 3.5 libicudata.so.74 (30 MB)
282282+283283+ICU Unicode data tables. Only loaded by libLLVM (which loads it via libxml2 -> libicuuc -> libicudata). Nothing else in the initramfs directly needs ICU.
284284+285285+**Fix:** Eliminating LLVM (3.4 above) would also eliminate ICU.
286286+287287+**Estimated savings:** 30 MB
288288+**Difficulty:** Tied to LLVM removal
289289+**Risk:** Same as LLVM removal
290290+291291+### 3.6 i915 GPU Firmware (27 MB, 128 blobs)
292292+293293+The build copies **all** i915 generations from the host. If AC Native OS only targets specific hardware (e.g., Meteor Lake laptops), most can be removed.
294294+295295+**Minimum set for Meteor Lake + Alder Lake:**
296296+- mtl (5 blobs, 2.1 MB) -- 14th gen
297297+- adlp (10 blobs, 1.7 MB) -- 12th gen
298298+- tgl (15 blobs, 4.3 MB) -- 11th gen (backwards compat)
299299+300300+**Everything else removable:** skl, kbl, bxt, glk, icl, cml, ehl, dg1, dg2, rkl, cnl, xe3lpd, xe2lpd, bmg, adls
301301+302302+**Estimated savings:** ~19 MB (keep ~8 MB for mtl+adlp+tgl)
303303+**Difficulty:** Low (filter in build-and-flash.sh line 369-383)
304304+**Risk:** Medium (breaks support for older Intel hardware)
305305+306306+### 3.7 Intel SOF Audio Firmware (31 MB)
307307+308308+The build copies ALL SOF firmware (legacy IPC + IPC4, all platforms, all topologies).
309309+310310+**Options:**
311311+1. **Remove legacy SOF (sof/ + sof-tplg/):** Modern Intel uses IPC4. Legacy is for pre-TGL.
312312+ - **Savings:** 16.4 MB
313313+ - **Risk:** Breaks audio on pre-Tiger Lake hardware
314314+315315+2. **Trim IPC4 topologies:** 129 topology files, most for specific codec combinations. The device likely only needs the HDA-generic topologies + its specific codec.
316316+ - **Savings:** ~4-5 MB
317317+ - **Risk:** Medium (need to identify correct topology)
318318+319319+3. **Trim IPC4 platforms:** Keep only mtl + arl (current targets), remove adl/tgl/rpl variants.
320320+ - **Savings:** ~4 MB
321321+ - **Risk:** Medium
322322+323323+**Total potential SOF savings:** ~20-25 MB
324324+**Difficulty:** Medium (requires knowing exact target hardware)
325325+326326+### 3.8 Flite TTS Voice Models
327327+328328+Two voices are bundled: slt (female, 4.0 MB) and kal (male, 1.4 MB).
329329+330330+**Options:**
331331+1. **Keep only one voice:** Remove kal (male) if unused.
332332+ - **Savings:** ~1.4 MB (plus duplicates = ~4.2 MB)
333333+ - **Difficulty:** Low
334334+ - **Risk:** Low
335335+336336+2. **Use kal only (smaller):** kal is 1.4 MB vs slt at 4.0 MB.
337337+ - **Savings:** ~2.6 MB (plus duplicates)
338338+ - **Difficulty:** Low
339339+ - **Risk:** Low (lower voice quality)
340340+341341+### 3.9 XKB Keyboard Data (4.0 MB)
342342+343343+The `symbols/` directory (2.5 MB) contains layouts for every language/country.
344344+345345+**Fix:** Keep only `us`, `gb`, and `inet` (for media keys). Remove all other layouts.
346346+347347+**Estimated savings:** ~2-3 MB
348348+**Difficulty:** Low
349349+**Risk:** Low (US keyboard is the primary target)
350350+351351+### 3.10 Git Binary (3.9 MB)
352352+353353+Git is bundled for Claude Code's git operations on-device.
354354+355355+**Options:**
356356+1. **Keep it:** Claude Code is significantly more capable with git. At 3.9 MB, it's not a major contributor.
357357+2. **Lazy-load from USB partition:** Similar to Claude binary lazy-loading.
358358+359359+**Recommendation:** Keep. The 3.9 MB is worth the capability.
360360+361361+### 3.11 Compression Algorithm
362362+363363+Currently: LZ4 for initramfs, gzip for kernel wrapping.
364364+365365+| Algorithm | Compress ratio | Decompress speed | Boot impact |
366366+|-----------|---------------|------------------|-------------|
367367+| LZ4 (current) | 2.55x | ~4 GB/s | Fastest boot |
368368+| zstd | ~3.0-3.5x | ~1.5 GB/s | +100-200ms |
369369+| xz/lzma | ~3.5-4.0x | ~200 MB/s | +1-3s |
370370+371371+The kernel already supports `CONFIG_RD_ZSTD=y`. Switching initramfs to zstd would save ~30-60 MB in vmlinuz at the cost of slightly slower boot.
372372+373373+For the kernel wrapper, switching from gzip to zstd (`CONFIG_KERNEL_ZSTD=y`) would save another ~10-20 MB.
374374+375375+**Total compression savings (zstd):** ~40-80 MB in final vmlinuz
376376+**Difficulty:** Low (kernel config change)
377377+**Risk:** Low (adds ~200-400ms boot time)
378378+379379+### 3.12 ALSA UCM2 Config (2.4 MB)
380380+381381+The UCM2 directory contains Use Case Manager configs for many different sound cards.
382382+383383+**Fix:** Keep only the configs for the target hardware's codec.
384384+385385+**Estimated savings:** ~2 MB
386386+**Difficulty:** Medium (need to identify correct configs)
387387+**Risk:** Low
388388+389389+---
390390+391391+## 4. Recommended Reductions (Prioritized)
392392+393393+| # | Change | Savings (uncompressed) | Est. vmlinuz savings | Difficulty | Risk |
394394+|---|--------|----------------------|---------------------|------------|------|
395395+| 1 | **Deduplicate lib/x86_64 -> lib64 symlink** | **214 MB** | **~84 MB** | Low | Low |
396396+| 2 | **Remove legacy SOF firmware (sof/ + sof-tplg/)** | **16.4 MB** | ~6 MB | Low | Medium |
397397+| 3 | **Trim i915 to target generations only** | **19 MB** | ~7 MB | Low | Medium |
398398+| 4 | **Fix Flite .so.1/.so.2.2 duplicates (symlinks)** | **6.4 MB** | ~2 MB | Low | Low |
399399+| 5 | **Switch to zstd compression** | 0 (compression only) | **~40-80 MB** | Low | Low |
400400+| 6 | **Trim XKB to US layout only** | **3 MB** | ~1 MB | Low | Low |
401401+| 7 | **Trim SOF IPC4 topologies + platforms** | **8 MB** | ~3 MB | Medium | Medium |
402402+| 8 | **Lazy-load Claude from USB partition** | **226 MB** | **~89 MB** | Medium | Low |
403403+| 9 | **Build Mesa without LLVM (custom package)** | **167 MB** | **~65 MB** | High | Medium |
404404+| 10 | **UPX-compress Claude binary** | **~130 MB** | **~51 MB** | Medium | Medium |
405405+| 11 | **Remove second TTS voice (kal)** | **4.2 MB** | ~1.5 MB | Low | Low |
406406+| 12 | **Trim ALSA UCM2 configs** | **2 MB** | ~1 MB | Medium | Low |
407407+408408+### Quick Wins (items 1-6): ~259 MB uncompressed, ~140-180 MB vmlinuz savings
409409+410410+Applying just the top 6 low-difficulty changes would reduce the vmlinuz from **271 MB** to approximately **~90-130 MB**.
411411+412412+### Full Optimization (items 1-12): ~796 MB uncompressed savings
413413+414414+With all changes including LLVM removal and Claude lazy-loading, the vmlinuz could theoretically reach **~50-80 MB**.
415415+416416+### Fastest Single Fix
417417+418418+**Item #1 alone** (lib deduplication) saves 214 MB uncompressed / ~84 MB compressed. It requires changing approximately one line in `build-and-flash.sh` to create a symlink instead of a separate directory. This should be done immediately -- it's free savings from a build bug.
···66 <title>papers · Aesthetic Computer</title>
77 <meta name="description" content="Academic papers on Aesthetic Computer and KidLisp.">
88 <meta property="og:title" content="papers — Aesthetic.Computer" />
99- <meta property="og:description" content="18 papers by Jeffrey Alan Scudder on creative computing, KidLisp, and Aesthetic Computer." />
99+ <meta property="og:description" content="Papers by @jeffrey on creative computing, KidLisp, and Aesthetic Computer." />
1010 <meta property="og:image" content="https://papers.aesthetic.computer/papers-og.jpg" />
1111 <meta property="og:image:width" content="1200" />
1212 <meta property="og:image:height" content="630" />
···833833 if (e.target.closest('a')) return;
834834 const link = p.querySelector('.title a');
835835 if (link) link.click();
836836+ });
837837+ });
838838+839839+ // Track paper PDF views/downloads
840840+ function trackHit(paper, format) {
841841+ const lang = currentLang || 'en';
842842+ navigator.sendBeacon(
843843+ 'https://aesthetic.computer/api/paper-hit',
844844+ JSON.stringify({ paper, format, lang })
845845+ );
846846+ }
847847+ document.querySelectorAll('.p').forEach(p => {
848848+ const id = p.dataset.paperId;
849849+ if (!id) return;
850850+ p.querySelectorAll('a[href$=".pdf"], a[href*=".pdf?"]').forEach(a => {
851851+ const fmt = a.href.includes('-cards') ? 'cards' : 'pdf';
852852+ a.addEventListener('click', () => trackHit(id, fmt));
836853 });
837854 });
838855
+1-1
system/public/sitemap.html
···953953 <span class="section-title">papers.aesthetic.computer</span>
954954 <span class="section-count">Research</span>
955955</summary>
956956-<div class="section-desc">Academic papers portal. Four publications (arXiv preprints + JOSS summaries) on Aesthetic Computer and KidLisp by Jeffrey Alan Scudder, March 2026. Dark/light theme toggle.</div>
956956+<div class="section-desc">Academic papers portal by @jeffrey. arXiv preprints, JOSS summaries, and ELS proceedings on Aesthetic Computer and KidLisp. Multi-language support, dark/light theme, PDF hit tracking.</div>
957957<div class="routes single-col">
958958 <div class="route full-url">papers.aesthetic.computer/ — Papers index with PDF downloads</div>
959959</div>