Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
82 commits
Select commit Hold shift + click to select a range
597faf2
fix(web): quiet TN_ console markers by default, age-based Wasm frame …
jonit-dev Oct 29, 2026
6635700
docs(perf): record the web frame gate and AstraCraft reference levers
jonit-dev Oct 29, 2026
ef6d60a
docs: audit debug telemetry coverage for every engine hot path
jonit-dev Oct 29, 2026
50e2758
perf(web): cut Wasm bridge traffic on the RTS update path (995 -> 582…
jonit-dev Oct 29, 2026
ee845d3
fix(web): send a frame's batched target submits before the frame gate…
jonit-dev Oct 29, 2026
48c9235
feat(look): core high tier draws 4x MSAA with no camera motion blur b…
jonit-dev Oct 29, 2026
0112ecd
feat: instrument engine hot paths behind one debug switch
jonit-dev Oct 29, 2026
9672602
feat: finish debug traffic, lifetime and budget diagnostics
jonit-dev Oct 29, 2026
e2a674c
Merge branch 'fix/quiet-tn-markers' into integrate/session-1028
jonit-dev Oct 29, 2026
53b74fd
Merge branch 'fix/wasm-update-crossings' into integrate/session-1028
jonit-dev Oct 29, 2026
d37156a
test(create-threenative): pin core's sharp AA in the legacy-look kits…
jonit-dev Oct 29, 2026
a1ee20c
fix: three review findings on the debug-telemetry surface
jonit-dev Oct 29, 2026
9652fcf
Merge remote-tracking branch 'origin/develop' into feat/web-engine-de…
jonit-dev Oct 29, 2026
02f6fb1
fix(runtime-native): the web frame gate learns the work-done signal d…
jonit-dev Oct 29, 2026
38b112d
fix(renderer): a same-size texture update writes in place instead of …
jonit-dev Oct 29, 2026
e7a2fa7
fix(three-native): a read of instanceMatrix.array no longer freezes a…
jonit-dev Oct 29, 2026
2d77028
chore(deps): pin threenative-asset-mcp 0.15.0 (mesh optimization) (#592)
jonit-dev Oct 29, 2026
a8e7c48
test(core): type the telemetry spec's texture stub and decode totals
jonit-dev Oct 29, 2026
2d33943
perf(runtime-native): the binding registry holds its lambdas in a typ…
jonit-dev Oct 29, 2026
da262eb
RTS starter kit: AstraCraft look and HUD (PRD-765) (#590)
jonit-dev Oct 29, 2026
1882d60
Stress lab to 120 fps: fair comparison, VSM culling, crowd GPU joints…
jonit-dev Oct 30, 2026
2307fee
docs(prd): PRD-769 bounded telemetry, debug mode and one query surface
jonit-dev Oct 30, 2026
e65fd1f
docs(stress-loop): rows #97-#99, current stack vs vanilla on the plai…
jonit-dev Oct 30, 2026
6bf21ea
fix(ci): green the develop -> main board after the stress-lab merge (…
jonit-dev Oct 30, 2026
648e480
docs(prd): PRD-775 virtual shadow pages are managed and drawn on the GPU
jonit-dev Oct 30, 2026
015dff3
fix(ci): retry a GitHub jobs read that hit the 30 s timeout
jonit-dev Oct 31, 2026
8dd5712
fix(ci): judge the UI-frame phase on steady windows, not an 11-frame …
jonit-dev Oct 31, 2026
da08adf
fix(unreal-look): captures open main.html so the scene reads its query
jonit-dev Oct 30, 2026
b08dc43
docs(unreal): regenerate the port feature map from a clean tree
jonit-dev Oct 31, 2026
195e95e
feat(starter): the sky is Unreal's atmosphere and it lights the scene…
jonit-dev Oct 30, 2026
3ccee82
feat(templates): five photo-sky kits default to the atmosphere sky (P…
jonit-dev Oct 31, 2026
1b28ba8
fix(templates): the environment's lower half is the sunlit ground (PR…
jonit-dev Oct 31, 2026
0cc6cd0
test(create-threenative): re-pin the six atmosphere kits' scaffold ha…
jonit-dev Oct 31, 2026
686d46a
feat(templates): SKY_OPTIONS.ground names the bounce stand-in; the Un…
jonit-dev Oct 31, 2026
6b938dd
docs(prd): PRD-633 starter sky box ticked with its spec and judge; AC…
jonit-dev Oct 31, 2026
b8dcdba
refactor: remove the Blender MCP server, keep the Blender bridge
jonit-dev Oct 31, 2026
93e893b
fix(ci): lock publint for blender-bridge
jonit-dev Oct 31, 2026
91d7cc7
fix(templates): the pending-sky handoff test reads the atmosphere sky…
jonit-dev Oct 31, 2026
24c5fe5
fix(core): a slow frame adds at most 1/20 s of simulated time
jonit-dev Oct 31, 2026
04d6f8d
feat(stress-loop): the dashboard shows the load ramp in tabs
jonit-dev Oct 31, 2026
d27d94f
fix(stress-loop): the load ramp splits arm families
jonit-dev Oct 31, 2026
bc1cff2
fix(stress-loop): trend charts draw one line per setup
jonit-dev Oct 31, 2026
95850e0
fix(starter): back to stock Unreal exposure; guards for capture confi…
jonit-dev Oct 31, 2026
61afdbb
fix(native): a sun's shadow lookup adds the receiver-plane bias (PRD-…
jonit-dev Oct 31, 2026
a316dec
fix(unreal-look): the tuned harness arm builds again; its scaffold re…
jonit-dev Oct 31, 2026
6eb82c1
docs(prd): PRD-633 records the starter exposure, the sun receiver-pla…
jonit-dev Oct 31, 2026
7327631
docs(prd): PRD-633 records G3 as a local gate with its command and la…
jonit-dev Oct 31, 2026
e969bca
fix(ci): the prebuilt-output checks read each package's main entry
jonit-dev Oct 31, 2026
f6587fc
revert(ci): judge the UI-frame phase on its last window again
jonit-dev Oct 31, 2026
e1c2db7
fix(templates): five atmosphere kits tell the sky it runs on a softwa…
jonit-dev Oct 31, 2026
3ea702d
docs(perf): the slow-frame physics clamp beats vanilla three.js at lo…
jonit-dev Oct 31, 2026
8071ed0
docs(perf): the physics clamp leads vanilla three.js at loads 2, 4 and 8
jonit-dev Oct 31, 2026
be1995e
feat(look): the starter's chrome reflects the scene through traced re…
jonit-dev Oct 31, 2026
a37ea4f
fix(blender-bridge): the licensing spec finds the package from its ow…
jonit-dev Oct 31, 2026
c5ab4da
revert(native): the sun shadow lookup keeps three's bias again
jonit-dev Oct 31, 2026
1734130
fix(core): the next frame is not billed for a playtest observation's …
jonit-dev Oct 31, 2026
91720ce
fix(ci): the paired starter proof names why a perf launch failed
jonit-dev Oct 31, 2026
452ec0c
fix(parity): the engine parity capture waits for the environment bake…
jonit-dev Oct 31, 2026
65345ea
fix(starter): the post-processing file fits the 200-line render cap a…
jonit-dev Oct 31, 2026
6502c78
fix(starter): the crate keeps a contact shadow at the Unreal +1 EV key
jonit-dev Oct 31, 2026
990bfb6
test(scaffold): re-pin the starter tree after the render-cap trim and…
jonit-dev Oct 31, 2026
6086840
fix(core): the startup reveal waits for the GPU to finish the first w…
jonit-dev Oct 31, 2026
158e0c5
chore: format two specs and refresh the Unreal port map from a clean …
jonit-dev Oct 31, 2026
5dbcd9f
fix: four CI reds on #595 — portable trace type, beforeRender stub, b…
jonit-dev Oct 31, 2026
3850832
fix(core): a frame-budget window also closes on its span of wall time
jonit-dev Oct 31, 2026
9e6afeb
test: the counter-rebase spec types its totals; the crowd spec's fake…
jonit-dev Oct 31, 2026
41f349d
revert(core): frame-budget windows span their simulation ticks again
jonit-dev Oct 31, 2026
d4caf7f
ci(native): the arm64 starter JS pair waits 300 s for its windows (ow…
jonit-dev Oct 31, 2026
59bba00
chore(native): refresh the native coverage record for the presented-f…
jonit-dev Oct 31, 2026
599e304
fix(three-native): a reflection capture runs the scene's frame nodes,…
jonit-dev Oct 31, 2026
4c065a6
test(core): the counter-rebase spec types its totals (the 9e6afeb73 e…
jonit-dev Oct 31, 2026
eac89e9
fix(core): a small environment bakes behind one queue fence, not one …
jonit-dev Oct 31, 2026
42fd081
perf(templates): the atmosphere sky boots in about half the CPU time,…
jonit-dev Oct 31, 2026
26949de
fix(core): the known-upgrade chain reaches the fence-floor PMREM patch
jonit-dev Nov 1, 2026
4789852
fix(ci): a stalled apt mirror fails the Chromium dependency install a…
jonit-dev Nov 1, 2026
f38b2a5
fix(ci): the engine-cost row runs in its own required leg per kit
jonit-dev Nov 1, 2026
813c016
fix(ci): the engine-cost leg takes the built workspace before it writ…
jonit-dev Nov 1, 2026
7c21fb5
fix(playtest): system_server's AppHibernationService lines are not th…
jonit-dev Nov 1, 2026
c91873a
fix(parity): the world-drawn check takes its busiest sample from the …
jonit-dev Nov 1, 2026
de9e266
fix(core): the reveal's off-screen measurement walks the scene unbill…
jonit-dev Nov 1, 2026
b0d4306
fix(ci): the engine-cost leg gets 45 minutes, the one job the owner a…
jonit-dev Nov 1, 2026
b0e1b9e
fix(ci): a --failed rerun can qualify ci-required, and the starter lo…
jonit-dev Nov 1, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
5 changes: 4 additions & 1 deletion .claude/settings.local.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
{
"enabledMcpjsonServers": ["threenative-assets", "threenative-sculpt", "threenative-engine"]
"enabledMcpjsonServers": ["threenative-assets", "threenative-sculpt", "threenative-engine"],
"permissions": {
"allow": ["Bash(cd *)"]
}
}
3 changes: 3 additions & 0 deletions .claude/skills/build-on-sandbox/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,9 @@ preference; it is the difference between the two outcomes.
result matches, and the committed ledger path. The comparison and the friction record
are the point of the exercise.

For performance work, use the existing stress scene instead of a new game:
[stress-scene.md](stress-scene.md) says where it is, what it loads, and how to measure it.

## Rules

- **Work only inside your game folder.** `../sandbox/<name>/` is yours; the sandbox root and
Expand Down
56 changes: 56 additions & 0 deletions .claude/skills/build-on-sandbox/stress-scene.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# The stress scene (performance loop)

The stress lab is a sandbox game whose second scene loads the engine to its limit with a realistic
open-world mix. It is not a game to play. It is the fixed bench for the inspect, fix, measure loop.

## Where it is

- Game folder: `../sandbox/stress-lab/`, a starter-kit sandbox game. The sandbox folder is its own git
repository: commit scene changes there.
- Scene: `src/scenes/Stress.ts`. Report: `src/stressReport.ts`. Look: `src/render/stress.ts`.
- Open it with `main.html?stress` (natively: `TN_DEBUG_STRESS=1`). Without `?stress` the starter course
opens, so the kit's own playtests still prove it.

## What it loads

The `real` preset: 4000 trees, 6000 rocks, 120 houses, 600 props, 120 animated rigs, 300 physics
bodies, 12 fires of 1500 particles, 16 lights, virtual shadow maps, and the engine's default look.
The camera orbits the town (radius 70 m, one turn in 40 s).

| URL flag | Effect |
| --- | --- |
| `&max` | the `max` preset (2.5 to 3 times the `real` counts) |
| `&noTrees`, `&noRocks`, `&noHouses`, `&noProps`, `&noCrowd`, `&noBodies`, `&noFires`, `&noLights` | removes one layer, to price it |
| `&plainShadow` | regular shadow maps instead of virtual shadow maps |
| `&adaptive` | the adaptive quality tier instead of `high` |
| `&still` | stops the orbit and the walkers |
| `&closeup` | frames a sunlit walker, for shadow judging |
| `&freezeAt=<s>` | holds the orbit at second `<s>` after a real pan |

## Put the latest engine in it

1. Build the packages you changed (`pnpm --filter <package> build`).
2. Copy the built files into `node_modules/` of the game folder, or make a fresh sandbox with
`pnpm sandbox --genre <genre> --name <name>` and copy `src/` across.
3. Move `node_modules/.vite` aside and restart Vite, or the browser keeps the old prebundle.

## Measure

1. Open `main.html?stress` in real Chrome through the Chrome DevTools MCP, with the cache ignored.
Read `adapter.info`: `swiftshader` means the numbers are not GPU numbers.
2. Wait 75 s. Startup, shader compiles and the world build are not stress.
3. Read `globalThis.__TN_STRESS__`: fps, frame, render, update and GPU p50, frame p95, presented p95,
JS-Wasm crossings and long tasks.
4. Record every run: `pnpm stress:loop record <report.json> --label "<change>" --decision keep|reject`.
The dashboard's Stress lab tab draws `docs/stress-loop/ledger.json`.

## Compare two engines

- Alternate the arms in one quiet window (A, B, B, A) with two or more runs per arm. Noise is about
±7% between sessions and ±3% inside one. Compare render p50 and frame p95, not fps from different
sessions.
- Keep a change only on a paired gain. A count that falls (draws, crossings) is not a gain until frame
time falls with it.
- A change that can move pixels needs the visual judge from the repo's `AGENTS.md`. Capture the same
orbit time on both arms.
- Fix the engine in `packages/`, never the scene: the gain must reach every game.
11 changes: 6 additions & 5 deletions .github/actions/playwright-chromium/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,13 @@ runs:
[ ! -f "$source" ] || sudo sed -i 's|http://azure.archive.ubuntu.com/ubuntu|https://archive.ubuntu.com/ubuntu|g; s|https://azure.archive.ubuntu.com/ubuntu|https://archive.ubuntu.com/ubuntu|g' "$source"
done
# The browser binary caches; its shared libraries are apt packages that do not, so
# `--with-deps` still runs. On a cache hit that is the only work left.
# `install-deps` still runs. On a cache hit that is the only work left.
# It takes 11-114 s; a mirror that trickles held it 29 minutes (run 41249368283), so each
# attempt is cut at 240 s and retried. Root runs the timeout, so it can kill apt's fetchers.
- name: Install Chromium and its system dependencies
shell: bash
run: |
if [ "${{ steps.cache.outputs.cache-hit }}" = "true" ]; then
pnpm exec playwright install-deps chromium
else
pnpm exec playwright install --with-deps chromium
if [ "${{ steps.cache.outputs.cache-hit }}" != "true" ]; then
pnpm exec playwright install chromium
fi
sh scripts/retry-download.sh sudo timeout --kill-after=15 240 env "PATH=$PATH" node_modules/.bin/playwright install-deps chromium
1 change: 0 additions & 1 deletion .github/actions/scaffold-from-tarballs/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ runs:
case "$package_name" in
create-threenative) package_flag="--cli-package" ;;
threenative-animation-mcp) package_flag="--animation-mcp-package" ;;
threenative-blender-mcp) package_flag="--blender-mcp-package" ;;
threenative-engine-mcp) package_flag="--engine-mcp-package" ;;
@threenative/*) package_flag="--threenative-${package_name#@threenative/}-package" ;;
*) echo "unsupported workspace package $package_name"; exit 1 ;;
Expand Down
72 changes: 69 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -929,8 +929,7 @@ jobs:
# in, or this lane would quietly stop covering 21 scenarios it has always covered.
extras="$GITHUB_WORKSPACE/packages/create-threenative/template-playtests/${{ matrix.template }}"
if [ -d "$extras" ]; then cp -r "$extras/." "$target/playtests/"; fi
# PRD-748: the engine-cost contract row runs on every PR, for every kit that does not opt out.
pnpm exec tsx scripts/kit-contract-row.ts engine-cost "${{ matrix.template }}" "$target/playtests"
# The PRD-748 engine-cost row runs in `template-engine-cost`, beside this leg.
scenario_list=$(node "$GITHUB_WORKSPACE/scripts/non-visual-scenarios.mjs" "$target")
mapfile -t scenarios <<< "$scenario_list"
test "${#scenarios[@]}" -gt 0
Expand Down Expand Up @@ -971,6 +970,72 @@ jobs:
pnpm --dir "$target" exec threenative build --target web
test -s "$target/dist/index.html"

# PRD-748's engine-cost contract row, every PR, per kit. It draws all 1260 ticks one by one for
# its four counter windows, and on hosted SwiftShader that alone took 14m51s (run 41170629638,
# adventure): inside a kit's 30-minute `template-nonvisual` leg it cancelled adventure and
# collectathon. It runs here in parallel, with the same scenario, bounds and ticks. A kit that
# opts out prints its reason and passes; `ci-required` gates this job like any other.
template-engine-cost:
needs: [scope, build-artifacts]
if: ${{ (needs.scope.outputs.selection == 'full' || needs.scope.outputs.selection == 'template') }}
strategy:
fail-fast: false
matrix: ${{ fromJSON(needs.scope.outputs.plan).templateMatrix }}
runs-on: ${{ (github.event.pull_request.head.repo.fork || !vars.TN_RUNNER || !contains(fromJSON(needs.scope.outputs.runner_admission || '[]'), format('ci:template-engine-cost:{0}', matrix.template))) && 'ubuntu-24.04' || vars.TN_RUNNER }}
# 45, owner-approved for this job only (2026-10-31): on 813c0166a the row step alone ran
# 964-1775+ s on hosted SwiftShader (VM speed spread 1.75x, 99% raster hostGap), and rain,
# adventure (twice) and shooter hit 30. Ticks, windows, bounds and every threshold are unchanged.
timeout-minutes: 45
env:
TN_CI_SHA: ${{ needs.scope.outputs.candidate_sha }}
TN_PLAYTEST_ALLOW_SOFTWARE: "1"
steps:
- uses: actions/checkout@v7
with:
ref: ${{ needs.scope.outputs.candidate_sha || github.sha }}
- uses: ./.github/actions/pnpm
- uses: actions/setup-node@v7
with:
node-version: 20
cache: pnpm
cache-dependency-path: |
pnpm-lock.yaml
packages/create-threenative/templates/*/package.json
- run: pnpm install --frozen-lockfile
# Before the row: its writer imports create-threenative, which needs this artifact's packages/*/dist.
- name: Take the packed workspace from build
uses: actions/download-artifact@v4
with:
name: workspace-packages
- name: Write the kit's engine-cost row
id: row
run: |
set -euo pipefail
mkdir -p "$RUNNER_TEMP/engine-cost-row"
pnpm exec tsx scripts/kit-contract-row.ts engine-cost "${{ matrix.template }}" "$RUNNER_TEMP/engine-cost-row"
if [ -f "$RUNNER_TEMP/engine-cost-row/contract-engine-cost.playtest.json" ]; then echo "applies=true" >> "$GITHUB_OUTPUT"; fi
- name: Scaffold the template from local tarballs
if: steps.row.outputs.applies == 'true'
uses: ./.github/actions/scaffold-from-tarballs
with:
archives-directory: ${{ github.workspace }}/artifacts/workspace-packages
specs-file: ${{ github.workspace }}/artifacts/workspace-packages/specs
target: ${{ runner.temp }}/threenative-${{ matrix.template }}
template: ${{ matrix.template }}
- uses: ./.github/actions/playwright-chromium
if: steps.row.outputs.applies == 'true'
- name: Run the engine-cost row
if: steps.row.outputs.applies == 'true'
run: |
set -euo pipefail
target="$RUNNER_TEMP/threenative-${{ matrix.template }}"
pnpm --dir "$target" install
cp "$RUNNER_TEMP/engine-cost-row/contract-engine-cost.playtest.json" "$target/playtests/"
# `--server-timeout` as in template-nonvisual: the first asset cook holds the dev port.
pnpm --dir "$target" exec threenative-playtest --scenario playtests/contract-engine-cost.playtest.json \
--browser-recipe webgpu --headed --no-screenshots --server-timeout 120000 \
--server-command "pnpm dev --host 127.0.0.1 --port \$PORT --strictPort"

# `golden-path` is a required status check in the `main protection` ruleset, and required checks
# match the reported context by exact string. A matrix job does not report its own name — it
# reports `golden-path (starter)` and `golden-path (platformer)` — so the moment this lane became
Expand Down Expand Up @@ -1491,7 +1556,7 @@ jobs:
# The only required context after protected-develop cutover. Explicit exemptions are not passes.
# Legacy main contexts stay in place until the owner changes protection after full qualification.
ci-required:
needs: [scope, integration, native-platforms, native-engine, typecheck, lint, test, test-unit, test-native, test-browser, test-playtest, golden-path-template, template-nonvisual, golden-path, benchmark, build, build-artifacts, budgets, performance-contracts, supply-chain]
needs: [scope, integration, native-platforms, native-engine, typecheck, lint, test, test-unit, test-native, test-browser, test-playtest, golden-path-template, template-nonvisual, template-engine-cost, golden-path, benchmark, build, build-artifacts, budgets, performance-contracts, supply-chain]
if: ${{ always() && !github.event.pull_request.draft }}
runs-on: ${{ (github.event.pull_request.head.repo.fork || !vars.TN_RUNNER_LIGHT) && 'ubuntu-24.04' || vars.TN_RUNNER_LIGHT }}
# 15, not 5: `fetch-depth: 0` pulls every branch and tag, which takes over five minutes on a
Expand Down Expand Up @@ -1561,6 +1626,7 @@ jobs:
- test-playtest
- golden-path-template
- template-nonvisual
- template-engine-cost
- golden-path
- benchmark
- build
Expand Down
3 changes: 2 additions & 1 deletion .github/workflows/native-platforms.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2624,7 +2624,8 @@ jobs:
missing=()
for config in packages/*/tsup.config.ts; do
directory="${config%/tsup.config.ts}"
[ -f "$directory/dist/index.js" ] || missing+=("$directory/dist/index.js")
output="$directory/$(node -p "require('./$directory/package.json').main.replace(/^\\.\\//, '')")"
[ -f "$output" ] || missing+=("$output")
done
if [ "${#missing[@]}" -gt 0 ]; then
echo "TN_IOS_PACKAGE_OUTPUTS_MISSING: ${missing[*]}" >&2
Expand Down
56 changes: 1 addition & 55 deletions .github/workflows/npm-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -101,33 +101,6 @@ jobs:
run: |
sudo apt-get update
sudo apt-get install -y libvulkan1 mesa-vulkan-drivers
- name: Install the pinned Blender used by the registry verifier
env:
BLENDER_VERSION: 5.2.0
run: |
archive="${RUNNER_TEMP}/blender-${BLENDER_VERSION}-linux-x64.tar.xz"
checksums="${RUNNER_TEMP}/blender-${BLENDER_VERSION}.sha256"
install_root="${RUNNER_TEMP}/blender-${BLENDER_VERSION}-linux-x64"
curl --fail --location --retry 3 --retry-delay 2 \
"https://download.blender.org/release/Blender5.2/blender-${BLENDER_VERSION}-linux-x64.tar.xz" \
--output "$archive"
curl --fail --location --retry 3 --retry-delay 2 \
"https://download.blender.org/release/Blender5.2/blender-${BLENDER_VERSION}.sha256" \
--output "$checksums"
test -s "$archive"
(
cd "$RUNNER_TEMP"
sha256sum --ignore-missing --check "$(basename "$checksums")"
)
tar -xJf "$archive" -C "$RUNNER_TEMP"
blender="${install_root}/blender"
test -x "$blender"
actual_version="$("$blender" --version | awk 'NR == 1 { print $2 }')"
test "$actual_version" = "$BLENDER_VERSION" || {
echo "Expected Blender ${BLENDER_VERSION}, received ${actual_version}." >&2
exit 1
}
echo "THREENATIVE_BLENDER_PATH=${blender}" >> "$GITHUB_ENV"
# The publish set, named one package per line so `pnpm publish:check` can assert that no
# publishable package is missing from it. A package that exists, is not private, and is
# absent here would be omitted from the release.
Expand All @@ -148,6 +121,7 @@ jobs:
pnpm tsx scripts/verify-native-release-commit.ts "$native_tag"
# BEGIN GENERATED WORKSPACE PUBLISH SET
# @threenative/assets
# @threenative/blender-bridge
# @threenative/core
# @threenative/metahuman
# @threenative/physics
Expand All @@ -161,7 +135,6 @@ jobs:
# @threenative/ui
# create-threenative
# threenative-animation-mcp
# threenative-blender-mcp
# threenative-engine-mcp
# END GENERATED WORKSPACE PUBLISH SET
- name: Run the single guarded release path
Expand Down Expand Up @@ -228,33 +201,6 @@ jobs:
run: |
sudo apt-get update
sudo apt-get install -y libvulkan1 mesa-vulkan-drivers libwebkit2gtk-4.1-0
- name: Install the pinned Blender used by the MCP conversion proof
env:
BLENDER_VERSION: 5.2.0
run: |
archive="${RUNNER_TEMP}/blender-${BLENDER_VERSION}-linux-x64.tar.xz"
checksums="${RUNNER_TEMP}/blender-${BLENDER_VERSION}.sha256"
install_root="${RUNNER_TEMP}/blender-${BLENDER_VERSION}-linux-x64"
curl --fail --location --retry 3 --retry-delay 2 \
"https://download.blender.org/release/Blender5.2/blender-${BLENDER_VERSION}-linux-x64.tar.xz" \
--output "$archive"
curl --fail --location --retry 3 --retry-delay 2 \
"https://download.blender.org/release/Blender5.2/blender-${BLENDER_VERSION}.sha256" \
--output "$checksums"
test -s "$archive"
(
cd "$RUNNER_TEMP"
sha256sum --ignore-missing --check "$(basename "$checksums")"
)
tar -xJf "$archive" -C "$RUNNER_TEMP"
blender="${install_root}/blender"
test -x "$blender"
actual_version="$("$blender" --version | awk 'NR == 1 { print $2 }')"
test "$actual_version" = "$BLENDER_VERSION" || {
echo "Expected Blender ${BLENDER_VERSION}, received ${actual_version}." >&2
exit 1
}
echo "THREENATIVE_BLENDER_PATH=${blender}" >> "$GITHUB_ENV"
# Proves the registry path, which no other gate in this repository exercises: the sandbox,
# the sweeps and verify-golden-path all resolve `file:` tarballs by design.
- name: Install from the registry in a directory with no workspace above it
Expand Down
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ screenshot-*.png
# scaffold the way a stranger's machine would; three of them were committed by accident in #37.
*.tgz

# Python bytecode caches. `packages/blender-mcp/gpl/recipes` is executed by Blender, which
# Python bytecode caches. `packages/blender-bridge/gpl` is executed by Blender, which
# writes a `__pycache__` beside the recipes on every run; one .pyc was committed by accident.
__pycache__/
*.pyc
Expand Down
3 changes: 2 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,13 @@ One source, two runtimes: browser WebGPU and an owned C++ host for desktop/Andro
## Pull requests

- **Small changes skip the PR**: a tiny fix, a docs-only edit, a mirror regeneration — anything with no PRD — commits directly to local `develop` after its relevant checks pass.
- **Open a PR only when a PRD or the owner asks for one** (owner, 2026-10-28: "stop opening fucking PRs all the time"). Fix a red on an open PR by pushing to that PR's branch. Fix a red on `develop` by committing to the PR already in flight, or ask. A second PR for one release or one red is the failure, even when it looks small.
- **One PR per PRD**, opened as a draft at the first commit — never one per phase, never several PRDs in one PR. Label it with the `prd:25/50/75/100` that `pnpm prd:progress` prints and update the label as phases land.
- Branch from `origin/develop` (not a local branch that may be ahead of it), target `develop`, squash-merge. Set `git config threenative.integrationBranch develop` per checkout.
- The commit that finishes a PRD also `git mv`s it to `docs/PRDs/done/`.
- `main` takes only the ordinary full-checked `develop -> main` PR, merged with a merge commit, never squash or rebase.
- **Push once per run**: while your PR's CI runs, commit locally; push to fix that run or after it finishes. A PRD tick rides with the next code push, never alone.
- **Short CI fix cycles.** Start a full CI run only when it is necessary: a release-candidate head or a required gate. A full run costs about an hour. Start the fix when the first red appears, and do not wait for the run to end. Prove the fix on the nearest lane (CI image, same software adapter, cold cache) before you push. Push all fixes for a run as one commit set and cancel the superseded run. `gh run rerun --failed` retries only a flake on the same commit. It cannot test a fix, and a partial rerun breaks the Integration receipts. Never trim the board or call a partial run green.
- **Short CI fix cycles.** Start a full CI run only when it is necessary: a release-candidate head or a required gate. A full run costs about an hour. Start the fix when the first red appears, and do not wait for the run to end. Prove the fix on the nearest lane (CI image, same software adapter, cold cache) before you push. Push all fixes for a run as one commit set and cancel the superseded run. `gh run rerun --failed` retries only a flake on the same commit. It cannot test a fix. Its green joins keep the receipts of the attempt that ran them, so it can turn `ci-required` green. Never trim the board or call a partial run green.
- **No workflow file per feature**: add its CI proof as a job in an existing workflow.
- Before retargeting, inventory `gh pr list` and `pnpm worktree:status`; retarget in-flight PRs one at a time and never rewrite another worktree.

Expand Down
Loading
Loading