Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,46 @@ jobs:
fi
echo "No Verify snapshots drifted."

# The browser bundle ships with LocationAsStruct=false (see build-wasm in
# src/vue/package.json and issue #139), so that combination must be tested on
# its own, not only built. UseLuaSettings also turns it off, but changes five
# other flags with it.
test-location-as-class:
name: Test .NET with Location as a class (the shipped WASM setting)
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
submodules: recursive

- name: Setup .NET
uses: actions/setup-dotnet@a98b56852c35b8e3190ac28c8c2271da59106c68 # v6
with:
global-json-file: global.json

- name: Restore workloads
run: dotnet workload restore

- name: Restore dependencies
run: dotnet restore

- name: Build release, Location as a class
run: dotnet build --configuration Release /p:LocationAsStruct=false

- name: Test with Location as a class
run: dotnet test --no-build --configuration Release --logger "console;verbosity=normal"

- name: Check no Verify snapshots drifted
run: |
if [ -n "$(git status --porcelain -- '*.verified.*')" ]; then
echo "::error::A Verify snapshot changed during the test run. The committed snapshot does not match current behavior - regenerate locally and commit, or fix the regression."
git status --porcelain -- '*.verified.*'
git diff -- '*.verified.*'
exit 1
fi
echo "No Verify snapshots drifted."

test-default:
name: Test .NET with default settings
runs-on: ubuntu-latest
Expand Down
13 changes: 11 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,8 @@ npm run build-wasm # publish BrowserWasm and copy the bundle (incl. dotnet.j
resulting shape, on pull requests as well as on deploys, so this is now
checked rather than trusted. Requires the .NET 10 SDK plus the wasm-tools
workload; without a local .NET 10 SDK, publish via `./docker-build.sh` (the SDK image
also needs `python3` on PATH for the emscripten native relink step) and copy
also needs `python3` on PATH for the emscripten native relink step), passing
`--property:LocationAsStruct=false` the way `build-wasm` does, and copy
`src/BrowserWasm/bin/Release/net10.0/browser-wasm/AppBundle/_framework` into
`src/vue/public/framework`. `npm run dev` / `npm run build` serve those assets.

Expand Down Expand Up @@ -124,7 +125,15 @@ Blueprint string parsing and emitting live here, separate from the core lib: `Pa
- `src/vue` - the primary front-end (Vue 3 + Vite + Pinia, persisted settings). This is what's deployed, as a Cloudflare Worker with static assets (see "Deployment" below); it plans in-browser via the WASM bundle and no longer calls a hosted API. Planner constants (pole presets, geometry defaults, strategy defaults, quality levels) are not retyped in TypeScript - they come from `src/vue/src/lib/plannerDefaults.verified.json`, which `PlannerDefaultsTest` generates from `OilFieldOptions` (most fields) and the `Quality` enum (`qualityLevels`). Change a default in the C# and `dotnet test` rewrites that file; commit it with your change.
- `src/BrowserWasm` - runs the planner fully client-side via .NET WASM (trimmed). Lets the SPA plan without the API.

**It runs on the .NET interpreter, not AOT.** `BrowserWasm.csproj` sets `RunAOTCompilation=false`, a deliberate choice in `docs/superpowers/specs/2026-06-18-in-browser-wasm-planner-design.md` (decision 2) for a smaller download and faster CI. Measured 2026-10-06 in issue #139, it plans 17x slower than native .NET. Turning AOT on alone only gets to 14x, because Mono's AOT falls back to the interpreter for generic code over value types, and `Location` is a struct by default. AOT plus `LocationAsStruct=false` removes that fallback and gets to 4.3x, at 2.5 MiB brotli instead of 1.35 MiB. Read #139 before changing either flag, and measure again after.
**It ships as AOT with `Location` as a class, and the two flags only work together.** `BrowserWasm.csproj` sets `RunAOTCompilation=true`, and `build-wasm` passes `--property:LocationAsStruct=false`. That flag must stay on the command line: it is a global property that has to reach the referenced projects, and setting it in the `.csproj` does not. Measured 2026-10-06 in issue #139, against native .NET on the same machine:

| Build | Planning speed | Time to first plan | Download (brotli) |
| --- | --- | --- | --- |
| Interpreter (shipped until #139) | 17x slower | 1.22 s | 1.35 MiB |
| AOT, `Location` a struct | 14x slower | 0.78 s | 2.6 MiB |
| **AOT, `Location` a class (shipped)** | **4.3x slower** | **0.54 s** | **2.5 MiB** |

AOT alone barely helps because Mono's AOT falls back to the interpreter for generic code over value types, and `Location` is a struct by default. A `dotnet publish` of `BrowserWasm` without the property quietly gives you that middle row. The interpreter was first chosen in `docs/superpowers/specs/2026-06-18-in-browser-wasm-planner-design.md` (decision 2) for a smaller download and faster CI; the AOT publish takes about 64 s against 23 s. The `test-location-as-class` CI job tests the shipped setting on its own. Read #139 before changing either flag, and measure again after.
- `src/BlazorWebApp` - alternate Blazor host.
- `src/FactorioTools.Cli` (`System.CommandLine`) - `oil-field` subcommands `sample`, `normalize`, `sandbox`. Output assembly is `Knapcode.FactorioTools.Sandbox`.
- `src/Benchmark` - BenchmarkDotNet harness.
Expand Down
8 changes: 7 additions & 1 deletion src/BrowserWasm/BrowserWasm.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,13 @@
<OutputType>Exe</OutputType>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>

<RunAOTCompilation>false</RunAOTCompilation>
<!-- AOT on, but only pays off together with LocationAsStruct=false, which
`npm run build-wasm` passes on the command line. It cannot live here:
it is a global property that must reach the referenced projects, and
a property set in this file does not. With Location as a struct, Mono's
AOT falls back to the interpreter for generic code over it and plans
14x slower than native; as a class, 4.3x. See issue #139. -->
<RunAOTCompilation>true</RunAOTCompilation>

<WasmEnableES6>true</WasmEnableES6>

Expand Down
2 changes: 1 addition & 1 deletion src/vue/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"lint": "eslint . --fix",
"format": "prettier --write src/",
"swagger-gen": "swagger-typescript-api generate --path ../WebApp/swagger.json --output src/lib --name FactorioToolsApi",
"build-wasm": "dotnet publish ../BrowserWasm/BrowserWasm.csproj -c Release && shx rm -rf public/framework && shx mkdir -p public && shx cp -r ../BrowserWasm/bin/Release/net10.0/browser-wasm/AppBundle/_framework public/framework",
"build-wasm": "dotnet publish ../BrowserWasm/BrowserWasm.csproj --configuration Release --property:LocationAsStruct=false && shx rm -rf public/framework && shx mkdir -p public && shx cp -r ../BrowserWasm/bin/Release/net10.0/browser-wasm/AppBundle/_framework public/framework",
"test": "vitest run",
"test:watch": "vitest"
},
Expand Down
Loading