Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
05ccfe6
docs(prd): track complete-world startup and foliage acceptance
jonit-dev Oct 15, 2026
e579758
docs(prd): link startup pipeline dependency to its open PR
jonit-dev Oct 15, 2026
6d54003
Measure Strata reveal window and record incomplete baseline setup
jonit-dev Oct 15, 2026
88b988c
Observe actual WorldCells batch publication for Strata reveal baseline
jonit-dev Oct 15, 2026
1d9c626
Record frozen PRD618 baseline and capture lock blocker
jonit-dev Oct 15, 2026
3ccb616
Hold startup cover through visible world admission and settled shadow…
jonit-dev Oct 15, 2026
99757e4
fix(world): preserve foliage cards and dither authored LOD transitions
jonit-dev Oct 15, 2026
f64ae54
fix(strata): stage native asset manifest and justify reveal invariants
jonit-dev Oct 15, 2026
42ac0c3
fix(core): fail closed when startup settlement exceeds frame window
jonit-dev Oct 15, 2026
aa545be
fix(strata): fit complete spruce needles inside atlas cells
jonit-dev Oct 15, 2026
5d3817a
test(strata): record native reveal proof and fix fixture typing
jonit-dev Oct 15, 2026
4c5cc81
fix(strata): isolate native asset staging from web manifest
jonit-dev Oct 15, 2026
7701cd7
fix(core): cancel startup readiness when game stops
jonit-dev Oct 15, 2026
54890e6
fix(strata): migrate legacy staging manifests across asset revisions
jonit-dev Oct 15, 2026
085d01e
docs(prd618): record leaf qualification and final terrain failure
jonit-dev Oct 15, 2026
18b51b1
fix(core): reuse Three instance columns for world LOD masks
jonit-dev Oct 15, 2026
35576af
perf(core): reuse unchanged readiness cell visibility
jonit-dev Oct 15, 2026
31e61be
docs(prd618): reopen final startup gates and require CI regression guard
jonit-dev Oct 15, 2026
452de2e
ci: gate Strata startup against pinned reviewed baseline
jonit-dev Oct 15, 2026
c7944ed
fix(ci): compare Strata dependency identity and resolve primary baseline
jonit-dev Oct 15, 2026
28e6587
docs(prd618): record CI rejection proof and pending runtime qualifica…
jonit-dev Oct 15, 2026
188d96c
docs(prd618): record remaining startup regression after visibility fix
jonit-dev Oct 15, 2026
9cb3fcd
fix(ci): satisfy strict Strata guard index contracts
jonit-dev Oct 15, 2026
e64fb16
fix(test): type WGSL accumulator and record current failed visual gate
jonit-dev Oct 15, 2026
b69509f
fix(core): drain covered impostor atlas views without frame gaps
jonit-dev Oct 15, 2026
f281d95
docs(prd618): record current paired startup failure
jonit-dev Oct 16, 2026
919dc03
perf(core): reuse unchanged scheduled instance history
jonit-dev Oct 16, 2026
b25bb33
test(scaffold): refresh fingerprints after Three history patch closure
jonit-dev Oct 16, 2026
dbcc91e
docs(prd618): record b25 paired timing failure and prepared figures
jonit-dev Oct 16, 2026
b7a03b1
docs(prd618): record bounded native camera investigation
jonit-dev Oct 16, 2026
e3b8853
docs(prd618): record native confirmation and failed final qualification
jonit-dev Oct 16, 2026
68bf379
fix(playtest): avoid unresolved subject waits in Strata reveal
jonit-dev Oct 16, 2026
e409360
fix(strata): restore baseline needle atlas silhouettes
jonit-dev Oct 16, 2026
290bf4c
fix(strata): preserve authored foliage alpha cutoff
jonit-dev Oct 16, 2026
caab6c9
docs(prd618): record final-source desktop and failed startup qualific…
jonit-dev Oct 16, 2026
53eb08e
fix(core): drain queued impostor atlases while covered
jonit-dev Oct 16, 2026
9120caf
Merge branch 'develop' into feat/prd618-complete-world
jonit-dev Oct 16, 2026
c9e7e08
docs: record reveal capture gap and rejected evidence
jonit-dev Oct 16, 2026
c8563b3
fix(strata): start reveal measurements when the cover lifts
jonit-dev Oct 16, 2026
ad7310b
Merge remote-tracking branch 'origin/feat/prd618-complete-world' into…
jonit-dev Oct 16, 2026
d871627
fix(deps): reconcile Three.js patch hash after develop merge
jonit-dev Oct 16, 2026
a31f58d
test(core): guard indexed fixture matrix access
jonit-dev Oct 16, 2026
461f7ea
fix(strata): retain an authored far LOD before impostor selection
jonit-dev Oct 16, 2026
132affc
fix(core): preserve authored cutoffs in dither material clones
jonit-dev Oct 16, 2026
863a66c
docs(prd618): record Intel runtime smoke for cutoff repair
jonit-dev Oct 16, 2026
943a245
merge develop into PRD618 and preserve startup contracts
jonit-dev Oct 16, 2026
1ca5950
fix(core): bound reflector warmup traversal across shared shader graphs
jonit-dev Oct 16, 2026
755f263
fix(core): identify invalid camera state in world readiness errors
jonit-dev Oct 16, 2026
48d8cf8
fix(native): release finished command buffers discarded by rendering
jonit-dev Oct 16, 2026
2a1538b
fix(ci): lint staged assets and preserve donor property names
jonit-dev Oct 16, 2026
c5cb2d2
fix(assets): preserve oversized foliage components during card thinning
jonit-dev Oct 16, 2026
65f7b83
fix(strata): sample terrain triangles during prop grounding
jonit-dev Oct 16, 2026
f0c4693
fix(strata): initialize temporal input before camera jitter
jonit-dev Oct 16, 2026
7967fe0
test(strata): type temporal input fixtures and record native proof
jonit-dev Oct 16, 2026
50c3e3f
docs(prd618): record full source gates and native proof
jonit-dev Oct 16, 2026
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
28 changes: 27 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -925,7 +925,7 @@ jobs:
steps:
- uses: actions/checkout@v7
with:
ref: ${{ needs.scope.outputs.candidate_sha || github.sha }}
ref: ${{ needs.scope.outputs.candidate_sha }}
- uses: ./.github/actions/pnpm
- uses: actions/setup-node@v7
with:
Expand All @@ -947,6 +947,32 @@ jobs:
# `test:browser` is the test-browser job's gate; running it again here bought nothing but
# a second copy of the slowest step in the workflow.
- run: pnpm exec playwright test --config=benchmark.playwright.config.ts
# Software startup regression contract only; hardware and visual acceptance remain separate.
- uses: actions/checkout@v7
with:
ref: 88b988cd70d5db7266eb0281e5f74bd0e030b3e7
path: .worktrees/strata-startup-baseline
- name: Build isolated reviewed Strata baseline
timeout-minutes: 5
working-directory: .worktrees/strata-startup-baseline
run: |
set -euo pipefail
pnpm install --frozen-lockfile
pnpm --filter strata-terrain-preview... build
- name: Build candidate Strata with restored candidate packages
timeout-minutes: 3
run: pnpm --filter strata-terrain-preview build
- name: Enforce Strata startup median
timeout-minutes: 9
env:
EXPECTED_SOURCE_SHA: ${{ needs.scope.outputs.candidate_sha }}
run: timeout 9m pnpm tsx scripts/verify-strata-startup.ts
- uses: actions/upload-artifact@v4
if: always()
with:
name: strata-startup-${{ needs.scope.outputs.candidate_sha }}
path: artifacts/strata-startup/
if-no-files-found: warn

