Sprint 18 โ C# Shell Reimplementation Assessment (Post-MVP Spike)
Goal: Produce a written plus/minus and feasibility assessment of re-implementing playos-shell in C#, and record it as a post-MVP investigation sprint. No C# shell is implemented in this sprint.
Primary Outcome: A decision-ready Sprint-18.md record that (a) analyses the runtime, packaging, interop, and rendering consequences of a C# shell, (b) states a clear feasibility verdict, and (c) scopes an optional bounded host-only de-risking spike โ while explicitly leaving a product-direction C# rewrite unplanned.
Status: ๐ก Post-MVP โ assessment only; not scheduled. No implementation work is approved.
Prerequisites: MVP stable (Sprint 15โ16); the Raylib 6.0 shell landed (Sprint 5.5); rcore_playos.c is the single rendering backend (ADR-0006); the musl-only constraint is in force (ADR-0003).
Why This Sprint Exists
The shell is currently ~8k lines of C plus a vendored Raylib with a custom backend. A recurring question is whether a managed language (C# / .NET) would reduce the memory-safety and manual-parsing risk of that C code and speed UI iteration. This sprint does not commit to a rewrite; it answers the question with an assessment, so the roadmap can explicitly accept or reject the direction.
This is a decision-support sprint, not an implementation sprint. Its output is the assessment itself, and the default recommendation is to not pursue a C# shell as a product direction.
Assessment Inputs
The assessment rests on the following authoritative facts:
ADR-0003 โ libc Choice (musl)โ musl only, no glibc. This is the decisive runtime constraint.ADR-0006 โ UI Framework (Raylib)โ Raylib is the single UI framework for shell, overlay, and games.architecture.mdยง14 explicitly excludes "libc other than musl" from PlayOS v1.playos-shell-spec.mdโ shell responsibilities; Raylib is rendering-only, and controller input is read directly from evdev (src/input.c) so reserved SYSTEM/QUICK_MENU buttons survive.- The shell links
wayland-client,wayland-egl, EGL, GLESv2,libplayos(fromplayos-platform-api), vendored staticraylib, and optionallylibplayos-trusted(fromplayos-runtime). - Wayland protocol code is generated to C from
playos-v1.xml+ xdg-shell viawayland-scanner.
Assessment Constraints (Locked)
These constraints are not re-negotiated by this sprint; any C# rewrite would have to re-establish them:
- musl-only is non-negotiable (ADR-0003, architecture.md ยง14).
- Single rendering framework (ADR-0006). A rewrite that forks a second rendering path must justify it, not silently drop it.
- Shell invariants carried by the existing C code and required of any rewrite:
- always alive, supervised by
playos-init; - 60 fps target, controller-only navigation;
- no blocking I/O on the render thread;
- no direct IPC socket access except the trusted evdev input path;
- rendering stops while a game is foreground, but the process stays alive.
- always alive, supervised by
Feasibility Assessment
Verdict
Technically feasible as a host-side proof-of-concept; not advisable as a shippable on-device product direction. The decisive factors are: (1) .NET-on-musl / NativeAOT risk, (2) Buildroot toolchain effort, and (3) loss of the single Raylib backend story.
Plus / Minus
| Dimension | Plus | Minus |
|---|---|---|
| Memory safety | Eliminates buffer overflows and manual string-parsing bugs in the current C shell (e.g. hand-rolled JSON in main.c) | GC/allocator behaviour and working-set size on an always-on 60 fps embedded process must be re-validated |
| UI iteration | Richer abstractions (records, LINQ, test framework) can speed state/UI iteration | Raylib's UI layer is intentionally thin; C# does not remove the need to bind Raylib or re-derive rendering |
| Native interop | P/Invoke + source generators can wrap a C ABI | The entire surface โ libplayos, wayland-client/wayland-egl, EGL, GLESv2, trusted IPC, evdev โ must be wrapped or generated; marshaling on musl is untested |
| Runtime & packaging | Self-contained .NET removes a host dependency | Supported Linux RIDs assume glibc; linux-musl-x64 exists but NativeAOT still depends on the OS libc/ICU and needs validation; Buildroot has no first-class .NET SDK package |
| Rendering | โ | Raylib's native library is C with a custom rcore_playos.c backend; C# bindings (Raylib-cs) target upstream Raylib, so the PlayOS backend is lost or must be kept in C and P/Invoked โ weakening the ADR-0006 single-backend rationale |
| Wayland protocols | Community C# Wayland bindings exist | playos-v1.xml + xdg-shell are generated to C via wayland-scanner; a C# binding generator would need the private protocols ported |
| Total cost/benefit | Modest long-term maintainability upside | Replaces ~8k lines of working, shipped C with a high-risk multi-week-to-month effort for a persistent controller UI that is not the product's growth area |
Runtime (musl / NativeAOT) โ hard blocker to validate
- .NET's supported Linux runtime identifiers assume glibc (
linux-x64). Alpine'slinux-musl-x64RID exists, but self-contained deployment still relies on the OS libc, and ICU/globalization behaviour historically differs on musl. - NativeAOT reduces startup and footprint but does not remove the libc/ICU dependency; threading, P/Invoke marshaling, and finalizer behaviour on musl are exactly the areas with the least field coverage.
- This is the single highest-risk item and must be proven by a spike, not assumed. Until a self-contained NativeAOT binary runs on the actual musl rootfs with the same EGL/Wayland bindings, a C# shell is a research bet, not a plan.
All runtime claims above are architectural and to be validated by a spike; they are not asserted as tested.
Buildroot packaging โ hard blocker to estimate
- Buildroot has no first-class .NET SDK package. Shipping means either a host .NET SDK toolchain with cross-compilation, or NativeAOT produced on a host and injected into the image.
- Either path is new
br2-externalmachinery with no precedent in this repository โ substantial, unproven infrastructure work.
Native interop surface โ medium
A C# shell would need bindings for, at minimum:
libplayos(the public C ABI): lifecycle, storage paths, device info, logical input, audio/display/power queries, structured logging.libwayland-client,libwayland-egl, EGL, GLESv2.- Trusted IPC: the
playos-runtimerestricted control client (control.sock) and optionallibplayos-trusted. - Direct evdev input, to preserve reserved SYSTEM/QUICK_MENU button survival exactly as
src/input.cdoes today.
Raylib backend loss โ strongest architectural minus
- Raylib is C. The PlayOS value is the custom
rcore_playos.cbackend shared by shell, overlay, and games. - A C# shell either P/Invokes a C Raylib build (keeping the backend in C, so C# gains little in the rendering hot path) or re-derives Wayland/EGL/GLES3 surface management in managed code โ a second rendering path that directly contradicts ADR-0006's unified-framework rationale.
- Neither option reduces the amount of C the project must own; both add a managed/native boundary across every frame and every input event.
What is cheap and worth doing
- A host-only C# proof-of-concept of the state and screen layer only: screen enum + navigation stack,
manifest.jsonparsing, power/thermal model types, toast/screenshot state. This exercises the "C# is nicer for UI state" hypothesis without touching Buildroot, musl, Wayland, or Raylib.
Recommended De-Risking Spike (Bounded)
If ever funded, a single bounded spike would, in order:
- Host-only state/screen POC โ a .NET console/unit-test harness against a re-typed
manifest.jsonand state model. - musl proof โ compile a minimal self-contained NativeAOT "hello" binary and run it on the existing musl rootfs (no Wayland/EGL), recording libc/ICU/startup/size results.
- Binding probe โ generate a C# binding for
playos-v1.xml+ xdg-shell and complete onewl_surfaceround-trip on a host Wayland compositor. - Explicit stop โ do not proceed to a device C# shell, Buildroot packaging, or a Raylib-backend replacement.
Scope
In Scope (this sprint)
- This
Sprint-18.mddocument. - The feasibility analysis and verdict above.
- Definition of the bounded de-risking spike (S18-T1โฆT4) โ not its execution.
Explicitly Out of Scope / Not Planned
- Any C# shell implementation.
- Buildroot / .NET toolchain work.
- Raylib backend port or replacement.
- Changing the existing C shell.
Required Repository Changes
| Repo | Required work |
|---|---|
playos-spec | Add Sprint-18.md; link from SUMMARY.md and post-mvp.md |
| (none else) | No implementation repositories change in this sprint |
Expected Files and Directories
playos-spec/src/sprints/Sprint-18.md # NEW: this assessment
playos-spec/src/SUMMARY.md # UPDATE: link
playos-spec/src/post-mvp.md # UPDATE: entry
Agent Task Breakdown
Task Status Grid
| Task ID | Task | Primary repo | Status | Notes / evidence |
|---|---|---|---|---|
| S18-T1 | Runtime + Buildroot feasibility (musl / NativeAOT) | playos-spec | not started | ADR-0003, architecture ยง14 |
| S18-T2 | Native interop + Wayland protocol bindings | playos-spec | not started | libplayos, wayland-client, playos-v1.xml |
| S18-T3 | Raylib backend loss + rendering-path options | playos-spec | not started | ADR-0006, rcore_playos.c |
| S18-T4 | Bounded host-only de-risking spike definition | playos-spec | not started | state/screen-layer POC only |
S18-T1 โ Runtime + Buildroot feasibility
- Confirm the .NET supported Linux RID situation against ADR-0003 (musl only):
linux-x64(glibc) vslinux-musl-x64, and NativeAOT's remaining libc/ICU dependency. - Assess Buildroot packaging options: host .NET SDK cross-compilation vs host-produced NativeAOT injected into the image, and the new
br2-externalmachinery each requires. - Record startup time, binary size, and working-set expectations as to be validated, not proven.
Done when: the sprint records a clearly-labelled runtime/packaging risk verdict and identifies the exact spike step (S18-T4 step 2) that would validate it.
S18-T2 โ Native interop + Wayland protocol bindings
- Enumerate the concrete surface a C# shell must bind:
libplayosC ABI,libwayland-client/libwayland-egl, EGL, GLESv2, trusted IPC (control.sock), and direct evdev. - Assess how
playos-v1.xml+ xdg-shell (currently generated to C bywayland-scanner) would be generated for C#, and whether community C# Wayland bindings can absorb the private protocols. - Identify marshaling/threading risks specific to musl.
Done when: the sprint records the full binding surface and names the binding-probe step (S18-T4 step 3) that would de-risk it.
S18-T3 โ Raylib backend loss + rendering-path options
- Evaluate the two options: P/Invoke a C Raylib build that keeps
rcore_playos.c, versus re-deriving Wayland/EGL/GLES3 in managed code. - State why the second option violates ADR-0006's single-framework rationale, and why the first leaves the rendering hot path in C.
- Conclude with the recommendation that this is the strongest architectural minus.
Done when: the sprint names the Raylib-backend loss as the decisive architectural minus and ties it to ADR-0006.
S18-T4 โ Bounded host-only de-risking spike definition
- Define the four-step spike: host state/screen POC โ musl "hello" proof โ binding probe โ explicit stop.
- Scope it strictly to host-only, with no Buildroot work and no Raylib backend replacement.
- State the acceptance that marks the spike complete and the condition under which it would not advance to a product C# shell.
Done when: the sprint records a bounded, stoppable spike definition and a default recommendation not to pursue a C# rewrite.
Verification and Evidence
| Evidence | How it is produced |
|---|---|
| Assessment recorded | Sprint-18.md present with plus/minus table and verdict |
| Roadmap indexed | SUMMARY.md and post-mvp.md link the sprint |
| Link integrity | mdbook build passes |
| No implementation drift | No C# files or Buildroot changes are produced by this sprint |
Acceptance Criteria
- The assessment states a clear feasibility verdict with a plus/minus table
- The musl/NativeAOT and Buildroot risks are labelled "to be validated", not asserted as proven
- The Raylib backend loss is identified as the strongest architectural minus and tied to ADR-0006
- A bounded host-only de-risking spike is defined with an explicit stop
- A C# rewrite is explicitly left unplanned as a product direction
-
SUMMARY.mdandpost-mvp.mdare updated -
mdbook buildpasses
Handoff to Post-MVP
After this sprint:
- The "C# shell?" question has a written answer and a default recommendation (do not pursue as a product direction).
- A future spike, if funded, can pick up S18-T4's bounded scope without re-deriving the assessment.
Exit Gate
The assessment is written, indexed, and link-verified; it clearly concludes that a C# shell reimplementation is technically feasible as a host POC but not advisable as a product direction, and it scopes an optional bounded spike while leaving the actual rewrite unplanned.
Previous: Sprint 17