Skip to content

Sync develop with main (v0.0.6)#13

Merged
squirelboy360 merged 65 commits into
developfrom
claude/oscortex-progress-6h6ugo
Jun 12, 2026
Merged

Sync develop with main (v0.0.6)#13
squirelboy360 merged 65 commits into
developfrom
claude/oscortex-progress-6h6ugo

Conversation

@squirelboy360

Copy link
Copy Markdown
Contributor

What

Brings develop up to date with main at v0.0.6.

develop and main had unrelated commit histories (no common ancestor — develop was started as a fresh lineage on Jun 9, main roots at the May 19 initial commit), so git refused a normal merge. This PR carries a --allow-unrelated-histories merge of origin/main into develop, resolving the 21 overlapping files in favor of main.

Result

After this merge, develop's tree is byte-identical to main (git diff origin/main HEAD is empty). It picks up all the v0.0.5 → v0.0.6 release work that had only landed on main, notably the aarch64 hardware-render bring-up:

  • kernel/src/arch/aarch64/boot_limine.rs, mmu.rs, virtual-timer (CNTV/PPI27) scheduler/vsync tick
  • kernel/linker/aarch64-limine.ld, scripts/build-iso-aarch64.sh, aarch64 iso_root boot assets
  • syscall/poll, process, and mm fixes from the release line
  • .github/workflows/release.yml updates

Net: 21 files changed, 728 insertions(+), 43 deletions(-) plus the new aarch64 boot/linker/iso files.

Why a PR

Direct push to develop is blocked by branch protection (403), so the merge commit is delivered via this branch for review/merge.

https://claude.ai/code/session_01MSk4MAsfTvKoXfEzrWiZ3Q


Generated by Claude Code

squirelboy360 and others added 30 commits June 9, 2026 18:06
Cortex PID-0 dispatch, embedder vsync yield, dart:ui shell, CI/CD skill, widget test fix.
P9 cave/log stubs, diagnostic fill flags, merge develop after #1; drop __pycache__ and gitignore Python bytecode.
Merge fix-p9-cave: engine_patch P9 offsets/diagnostics, libm math syscalls, shell/embedder fixes, vfs_stat, README. Conflicts with develop resolved in engine_patch.py.
* Flesh out Cortex PID-0 syscalls and stabilize embedder vsync.

Wire context, healing, inference, and IPC into the Cortex dispatch path;
add heap growth hooks and WM/sched integration. Yield after OnVsync so
runner threads can drain raster work before the next frame.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Simplify shell to dart:ui-only and add optional engine patch skip.

Drop Material/framework JIT overhead for faster QEMU bring-up; allow
OSC_SKIP_ENGINE_PATCH=1 when staging a pristine libflutter_engine.so.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Add oscortex-ci-cd agent skill for branching and release flow.

Documents develop → PR → main → auto ISO release so agents work on the
right branch with CI gates; wire skill into core rule and related skills.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Fix widget test after dart:ui-only shell pivot.

OscortexShellApp was removed from main.dart; update CI test so Flutter
analyze passes on the feature PR into develop.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Document three-tier branch flow and light develop vs strict main gates.