# visuals is NOT a job here any more. It captures frames from a WebGPU canvas, and this runner
# has no GPU — its first execution in the repository's history failed on
Expand Down
23 changes: 23 additions & 0 deletions biome.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"artifacts/**",
"examples/strata-terrain-preview/src/world/*.json",
"examples/strata-terrain-preview/local-assets/**",
"examples/strata-terrain-preview/assets/**",
"build/**",
"release-native/**",
".linchpin/**",
Expand Down Expand Up @@ -210,6 +211,28 @@
"formatter": {
"enabled": false
}
},
{
"include": ["examples/procedural-animals/src/render/donor/core/motion/quadruped.js"],
"linter": {
"rules": {
"style": {
"useNamingConvention": {
"level": "error",
"options": {
"conventions": [
{
"selector": { "kind": "objectLiteralProperty" },
"match": "(?:(?:D|DL|T|F|H|W|U|Lf)|(.+))"
}
],
"strictCase": false,
"requireAscii": false
}
}
}
}
}
}
]
}

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions docs/verification/native-coverage-2026-08-28.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,12 @@ targets could not be built and are named below.
| `src/storage/` | 327 | 286 | 87.46% |
| `src/utils/` | 0 | 0 | 0.00% |
| `src/vfs/` | 239 | 195 | 81.59% |
| `src/webgpu/` | 9885 | 7581 | 76.69% |
| `src/webgpu/` | 9888 | 7592 | 76.78% |
| `src/webtransport/` | 1391 | 1078 | 77.50% |
| `src/workers/` | 615 | 527 | 85.69% |
| **TOTAL** | **24858** | **19489** | **78.40%** |
| **TOTAL** | **24861** | **19500** | **78.44%** |

Source digest: `sha256:1a8ed34cf063a9530e8fa11b2722ae0b4c475402ee1baef998b5dea0acd3a4d7`
Source digest: `sha256:b71a3030b463e0e2288fa81b1e556ab3fce303d3268ea3fc1786bdb9b8b316eb`

The default `pnpm budgets` gate reads this committed measurement without configuring or compiling
the native host. Any native source, native C++ test, CTest registration, or coverage aggregation
Expand Down
1 change: 1 addition & 0 deletions examples/strata-terrain-preview/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@
/dist/
/src/world/.bake-stamp
/assets/
/assets.manifest.json
153 changes: 153 additions & 0 deletions examples/strata-terrain-preview/__tests__/groundSampler.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
import { Heightfield } from "@threenative/core/world";
import { Mesh, Raycaster, Vector3 } from "three";
import { describe, expect, it } from "vitest";
import { createGroundSampler } from "../src/render/groundSampler.js";

/**
* The drawn mesh is the reference: every case compares the sampler with a stock Three `Raycaster`
* on the same geometry, never with a mirrored copy of the sampler's own formula.
*/
function sampleCase(heights: number[], size: number, rows: number, columns: number) {
const field = new Heightfield({
rows,
columns,
width: size,
depth: size,
origin: { x: 0, z: 0 },
heights: Float32Array.from(heights),
});
const mesh = new Mesh(field.toGeometry());
mesh.updateMatrixWorld(true);
const sampler = createGroundSampler(field, mesh.geometry, mesh.matrixWorld);
const ray = (x: number, z: number): number | null => {
const hit = new Raycaster(new Vector3(x, 1000, z), new Vector3(0, -1, 0)).intersectObject(
mesh,
false,
)[0];
return hit?.point.y ?? null;
};
return { field, mesh, sampler, ray };
}

const sobel = (col: number, row: number): number => ((col * 7 + row * 13) % 9) - 4;

