ludic/changes/windows-target.md
Orkuncakilkaya 55b0e2ebf6 feat(windows): the ludic CLI and ludic bundle on Windows
The CLI's shell commands go through shell(), which is run() on POSIX and a
scratch script handed to Git for Windows' bash on Windows (exit codes read
directly there). compile_app links through `ludicc -o` on Windows, ludic run
starts the .exe, and ludic-dev build and ensure_ludicc assemble
selfhost/ludicc.win.seed.ll, which ludic-dev reseed now writes beside the
macOS seed. ludic bundle makes build/<name>/ with a GUI-subsystem exe carrying
the .ico beside `app icon` as an llvm-rc resource, game.lpak and packs.index;
ludicc gains --gui and --link, and quotes its whole link line for cmd.exe.

Verified: ludic-dev test 135/135, selfhost-test 32/32; on the PC a checkout
bootstraps from the Windows seed, ludic-dev build makes the toolchain, and
`ludic bundle` makes build/Maroon Lake/, which runs from its own folder.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-13 03:01:33 +03:00

3.4 KiB

bump: minor type: feat Windows target — ludicc builds and runs headless programs on Windows, and builds itself there.

  • --target <triple> — a triple naming windows selects the Windows runtime; without the flag the target is the host, read at run time from OS=Windows_NT. The IR still carries no triple, so clang assembles it for the machine it runs on.
  • The libc surface, in IR — on Windows the header emits emit_win.ludic, which defines the POSIX names the backend calls (fopen, ftell, rename, opendir, mmap, fmemopen, uname, …) over the UCRT and Win32. Three of them fixed silent breakage rather than link errors: rename onto an existing file (every Fs.write_text after the first), a 32-bit ftell, and text-mode fopen rewriting \n as \r\n.
  • Known folders — Os.save_dir / config_dir are %APPDATA%\<app>, cache_dir is %LOCALAPPDATA%\<app>, temp_dir is %TEMP%, all with forward slashes; a bundle's home line points at %APPDATA%\<name>.
  • The driver — finds C:\Program Files\LLVM\bin\clang.exe when clang is not on %PATH%, writes .exe outputs, speaks cmd.exe for its directory and cleanup commands, and reads argv[0], %PATH% and $LUDIC_HOME with either separator.
  • A window — win32.ll is cocoa.ll's contract over user32: the held-key set and frame key in Ludic codes, the mouse in framebuffer pixels with raw input for cursor mode 2, cursor hide/lock/confine that lets go when the window loses the foreground, XInput pads in SDL order with rescaled deadzones, and the software win_present. win32_gl.ll puts the WGL context on that window with vsync and borderless full screen; a headless build links gl_win_nowin.ll instead. The process is per-monitor DPI aware.
  • Sound — audio_win.ll is audio.ll's contract over XAudio2: one source voice per clip, loops, volume, rate and a balance pan through the output matrix, RIFF WAVE in PCM or float. It reads a clip through the asset pack, so a bundled game's first Audio.load succeeds - AVAudioPlayer takes a filesystem path, which is why macOS cannot.
  • The CLI on Windows — ludic build, ludic run and ludic-dev build work from a Windows checkout. Every shell command the CLI issues runs through Git for Windows' bash ($LUDIC_BASH names another), compile_app links through ludicc -o, and a checkout bootstraps from the new selfhost/ludicc.win.seed.ll, which ludic-dev reseed now writes beside the macOS seed.
  • ludic bundle on Windows — build/<name>/ holding a GUI-subsystem <name>.exe, the game.lpak and packs.index; an .ico beside app icon is compiled with llvm-rc and linked in. ludicc gains --gui (no console behind the window) and --link <file>.
  • Not yet — the splash and App.set_icon do nothing, and Http.* is still a macOS platform layer; a Windows build that uses it stops with a message saying so.
  • Gl.* on Windows — gl_win.ll makes a WGL 4.1 core context on a hidden window's DC, and gl_thunks_win.ll (generated by ludic-dev glgen beside gl_thunks.ll) calls every entry point through a table @lgl_win_load fills from wglGetProcAddress, falling back to opengl32.dll for the 1.1 functions it will not return. A headless GL program renders on the GPU there: gl_triangle matches the macOS frame to within one level per channel.