jevsnes.git / flake.nix
1{
2  description = "jev plays Zelda — a Rust app that hosts a SNES core and lets Jev (TypeSafe AI) play A Link to the Past, natively and as WASM";
3
4  inputs = {
5    # Same branch as ~/config, and locked to the same rev, so Mesen and
6    # ffmpeg-full are the store paths the system already has rather than a
7    # second 600 MB copy.
8    nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
9
10    # The Rust toolchain comes from fenix, not nixpkgs: the emulator core
11    # (jgenesis' snes-core) uses `float_algebraic`, stabilised after the
12    # rustc 1.95 nixos-26.05 carried — measured 2026-09-20, 16 E0658
13    # errors on 1.95, clean on 1.98.1. fenix's `stable` is the release
14    # channel, i.e. the newest released compiler; `nix flake update fenix`
15    # moves it forward.
16    fenix = {
17      url = "github:nix-community/fenix";
18      inputs.nixpkgs.follows = "nixpkgs";
19    };
20
21    # The estate package set, for buck2 (see the devshell).
22    nix-pkgs.url = "git+ssh://git@github.com/deizel/nix-pkgs";
23    nix-pkgs.inputs.nixpkgs.follows = "nixpkgs";
24  };
25
26  outputs =
27    {
28      nixpkgs,
29      fenix,
30      nix-pkgs,
31      ...
32    }:
33    let
34      system = "x86_64-linux";
35      pkgs = nixpkgs.legacyPackages.${system};
36      rust = fenix.packages.${system};
37
38      # One toolchain for both outputs of the app: the native window and the
39      # browser build. wasm32-unknown-unknown needs only its rust-std — the
40      # linker for it (rust-lld) ships inside rustc.
41      rustToolchain = rust.combine [
42        rust.stable.rustc
43        rust.stable.cargo
44        rust.stable.clippy
45        rust.stable.rustfmt
46        rust.stable.rust-src
47        rust.stable.rust-analyzer
48        rust.targets.wasm32-unknown-unknown.stable.rust-std
49      ];
50
51      mesen = pkgs.callPackage ./nix/mesen.nix {
52        enforce = {
53          Video.UseSoftwareRenderer = true;
54          Debug.ScriptWindow = {
55            AllowIoOsAccess = true;
56            AllowNetworkAccess = true;
57          };
58        };
59      };
60    in
61    {
62      packages.${system}.mesen = mesen;
63
64      devShells.${system}.default = pkgs.mkShell {
65        packages = [
66          rustToolchain
67
68          # The build: buck2 drives it, reindeer turns Cargo.lock into the
69          # third-party BUCK targets.
70          # nix-pkgs' buck2, not nixpkgs': the 2026-08-22 binary drops its
71          # forkserver under test load (fixed in facebook/buck2 1cc3e05f85,
72          # released 2026-09-15); the nix-pkgs package header has the account.
73          nix-pkgs.packages.${system}.buck2
74          pkgs.reindeer
75
76          # buck2's system cxx toolchain looks up plain `clang`/`clang++` by
77          # name and links with -fuse-ld=lld; without both, every Rust binary
78          # and build script fails at the link step. Same wall ~/dashboard and
79          # ~/mobo hit.
80          pkgs.clang
81          pkgs.lld
82
83          # cpal compiles its ALSA host on every Linux build, and alsa-sys's
84          # build script finds libasound through pkg-config or panics.
85          pkgs.pkg-config
86          pkgs.alsa-lib
87
88          # Mesen 2: the reference emulator, for checking our core's RAM and
89          # picture against a known-good one.
90          mesen
91
92          # Capture. -full, not plain ffmpeg: only -full is built with XCB,
93          # so only it has the `x11grab` input device that grabs a WSLg
94          # window by id. xwininfo is what finds that id.
95          pkgs.ffmpeg-full
96          pkgs.xwininfo
97
98          # ROM archives arrive as .7z / .zip.
99          pkgs.p7zip
100          pkgs.unzip
101
102          pkgs.jq
103
104          # The zbanks C bot (third-party/c) was written to run under gdb:
105          # its assert_bp is an int3 breakpoint. gdb is how its static state
106          # (targets, goal scores) is read mid-run.
107          pkgs.gdb
108
109          # Poking the Jev HTTP API by hand.
110          pkgs.curl
111
112          # Linting tools/*.sh - code-bash.md requires shellcheck-clean.
113          pkgs.shellcheck
114
115          # "Which GPU did I actually get": glxinfo/eglinfo and vulkaninfo.
116          pkgs.mesa-demos
117          pkgs.vulkan-tools
118        ];
119
120        # Reaching the GPU under WSL. `wsl.useWindowsDriver` links the D3D12
121        # libraries into /run/opengl-driver/lib and stops there: Mesa dlopens
122        # libd3d12.so / libdxcore.so by bare name with no runpath to that
123        # directory, and with no DRM node it never picks d3d12 unprompted.
124        # Without these two, GL, EGL and Vulkan all report llvmpipe; with
125        # them, all three report the RTX 3080 (measured 2026-09-20). Vulkan
126        # (Dozen) needs only the library path, GL needs both — and
127        # GALLIUM_DRIVER without the path is worse than neither: GL programs
128        # fail to start.
129        #
130        # The rest of the path is for our own window. winit, wgpu and
131        # xkbcommon-dl link none of their system libraries: they dlopen them
132        # by bare name at startup, so a Nix-built binary finds nothing unless
133        # the directories are on the search path.
134        #
135        # alsa-lib is the one that IS linked (cpal's ALSA host, a DT_NEEDED
136        # on libasound.so.2), but buck2's link writes no runpath to the store,
137        # so the loader still has to be told where it is: without it the
138        # binary dies before main with "libasound.so.2: cannot open shared
139        # object file" (measured 2026-09-20).
140        GALLIUM_DRIVER = "d3d12";
141        LD_LIBRARY_PATH = "/run/opengl-driver/lib:" + pkgs.lib.makeLibraryPath [
142          pkgs.alsa-lib
143          pkgs.wayland
144          pkgs.libxkbcommon
145          pkgs.vulkan-loader
146          pkgs.libGL
147          pkgs.libx11
148          pkgs.libxcursor
149          pkgs.libxi
150          pkgs.libxrandr
151        ];
152      };
153    };
154}