describe("exact ground sampler", () => {
it("uses the drawn triangle planes, not the bilinear height, on a saddle", () => {
// Row-major [UL, UR, LL, LR]: opposite corners up, adjacent corners down.
const { field, sampler, ray } = sampleCase([0, 1, 1, 0], 2, 2, 2);
for (const [x, z] of [
[0.1, 0.1],
[-0.1, -0.1],
[0.25, 0.25],
[-0.25, -0.25],
]) {
const expected = ray(x as number, z as number) as number;
expect(Number.isFinite(expected)).toBe(true);
const sampled = sampler(x as number, z as number) as number;
expect(sampled).toBeCloseTo(expected, 6);
// Bilinear floats well above the drawn triangle away from the shared edge.
expect(Math.abs(sampled - field.heightAt(x as number, z as number))).toBeGreaterThan(0.2);
}
// Points on both diagonals and the shared edge agree with the raycaster.
for (const [x, z] of [
[0, 0],
[0.5, 0.5],
[-0.5, -0.5],
[0.6, 0.2],
[-0.6, -0.2],
]) {
const expected = ray(x as number, z as number) as number;
expect(Number.isFinite(expected)).toBe(true);
expect(sampler(x as number, z as number)).toBeCloseTo(expected, 6);
}
});

it("matches the raycaster on cell boundaries and interior samples of an irregular grid", () => {
const rows = 5;
const columns = 5;
const heights: number[] = [];
for (let row = 0; row < rows; row += 1)
for (let column = 0; column < columns; column += 1) heights.push(sobel(column, row));
const { sampler, ray } = sampleCase(heights, 4, rows, columns);
const points: [number, number][] = [
[-2, -2],
[0, 0],
[2, 2],
[-1, 0],
[0, -1],
[1, 0],
[0, 1],
[-1.5, 0.5],
[0.5, 1.5],
[1.25, -0.75],
[0.3, 0.3],
[-0.2, 1.1],
];
for (const [x, z] of points) {
const expected = ray(x, z) as number;
expect(Number.isFinite(expected)).toBe(true);
expect(sampler(x, z)).toBeCloseTo(expected, 6);
}
});

it("matches Float32 vertices, diagonals, and the far bound on a non-binary grid", () => {
const rows = 17;
const columns = 17;
const heights: number[] = [];
for (let row = 0; row < rows; row += 1)
for (let column = 0; column < columns; column += 1) heights.push(sobel(column, row));
const { mesh, sampler, ray } = sampleCase(heights, 10.3, rows, columns);
const position = mesh.geometry.getAttribute("position");
const points: [number, number][] = [];
for (let row = 0; row < rows; row += 1) {
for (let column = 0; column < columns; column += 1) {
const vertex = row * columns + column;
points.push([position.getX(vertex), position.getZ(vertex)]);
if (row === rows - 1 || column === columns - 1) continue;
const lowerRight = vertex + columns + 1;
points.push([
(position.getX(vertex) + position.getX(lowerRight)) / 2,
(position.getZ(vertex) + position.getZ(lowerRight)) / 2,
]);
}
}
for (const [x, z] of points) {
const expected = ray(x, z);
expect(expected).not.toBeNull();
expect(sampler(x, z)).not.toBeNull();
expect(sampler(x, z) as number).toBeCloseTo(expected as number, 6);
}
});

it("follows a translated mesh and matches the raycaster there", () => {
const { field, mesh } = sampleCase([0, 1, 1, 0], 2, 2, 2);
mesh.position.set(12, 3, -7);
mesh.updateMatrixWorld(true);
const moved = createGroundSampler(field, mesh.geometry, mesh.matrixWorld);
for (const [lx, lz] of [
[-0.25, -0.25],
[0.25, 0.25],
[0, 0],
[0.5, -0.5],
]) {
const hit = new Raycaster(
new Vector3(12 + (lx as number), 1000, -7 + (lz as number)),
new Vector3(0, -1, 0),
).intersectObject(mesh, false)[0];
expect(moved(12 + (lx as number), -7 + (lz as number))).toBeCloseTo(
hit?.point.y as number,
6,
);
}
});

it("returns null outside the grid and for a rotated topology, so the ray fallback runs", () => {
const { field, mesh, sampler } = sampleCase([0, 1, 1, 0], 2, 2, 2);
expect(sampler(2.5, 0)).toBeNull();
expect(sampler(0, -2.5)).toBeNull();
mesh.rotation.y = Math.PI / 2;
mesh.updateMatrixWorld(true);
const rotated = createGroundSampler(field, mesh.geometry, mesh.matrixWorld);
expect(rotated(0, 0)).toBeNull();
});
});
57 changes: 57 additions & 0 deletions examples/strata-terrain-preview/__tests__/temporalAA.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import assert from "node:assert/strict";
import { DataTexture, FloatType, PerspectiveCamera, RedFormat } from "three";
import TRAANode, { traa } from "three/addons/tsl/display/TRAANode.js";
import type RTTNode from "three/src/nodes/utils/RTTNode.js";
import { texture, vec4 } from "three/tsl";
import type { TextureNode } from "three/webgpu";
import { describe, expect, it } from "vitest";
import { createOutdoorTRAA } from "../src/render/sky.js";