Skill now mandates feature/* work, human-only merges, and tiered CI/review
rules; setup-github.sh drops CODEOWNERS requirement on develop only.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Add present diagnostics; fix p9 cave device offsets

tools/flutter-embedder: add a small diagnostic scan in present_callback that (for the first few presents) scans up to 1,000,000 bytes of the present buffer to find the first non-zero byte and count non-zero pixels, and logs pixel_len, first_nz_off and nz_count. This is gated by PRESENT_TRACE_COUNT to avoid spamming.

tools/flutter-engine: update build_p9_cave docstring and emitted assembly to load device.fPixels/fRowBytes from offsets 0x140 and 0x148 into Draw.fDst (rsp+0x10 / rsp+0x18) instead of the old 0x180+0x18 addresses. The docstring explains that the previous offsets addressed a clip/matrix pointer, which caused the blitter to write to the wrong place (resulting in zero fills); the change fixes the target so drawPaint operates on the actual pixel buffer.

* Add diagnostic logging and fill to engine patch

Introduce optional diagnostic logging and buffer fill to the engine patch and embedder. Added DIAG_LOG/DIAG_FILL flags, a build_log_stub() that emits a SYS_WRITE-based tracer for several device fields, and a LOG_CAVE region. The P9 cave now can optionally memset the surface to a sentinel color before draw and call the log stub; cave-overrun checks were updated to avoid colliding with the log stub. Also added small present callback logs in the embedder (alloc_addr and row_bytes) to aid debugging of the present/framebuffer path. Updated PATCHES and main to include LOG_CAVE when diagnostics are enabled.

* Fix P9 cave diagnostics and drop accidental __pycache__ from tree.

Merge develop after PR #1; keep engine_patch P9 cave/log stubs and ignore
Python bytecode in git.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Add libm math syscalls, UI shell & embedder fixes

Introduce a kernel-side libm path and a new Flutter-based shell UI, plus various embedder and tooling fixes.

- Add libm crate to kernel Cargo.toml/Cargo.lock.
- Implement MathSyscall1/2/3 trampoline kinds and encode native trampolines that move xmm registers into integer args, perform a syscall and return results in xmm0.
- Add libm_call() to dispatch syscall numbers to libm functions (double/float variants) and expose LIBM_NR_LO/H I range; wire dispatch_fast() to short-circuit and handle libm syscall range for fast math calls.
- Update STUBS table to map many POSIX math symbols to MathSyscall entries with padding slots.
- Fix compositor RGBA packing for Flutter's kN32/BGRA8888 software surface by swapping B/R while packing pixels.
- Replace minimal dart:ui demo with a Material-based Oscortex shell app that uses a BasicMessageChannel for shell commands, lists/installs/launches apps, and provides a tiled app grid UI.
- Improve embedder platform-message handling: reply synchronously with StandardMethodCodec null envelope where required to avoid hangs, respond to apps request channel, add send_platform_response_now/respond_platform_message helpers, task runner (run_due_platform_tasks), and lightweight logging.
- Make run-qemu.sh configurable for serial output (default file:/tmp/osc_serial.log) and add --serial-stdio option.

These changes enable efficient in-kernel math for hot libm calls (used by Skia/Dart), stabilize platform-channel interactions during engine init, provide a usable shell UI for app management, and fix compositor pixel ordering and QEMU serial options.

* Add vfs_stat and path_len helper

Introduce SYS_VFS_STAT and implement vfs_stat(path) to return a VFS file's byte size (or a negative errno) using a syscall. Extract a private path_len helper to normalize path byte slices (trim trailing NUL) and reuse it from vfs_read to avoid duplicated logic.

* Added read me

* Add OSCortex agent docs and soft cursor support

Add comprehensive OSCortex agent guidance and skills (.agents/rules/* and .agents/skills/*) plus a Copilot instructions file and repo-level core instructions. Introduce docs/multiarch.txt and update docs/arch.txt to reference it. Remove .vscode/settings.json to drop project-specific editor settings.

Implement a simple software cursor path: add FRAME_DIRTY/invalidate and draw_software_cursor in the compositor, update render_frame/tick to honor cursor redraws, wire PS/2 driver to update cursor position and call invalidate, and sync cursor state in the WM input path. Also add set_cursor_pos API to the PS/2 driver.

Update the Flutter embedder with write_hex and install_jit_snapshot_paths to pass JIT snapshot file paths to the engine (debug engine builds). These changes provide agent guidance for architecture/cleanup work and add a basic OS-side pointer overlay for PS/2 input.

* Update SKILL.md

* Improve input scheduling and shell UI robustness

Add multiple host/WM input fairness and reliability fixes and UI improvements.

UI (apps/oscortex_app): safer host messaging and JSON parsing, status text, install button state, pointer smoke-test (tap debug), F12 hotkey, pointer event tracing, and layout/style tweaks to make demo install/refresh flows more robust and observable.

Kernel: prioritize WM/embedder (focus) for input delivery on single-core boots — add timer-handler preempt path, cooperative scheduling adjustments in next_runnable_pid, and extensive epoll_wait/wm_event_wait changes to de-starve the focus pid (hand off or re-enter epoll_wait rather than returning EINTR). Also added a short spin window during Flutter bootstrap, framebuffer clear before rendering, and a small PS/2 mouse acceleration curve to improve pointer responsiveness in QEMU.

Misc: small syscalls/poll/posix refactors and formatting, add many qemu-click verification logs, and minor tooling/script updates. These changes aim to ensure pointer/key events reach the embedder promptly and to make host<->UI interactions more resilient and debuggable.

* update

* cleanup of userspace ui + added ios for ui testing on ipads

* update

* fix: resolve sibling thread starvation on mouse moves while maintaining high-priority click preemption

* fix: implement clean priority scheduling for input target without force-waking blocked threads

* fix: remove vsync baton queued scheduling overrides to prevent worker starvation deadlocks

* Calibrate APIC timer; per-CPU GDT/TSS

Calibrate APIC/TSC timing and switch APIC timer to a calibrated periodic 1ms tick, expose APIC_TICKS_PER_MS and add helper calibration/printing routines (kernel/src/arch/x86_64/apic.rs). Use calibrated tick values when arming BSP and AP timers and add send_resched_ipi helper.

Introduce per-CPU GDT and TSS data structures and per-CPU interrupt stacks to support SMP: replace single GDT/TSS with arrays indexed by CPU, set up TSS entries per CPU and update init/init_ap to load the correct per-CPU GDT (kernel/src/arch/x86_64/gdt.rs).

Also add the OSCortex UI design specification HTML (docs/oscortex-ui-spec.html).

Notes: changes prepare the kernel for accurate timer interrupts across CPUs and add SMP-safe GDT/TSS setup; see added calibration functions and APIC_TICKS_PER_MS for timer tuning.

* update

* Add oscortex_ui package and refactor UI usage

Introduce a new packages/oscortex_ui package providing shared design tokens and widgets (colors, radii, shadows, typography, OscCard, OscScaffold, OscToolbar, OscAppRail, OscHubDock, OscIntentBar, OscWaveform, tags, badges, etc.). Refactor apps to consume oscortex_ui: replace local color/constants, duplicated widgets and custom styling with OscTheme, OscColors, OscTypography and reusable components across apps (notably apps/oscortex_app and apps/oscortex_canvas). Update app pubspecs to add a path dependency on oscortex_ui and simplify many UI implementations by removing in-file implementations (AppRail, HubDock, IntentBar, waveform, tag/badge widgets, etc.) in favor of the shared library to ensure consistent design tokens and reduce duplication.

* Add UI previews; fix kernel scheduler & epoll

Add preview infrastructure and many Preview widgets for the Flutter UI package, including:
- new Preview helper OscPreview and OscAccentPreview (packages/oscortex_ui/lib/src/preview/osc_preview.dart)
- exported preview entry in packages/oscortex_ui/lib/oscortex_ui.dart
- numerous @OscPreview preview functions added across UI widgets and apps (osc_card, osc_toolbar, osc_app_rail, osc_hub_dock, osc_intent_bar, osc_scaffold, osc_section_label, osc_status_badge, osc_tag, osc_waveform, multiple apps)
- extension_discovery cache files added under packages/oscortex_ui/.dart_tool (devtools.json, vs_code.json, README)

Kernel changes to cooperative scheduling and preemption handling:
- Introduce locked variants for scheduler helpers (next_runnable_sibling_thread_locked, next_runnable_pid_locked) and centralize PTABLE_LOCK usage to ensure correct per-CPU accounting
- Update next_runnable_sibling_thread/next_runnable_pid wrappers to acquire PTABLE_LOCK and manage p.current_cpu
- timer_preempt_switch reworked to operate under PTABLE_LOCK: pick next under lock, save outgoing regs/XSTATE, restore incoming XSTATE, set per-CPU state and update active syscall stack and fs base
- spawn_thread now leaves p.current_cpu = None (thread not pinned at creation)
- cooperative yield logic (kernel/src/syscall/poll.rs) inlined/rewritten to run selection under PTABLE_LOCK, handle cond-waiters, set current_cpu on chosen targets, and added *_locked helpers for target checks

Epoll/struct layout fixes in syscall handling:
- Change epoll event packing/stride from 12 -> 16 bytes and adjust data offset reads/writes from +4 to +8 accordingly (epoll_collect_ready, sys_epoll_wait_real logs)

These changes add consistent preview tooling for UI development and harden the kernel scheduler for multi-CPU cooperative scheduling and timer preemption, while fixing the epoll event layout used by the kernel.

* Add UI widgets, CPU claim helper, favicon script

Introduce a safe CPU-claim helper in the kernel (process::try_claim_cpu_for) and use it in several syscall paths (futex/sys_thread_create, posix pthread mutex/cond paths) to avoid races when attempting to enter user mode on a target PID. Add a suite of new oscortex_ui widgets (banner, breadcrumbs, button/outline, chip, icon button, popup menu, status bar, text field) and export them from the package barrel. Include a small scratch script to generate favicon.ico from a vector-like polygon. These changes group scheduler safety fixes with UI component additions and a tooling helper for the web landing/favicon.

* Add A2UI UI package and CPU claim gating

Kernel: prevent blind transitions into user context by claiming the CPU first — add try_claim_cpu_for checks before enter_user_by_pid_noreturn in the APIC timer handler and epoll wait paths. Initialize new process fields (current_cpu, cpu_ticks, slice_left, pending_sigs, sig_mask, sig_handlers, errno_to_deliver, preempted_by_timer, user_stack_* where applicable) in spawn_with_bootstrap and clone_thread to support scheduling/signals.

UI: Add A2UI (Agent-to-UI) protocol support to oscortex_ui. Export the A2UI entry from packages/oscortex_ui/lib/oscortex_ui.dart and add new source files implementing parsing, types, a reactive data store, icon mapping, renderer, and a surface widget (a2ui.dart, a2ui_data_store.dart, a2ui_icons.dart, a2ui_parser.dart, a2ui_renderer.dart, a2ui_surface.dart, a2ui_types.dart).

Misc: add small scratch utilities (generate_detailed_favicon.py, test_draw.py) and a test_logo.png.

* Initialize pending init PID to 1

Change PENDING_INIT_PID's initial value from 0 to 1. This ensures the PID stored before schedule_user_launch defaults to 1 (the typical init PID) rather than 0, avoiding treating 0 as an unset/invalid PID when spawning the user-init kernel task.

* fix(kernel): same-CPU recursive lock reentrancy and scheduler log cleanup

* Support AOT/JIT snapshots; improve dlsym

Refactor embedder to detect engine mode and handle Dart snapshots for AOT and JIT builds: add IS_AOT flag, detect FlutterEngineRunsAOTCompiledDartCode at startup, add configure_aot_snapshots and load_jit_snapshot helpers, log snapshot contents, and wire AOT snapshot loading from mapped memory or libapp.so. For JIT mode the previous JIT asset-path logic is removed and snapshot pointers are left NULL.

Improve kernel dlsym to scope lookups to the current process, allow handle==0 to search across libs and fall back to posix trampolines if available.

Misc: comment out engine_patch invocation in scripts/build-iso.sh and add a small scratch script (scratch/parse_transcript.py) for filtering transcript logs.

* Add Osc UI widgets, A2UI mappings; fix poll lock

Introduce several native OSCortex UI components (OscCheckbox, OscDatePicker, OscModal, OscSlider, OscTabs) and export them from packages/oscortex_ui. Wire these widgets into the A2UI system: update docs, a2ui barrel, and the A2UI renderer to map interactive components to Osc* implementations (replacing prior Material usage and ensuring accessibility, weight, and binding handling). Add an A2UI compliance SKILL.md to enforce renderer/exports for new widgets. Also apply a concurrency/safety fix in kernel/src/syscall/poll.rs by using try_lock guards when accessing COND_WAIT_STATE to avoid potential deadlocks. Misc: remove accumulated qemu/run log files and minor script/tool tweaks.

* Add on-demand package delivery subsystem

Introduce an on-demand package system: kernel/pkg with resolver, http client, SHA-256 verifier, manifest format, and an LRU cache for ephemeral apps. Expose package syscalls (resolve, catalog, set_server, evict) and wire pkg init into kernel boot; add a /sys/pkg/cache sysfs entry. Integrate with the shell/UI: AppTile gains source/size/state, ShellService RPCs for catalog/resolve/server, app_card shows remote/resolving states and shell_desktop merges remote catalog and resolves+launches packages on demand. Add a new tools/pkg-server crate and include it in the workspace. Also include small kernel tweaks (idt/process call rename and related syscall plumbing) to support the feature.

* Add on-demand package delivery docs & pkg module

Introduce an on-demand package delivery design and implementation updates: add detailed docs (boot-sequence, developer-guide, pkg-delivery) and update architecture notes. Integrate kernel pkg support by modifying kernel/src/pkg/mod.rs and http.rs (HTTP client for fetching bundles), and wire in package delivery concepts (catalog, LRU cache, SHA-256 verification, syscalls). Also tweak user-side preview UI, ISO build script, and engine patch to support the new workflow and tooling (tools/pkg-server, docs and scripts). This change documents the design and prepares kernel/userland plumbing for fetching, verifying, caching, and launching .osx bundles on demand.

* Use dlopen for AOT apps and auto-update SDK hash

Load AOT ELF bundles via the dynamic loader instead of manually mmap+copying: app_registry now calls dlopen and uses a new get_load_base helper to obtain the load VA. The embedder registers the shell libapp.so at startup. The engine patcher (engine_patch.py) can extract the engine/SDK hash from libapp.so and updates multiple P_SDK_HASH offsets (with a fallback), adds a runs-AOT patch, and expands verify/apply lists accordingly. Added a small ELF hash extractor (scratch/test_parse_elf.py) and updated docs (pkg-delivery, added use-cases) and .gitignore entries. These changes centralize AOT handling, keep the engine-patching in sync with the built libapp, and add documentation/examples.

* Make dl symbol lookup group-aware

Use process group leader when matching loaded libraries so dlopen/dlsym/dlclose/get_* resolve symbols across a process group rather than per-pid. Add sym_name_eq to normalize and compare symbol names (strips leading underscores). Log dlsym queries in the syscall handler for easier debugging. Update the Flutter embedder argv to pass precompiled-mode flags. Extend engine_patch to include JIT/AOT and snapshot-features bypass patches and include them in verification/apply lists. Add a scratch find_ref.py helper script for searching RIP-relative references.

* chore: save progress before pivoting landing page to embedded devices goal

* update

* update

* update

* update

* fix: update .gitignore to include backup files and improve file skipping logic in build.rs

* fix(kernel,shell): resolve Fontconfig SkFontMgr PageFault and clean unused imports for CI

---------

Co-authored-by: Tahiru Agbanwa <130235676+squirelboy360@users.noreply.github.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: SudoMe <152814787+SudoGhostUser@users.noreply.github.com>
The Flutter shell never rendered — boot showed only the kernel
compositor's blue background and software cursor (zero surfaces
presented). Root cause was in the embedder: CUSTOM_TASK_RUNNERS set
ui_task_runner to null, so the engine never registered a UI TaskQueueId.

The first Shell::OnPlatformViewScheduleFrame then called
fml::MessageLoopTaskQueues::RegisterTask on an unregistered queue and
pushed a DelayedTask into a null TaskQueueEntry (delayed_tasks member at
offset 0x50), faulting on a user write to address 0x50 and killing pid 1
(exit -9) before any frame was presented.

Point ui_task_runner at the same PLATFORM_TASK_RUNNER_DESC (identifier=1)
so the engine merges UI onto the platform thread and the UI queue is the
registered platform queue. Verified: the null-TaskQueue crash is gone —
post_task now reports runner=0x1 and tasks post successfully.
apply() writes data[offset:...] by raw file offset, but the
_POSTLOADS_TO_BYPASS addresses came from objdump (virtual addresses).
The R+X LOAD segment has vaddr = fileoffset + 0x1000, so every 0xC3
(ret) bypass landed 0x1000 too high at runtime. Concretely,
StringDeser::PostLoad (vaddr 0x22a1c70) overwrote
VMDeserializationRoots::AddBaseObjects+0x350 (vaddr 0x22a2c70),
turning `mov rsi,[rcx+rax*8-8]` into a `ret` that popped a heap object
(0x312c02010) off the stack and jumped into the Dart heap -> SIGSEGV.

Fix: wrap each PostLoad vaddr in va_to_file() in the bypass loop, like
every other patch already does. Verified via lldb: AddBaseObjects is now
untouched (0x48) and the 0xC3 lands on the real PostLoad entries. The VM
snapshot now deserializes fully (4001 Code objects resolve correct
instruction pointers) and execution reaches isolate-snapshot Code
deserialization.
A late isolate Code object decodes an instruction offset of 0x80380000
(sext -0x7FC80000, ~2GB below the image base) -> unmapped ptr -> SIGSEGV
in Code::InitializeCachedEntryPointsFrom. 8000+ other isolate Code
objects decode sane small signed offsets. Add a clamp cave (file
0x1F44C60): if the signed offset is outside [-0x100000, image_size], it
returns base+1 (a mapped addr) so deserialization continues. Hooks
ImageReader::GetInstructionsAt (0x22cfe60) with a jmp to the cave; the
kernel dl.rs Patch7 byte-match guard auto-skips once these bytes change.

This unblocks the instruction crash: boot now reaches the Array
deserialization cluster (next blocker: data-stream cursor drifts into
the instructions image -> garbage array length -> OOM).
Root cause of all prior deser failures: OSCortex ships the full-JIT (debug)
Flutter engine but fed it an AOT precompiled snapshot (libapp.so). A JIT
engine cannot deserialize an AOT snapshot — proven by running OSCortex's
exact engine in clean Docker (8GB), where it crashes deser identically with
and without the caves. Every engine_patch hack was a doomed workaround.

Proven fix (rendered a real 1280x800 frame in a Docker reference embedder):
run JIT mode from kernel_blob.bin (already shipped in flutter_assets) with
the kernel SDK hash set to the engine's SdkHash 1a420a3f9a (not the AOT
snapshot version 78da37fed6).

Changes:
- engine_patch.py: all_patches reduced to JIT-minimal — keep rendering/
  framebuffer (P1-P6, P9*, P10) + P_ALLOW_ALL_DART_FLAGS + the two service
  bypasses; REMOVE all AOT flag-flips (P_RUNS_AOT, P_*PRECOMPILED*,
  P_JIT_AOT_CHECK) and AOT deser hacks (P_READ_INSTRUCTIONS, P_DEBUG_CAVE,
  P_GETINSTR_CLAMP_*, P_POSTLOAD_BYPASS_*, FINALIZE_CLASS_NAMES, TTS/init).
- engine_patch.py patch_kernel_blob: target hash -> 1a420a3f9a.
- kernel/build.rs: embed the shell kernel_blob.bin in the initramfs (was
  excluded for AOT); keep per-app blobs excluded to limit kernel bloat.
- Embedder auto-selects JIT: with P_RUNS_AOT gone, is_aot=false, so SHELL
  mode uses assets_path and skips the AOT libapp.so path (no code change).

Boot now reaches JIT codegen (mprotect R+X fires) and deep kernel loading
before SIGSEGV on an unmapped engine R-data page (dl.rs mapping gap) — far
past the AOT dead-end. Next: map the engine's full R LOAD segment.
sys_madvise(MADV_DONTNEED/MADV_FREE) called unmap_user_range, which
permanently unmaps and frees the frames. That is only safe for anonymous
memory (re-faults as zeros). Library regions [LIB_VA_BASE, ANON_VA_BASE)
are mapped eagerly and are NOT demand-pageable, so unmapping loses the
data permanently.

The Dart VM (JIT mode) calls madvise(MADV_DONTNEED) on its loaded
platform-kernel data — which lives inside the engine image at ~0x1412f8000
— to release memory. OSCortex unmapped ~6.6MB of the engine's read-only
data segment; a later read of KernelProgramInfo::KernelLibraryStartOffset's
table (0x141d2d908) then SIGSEGV'd. madvise is advisory, so ignoring it for
lib regions is correct.

Fix: only honor MADV_DONTNEED/MADV_FREE for addr >= ANON_VA_BASE.

Boot now runs far past this point (threads spawn/exit, vsync frames push)
before a separate stack-region fault.
…ne-thread stack bounds

- sys_madvise: only unmap [0x3_0000_0000, 0x100_0000_0000) (the demand-pageable
  anon window). Both library images (low) AND high user stacks (0x7FFF_xxxx,
  above the demand range) are mapped without a backing pager, so unmapping them
  on MADV_DONTNEED loses data permanently. The Dart VM madvise()s both regions.
- pthread_getattr_np / pthread_attr_getstack: clone-threads (Dart VM mutator/GC/
  helper threads) have no recorded user_stack_base, so the VM's
  GetAndValidateThreadStackBounds got 0,0. Derive bounds by scanning the mapped
  stack region around the thread's current RSP.
Threads share the parent's PML4. Teardown unmapped p.fs_base / p.user_stack_base
unconditionally — but the Dart VM places some thread fs_base/stack inside the
MAIN user stack (0x7FFF_xxxx, shared by pid 1) or other non-private regions.
Unmapping those clobbered pid 1's stack (a worker's fs_base was 0x7ffffffef000),
causing pid 1 to SIGSEGV in Location::ToCString during error formatting.

Fix: only reclaim thread-private memory in the demand-pageable anon window
[0x3_0000_0000, 0x100_0000_0000). With this, pid 1 stays alive and the engine
keeps running (the stack-clobber crash is gone). Also extends the debug
region-unmap log to cover the main stack.
…t code

sys_thread_join returned `code as i64` (the joined thread's exit code) plus
-10/-11 for ECHILD/EAGAIN. But pthread_join(thread, retval) returns 0 on
success — the exit value goes into *retval. The Dart VM wraps it in
VALIDATE_PTHREAD_RESULT and FATALs ("pthread error: %d", os_thread_linux.cc:181)
on any non-zero return, which aborted worker threads (the -6/-10/-11 garbage
values were exactly this handler's returns).

Fix: block until the target exits (busy-wait + pause; preemption runs it),
write the exit value to *retval, and return 0. Pass arg1 (retval) through.

Result: thread aborts gone (0, was 6). The engine now reaches
FlutterEngineRunInitialized OK, runApp schedules the initial frame, and the
platform thread enters its event loop — the Flutter app is running.
present_callback is now called continuously (140+ frames) — the Flutter UI
is painting. The raster thread submits real RGBA frames (gpu_submit_strided
sid=1 row_bytes=5120, pixel 0xff0c1c26 = app background) to the compositor.

Root cause of "app runs but no pixels": the custom MERGED task runners
(platform+UI on one thread) plus a custom vsync_callback the engine never
drove. With both disabled, the engine spawns its own UI/raster threads and
self-drives the frame pipeline via its internal vsync timer — the exact
config proven in the Docker reference embedder. This only works now that
thread creation is sound (pthread_join fix) so the engine's worker threads
run instead of aborting.

- project_args.vsync_callback = 0 (engine-internal vsync, not embedder-driven)
- project_args.custom_task_runners = 0 (engine spawns UI/raster threads)

OSCortex now boots, JIT-runs the stock Flutter engine, executes the Dart app,
and renders its UI to the framebuffer.
Captures the remaining source changes for the booting, rendering build
(diagnostics + bare-metal embedder/loader tweaks accumulated while bringing
up JIT-mode Flutter). The OS now boots, JIT-runs the stock Flutter engine
from kernel_blob.bin, executes the Dart app, and paints its UI to the
framebuffer (verified via QEMU screendump: app toolbar on dark navy bg).

(Stale 1.46GB initramfs.tar artifact intentionally not staged; kernel/build.rs
generates its own initramfs into OUT_DIR.)
…Vsync clock

Three real correctness/scalability fixes found while debugging sporadic render:
- sched: cooperative_sched_target preferred hardcoded pids [2,3,4,7,1] and only
  considered siblings when sib<=7, permanently STARVING Dart VM helper threads
  (8,9,10,11: JIT/GC/pool). Use fair round-robin over all runnable threads.
- sync: cond-pending-consume returned 0 without yielding -> hot Dart mutator spun
  consuming its own pending signals, starving the helper it waited on (smp=1
  livelock). Yield to a sibling before returning.
- embedder: FlutterEngineOnVsync timestamps must use FlutterEngineGetCurrentTime,
  not rdtsc_ns (which adds ~1.7e18 epoch offset -> frame scheduled ~never).

Known remaining: heavy real app still livelocks in Dart GC-safepoint coordination
on bare metal; trivial widget tree renders at smp=1. WIP.
…obes

- embedder: pass --dart-flags=--no-background_compilation --no-concurrent_mark
  --no-concurrent_sweep --marker_tasks=0 --scavenger_tasks=0 (single-threaded VM
  to avoid GC safepoint coordination livelock). NOTE: did NOT change behavior —
  flags may not be reaching the VM through the engine; needs verification.
- app: OSCX lifecycle print probes (main-start/binding-initialized/
  FIRST-FRAME-RENDERED) as render-success signal.

Status: trivial widget tree (ColoredBox) renders 25 frames at smp=1; heavy
Material shell still livelocks in Dart VM GC/heap coordination. WIP.
THE FIX — the real Material shell now renders reliably. Root causes were NOT a
single bug but a stack of them, found by bisecting widget trees + boot duration:

1. FONT: bare metal has no system font provider and the bundle ships no default/
   Roboto family, so Material's default-font text LIVELOCKED in font fallback
   (first frame never completed). Fix: alias Roboto/sans-serif/etc -> bundled
   NotoSans in FontManifest (scripts/build-iso.sh) + theme fontFamily=NotoSans.
2. SMP RACE: multi-core livelocks the engine threads in the cooperative sched/
   sync layer. Fix: run smp=1 (run-qemu-debug.sh) until the SMP race is fixed.
3. SLOW JIT WARMUP: the engine JIT-compiles the entire Material framework on
   emulated bare metal — ~60-90s before the first frame. Not a hang; be patient.
4. Dart VM flags now delivered correctly via --dart-flags (COMMA-separated,
   underscore names): --old_gen_heap_size=512 --new_gen_semi_max_size=64
   --no_background_compilation. (--marker_tasks=0/--verbose_gc are disallowed/
   assert — do NOT use.)

Builds on 0449aac (fair scheduling, cond-yield, OnVsync clock). Verified: 600-1097
present_callbacks/boot, full UI visible (title, Install demo button, icons, text).
…r livelocks

The scheduler is cooperative and single-core by design (one user thread runs at a
time, threads hand off via enter_user_by_pid_noreturn inside syscalls). At smp>1
the AP's timer-ISR kernel-mode wake-assist (idt.rs) was NOT gated on cpu_id, so an
AP would claim and enter a user thread, running it CONCURRENTLY with the BSP. Two
user threads in true parallel break the futex/condvar emulation's single-core
assumptions → nondeterministic livelock (0 frames at smp=2; smp=1 fine).

Fix: in the APIC timer handler, return early on any AP (cpu_id != 0) before the
user-thread-entry paths. APs EOI and halt; all user threads run BSP-only. smp=2
now renders the full shell identically to smp=1 (381 presents/100s, no panic).

Also removed dead code: save_preempt_xstate/restore_preempt_xstate + the single
global PREEMPT_XSTATE scratch buffer (no callers; a shared FPU buffer would be an
SMP hazard — preemption already saves per-process xstate via save_xstate(pid)).

NOTE: this makes multi-core STABLE but the AP still only halts — it does not yet
run threads. Using extra cores for real parallelism (to cut the ~86s JIT warmup)
needs a proper per-CPU SMP scheduler, a separate larger effort.
Mouse input was dead/flaky: the cursor froze and most movement was lost. Root
cause: the PS/2 mouse ACK byte (0xFA, the reply to the 0xF4 enable command) and
RESEND (0xFE) get injected into the data stream, but the byte-0 gate only checked
bit3 (always-one) — which 0xFA/0xFE also have set. The ACK was absorbed as a
packet flags byte, permanently MISALIGNING the 3-byte accumulator: every
subsequent packet was parsed with a data byte as flags (carrying overflow bits)
and discarded. Result: 127 mouse bytes in, 1 push_pointer out.

Fix: at packet start, also reject bytes with overflow bits set (0xC0). ACK/
RESEND/NAK all have 0xC0; a normal MOVEMENT flags byte never does. This skips the
spurious byte and resyncs on the next true flags byte. Verified: 15 injected
moves -> 12 push_pointer -> 13 Flutter pointer events, cursor tracks X and Y
reliably from (32,32) to (1279,799). Hover/click reach Flutter (rc=0) and buttons
highlight on interaction. (Bumped then trimmed mouse IRQ/pointer trace logs.)
With engine-default task runners (custom_task_runners=NULL, our render config) the
engine posts platform-thread work — INCLUDING platform-message delivery — to the
fml MessageLoop on pid 1, but nothing drained it. So platform_message_callback was
NEVER invoked: every MethodChannel/BasicMessageChannel was silently dead (the
startup oscortex/shell 'list' and the Install-demo 'install:' round-trips dropped,
the shell sat on 'No apps installed'; text input / navigation / mouse-cursor
channels also non-functional).

Fix: call __FlutterEngineFlushPendingTasksNow() each main-loop iteration to run the
expired platform tasks on this thread. Verified: 8 [embedder/pfm] messages now flow
across flutter/isolate, flutter/keyboard, flutter/navigation, flutter/platform and
oscortex/shell; shell 'list' round-trips (dispatch reply 238B). Unlocks the whole
platform-channel surface, not just the shell.
initramfs.tar (1.4GB!) is a stale artifact from the manual make-initramfs.sh
helper — the real kernel build (kernel/build.rs) generates its own copy in OUT_DIR
from the initramfs/ directory, so the root tar is never used by the build.
vdisk.img is a runtime virtio-blk disk that run-qemu.sh reads/writes and changes
every boot. Both bloated the repo and showed as 'modified' on every build.

git rm --cached keeps them on disk (run scripts still find them); .gitignore now
ignores them (root-anchored) so they no longer pollute status or future commits.
…eft)

Tapping an app tile now actually opens that app — it loads its OWN JIT blob,
renders its own UI, and the compositor switches to it. Remaining: a thread in the
second engine instance GPFs before the app fully settles.

Fixes that got it this far:
- embedder _start: PRESERVE the kernel's bootstrap registers (rdi=host_mode,
  rsi=app_id, rdx=aot_va) across the early breadcrumb SYS_WRITE. That syscall
  clobbered rdi to 1, so every launched app (kernel sets rdi=HOST_MODE_APP=2)
  booted as a SECOND SHELL and loaded the shell's blob. Now they boot APP mode and
  load /Applications/<name>.app/flutter_assets/kernel_blob.bin.
- build.rs: embed per-app kernel_blob.bin (each Flutter host needs its own JIT
  blob). NOTE: ~40MB/app bloats the kernel to ~315MB — scalability follow-up is to
  deliver app blobs as Limine modules / from disk instead of embedding.
- app_registry::launch: set_focus_pid(app) on launch — gives the new host CPU
  priority AND makes the compositor show it.
- embedder: foreground/background lifecycle. On EV_FOCUS, a backgrounded host
  STOPS its frame pump (sends AppLifecycleState.paused); a foregrounded one
  resumes. Without this the shell hogged the single core and starved the launched
  app (shell 589 presents, app 0). With it the shell pauses and the app renders.
- build-iso.sh: give each app NotoSans + Roboto-alias (same font fix as the shell).

Known: second-engine thread GPF at engine code ~0x143f615b9; smp=1 only; ~50s
warmup per host. Auto-launch test hook removed.

WIP on feat/multi-app-launch.
…x141M

Launched apps no longer dlopen their AOT libapp.so (JIT engine can't run it, and
loading it first shifted the app's engine base to 0x142M, where the engine GPF'd).
With it skipped the app's engine loads at 0x141000000, matching the shell, and the
engine-code crash is gone — Canvas now warms up and JIT-compiles.

New blocker (deeper): pid 11 (app VM thread) returns from a cond/mutex syscall to a
GARBAGE address (0x35b809185) and #GP's — a corrupted resume context. Root cause is
the cooperative scheduler mixing up thread contexts across the two address spaces
(shell + app); it isn't robust enough for two concurrent heavy Flutter VMs on one
core. Needs the proper preemptive/SMP scheduler. Also: kernel caps usable RAM at
~1.3GB regardless of QEMU -m.
…ully

THE fix that makes multi-app launch work. When an app is foreground (focus != the
shell pid 1), the scheduler now runs ONLY that app's thread group and leaves the
backgrounded shell suspended. Two concurrent heavy Flutter VMs overwhelmed the
cooperative scheduler's context save/restore — a backgrounded host's thread got
switched in with a corrupted resume RIP and #GP'd. Serialising to one VM group at a
time keeps the reliable single-host path.

Result: tapping an app opens it and it renders its FULL UI (verified: Canvas's
document-editor UI — title, paragraphs, line-numbered markdown editor — rendered
cleanly, no crash). focus=1 (shell foreground) leaves fg_group=1 so the whole
system schedules as before until an app is launched.

Remaining: back-navigation (focus -> shell), focus thrash on launch, and scalable
app-blob delivery (currently embedded, ~315MB kernel).
- PS/2 IntelliMouse 4-byte scroll wiring (kernel) -> EV_SCROLL -> embedder
  FlutterPointerEvent (signal_kind=kScroll); direction+speed configurable.
- Settings screen in the shell: natural-scroll toggle + speed slider, live via
  config:* messages over the oscortex/shell channel.
- Kernel boot spinner drawn on the framebuffer during JIT warm-up
  (drivers/fb.rs::draw_boot_splash, driven by compositor::tick); clears when
  Flutter presents its first frame. Plus a 'Launching <app>' overlay in the shell.
- Adaptive embedder frame pump: gentle during warm-up, 60fps after input, ~8fps
  idle (was a 1ms/1000-per-sec flood) -> req:frame ratio 115:1 -> ~7:1.
- Revert MAX_FRAMES to 1<<20 (RAM above 4GiB is not yet safely mappable; the cap
  is load-bearing) with an explanatory comment.
- docs/arch.txt: document the real JIT execution model + runtime status.
Build the Flutter engine from source as a first-class OSCortex AOT target
instead of shimming a Linux engine. Phases, port surface, build infra, and the
hack-removal checklist in docs/native-engine-port.md; direction recorded in
docs/arch.txt.
Draw the destination ahead of the work: three clean layers (kernel native ABI /
shared AOT engine / self-contained AOT app bundles with own PIDs), the shared-
framework property, and the hack-removal it enables. No Linux costume.
…ace map

Baseline libflutter_engine.so (377MB, x64, embedder API) builds from source in
the container — toolchain proven. Mapped the exact port surface: ~1,200 lines
(Dart VM os_oscortex/os_thread_oscortex ~1000, fml message_loop_oscortex+paths,
reuse posix) + GN glue. The fml message loop is the critical file — its emulated
equivalent is what livelocks rendering today, so the native port also fixes sync.
Capture the validated Phase 0 setup as one-command scripts so the repetitive,
heavy engine-build infra is reproducible, not tribal knowledge:
- setup-engine-build.sh: idempotent container + depot_tools + pinned gclient sync
  (encodes the name='.' fix and the flags that work).
- build-engine.sh: gn configure + ninja for baseline | oscortex targets.
- README.md: the contributor flow, the edit->build incremental loop, and the
  pitfalls already solved (gclient layout, emulation, disk, prebuilt-dart).
Engine checkout stays OUTSIDE the repo (22GB, never committed).
Add 'oscortex' as a --target-os. Since OSCortex has no sysroot/libc yet (runs
linux-ABI via emulation), it links against linux but sets a new is_oscortex GN
flag to select the OSCortex backend sources later; a true OSCortex toolchain is
a later sub-phase. gn configures cleanly (1714 targets; args.gn = target_os
linux + is_oscortex true).

- engine-port/patches/: the two tracked diffs (gn tool, BUILDCONFIG).
- engine-port/apply-port.sh: applies patches + (Phase 2) backend sources into a
  fresh checkout, idempotent.
Temp patch files cleaned from the workspace (no remnants).
squirelboy360 and others added 29 commits June 9, 2026 18:06
R2 needed dashboard activation; switched the artifact host to Google Cloud
Storage (gs://dotcorr-oscortex-engine, public read, billing active). Verified
end-to-end: gcloud upload -> anonymous https://storage.googleapis.com/... fetch.
- artifact.config: ARTIFACT_BASE_URL -> GCS public URL + GCS_BUCKET.
- publish-engine.sh: gcloud storage cp as the primary backend (wrangler/rclone/aws
  remain as fallbacks). fetch-engine.sh is unchanged (curl, host-agnostic).
The repo is public, so GitHub Release assets download anonymously — free, no
egress fees (vs GCS ~$0.12/GB), no separate cloud account, and gh is already
authed. Verified end-to-end: gh release upload -> anonymous
https://github.com/DotCorr/oscortex/releases/download/... fetch.

- artifact.config: ARTIFACT_BASE_URL -> GitHub releases download URL; GITHUB_REPO.
- publish-engine.sh: gh release create/upload as the PRIMARY backend (GCS/R2/S3
  remain as documented fallbacks). fetch-engine.sh unchanged (host-agnostic curl).
- README: GitHub Releases is the host; no bucket setup needed.
- Removed the unused GCS bucket (no remnants).
…erified

The release oscortex engine (33MB, vs 377MB debug — no JIT) and a version-matched
gen_snapshot (6.5MB) built from one tree. Published the first artifact to GitHub
Releases (oscortex-engine-1) and verified the full round trip:
  publish-engine.sh -> gh release  ->  fetch-engine.sh downloads+checksums+stages.
Engine + gen_snapshot from the same tree are version-matched, which dissolves the
old AOT dead-end (the '1247 base objects' mismatch).

Fix: publish-engine.sh derives the workspace from the container's /work mount
(robust to the workspace dir name) instead of assuming a fixed name.
shell app -> frontend_server -> AOT dill (24MB) -> our version-matched
gen_snapshot -> libapp.so (4.4MB native ELF). Verified a real AOT snapshot: all 4
_kDart*Snapshot{Data,Instructions} present with REAL native instructions (T/text,
not zeroed stubs), ELF64 x86-64. The multi-session 'Snapshot expects N base
objects, provided 0' blocker is dissolved because engine + gen_snapshot are built
from one tree (version-matched). compile-app-aot.sh makes it reproducible.
The native, from-source, AOT-compiled Flutter engine RENDERS the shell on
OSCortex: FlutterEngineRunsAOTCompiledDartCode -> libapp.so AOT path ->
present_callback 393 frames, ZERO JIT warmup (no kernel_blob, no codegen). The
UI comes up immediately, no 60-90s compile. This is the whole point of the port.

Fix to get here: the release/AOT engine rejects the JIT-era GC/heap dart-flags
(switches.cc:478 disallowed) — pass only the 5 AOT-safe engine args (argc=5)
when is_aot, dropping the old --old_gen_heap_size etc.

Known follow-up (separate, a regression from this session's IntelliMouse 4-byte
scroll wiring): mouse clicks/Y are mangled + pointer events not reaching the
engine; to fix next.
The 4-byte scroll-packet mode added this session corrupted pointer data: dy
mis-parsed (cursor pinned to the top, y=0) and the flags byte mis-parsed (buttons
stuck at 0), so clicks never registered and pointer events stopped reaching the
engine. Revert to the proven 3-byte packet mode (working clicks > broken wheel).
Verified: cursor Y tracks normally again (y=32 start, not pinned at 0). Scroll to
be re-added later with correct 4-byte parsing + resync that doesn't regress clicks.
Input is confirmed working under AOT (122 pointer events reached Flutter, hover +
clicks register, button presses detected, dropped_total=0 — the earlier '0' was
the broken IntelliMouse 4-byte mode + minimal interaction, fixed by the 3-byte
revert). Remove the temporary DIAG logging from the embedder + ipc_display.

engine-port/compile-app-aot.sh: per-app AOT compile (frontend_server -> dill ->
version-matched gen_snapshot -> libapp.so), no patch — used to give each app its
own AOT bundle so the AOT engine can run them.
Previously every launched app reused the shell's /system/flutter/libapp.so
(aot_va=0 JIT-era skip), so tapping a tile ran the shell snapshot in the
app host and stalled. Resolve the per-app snapshot by registry lookup:
build_app_libapp_path(app_id) -> /Applications/<name>.app/libapp.so, dlopen
it, and point the AOT snapshot loader at that path. Shell keeps its own path.

Each app is AOT-compiled to its own libapp.so and staged under its .app
bundle, so a launched host loads its own native snapshot, not the shell's.
pthread_cond_signal/broadcast with no waiter parked is a no-op by contract:
the predicate is mutex-protected, so a thread that hasn't yet entered
cond_wait observes it under the lock and never waits. The kernel was instead
recording such signals in COND_PENDING_SIGNALS and letting the next cond_wait
consume one as an immediate (spurious) return. A hot mutator (pid 2) that
signals its own monitor with woke=0 then re-waits would consume its own fake
pending, return 0, find its predicate still false, and re-wait forever — the
cond-pending-consume livelock that froze render at a fixed frame count.

Remove the mechanism end to end (consume in cond_wait, posts in
cond_signal/cond_broadcast, the state map, the import). cond_wait now relies
solely on the seq protocol, which delivers every real signal race-free under
cooperative single-core scheduling (the value-check and park cannot interleave
with a signaler). No remnants.
sys::dlopen forwards path.len() to the kernel as the path length, so a
NUL-terminated byte string makes the kernel read the trailing \0 as part of
the filename and the open fails. The per-app and shell AOT paths were passing
"...libapp.so\0"; strip the NUL at the dlopen call. The AOT snapshot itself
loads via aot_snapshot_load (which maps it executable), so this only silenced
a spurious failure path, but it is a real bug and removes the warning.
The Debug-level hot per-syscall traces (epoll_ctl, mprotect, cond-signal, every
keypress) each block on a synchronous COM1 UART write AND re-render the
framebuffer text console, with interrupts disabled. Measured: ~14000 log lines
during a single boot+app-launch. Dropping to Error cut serial volume ~12x
(14000 -> 1185 lines), removing tens of seconds of emulated-bare-metal warmup.
Raise the one line in logger::init back to Debug for deep tracing.
Root cause of the sporadic render crashes. The user GPR snapshot lives in a
PER-CPU scratch (gs:[..]) written by the syscall entry stub and shared by every
thread on the core. save_full_user_gprs read it LAZILY at yield time — but the
wait loops do sti;hlt and the timer ISR can switch threads mid-handler, so
another thread's syscall entry overwrites the per-CPU snapshot before our save
runs. We then stored the OTHER thread's callee-saved regs (rbx/rbp/r12-r15) into
our context; on resume rbx was garbage.

Pinpointed via addr2line/objdump: fml::MessageLoopOscortex::Run() resuming from
epoll_wait wrote running_ through this=0x1400 (=1280*4, a Skia row stride leaked
from another thread) -> SIGSEGV pid=3. This is the whole 0/16/140/489-frames-
across-identical-boots sporadicity.

Fix: capture_user_gprs_at_entry() snapshots the GPRs into the thread's own
PTABLE slot at dispatch_fast entry, while the per-CPU snapshot is still fresh
for this thread (before any handler/yield/interrupt window). A per-CPU
'captured' flag makes later yield-time save_full_user_gprs calls no-ops, so a
clobbered shared snapshot can't leak in.

Validation: 4/4 headless boots render cleanly (present 98-105) with ZERO engine
SIGSEGV, vs the prior sporadic crashes. Render is now reliable.
The frame pump only called FlutterEngineScheduleFrame in the no-event branch,
so a hover/click/scroll waited for the event queue to drain plus a wm_event_wait
timeout (up to ~16ms) before the repaint was even requested. Request the frame
in the same iteration input is received; the engine coalesces redundant requests
and Flutter flushes the batched pointer packet at BeginFrame, so the scheduled
frame reflects the event. Removes the scheduling-side input latency (most
visible on real hardware; under cross-arch QEMU TCG the emulation dominates).
Each submitted frame did up to 5 full 1M-pixel passes: a strided re-pack into a
freshly allocated 4MB Vec, a byte-indexed B<->R swap, a full-screen fill_rect
clear, blit_rgba32, and swap_buffers. Measured ~65ms median (wildly variable
10-92ms) -> a ~15fps ceiling from the blit alone, which read as a low refresh
rate / laggy feedback.

- Fuse the strided re-pack into the swap: submit_bgra_impl reads the source
  stride directly and packs in ONE u32-wise pass (read BGRA as u32, swap bytes
  0<->2), eliminating the intermediate buffer + the 4MB/frame allocation.
- Skip the full-screen fill_rect clear when a presented surface already covers
  the screen (the common case: one full-screen Flutter surface).

Measured after: blit median 5.4ms, steady 5.0-7.6ms (~12x faster, variance
gone). Render verified correct + crash-free; colors unchanged.
next_runnable_pid_locked computed the foreground-exclusive group AFTER the
input-target and embedder-baton shortcuts, so a due shell (pid 1) baton could
schedule the shell engine even while an app is foreground — two heavy Flutter
VMs on one cooperative core, the documented cause of the launched app's crash.
Compute fg/exclusive FIRST and gate both shortcuts: suppress the pid-1 baton
when an app is exclusive, and only honour the input shortcut for a target in the
foreground group. No behavioural change when the shell is foreground (the common
case): exclusive=false short-circuits both gates.

Closes the baton concurrency hole; full app-launch validation still pending an
interactive tile-tap (HMP mouse_button injection does not produce reliable
clicks headless).
Replace the generic reply-null catch-all in platform_message_callback with
a real channel dispatcher (match on channel name). Every channel the
framework reaches for is now routed to a concrete handler that does the
platform work or returns a codec-correct typed ack; the final catch-all
logs the channel name ([embedder/chan] unbound: <name>) so nothing stays an
invisible stub.

Bound channels:
- flutter/textinput (JSONMethodCodec): setClient/setEditingState/show/hide/
  clearClient. Editing state (text + selection) is maintained in the embedder.
  PS/2 set-1 scancodes are now mapped to characters (shift/caps, backspace,
  enter, tab, space, arrows, home/end, delete); on a key press with an active
  text client the stored editing state is mutated and TextInputClient.
  updateEditingState is pushed back over flutter/textinput. Adds a small
  inline JSON reader/writer (no_std, no crates) for the editing-state maps.
- flutter/mousecursor: parse activateSystemCursor kind and ack (no kernel
  set-cursor-shape syscall exists yet; logged + acked).
- flutter/platform: Clipboard.setData/getData/hasStrings backed by an
  in-embedder buffer; SystemNavigator.pop; SystemSound/HapticFeedback/
  SystemChrome acked.
- flutter/navigation, system, accessibility, spellcheck, processtext, menu,
  contextmenu, scribe, restoration, keyevent, platform_views, isolate,
  lifecycle: explicit JSON typed-null ack.
Replace the ack-only stubs from the platform-channel contract with actual
OS-provided capabilities. OSCortex is the platform under the stock engine, so
where Flutter needs a platform service the kernel now implements and binds it.

Mouse cursor shape (flutter/mousecursor.activateSystemCursor):
- compositor: ACTIVE_CURSOR_SHAPE atomic + vector cursor sprites (arrow, I-beam,
  hand/link, forbidden, grab, horizontal/vertical resize, hidden). draw_software_cursor
  dispatches on the active shape; set_cursor_shape() repaints immediately.
- new syscall SYS_CURSOR_SHAPE_SET (0x4B2). Embedder maps the Flutter cursor kind
  string to a CURSOR_SHAPE_* and calls it, so hovering a link shows a hand, a text
  field shows an I-beam, etc.

Semantics / accessibility:
- embedder wires update_semantics_callback2 (FlutterProjectArgs off 280) and calls
  FlutterEngineUpdateSemanticsEnabled(engine, true) after run. The callback receives
  the FlutterSemanticsUpdate2 tree and stores each node (id, label, rect, flags,
  actions) in a live embedder structure for a11y / automation consumers. flutter/
  accessibility now replies with the correct StandardMessageCodec null (0x00), not JSON.

System clipboard (flutter/platform Clipboard.*):
- kernel-global clipboard buffer (embedder::clipboard) shared across every app/host,
  with SYS_CLIPBOARD_SET (0x4B3) / SYS_CLIPBOARD_GET (0x4B4). The embedder routes
  setData/getData/hasStrings to the kernel, so clipboard survives across apps.

SystemNavigator.pop (flutter/platform):
- SYS_APP_CLOSE_FOREGROUND (0x4B5): refocuses the shell (pid 1) and wakes it. The
  app embedder, when it is a launched host, flushes its reply, calls the syscall and
  exits so focus returns to the shell.

SystemSound.play (flutter/platform):
- PC-speaker beep driver (drivers::beep) via PIT channel 2 + port 0x61, exposed as
  SYS_BEEP (0x4B6). click vs alert play distinct short tones.

Deliberate no-ops (no such hardware), acked with the correct codec:
- HapticFeedback.* (no vibration motor), SystemChrome.* (single full-screen
  compositor surface, no system UI overlays / orientation).
Hovering froze the whole shell. Two causes from the platform-capabilities work:
(1) flutter/mousecursor fires activateSystemCursor on every hover move, and the
handler called SYS_CURSOR_SHAPE_SET each time → the kernel redraws the cursor
(invalidate→composite) per event → a redraw storm that starves the cooperative
core. Fix: only call the syscall when the cursor shape actually CHANGES.
(2) Semantics was enabled, so the engine rebuilt the full accessibility tree on
every hover-change — far too expensive on the single cooperative core. Disable
the enablement (callback + channel stay wired; re-enable when throttled).

Hover is responsive again; cursor shapes still work on real transitions.
SwitchListTile.activeColor is deprecated in favour of activeThumbColor;
the required "Flutter shell analyze + test" check failed on it. Also set
the shell background to pure black.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ipeline

Brings the aarch64 port + ARM Flutter shell onto main (squashed from develop;
full history on develop). The ARM shell renders the Material UI reliably under
QEMU -M virt (-kernel + ramfb) and takes mouse hover/click via a virtio-input
driver; release.yml now also publishes an aarch64 kernel artifact.

Key ARM fixes (see develop history): epoll_event + vsnprintf va_list ABI,
syscall arg3 (x3) restore from r10, cooperative-only scheduling, font/asset
bundle, virtio-input pointer. Shared kernel/embedder paths refactored arch-neutral
(enable_and_halt, rdtsc_ns, spin_pause, save_return_context_reexec, ptable_cpu_idx,
lock_page_table) preserving x86 behavior. Verified: x86_64 + aarch64 build clean;
aarch64 renders (present>0, crash=0).
Fetch the prebuilt Flutter shell runtime (engine .so + AOT shell snapshot + ICU +
fonts/assets + embedder /init + libc + seed) from the oscortex-engine-1 release,
stage it into initramfs/, and build the aarch64 kernel with it embedded — so a
downloaded oscortex-aarch64-<tag>.kernel boots straight into the rendering shell
(qemu -M virt -kernel + ramfb + virtio input), not just the platform layer.

The runtime bundle (oscortex-arm64-shell-runtime.tar.gz) is a prebuilt snapshot on
oscortex-engine-1; refresh it when the shell app or engine changes. sha256-verified
on fetch.
Name the x86 ISO oscortex-x86_64-<tag>.iso (was oscortex-<tag>.iso — confusingly
unlabelled next to oscortex-aarch64-<tag>.kernel). Compute checksums from inside
dist/ so each .sha256 lists a bare filename and 'sha256sum -c' works after
downloading just the artifact+checksum pair (previously the file carried a dist/
path prefix and failed verification). Clarify in the notes that the two artifacts
differ in type because the arches boot differently (x86 Limine ISO vs aarch64
-kernel ELF).
Re-port of the Limine UEFI scaffolding (boot_limine.rs, aarch64-limine.ld,
mmu::limine_setup_ttbr0, kernel_main_arch_limine, limine-boot feature,
build-iso-aarch64.sh) onto origin/main — which has ALL the render fixes (epoll/
va_list ABI, x3 arg restore, cooperative scheduling, virtio-input). The earlier
attempt was mistakenly branched off stale develop (pre-fixes).
…the shell

Limine hands off at EL1t (SPSel=0), where `sp` aliases SP_EL0. Every kernel
stack write then went through SP_EL0, and enter_user's kernel-stack reclaim
(`mov sp, <syscall stack top>`) clobbered the user SP it had just delivered:
pid1 entered EL0 with SP pointing at the kernel syscall stack and its first
push faulted (EC=0x24, FAR=SP-0xa0). The QEMU -kernel path sets up EL1h
itself, which is why the identical kernel worked there.

One instruction: msr spsel, #1 before the boot-stack switch. Validated under
edk2: present_callback=281 on a full run.
…% resume crash

eret_to_el0/eret_to_el0_fp reset SP_EL1 then read the img/fp resume arrays that
still live on the abandoned cooperative-yield frame. A wait loop that yields
with IRQs unmasked leaves them unmasked here; a timer IRQ pushes a TrapFrame
over img/fp → corrupted resume image (intermittent data abort in eret_to_el0_fp
at the SPSR load with x18=0). Fix: msr daifset, #0xf before the SP reset; the
eret restores SPSR_EL0T so EL0 runs with interrupts enabled. 12/12 -kernel
boots clean (was ~2/8).
…UEFI ISO in CI

Kernel: finish_cond_timedout_return advanced the resume PC by a hardcoded 2
bytes — correct for x86's 2-byte `syscall`, but on aarch64 `svc #0` is 4 bytes,
so the parked cond_timedwait waiter resumed at svc+2 (a misaligned PC) →
EC=0x22 PC-alignment fault, deterministically ~280 frames into a run (every
cond_timedwait timeout was a live grenade). Use the cfg'd SYSCALL_INSN_LEN
(4 on aarch64) and set aarch64_ret_in_x0 so ETIMEDOUT actually reaches x0.

CI: release.yml now builds the aarch64 UEFI/Limine ISO via build-iso-aarch64.sh
(LIMINE_DIR=$HOME/limine) and publishes oscortex-aarch64-<tag>.iso + .sha256
alongside the raw -kernel ELF — a real UTM/VM/bare-metal-bootable ARM image.
build-iso-aarch64.sh: LIMINE_DIR is now overridable for CI.
…e (HVF/bare-metal) SP-alignment fault

The x86 SysV convention seeds entry SP at stack_top-8 (RSP%16==8, return addr
on the stack). AArch64 is the opposite: SP must be 16-byte aligned at ALL times
(SCTLR_EL1.SA, hardware-enforced) and the return address lives in x30/LR (already
seeded via p.user_lr=thread_return_trampoline_va). TCG doesn't enforce SP
alignment so it silently worked; real silicon (HVF / UTM / Raspberry Pi) faults
EC=0x26 the instant a spawned engine thread touches its stack — which is why it
rendered headless but never on real hardware. cfg-split spawn_thread and
spawn_with_bootstrap to align down to 16 on aarch64; x86 keeps the -8.

Verified under HVF (cpu=host, real Apple Silicon): engine now spawns all worker
threads with no SP fault and reaches the render event loop.
…PI27) — renders on real hardware

The kernel ticked the EL1 PHYSICAL timer (CNTP_CTL_EL0, PPI 30). On QEMU -M virt
TCG that interrupt is delivered, so it worked headless. But OSCortex runs at EL1
and under a hypervisor — Apple's HVF (what UTM uses), KVM, Xen — EL2 owns the
physical timer: EL1 CNTP accesses are trapped and PPI 30 is NOT delivered to the
guest. So under HVF the tick never fired → no vsync baton → the engine reached
its event loop, scheduled frames, and stalled with present=0 (nothing ever
rendered on real silicon, only in TCG emulation).

Switch to the architected EL1-guest VIRTUAL timer (CNTV_CTL_EL0 / CNTV_TVAL_EL0 /
CNTVCT_EL0, PPI 27). It is delivered under HVF, on bare metal (CNTVOFF=0 → virtual
time == physical time), AND on TCG virt — strictly more portable. vsync_due
already measured cadence against CNTVCT, so this is now consistent.

Verified under HVF (cpu=host, real Apple Silicon): present_callback reaches 2063
frames, crash=0 — the full Flutter shell renders on real hardware.
@squirelboy360 squirelboy360 merged commit cb4aed9 into develop Jun 12, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants