{
  description = "Westgate Urban Foundation MVP — Blender 4.0.2 tileset generator kit";

  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
    # Reuse the machine's pinned Blender 4.0.2 + NWN toolchain via the repo overlay.
    theorem.url = "git+file:///nix/nixos";
  };

  outputs =
    {
      self,
      nixpkgs,
      theorem,
    }:
    let
      system = "x86_64-linux";
      pkgs = import nixpkgs {
        inherit system;
        overlays = [ theorem.overlays.default ];
      };
      blender = pkgs.blender-402-bin; # /bin/blender-4.0.2 (NWN needs 4.0.x, not 5.x)
      py = pkgs.python3.withPackages (ps: [ ps.mcp ]);

      gen = ./generator; # whole dir so tiles.py sits beside each script for import

      build = pkgs.writeShellApplication {
        name = "wgt-build";
        runtimeInputs = [ blender ];
        text = ''
          out="''${1:-westgate_urban_foundation_mvp.blend}"
          exec blender-4.0.2 --background --python ${gen}/build.py -- "$out"
        '';
      };

      mkset = pkgs.writeShellApplication {
        name = "wgt-set";
        runtimeInputs = [ py ];
        text = ''exec python ${gen}/make_set.py "$@"'';
      };

      mkwok = pkgs.writeShellApplication {
        name = "wgt-wok";
        runtimeInputs = [ py ];
        text = ''exec python ${gen}/make_wok.py "$@"'';
      };

      # Pure-Python .mdl + .wok + solid-colour .tga (the shipping model path;
      # no Blender, and unlike Neverblender it keeps per-texture trimesh nodes).
      mkmdlpy = pkgs.writeShellApplication {
        name = "wgt-mdl";
        runtimeInputs = [ py ];
        text = ''exec python ${gen}/make_mdl.py "$@"'';
      };

      # Tileset palette (GFF ITP) so the toolset registers the set.
      mkitp = pkgs.writeShellApplication {
        name = "wgt-itp";
        runtimeInputs = [ py pkgs.neverwinter-nim ];
        text = ''exec python ${gen}/make_itp.py "$@"'';
      };

      # Real .mdl + .wok export via the Neverblender addon (must be enabled in
      # this Blender's user config — confirmed working headless).
      mkmdl = pkgs.writeShellApplication {
        name = "wgt-export";
        runtimeInputs = [ blender ];
        text = ''
          blend="''${1:?usage: wgt-export <blend> <outdir> [CODE...]}"
          outdir="''${2:-mdl}"
          shift 2 || true
          exec blender-4.0.2 --background "$blend" --python ${gen}/export_mdl.py \
            --python-exit-code 1 -- "$outdir" "$@"
        '';
      };

      # One shot: models (.mdl/.wok/.tga) + .set + .itp, staged into the toolset's
      # development/ folder (no .hak, no Blender). Work dir defaults to ./build.
      mkdev = pkgs.writeShellApplication {
        name = "wgt-dev-install";
        runtimeInputs = [ py pkgs.neverwinter-nim ];
        text = ''
          work="''${1:-$PWD/build}"
          dev="$HOME/Documents/Neverwinter Nights/development"
          mkdir -p "$work/mdl" "$dev"
          python ${gen}/make_mdl.py "$work/mdl"                 # .mdl + .wok + .tga
          python ${gen}/make_set.py "$work/twu01.set"
          python ${gen}/make_itp.py "$work/twu01palstd.itp"
          # replace only our own files in development/, leave everything else alone
          rm -f "$dev"/twu01_*.mdl "$dev"/twu01_*.wok "$dev"/twu01_*.tga \
                "$dev"/twu01.set "$dev"/twu01palstd.itp
          cp "$work"/mdl/twu01_*.mdl "$work"/mdl/twu01_*.wok "$work"/mdl/twu01_*.tga \
             "$work"/twu01.set "$work"/twu01palstd.itp "$dev"/
          n=$(find "$work/mdl" -name 'twu01_*.mdl' | wc -l)
          echo "installed $n tiles + twu01.set + twu01palstd.itp -> $dev"
        '';
      };
    in
    {
      packages.${system}.default = build;

      apps.${system} = {
        default = {
          type = "app";
          program = "${build}/bin/wgt-build";
        };
        build = {
          type = "app";
          program = "${build}/bin/wgt-build";
        };
        set = {
          type = "app";
          program = "${mkset}/bin/wgt-set";
        };
        wok = {
          type = "app";
          program = "${mkwok}/bin/wgt-wok";
        };
        mdl = {
          type = "app";
          program = "${mkmdlpy}/bin/wgt-mdl";
        };
        itp = {
          type = "app";
          program = "${mkitp}/bin/wgt-itp";
        };
        export = {
          type = "app";
          program = "${mkmdl}/bin/wgt-export";
        };
        dev-install = {
          type = "app";
          program = "${mkdev}/bin/wgt-dev-install";
        };
      };

      devShells.${system}.default = pkgs.mkShell {
        packages = [
          blender
          py
          pkgs.neverwinter-nim
          pkgs.cleanmodels
        ];
        shellHook = ''
          export BLENDER_BIN=${blender}/bin/blender-4.0.2
          echo "wgt01 dev shell — build: blender-4.0.2 --background --python generator/build.py -- out.blend" >&2
        '';
      };
    };
}
