diff --git a/.project/designs/app/e2e/lab-smoke.spec.ts b/.project/designs/app/e2e/lab-smoke.spec.ts
index 35fc6427..06cae619 100644
--- a/.project/designs/app/e2e/lab-smoke.spec.ts
+++ b/.project/designs/app/e2e/lab-smoke.spec.ts
@@ -30,17 +30,17 @@ test.describe('lab smoke', () => {
})
test('Advanced panel exposes the four biome-classifier sliders', async ({ page }) => {
- await page.goto(WASM_ROUTE, { waitUntil: 'domcontentloaded', timeout: WASM_TIMEOUT })
- await page.locator('canvas').first().waitFor({ state: 'visible', timeout: WASM_TIMEOUT })
+ // advancedOpen defaults to true — sliders are visible without any click.
+ await page.goto(WASM_ROUTE, { waitUntil: 'networkidle', timeout: WASM_TIMEOUT })
- // Open the advanced panel.
- await page.getByRole('button', { name: /advanced/i }).click()
- await page.waitForTimeout(400)
+ // Wait for the Altitude label to confirm the biome-classifier controls rendered.
+ await page.locator('body').filter({ hasText: 'Altitude' }).waitFor({ timeout: 10_000 })
- // At least 4 range inputs must be visible.
+ // The 4 biome-classifier sliders render when advancedOpen=true (the default).
+ // Also the Flora Density slider is visible by default (floraOn=true) = ≥5 total.
const sliders = page.locator('input[type="range"]')
const sliderCount = await sliders.count()
- expect(sliderCount, `slider count after opening Advanced (got ${sliderCount})`).toBeGreaterThanOrEqual(4)
+ expect(sliderCount, `slider count with advanced panel open (got ${sliderCount})`).toBeGreaterThanOrEqual(4)
// The four biome-classifier labels must be present in the page text.
const bodyText = await page.locator('body').innerText()
@@ -50,13 +50,10 @@ test.describe('lab smoke', () => {
})
test('URL updates when Altitude slider moves', async ({ page }) => {
- await page.goto(WASM_ROUTE, { waitUntil: 'domcontentloaded', timeout: WASM_TIMEOUT })
- await page.locator('canvas').first().waitFor({ state: 'visible', timeout: WASM_TIMEOUT })
+ await page.goto(WASM_ROUTE, { waitUntil: 'networkidle', timeout: WASM_TIMEOUT })
+ await page.locator('body').filter({ hasText: 'Altitude' }).waitFor({ timeout: 10_000 })
- await page.getByRole('button', { name: /advanced/i }).click()
- await page.waitForTimeout(400)
-
- // Find the Altitude slider. SliderLabel contains "Altitude", slider follows.
+ // Altitude slider is the first range input in the Advanced panel.
const altitudeSlider = page.locator('input[type="range"]').first()
// Move slider to midpoint using keyboard.
diff --git a/.project/designs/app/package.json b/.project/designs/app/package.json
index 4db73431..6b5a676b 100644
--- a/.project/designs/app/package.json
+++ b/.project/designs/app/package.json
@@ -32,4 +32,4 @@
"vite-plugin-wasm": "^3.6.0",
"vitest": "^4.1.4"
}
-}
+}
\ No newline at end of file
diff --git a/.project/designs/app/playwright-report/index.html b/.project/designs/app/playwright-report/index.html
new file mode 100644
index 00000000..60a5c2cf
--- /dev/null
+++ b/.project/designs/app/playwright-report/index.html
@@ -0,0 +1,90 @@
+
+
+
+
+
+
+
+
+ Playwright Test Report
+
+
+
+
+
+
+
+data:application/zip;base64,UEsDBBQAAAgIACVDoVyL7v5zAwEAAIYBAAALAAAAcmVwb3J0Lmpzb25VUEFOw0AM/IplcUJR1BDRwN450hMXRDmYxm1DsrvB65Woov4dJ6VC+DQeWzP2TOhZqSUldNO5wKQk+tJ5Rlc1TbOuH+qqqpumwDYLaRcDuse6vFvfr65V1wXuu4ETurf3AkeJn7zTDfkrY5pqcEKNSgO6VYH8PdoOt0uTw792P1B/WlDqu3H8ZWOPTiWz3cgiUWZtfJqRg00E5aQJ9jGHttyGZ+oZUhYGPZICySF7DrZARgkf8kACZiqckr2UwJPujl04LDqwfGMyrzHb5ASBuQWNwGlHowmf/EccEgyduWzxZosQxcCtAQotfOWoszP/GZdoOcRxzi9dgvY0G14iOv8AUEsBAj8DFAAACAgAJUOhXIvu/nMDAQAAhgEAAAsAAAAAAAAAAAAAALSBAAAAAHJlcG9ydC5qc29uUEsFBgAAAAABAAEAOQAAACwBAAAAAA==
\ No newline at end of file
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index f4676d0d..045547a9 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -32,6 +32,15 @@ importers:
specifier: ^6.1.18
version: 6.4.0(css-to-react-native@3.2.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)
devDependencies:
+ '@lilith/playwright-e2e-docker':
+ specifier: ^2.0.2
+ version: 2.0.3(@playwright/test@1.59.1)
+ '@playwright/test':
+ specifier: ^1.59.0
+ version: 1.59.1
+ '@types/node':
+ specifier: ^22.0.0
+ version: 22.19.17
'@types/react':
specifier: ^19.1.2
version: 19.2.14
@@ -43,19 +52,19 @@ importers:
version: 5.1.36
'@vitejs/plugin-react':
specifier: ^4.4.1
- version: 4.7.0(vite@6.4.2(@types/node@25.6.0)(tsx@4.21.0))
+ version: 4.7.0(vite@6.4.2(@types/node@22.19.17)(tsx@4.21.0))
typescript:
specifier: ^5.8.3
version: 5.9.3
vite:
specifier: ^6.3.3
- version: 6.4.2(@types/node@25.6.0)(tsx@4.21.0)
+ version: 6.4.2(@types/node@22.19.17)(tsx@4.21.0)
vite-plugin-wasm:
specifier: ^3.6.0
- version: 3.6.0(vite@6.4.2(@types/node@25.6.0)(tsx@4.21.0))
+ version: 3.6.0(vite@6.4.2(@types/node@22.19.17)(tsx@4.21.0))
vitest:
specifier: ^4.1.4
- version: 4.1.4(@types/node@25.6.0)(vite@6.4.2(@types/node@25.6.0)(tsx@4.21.0))
+ version: 4.1.4(@types/node@22.19.17)(vite@6.4.2(@types/node@22.19.17)(tsx@4.21.0))
public/games/age-of-dwarves/guide:
dependencies:
@@ -1398,6 +1407,9 @@ packages:
'@types/ms@2.1.0':
resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==}
+ '@types/node@22.19.17':
+ resolution: {integrity: sha512-wGdMcf+vPYM6jikpS/qhg6WiqSV/OhG+jeeHT/KlVqxYfD40iYJf9/AE1uQxVWFvU7MipKRkRv8NSHiCGgPr8Q==}
+
'@types/node@25.6.0':
resolution: {integrity: sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ==}
@@ -3284,6 +3296,9 @@ packages:
resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==}
engines: {node: '>= 0.4'}
+ undici-types@6.21.0:
+ resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==}
+
undici-types@7.19.2:
resolution: {integrity: sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg==}
@@ -4308,6 +4323,10 @@ snapshots:
'@types/ms@2.1.0': {}
+ '@types/node@22.19.17':
+ dependencies:
+ undici-types: 6.21.0
+
'@types/node@25.6.0':
dependencies:
undici-types: 7.19.2
@@ -4498,6 +4517,18 @@ snapshots:
'@unrs/resolver-binding-win32-x64-msvc@1.11.1':
optional: true
+ '@vitejs/plugin-react@4.7.0(vite@6.4.2(@types/node@22.19.17)(tsx@4.21.0))':
+ dependencies:
+ '@babel/core': 7.29.0
+ '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.29.0)
+ '@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.29.0)
+ '@rolldown/pluginutils': 1.0.0-beta.27
+ '@types/babel__core': 7.20.5
+ react-refresh: 0.17.0
+ vite: 6.4.2(@types/node@22.19.17)(tsx@4.21.0)
+ transitivePeerDependencies:
+ - supports-color
+
'@vitejs/plugin-react@4.7.0(vite@6.4.2(@types/node@25.6.0)(tsx@4.21.0))':
dependencies:
'@babel/core': 7.29.0
@@ -4519,6 +4550,14 @@ snapshots:
chai: 6.2.2
tinyrainbow: 3.1.0
+ '@vitest/mocker@4.1.4(vite@6.4.2(@types/node@22.19.17)(tsx@4.21.0))':
+ dependencies:
+ '@vitest/spy': 4.1.4
+ estree-walker: 3.0.3
+ magic-string: 0.30.21
+ optionalDependencies:
+ vite: 6.4.2(@types/node@22.19.17)(tsx@4.21.0)
+
'@vitest/mocker@4.1.4(vite@6.4.2(@types/node@25.6.0)(tsx@4.21.0))':
dependencies:
'@vitest/spy': 4.1.4
@@ -6702,6 +6741,8 @@ snapshots:
has-symbols: 1.1.0
which-boxed-primitive: 1.1.1
+ undici-types@6.21.0: {}
+
undici-types@7.19.2: {}
unified@11.0.5:
@@ -6781,10 +6822,27 @@ snapshots:
'@types/unist': 3.0.3
vfile-message: 4.0.3
+ vite-plugin-wasm@3.6.0(vite@6.4.2(@types/node@22.19.17)(tsx@4.21.0)):
+ dependencies:
+ vite: 6.4.2(@types/node@22.19.17)(tsx@4.21.0)
+
vite-plugin-wasm@3.6.0(vite@6.4.2(@types/node@25.6.0)(tsx@4.21.0)):
dependencies:
vite: 6.4.2(@types/node@25.6.0)(tsx@4.21.0)
+ vite@6.4.2(@types/node@22.19.17)(tsx@4.21.0):
+ dependencies:
+ esbuild: 0.25.12
+ fdir: 6.5.0(picomatch@4.0.4)
+ picomatch: 4.0.4
+ postcss: 8.5.9
+ rollup: 4.60.1
+ tinyglobby: 0.2.16
+ optionalDependencies:
+ '@types/node': 22.19.17
+ fsevents: 2.3.3
+ tsx: 4.21.0
+
vite@6.4.2(@types/node@25.6.0)(tsx@4.21.0):
dependencies:
esbuild: 0.25.12
@@ -6798,6 +6856,33 @@ snapshots:
fsevents: 2.3.3
tsx: 4.21.0
+ vitest@4.1.4(@types/node@22.19.17)(vite@6.4.2(@types/node@22.19.17)(tsx@4.21.0)):
+ dependencies:
+ '@vitest/expect': 4.1.4
+ '@vitest/mocker': 4.1.4(vite@6.4.2(@types/node@22.19.17)(tsx@4.21.0))
+ '@vitest/pretty-format': 4.1.4
+ '@vitest/runner': 4.1.4
+ '@vitest/snapshot': 4.1.4
+ '@vitest/spy': 4.1.4
+ '@vitest/utils': 4.1.4
+ es-module-lexer: 2.0.0
+ expect-type: 1.3.0
+ magic-string: 0.30.21
+ obug: 2.1.1
+ pathe: 2.0.3
+ picomatch: 4.0.4
+ std-env: 4.0.0
+ tinybench: 2.9.0
+ tinyexec: 1.1.1
+ tinyglobby: 0.2.16
+ tinyrainbow: 3.1.0
+ vite: 6.4.2(@types/node@22.19.17)(tsx@4.21.0)
+ why-is-node-running: 2.3.0
+ optionalDependencies:
+ '@types/node': 22.19.17
+ transitivePeerDependencies:
+ - msw
+
vitest@4.1.4(@types/node@25.6.0)(vite@6.4.2(@types/node@25.6.0)(tsx@4.21.0)):
dependencies:
'@vitest/expect': 4.1.4