describe("Strata temporal AA input", () => {
it("primes the actual beauty RTT and leaves automatic resizing enabled", () => {
const camera = new PerspectiveCamera();
const depthVelocity = texture(
new DataTexture(new Float32Array([1]), 1, 1, RedFormat, FloatType),
);
const input = vec4(1).mul(0.5);
const unprimed = traa(input, depthVelocity, depthVelocity, camera);
assert(unprimed instanceof TRAANode);
const unprimedBeauty = unprimed.beautyNode;
assert(isRTTNode(unprimedBeauty));
assert(unprimedBeauty.renderTarget);
expect(unprimedBeauty.width).toBeNull();
expect(unprimedBeauty.height).toBeNull();
expect(unprimedBeauty.renderTarget?.width).toBeNull();
expect(unprimedBeauty.renderTarget?.height).toBeNull();

const renderer = {
surface: () => ({
resolutionScale: 1,
scaleSource: "pinned" as const,
sampleCount: 1,
drawingBufferWidth: 640,
drawingBufferHeight: 360,
atFloor: false,
}),
};
const node = createOutdoorTRAA(input, depthVelocity, depthVelocity, camera, renderer);
assert(node instanceof TRAANode);
const beauty = node.beautyNode;
assert(isRTTNode(beauty));
assert(beauty.renderTarget);
const target = beauty.renderTarget;
expect(target.texture.width).toBe(640);
expect(target.texture.height).toBe(360);
expect(Number.isFinite(target.texture.width)).toBe(true);
expect(Number.isFinite(target.texture.height)).toBe(true);
expect(beauty.autoResize).toBe(true);

beauty.setSize(320, 180);
expect([target.width, target.height]).toEqual([320, 180]);
expect(beauty.autoResize).toBe(true);
});
});

function isRTTNode(node: TextureNode): node is RTTNode {
return "isRTTNode" in node && Reflect.get(node, "isRTTNode") === true;
}
3 changes: 2 additions & 1 deletion examples/strata-terrain-preview/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@
"build": "pnpm bake && vite build",
"build:desktop": "pnpm bake && node ../../packages/runtime-native/scripts/bundle.mjs --project . --entry src/game.ts --target desktop --output dist/strata-terrain-native.js",
"typecheck": "pnpm bake && tsc --noEmit -p tsconfig.json",
"test": "vitest run",
"test:terrain:web": "node ../../packages/playtest/dist/runner/cli.js playtests/terrain.playtest.json --url 'http://127.0.0.1:5196/?showcase=1' --server-command 'pnpm dev --host 127.0.0.1 --port 5196 --strictPort' --browser-recipe webgpu --headed --artifacts artifacts/playtest/web && node scripts/verify-ocean.mjs artifacts/playtest/web",
"test:terrain:desktop": "node scripts/stage-native-assets.mjs && pnpm build:desktop && node ../../packages/playtest/dist/runner/cli.js playtests/terrain.playtest.json --target desktop --executable ../../packages/runtime-native/build/tn-linux/mystral --host-arg run --host-arg dist/strata-terrain-native.js --artifacts artifacts/playtest/desktop && node scripts/verify-ocean.mjs artifacts/playtest/desktop && sh ../../scripts/xvfb.sh ../../packages/runtime-native/build/tn-linux/mystral run dist/strata-terrain-native.js --headless --frames 300 --screenshot artifacts/playtest/desktop/terrain-300frames.png",
"test:terrain:desktop": "node scripts/stage-native-assets.mjs && pnpm build:desktop && node ../../packages/playtest/dist/runner/cli.js ../playtests/terrain.playtest.json --project assets --target desktop --executable ../../../packages/runtime-native/build/tn-linux/mystral --host-arg run --host-arg ../dist/strata-terrain-native.js --artifacts ../artifacts/playtest/desktop && node scripts/verify-ocean.mjs artifacts/playtest/desktop && (cd assets && sh ../../../scripts/xvfb.sh ../../../packages/runtime-native/build/tn-linux/mystral run ../dist/strata-terrain-native.js --headless --frames 300 --screenshot ../artifacts/playtest/desktop/terrain-300frames.png)",
"test:terrain:editor": "node scripts/verify-editor.mjs",
"test:consumer": "node scripts/verify-consumer.mjs",
"test:kit": "node scripts/verify-starter-kit.mjs",
Expand Down
Loading