steps:
- name: write-version
run: |
# CI runs in a `git archive | tar -x` extraction with no .git/
# directory, so `git rev-list` returns empty here. The orchestrator
# injects git metadata via env vars instead — use those.
COUNT="${GIT_COMMIT_COUNT:-0}"
SHORT="${GIT_COMMIT_SHORT:-unknown}"
BUILD_TS=$(date -u +%Y%m%d-%H%M)
echo "v${COUNT}-${BUILD_TS}-${SHORT}" > version.txt
sed -i "s/\"version\": \"[^\"]*\"/\"version\": \"0.${COUNT}.0\"/" package.json
- name: overlay-sync
# The git archive contains pointer files where binary assets used to
# be (hyper-overlay's git-LFS-style routing). Pull real bytes from
# the publisher's pgh-sidecar before godot imports.
#
# 108 pointer files at ~1MB/s aggregate (sequential walk, per-file
# Hyperswarm setup overhead) → ~10 min minimum, often more on cold
# connection setup. Bump to 30 min until pgh overlay sync gains
# parallel fetch (filed separately).
run: pgh overlay sync
timeout: 1800
- name: import-godot
# Engine is nvidia-rtx-godot-v3 @ linux-rtx-v3-dev, at 4480abf76b (4.8.dev + upstream
# #99404 VisualShader node groups). Editor 15:14, linux template 15:23, windows
# template 15:36 on 2026-09-29, all rebuilt from that commit.
# Since b7b5b330f3 that adds, in order: GODOT_NGX_NO_DLSS=1 as a runtime DLSS
# kill switch, use_ngx_dlss defaulting ON, the DLSS-RR sky albedo guide
# tone-map (the sun disc was drawn as a black square by the demodulation
# seam), and explicit NGX render presets (RR=D, SR=K -- the Linux path set
# NONE before, so presets silently did nothing while they worked on Windows).
# ⚠ The TEMPLATES are what the shipped game runs, not the editor. Rebuilt
# 2026-09-21: linux 16:49, windows 16:54, both after the last engine edit.
# A stale template ships old behaviour while every check here still passes.
# ⛔ VERIFY THE WINDOWS TEMPLATE MTIME, do not trust scons' exit code: on
# this rebuild scons recompiled the changed objects, relinked BOTH static
# libraries, printed "done building targets" and exited 0 -- without ever
# linking godot.windows.template_release.x86_64.exe. The .exe stayed 22
# minutes older than the source it supposedly contained. Deleting the .exe
# forces the link. It only ships on v* tags, so a stale one hides until a
# tagged release.
# NOTE THE DIFFERENT CHECKOUT: nvidia-rtx-godot-v3/, not nvidia-rtx-godot/.
# The v2 tree (Godot 4.7-beta, linux-rtx-v2-dev) still exists but nothing in
# this file uses it any more -- Linux AND Windows both build from v3.
#
# The BLAS fix is still load-bearing for RT in RELEASE builds -- max_index is
# populated only under DEBUG_ENABLED, so template_release stores an "unknown"
# sentinel and blas_create's unconditional assert failed EVERY indexed mesh,
# and editor builds never reproduce it (do not "verify" an RT change in the
# editor). But on THIS engine it no longer comes from our reapplied commit:
# NVIDIA landed the same fix independently in the 4.8 base, spelled
# 0xFFFFFFFFu rather than our 0xFFFFFFFF, so our three BLAS commits
# (fix + revert + reapply) were dropped from the port as redundant. Grepping
# for one spelling will falsely report the other missing.
# Verified on this engine 2026-09-21: a template_release export path-traces a
# scene with geometry and a cast shadow, i.e. the release-build failure this
# paragraph exists to prevent does not occur.
#
# NOTE: the engine binaries are untracked build artifacts, so this comment is the
# ONLY record of which engine built a release. Update it when you rebuild.
#
# Both hyper-games #10 blockers are FIXED as of this engine (do not re-diagnose
# them from a stale comment — this one said they were open for a week after they
# were closed):
# - the RT bindless table now has typed texture2DArray/3D/Cube/CubeArray
# entries, so sampler2DArray materials (liquid_warp, dc_level) compile
# - build_tlas's null uniform set is guarded by uniform_set_is_valid() before
# the trace dispatch, instead of binding it and crashing
# Path tracing is exposed in settings and defaults OFF; Render Scale defaults 100%.
#
# godot-rtx (our NVIDIA-RTX/godot fork), NOT the system godot4 (4.6.stable).
# The fork is now Godot 4.8-dev, rebuilt on NVIDIA-RTX/godot's own 4.8 release
# branch (nvidia-pt-dlss @ 135dff3887) with our 28 commits replayed on top --
# the Linux NGX DLSS backend, the RT correctness fixes, and the Vulkan
# device-creation guards. It still provides AreaLight3D (4.7+, PR #108219) and
# Environment.pathtracing_enabled.
# project.godot still declares config/features=["4.7"]. That is cosmetically
# stale, not load-bearing: a 4.8 run does not rewrite it, and the full gate
# suite passes on this engine (27/28, the one failure -- gate-nav-end-to-end --
# fails identically on the 4.7 engine and is unrelated to the port).
# All five GDExtensions load unmodified; their compatibility_minimum is 4.3/4.4,
# so no godot-cpp rebuild was needed.
# Absolute path required: CI runs from a `git archive` extraction with no PATH
# guarantees, which is why every other host path in this file is absolute too.
#
# ⛔ `|| true` is for the ERRORS a first import reports about assets it is still
# generating -- NOT for the engine dying. This engine's headless import aborts
# intermittently (SIGABRT in a worker thread right after the benchmark CSVs:
# 1 of 3 fresh imports of 17fde44 on 2026-09-29; the other 2 exited 0). The
# swallowed crash left assets unimported and gate-map-wiring failed 30 steps
# later on "non-existent resource", which reads as a broken map, not a crash.
# So: exit >= 128 (killed by a signal) is retried once, and a second crash fails
# HERE, naming itself.
run: |
GODOT=/storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64
for attempt in 1 2; do
"$GODOT" --headless --import; rc=$?
[ "$rc" -lt 128 ] && exit 0
echo "import-godot: the engine was killed by signal $((rc - 128)) (attempt $attempt)"
done
echo "ERROR: headless import crashed twice -- see the backtrace above"; exit 1
timeout: 1200
- name: gate-proc-graphs
# Every res://graphs/*.tres must load with all nodes present and bake to a
# mesh. graphs/terrain.tres shipped from 2026-03-27 to 2026-09-04 with
# `nodes` holding null in slot 3 while `connections` still routed two links
# into node 3 — it loaded, looked right in the inspector, and evaluated to
# nothing. A null node is not an error to Godot: ProcNodeGraph::_set("nodes")
# pushes whatever the array holds, so only baking catches it.
#
# HERE, and not later: this is the first step at which the project is
# imported and the GDExtensions are loaded, and it is upstream of the
# ~10-minute export. A broken graph aborts the deploy before that cost, and
# before pgh-publish, so the live version is untouched.
#
# --headless is correct for this one (unlike export-godot, which needs a
# real Vulkan device for the shader baker): the gate sets use_gpu = false
# and bakes on the CPU path, which is the path being verified.
#
# No `|| true`. import-godot swallows its status because a first import
# reports errors for assets it is in the middle of generating; this step has
# no such excuse and is meant to stop the build.
run: "/storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . --script res://scripts/gate_terrain_graph.gd"
timeout: 300
- name: gate-containment
# An entity must die with its container. "Survive" means three things declared
# in three different places -- statebase keys, scene-tree node, and whoever runs
# the brain -- and this checks all three against ONE map change.
#
# It guards a regression that already shipped once: creature visuals were
# parented to /root/Game while their listener lived in the map scene, so they
# outlived every map change AND became unreclaimable, because _creatures (the
# only handle) died with the listener while _remove_visual stayed the sole path
# that frees one. The statebase rows were map-scoped the whole time -- scoping
# controls the key NAME, not node lifetime. See spec/bot-testbed.md (e1).
#
# Cheap to run (no GPU, no import of a terrain map) and it fails loudly, so it
# belongs in front of every deploy rather than in a manual checklist.
run: "/storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . --script res://scenes/test/gate_containment.gd"
timeout: 300
- name: gate-bot-registry
# Containment must stay DERIVED from one declaration rather than remembered in
# three places. Most of what this guards is the REFUSALS: an unknown binding
# silently means "this container never tears me down", which is the creature
# leak returning through the front door, and a body we cannot build must not
# register as though we could.
run: "/storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . --script res://scenes/test/gate_bot_registry.gd"
timeout: 300
- name: gate-bot-end-to-end
# The pieces are unit-covered; this is the only thing asserting they COMPOSE --
# registering a bot compiles and runs a brain, the brain's writes reach shared
# state, and a map change tears the whole chain down INCLUDING the state the bot
# claimed. That last assertion is the one worth the build time: containment on
# the roster is easy, containment on the consequence is what the creature leak
# actually violated.
run: "/storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . --script res://scenes/test/gate_bot_end_to_end.gd"
timeout: 300
- name: gate-input-sync-root
# player_procedural.tscn's InputSynchronizer shipped with NO root_path, so it
# defaulted to ".." -- the InputSync node it hangs under -- while its config
# addressed "InputSync:input_direction". It therefore looked for a child named
# InputSync INSIDE InputSync, failed, and logged every frame for as long as a
# peer was connected. player.tscn had the line and was fine, so the defect only
# showed with use_procedural_player, which is to say always.
#
# A missing line in a .tscn is invisible to every other check we have: the
# scene loads, the game runs, and the only symptom is console spam that looks
# like a netcode problem.
run: "/storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . --script res://scenes/test/gate_input_sync_root.gd"
timeout: 300
- name: gate-reconnect-rearm
# A peer that restarts must be able to dial the room again. It could not:
# public keys were learned ONLY in _on_peer_connected, held in the in-memory
# _peer_keys, and cleared by reset_for_rejoin, so swarm_join_peer was
# reachable only for a peer already met this session. After a restart there
# was nothing to call it with.
#
# The retry was never the missing part -- SwarmManager::_select_timer sends
# EXPLICIT peers to the long retry timer for ever and drops non-explicit ones,
# and swarm_join_peer is what marks a peer explicit. So the fix publishes the
# stable DHT key into durable presence and re-arms from there, which also lets
# a peer dial members it has NEVER met.
#
# Checks the key survives into the presence entry and that game.gd re-arms
# from somewhere other than the disconnect handler. Unlike its neighbours this
# gate exits non-zero on failure.
run: "/storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . --script res://scenes/test/gate_reconnect_rearm.gd"
timeout: 300
- name: gate-script-host-res
# GDScript exports as .gdc binary tokens, so ScriptHost's FileAccess+compile
# read of a res:// path returned error 7 for every shipped brain in an
# exported build. bots/*.gd reached the pck in 0.1141.0 and still could not
# run. hyper-games #77 item 3.
#
# ⛔ The real condition exists ONLY in an exported build: from the source
# tree res://bots/*.gd are ordinary .gd files, so the functional half of this
# gate passes with or without the fix. The structural half -- that run_file
# has a res:// branch using load() -- is what actually guards it, and it
# carries a self-check. Read the gate's header before weakening it.
run: "/storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . --script res://scenes/test/gate_script_host_res.gd"
timeout: 300
- name: gate-headless-map
# A headless peer's map was a hardcoded branch loading map_server, and
# spec/bot-testbed.md (e1) lists "a map argument for headless, so a peer-bot's
# map is a declaration rather than a hardcoded branch" as an unbuilt
# obligation of the containment work.
#
# The second defect in the same six lines was silent and worse:
# current_map_path was never updated, so GameState.current_map_id was seeded
# from a map that was NOT loaded, and every map-scoped key on a headless peer
# -- world:bots: included -- was namespaced to the wrong map. Bot containment
# is map-scoped, so it landed on the peer the bots run on.
run: "/storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . --script res://scenes/test/gate_headless_map.gd"
timeout: 300
- name: gate-admin-cmd-applied
# admin:cmd: is a DURABLE route, so the room replicates every command ever
# issued, and _seen_seqs (the re-dispatch guard) is in-memory and empty in a
# fresh process. Whoever became authority on join re-executed the entire
# history -- ashatron replayed stortron's walker and probe commands against
# files that only exist on stortron. hyper-games #77 item 5.
#
# ⛔ The gate's seq fixtures are copied VERBATIM from the live room. A TTL
# was implemented first and covered 0 of the 9 real commands, while passing
# a gate written with synthetic TUI-shaped seqs. Do not replace them with
# invented ones.
run: "/storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . --script res://scenes/test/gate_admin_cmd_ttl.gd"
timeout: 300
- name: gate-bot-call-scope
# BotRegistry declared every admin method "local", so every peer that saw the
# admin:cmd: ran it. A body=none brain driving the local player therefore
# walked EVERY peer's avatar and took every player's keyboard. It was wrong
# for body=position too: remote peers already build the marker from the
# replicated rows, so running register per-peer starts a second simulator for
# one bot. hyper-games #77 item 6.
run: "/storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . --script res://scenes/test/gate_bot_call_scope.gd"
timeout: 300
- name: gate-bot-key-scope
# world:bots: is map-scoped unconditionally, so a room- or session-bound bot
# got a MAP-SCOPED durable row: its node survives a map change while the row
# announcing it is dropped with the map. Binding and namespace disagreeing for
# exactly the non-map bindings — the three-lifetimes problem spec/bot-testbed.md
# (e2) exists to prevent, in the one case it did not cover.
#
# Found by standing up the bot matrix in a live room, not by a gate.
run: "/storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . --script res://scenes/test/gate_bot_key_scope.gd"
timeout: 300
- name: gate-bot-marker
# Every position body was the same grey box, so a room of bots was a room of
# identical boxes and the testbed could not be read by looking at it. Colour
# carries the containment binding (what a map change is meant to prove),
# emission carries "a brain is running", shape derives from the body, and a
# Label3D spells out the declaration.
#
# ⚠ Arity is checked before calling _make_marker: has_method() is true for the
# old one-argument version, and calling it with four raises a SCRIPT ERROR
# that aborts the gate before any FAIL is recorded — which is exactly how the
# first run of this gate printed PASS against a registry with none of it.
run: "/storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . --script res://scenes/test/gate_bot_marker.gd"
timeout: 300
- name: gate-bot-spawn-spread
# register() wrote [0,0,0] for every position body and never touched the
# node's transform, so bots with brain=none stacked into one box at the
# container origin and the whole matrix read as a single bot.
#
# The offset is derived from the id so every peer agrees without a round trip:
# a remote peer builds the marker BEFORE any position row arrives, and if the
# two sides chose independently the body would jump when the first row landed.
#
# This gate earned its place twice while being written: it caught a ring
# placement putting two ids 0.31 apart, and then an exact cell collision
# between "a" and "b" from taking both lattice axes out of FNV's low bits.
run: "/storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . --script res://scenes/test/gate_bot_spawn_spread.gd"
timeout: 300
- name: gate-objects-without-level
# refold() returned {} before touching `objects` whenever there was no DCLevel,
# conflating "this map has no brush level" with "there is nothing to fold". It
# never showed because map_dc_level is the only map that instantiates
# SceneObjects and it also has a DCLevel. Bringing the object system to
# map_flat -- a bare ground plane -- makes every object.create journal,
# replicate, and then silently never build, on every peer.
run: "/storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . --script res://scenes/test/gate_objects_without_level.gd"
timeout: 300
- name: gate-bevelled-block
# The block a builder bot stacks: 10 x 10 x 20, chamfered, and ONE ArrayMesh
# shared by every instance. "Instanced" is the load-bearing word — building the
# mesh per instance looks identical on screen and is a copy per block in
# memory, which is invisible without asking.
run: "/storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . --script res://scenes/test/gate_bevelled_block.gd"
timeout: 300
- name: gate-map-wiring
# Every map loads, every teleporter's target_map names a map that EXISTS,
# and no two pads in a map overlap. target_map is a STRING: a renamed or
# moved map breaks nothing until a player walks into the pad. Also pins the
# rule that map_flat carries no DCTerrain -- a second child answering
# nav_find_path with a disjoint graph is what left the bots inert.
run: "/storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . --script res://scenes/test/gate_map_wiring.gd"
timeout: 180
- name: gate-settings-dump
# Ctrl+Shift+F5's SettingsDump must report exactly what was tuned at runtime --
# a node property and a property INSIDE a resource, each with the file to edit
# -- and nothing untouched. A cached load of the scene would return the very
# instance that was edited and report no changes at all.
run: "/storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . --script res://scenes/test/gate_settings_dump.gd"
timeout: 180
- name: gate-map-flat-objects
# map_dc_level was the only map that instantiated SceneObjects, so "building"
# and "the Quake brush level" were one thing by accident of where the wiring
# lived. Instances the REAL map_flat.tscn: the wiring under test is in that
# scene's script, so a fixture would assert against itself.
run: "/storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . --script res://scenes/test/gate_map_flat_objects.gd"
timeout: 300
- name: gate-builder-bot
# The builder stacks one block per interval, each a course above the last, and
# STOPS at its bound — an unbounded builder writing a durable journal is a leak
# that replays on every join, not a testbed.
run: "/storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . --script res://scenes/test/gate_builder_bot.gd"
timeout: 300
- name: gate-brain-params
# BotRegistry handed ScriptHost exactly {"bot_id": id}, so every published
# brain was hard-coded and changing a builder's block count meant editing the
# brain rather than the registration. The brain dictionary gains `params`.
#
# bot_id is applied LAST and wins: params arrive from an admin:cmd, so a
# caller could otherwise pass {"bot_id": "someone-else"} and have the brain
# act as another bot, since move() and despawn() are keyed on it.
run: "/storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . --script res://scenes/test/gate_brain_params.gd"
timeout: 300
- name: gate-chat-names
# keet-daemon's `read` is flat {memberId, name, text}; the overlay read
# msg.sender.displayName and showed "?" for every received line, and echoed
# its own sends under a hard-coded "Player" that the poll then duplicated.
# A nameless member reads "anon-<4 of member id>", never "?", "Player" or a
# key fragment (the daemon still fills a missing name with the id prefix).
# HYPERTPS_DAEMON_TCP points KeetService at a dead port: no daemon needed. (#89)
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . --script res://scenes/test/gate_chat_names.gd"
timeout: 300
- name: gate-purge-confirm
# The room list's "Purge" called room.purge (= core.leaveRoom: rejoining
# needs a new invite) on the first click. It now opens a confirmation that
# names the room; only OK purges, cancel does nothing. KeetService.room_mgr
# is a recording stub, so no daemon is touched. (#93 §1)
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . --script res://scenes/test/gate_purge_confirm.gd"
timeout: 300
- name: gate-home-button
# Resume is now HOME: this identity's own keet room (keet-daemon home.get,
# docs/home-world-design.md §3). Button states (waiting / daemon down /
# unavailable with the error / Home), the home room on the OFFLINE peer with
# no mesh, leaving home ending that session, no Purge for home.
# KeetService.room_mgr is a recording stub; no daemon or network is touched.
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . --script res://scenes/test/gate_home_button.gd"
timeout: 300
- name: gate-save-banner
# With no keet-daemon, edits live only in GameState's memory and are lost on
# quit. The HUD now says so while a room (Home included) is selected, and
# names a durable write that was refused. Signals emitted by hand; no daemon.
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . --script res://scenes/test/gate_save_banner.gd"
timeout: 300
- name: gate-home-starter
# A release's starter map (res://home/<map>.home.json, made by the dev's
# tools/release-home-map.sh) is copied into a player's HOME once per map:
# positions/undone dropped, only absent keys written, the marker only when
# every write landed, deferred until the role (admin at Home) is known, never
# into another room. docs/home-world-design.md §6.
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . --script res://scenes/test/gate_home_starter.gd"
timeout: 300
- name: gate-param-shapes
# Parametric shapes (docs/parametric-shapes-design.md): stairs / spiral stairs /
# tunnel generators yield pieces the REAL brush store accepts (control: an
# inside-out box is refused), and the fold turns shape.* records into brushes —
# deterministic, per-param last-write-wins, xform, Make Real (detach), undo.
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . --script res://scenes/test/gate_param_shapes.gd"
timeout: 300
- name: gate-shape-journal
# The same shapes through the REAL journal: persist.record(shape.*) ->
# _to_record -> ShapeFold -> DCLevel. Catches a kind/field _to_record drops,
# and the by-name restore trap (pieces must be renamed when the shape changes).
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . res://scenes/test/gate_shape_journal.tscn"
timeout: 300
- name: gate-identity-flow
# In-game keet identity (#89 §3.2/§5): an unnamed identity gets a required
# first-run step (name + vault password) → vault.setup, identity.setName;
# Back up / change password, Recovery phrase, Restore (identity-manager.import
# — a NEW identity, the current one kept; never vault.import), rename on
# Enter. A headless run is never blocked. Scripted stub client, no daemon.
# Live counterpart: scenes/test/_probe_identity_flow_live.gd (real daemon
# over TCP, keet-daemon test/harness-tcp-scratch.mjs).
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . --script res://scenes/test/gate_identity_flow.gd"
timeout: 300
- name: gate-nameplate
# Nameplates show the VERIFIED keet member's ROSTER name (#89 §3.3): each
# peer proves its mesh key belongs to its keet member (daemon identity.attest),
# the receiver verifies against the handshake-authenticated key
# (identity.verify) — sent per connection, so late joiners get it; forged and
# replayed proofs stay anon; renames follow the roster. The any_peer
# _set_player_name RPC, player:name: and the "Player" default are gone. Bots
# bound to a peer read "<peer>.bot", others "<id>.bot". Stub daemon; live
# counterpart: scenes/test/_probe_peer_identity_live.gd (two real daemons).
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . --script res://scenes/test/gate_nameplate.gd"
timeout: 300
- name: gate-graphics-presets
# Settings: a Low / High preset row (an action — writes its keys as overrides,
# reads back only while all match; never stored) and a Volumetric Clouds
# switch that toggles SunshineCloudsGD.enabled, never forcing on an effect the
# addon disabled itself (#93 §2). Measured on a 2070: clouds on 9.5 ms GPU,
# off 0.2 ms (scenes/test/_probe_clouds_switch_cost.gd, needs a display).
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . --script res://scenes/test/gate_graphics_presets.gd"
timeout: 300
- name: gate-mountain-stats-worst
# [MountainStats] worst= took max(delta), and the engine clamps delta to
# max_physics_steps_per_frame × physics step (~133 ms): a 4117 ms stall printed
# 135 ms. It now uses the wall clock, like the hitch logger. (#93 §2)
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . --script res://scenes/test/gate_mountain_stats_worst.gd"
timeout: 300
- name: gate-brush-maps
# Every demo map builds brushes (user decision 2026-10-02): Mountain, the hub
# (map_test), Erosion and Flat all run map_dc_level with level_import off -- an
# empty brush level -- and carry a DCLevel; in the tree, each journal folds into
# its own DCLevel. Flat's own journal/objects pair became the inherited one.
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . --script res://scenes/test/gate_brush_maps.gd"
timeout: 300
- name: gate-terrain-panel-restore
# Saved / remote / map-change terrain knobs reach the GENERATOR on generator
# maps. The panel saved through `_target` (the generator) but restored onto the
# DCTerrain node, which has none of those knobs: Erosion's terrain settings
# moved the slider and never reached the world. Fails if < 6 checks ran.
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . --script res://scenes/test/gate_terrain_panel_restore.gd"
timeout: 300
- name: gate-journal-texture
# Texture intent through the REAL journal path (#94 P1): record -> GameState ->
# LevelPersistence._to_record -> DCLevel decode -> fold -> get_brush and the
# derived projection table (import rows first, pack layers, missing layer,
# tile size, dedup). Also guards brush.create's clearance + group, which
# _to_record silently dropped until 2026-10-02 (refolds made clearance volumes
# solid). Scene gate: level_persistence.gd names GameState at compile time.
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . res://scenes/test/journal_texture_gate.tscn"
timeout: 300
- name: gate-operators
# The operator contract (nothing written before confirm, escape first-wins,
# switching tools cancels) and every shape the box tool makes. Since #94 P1
# step 4 also gate 6: a new box carries the map's CURRENT texture on every
# plane, in the record AND on the local brush; arch clearance stays untextured.
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . res://scenes/test/operator_gate.tscn"
timeout: 300
- name: gate-editor-e0
# Editor redesign E0 (docs/editor-redesign.md §1-2): a focused text field is an
# exclusive `text_input` context (no builder key fires, movement stops reading
# the keyboard); OperatorStack pushes `tool`, and while it is up without RMB the
# REAL fly camera ignores Space/Ctrl/Shift (measured: rises 0 m vs ~7 m); one
# Shortcut per action shows the live key; Keymap.run reaches the same dispatch.
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . --script res://scenes/test/gate_editor_e0.gd"
timeout: 300
- name: gate-editor-e1
# Editor redesign E1 on a real map_flat: four collapsible regions (top / bottom /
# left / right, ▸/▾ each, saved per player), the top bar (menus generated from the
# keymap with live accelerators, select modes, grid, texture lock — all acting via
# Keymap.run, read back from the map), the toolbar (tooltips = label + live key) and
# the generated status bar (idle hints, tool-up hints, grid / selection).
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . --script res://scenes/test/gate_editor_e1.gd"
timeout: 300
- name: gate-editor-e2
# Editor redesign E2 (snapping) on a real map_flat: grid 0.25-256u; snap toggle
# (top-bar magnet + Shift+Tab), OFF = 1-unit steps; while a tool is up Ctrl
# inverts and Shift = 1/8 — and only then; operators and placement snaps read
# the effective units; units u|m drive the grid dropdown and the status bar.
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . --script res://scenes/test/gate_editor_e2.gd"
timeout: 300
- name: gate-editor-e3
# Editor redesign E3 helpers on a real map_flat. E3a: the F3 command palette lists
# every action reachable now (from the keymap: label, menu, live key), fuzzy search,
# Enter runs it through Keymap.run (the key path), Escape closes, recents remembered.
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . --script res://scenes/test/gate_editor_e3.gd"
timeout: 300
- name: gate-uv-controls
# UV controls (#94 §9, docs/uv-controls-design.md) on a real map_flat: fit / justify /
# align / nudge / mode / freeze / reset each change only their masked fields, land
# locally AND in the journal (refold agrees), brush selections group records; the
# face_sel keys through the real handler; the Face panel's buttons and read-outs.
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . --script res://scenes/test/gate_uv_controls.gd"
timeout: 300
- name: gate-hotspot
# Hotspot faces (#94 P2): Fit rect picks a fitting strip of the composed industrial
# sheet, re-roll cycles the near-equal strips, pin holds through re-rolls AND a refold
# (level_persistence keeps rect/seed), textures without rects are skipped, panel buttons.
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . --script res://scenes/test/gate_hotspot.gd"
timeout: 300
- name: gate-selection-outline
# Selection is outlined, not washed: no fill for brushes, active #ffa028 3 px vs other
# selected #ed5700 2 px with faint hidden copies, face outline + depth-tested tint, grey wireframe.
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . --script res://scenes/test/gate_selection_outline.gd"
timeout: 300
- name: gate-move-proxy
# Move shows the actual brush: hidden + proxy mesh with the level material, proxy and
# outline follow the drag, texture lock shifts the projection, confirm/cancel restore.
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . --script res://scenes/test/gate_move_proxy.gd"
timeout: 300
- name: gate-rotate
# R turns the selection about its union centre: ] 15° / Shift 1°, proxies + outline
# turn with it, one brush.rotate per brush about ONE pivot, refold agrees, cancel restores.
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . --script res://scenes/test/gate_rotate.gd"
timeout: 300
- name: gate-editor-e4
# Tool settings row generated from tool_settings(): every tool has a page, keys show in
# it, controls drive the tool, typed values hold, sticky prefs, readout, plugin shape.
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . --script res://scenes/test/gate_editor_e4.gd"
timeout: 300
- name: gate-editor-e5
# Gestures: a stairs / group rotate undo+redo in ONE press; Adjust Last re-runs as a new
# gesture; a new edit discards the redo branch; History lists + seeks gestures.
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . --script res://scenes/test/gate_editor_e5.gd"
timeout: 300
- name: gate-scene-objects
# The object fold: create / transform / set_prop. set_prop's names and values come
# from peers, so the catalogue allow-list, clamping, per-prop last-write-wins and
# undo-to-default are all asserted (and each prefab's defaults against its .tscn).
run: "/storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . --script res://scenes/test/scene_objects_gate.gd"
timeout: 300
- name: gate-object-panel
# Object panel on a real map_flat: one record per change ("Set <Label>"), slider drags
# preview without writing, undo takes it back, every widget kind reaches the node.
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . --script res://scenes/test/gate_object_panel.gd"
timeout: 300
- name: gate-glb-check
# A peer's .glb is checked BEFORE Godot parses it: no uris (GLTFDocument reads local
# disk through them), node / texture / triangle limits, image sizes from headers.
run: "/storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . --script res://scenes/test/gate_glb_check.gd"
timeout: 300
- name: gate-admin-role
# Admin commands run with the AUTHOR'S keet room role (admin:cmd:<memberId>:<seq>),
# never a role the command claims. Interim: the daemon-side writer check is the
# hyper-games #92 follow-up.
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . --script res://scenes/test/gate_admin_role.gd"
timeout: 300
- name: gate-shapes
# Create-tool shapes (#31 BasicShapes): cylinder / cone / sphere are ONE convex brush
# each, standing on the aimed surface with the requested footprint.
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . --script res://scenes/test/gate_shapes.gd"
timeout: 300
- name: gate-editor-grid
# The active grid on the aimed surface (#31 GridRenderer): world-lattice lines, rebuilt
# per cell, Level editor only, apostrophe toggles the per-player pref.
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . --script res://scenes/test/gate_editor_grid.gd"
timeout: 300
- name: gate-scale
# The scale tool (O): grid / ratio steps about the base or centre, axis lock, one
# brush.scale per brush about ONE pivot; models scale uniformly via their Scale prop.
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . --script res://scenes/test/gate_scale.gd"
timeout: 300
- name: gate-undersea-geiss-graph
# Our Geiss-style undersea density graph (§4 P1): compiles to GLSL, the bridged scene
# blender/undersea_geiss.tscn holds a graph that compiles and carries every knob,
# untouched above the coast band, altitude coast mask, floors at random 10-100 m
# spacings, Geiss term present below, detail_of cuts exactly G, install_detail works.
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . --script res://scenes/test/gate_undersea_geiss_graph.gd"
timeout: 300
- name: gate-undersea-gpu-parity
# The undersea field as dc-terrain runs it on the GPU (bounded generator ->
# proc_density) equals the graph sampled on the CPU, at three blocks; also the
# sign (positive = solid). WINDOWED like export-godot: needs a RenderingDevice.
run: "DISPLAY=:0 HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --path . --script res://scenes/test/gate_undersea_gpu_parity.gd"
timeout: 300
- name: gate-undersea-erosion
# Our undersea term inside the erosion / diffusion generator on the REAL maps:
# compiles, the combined shader runs (two graphs in one shader — needs proc-nodes'
# guarded globals), nothing changes above the coast band, the term is present below
# it where there is sea (diffusion has none, by design). WINDOWED: needs a device.
run: "DISPLAY=:0 HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --path . --script res://scenes/test/gate_undersea_erosion.gd"
timeout: 600
- name: gate-glsl-publish-race
# A generator recompile never hands the GPU thread a half-built shader: two
# recompiles per frame for 150 frames while the erosion map streams; zero empty
# bodies and zero density compile failures (DCTerrain.get_gpu_shader_stats).
# Red before dc-terrain cd3d776 (3 empty, 2 failed). WINDOWED: needs a device.
run: "DISPLAY=:0 HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --path . --script res://scenes/test/gate_glsl_publish_race.gd"
timeout: 600
- name: gate-undersea-bridge
# The undersea scene in a REAL blender-bridge session, Godot leading (what "Open in
# Blender" starts): the scene crosses as a density preview with its 22 knobs, the
# preview meshes the seabed, a knob tuned on its Blender node reaches Godot's live
# graph and nothing else changes. Needs Blender 5.2 + the blender-bridge addon on
# this host; SKIPs without them.
run: "/home/fleeky/3d-prog/blender/blender-5.2.0-linux-x64/blender --background --factory-startup --python scripts/gate_undersea_bridge.py"
timeout: 600
- name: gate-state-routes
# The route table: every row complete, the derived map/author lists exactly as
# declared, keys scope and attribute per their row. Was not in CI, so the admin:cmd:
# author row (2026-10-04) left it red unnoticed.
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . --script res://scenes/test/state_routes_gate.gd"
timeout: 300
- name: gate-ghost-creatures
# Ghost bots (demo sprint §5) on the REAL wiring (game.tscn + change_map), creatures
# as bots (#114 phase 5): body inside the map, no authority twin, a declared row
# revived (not frozen), hand-off on losing / taking authority, nothing left after a
# map change, and a bot row held for the old map is not revived in the new one.
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . --script res://scenes/test/gate_ghost_creatures.gd"
timeout: 300
- name: gate-no-default-creatures
# No shipped map spawns creatures (user 2026-10-13: a room adds them as placed bots);
# an older session's world:creatures: rows draw nothing (kept, not deleted).
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . --script res://scenes/test/gate_no_default_creatures.gd"
timeout: 300
- name: gate-bot-tiers
# Creatures → bots phase 1 (#114): a placed bot's body is ticked by BotRegistry's one
# scheduler — near ticks, every observer far = asleep (zero ticks), body swaps clean.
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . --script res://scenes/test/gate_bot_tiers.gd"
timeout: 300
- name: gate-roam-brain
# Phase 2: the roam brain (default for a placed bot) publishes targets within its
# radius of home, moving on every pause; its settings reach it.
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . --script res://scenes/test/gate_roam_brain.gd"
timeout: 300
- name: gate-bot-creature-body
# Phase 3: registered bots with crawler / hopper / serpent bodies — placed at their
# row's position, scheduled, steered, revived, rendered remotely, freed unscheduled.
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . --script res://scenes/test/gate_bot_creature_body.gd"
timeout: 300
- name: gate-spawner-object
# Phase 4: the placeable spawner keeps roaming creature bots near players on the
# authority, despawns far ones, retires its left-overs, and its bots go with it.
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . --script res://scenes/test/gate_spawner_object.gd"
timeout: 300
- name: gate-object-parent
# Reparenting (object.parent): parent-local transforms, cycles / bot children refused,
# delete takes children, Ctrl+P / Alt+P, a selected child moves once, Outliner nesting
# + drop, undoing the parent's create keeps the child.
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . --script res://scenes/test/gate_object_parent.gd"
timeout: 300
- name: gate-creature-target
# Bot bodies: crawler / hopper / serpent walk (fly) to a TARGET with their own gait
# and stop / circle there; far targets teleport; no target = wander as before.
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . --script res://scenes/test/gate_creature_target.gd"
timeout: 300
- name: gate-bot-object
# Bots as scene objects: attach / brain on the authority, settings restart it,
# move -> body, render-peer steering, delete/undo (incl. same frame), allow-listed
# brains, the same creature on every peer.
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . --script res://scenes/test/gate_bot_object.gd"
timeout: 300
- name: gate-bot-editor
# The Level / Bot editor tabs: workspace panels, Place bot as one gesture, the bot
# list, brush keys ignored in the Bot editor (with a level control).
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . --script res://scenes/test/gate_bot_editor.gd"
timeout: 300
- name: gate-object-pick
# Click selects scene OBJECTS (lights have no collider: bounds pick, nearer wins) and
# the Outliner follows; a brush pick drops the object (G used to move a stale one).
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . --script res://scenes/test/gate_object_pick.gd"
timeout: 300
- name: gate-gltf-packer
# .gltf -> one .glb at import: side files packed, nothing read outside the model's
# folder ('..', absolute paths, URLs, symlinks refused). Peers only ever see .glb.
run: "/storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . --script res://scenes/test/gate_gltf_packer.gd"
timeout: 300
- name: gate-model-collision
# The editor's -col / -convcol / -colonly / -convcolonly rules, ported, and the
# convex / trimesh / decompose (VHACD) / named / none modes with scale baked in.
run: "/storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . --script res://scenes/test/gate_model_collision.gd"
timeout: 300
- name: gate-model-object
# ModelCache + the model prefab through the fold: shared meshes, refused files say
# why, placeholder -> model, collision layers, per-copy -colonly removal.
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . --script res://scenes/test/gate_model_object.gd"
timeout: 300
- name: gate-model-import
# Import -> content cache -> room library -> placed as one gesture; Place from the
# library; a refused file publishes nothing; the frame loop alone finishes a load.
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . --script res://scenes/test/gate_model_import.gd"
timeout: 300
- name: gate-bot-smoothing
# Bot markers glide to published positions (first row / > 5 m snap, bounded extrapolation).
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . --script res://scenes/test/gate_bot_smoothing.gd"
timeout: 300
- name: gate-settings-panel
# The Settings panel is usable at 1280x720: panel + Apply/Cancel on screen (they
# were off screen before the redesign), every SCHEMA row on a tab with its widget,
# real value names, collapsed groups below the visible rows, RTX rows greyed while
# path tracing is off, Custom preset state, help on focus, widgets write Settings.
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . --script res://scenes/test/gate_settings_panel.gd"
timeout: 300
- name: gate-keymap
# The keymap layer's own rules: every action registered, no two actions in one
# context share a key, plain keys don't fire under a modifier, inactive contexts
# dispatch nothing, rebinds refuse collisions. Caught the F3 change the other
# gates missed. Runs as a SCENE (autoloads aren't registered under --script).
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . res://scenes/test/keymap_gate.tscn"
timeout: 300
- name: gate-keys-with-prefix
# GameState.keys_with_prefix is linear (was quadratic: `k not in result` on an
# Array), checked as a time RATIO 4000 vs 1000 keys so load cannot flake it.
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . --script res://scenes/test/gate_keys_with_prefix.gd"
timeout: 300
- name: gate-flatten-history
# Flatten one map's history: the minimum non-undoable BASE records that rebuild
# what is there now, old records erased after a backup. Geometry and objects
# identical, History empty, Ctrl+Z inert, backup complete, source key kept.
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . res://scenes/test/gate_flatten_history.tscn"
timeout: 300
- name: gate-flatten-room
# Flatten the WHOLE room: visit every map with level history, flatten each while it
# is loaded, report a map that cannot load, return to the starting map.
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . --script res://scenes/test/gate_flatten_room.gd"
timeout: 120
- name: gate-history-role
# The admin-only Flatten buttons follow KeetService.role_changed, not only the
# journal's folds: at Home the role lands after the last fold (#109, greyed out).
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . --script res://scenes/test/gate_history_role.gd"
timeout: 120
- name: gate-outliner-locked
# System rows (NavVolume, EntityLinks, the command palette, DCLevel) are LOCKED with
# a note saying what they are; Delete refuses with that note; their eye hides them and
# a mode change does not undo it (#109).
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . --script res://scenes/test/gate_outliner_locked.gd"
timeout: 120
- name: gate-pick-range
# Build mode: object pick and the aim ray reach the camera's far plane (120-130 m
# targets hit, past-far not). Play mode and the K carve ray keep 50 m (#109).
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . --script res://scenes/test/gate_pick_range.gd"
timeout: 120
- name: gate-confirm-focus
# Enter through the viewport (push_input) confirms the active tool even when the
# Outliner's Tree holds the keyboard from the row you clicked (#109).
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . --script res://scenes/test/gate_confirm_focus.gd"
timeout: 120
- name: gate-click-through
# A click on any dock panel (padding, centre, a Label, the gap between two panels)
# never selects what is behind it; a click in the open still does (#109).
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . --script res://scenes/test/gate_click_through.gd"
- name: gate-areas-layout
# #124 B1: opaque areas in a split layout around one free 3D rectangle; borders drag,
# sizes saved, collapse grows R and expand restores it, play mode hides the areas.
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . --script res://scenes/test/gate_areas_layout.gd"
- name: gate-tool-owns-mouse
# #124: while a tool is up, panels neither click nor hover; LMB confirms / RMB cancels
# over a panel; the shield drops while looking (pointer captured).
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . --script res://scenes/test/gate_tool_owns_mouse.gd"
- name: gate-view3d-rect
# #124: overlays (membership diag, peer/FPS labels) follow the 3D View's corner, not
# the window's, so they never draw over the areas.
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . --script res://scenes/test/gate_view3d_rect.gd"
timeout: 120
- name: gate-editor-theme
# #124 spacing audit: Blender-sized widgets (<= 22 px, 12 px text) scoped to the areas;
# 2 px gaps, no area borders, a thin status bar.
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . --script res://scenes/test/gate_editor_theme.gd"
timeout: 120
- name: gate-areas-types
# #124 B3: right-click a panel header -> Move to ▸ a column; saved per player; reset.
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . --script res://scenes/test/gate_areas_types.gd"
timeout: 120
- name: gate-outliner-tree
# #124 B4: the organizer is ONE tree (collections first, members listed once), collapse
# / expand all, and the Tree fills the organizer area.
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . res://scenes/test/gate_outliner_tree.tscn"
timeout: 240
- name: gate-chat-key
# T toggles chat in play only: build mode suppresses it, Ctrl+T is not chat.
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . --script res://scenes/test/gate_chat_key.gd"
timeout: 180
- name: gate-param-builtins
# box / cylinder / cone / sphere are parametric types that generate exactly the baked
# pieces; ParamShapes in Tab order; stairs auto_riser default keeps gv 1 (§13).
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . --script res://scenes/test/gate_param_builtins.gd"
timeout: 120
- name: gate-create-param
# The create tool makes ONLY parametric shapes: one shape.create, old prefs migrate,
# tunnel wall from the arch rule, Auto steps riser/grid stored in the record (§13).
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . --script res://scenes/test/gate_create_param.gd"
timeout: 120
- name: gate-shape-texture
# A shape is textured at birth on every solid plane (clearance bare); a texture on a
# selected shape is ONE shape.set; it survives a param edit; undo restores (§13.3).
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . --script res://scenes/test/gate_shape_texture.gd"
timeout: 120
- name: gate-unbounded-scale
# No ceiling on create size/height or model scale (floors kept); the Object panel's
# Scale slider shows a value past its drag range instead of clamping it (#109).
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . --script res://scenes/test/gate_unbounded_scale.gd"
timeout: 120
- name: gate-shape-child-names
# A parametric shape's Outliner children are named by role (step N, landing N,
# voussoir N, floor, column, box) in piece order, the cut is "passage" (#109, §13.4).
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . --script res://scenes/test/gate_shape_child_names.gd"
timeout: 120
- name: gate-pointer-wrap
# G / element move / extrude: the mouse wraps at the window edge and the drag carries
# on through a virtual pointer with no jump; Rotate and play mode never wrap (#109).
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . --script res://scenes/test/gate_pointer_wrap.gd"
timeout: 120
- name: gate-move-plane
# Move: X = axis, X again = X locked (move in the YZ plane), X again = axis; the plane
# delta is on the grid and commits as one brush.translate (#109).
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . --script res://scenes/test/gate_move_plane.gd"
timeout: 120
- name: gate-editor-view
# Orthographic + Blender numpad views (5, 1/3/7, 2/4/6/8, 9, .), auto-perspective,
# wheel zoom in ortho, picking in ortho, on a bare camera and the player rig (#109).
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . --script res://scenes/test/gate_editor_view.gd"
timeout: 120
- name: gate-editor-cursor
# The 3D cursor: Shift+RMB places it on the surface (view orientation), drag slides it
# and saves once; to selected (median) / active / grid / origin; menu-only actions run
# through Keymap.run; Shift+S opens its menu only when not moving (#109).
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . --script res://scenes/test/gate_editor_cursor.gd"
timeout: 120
- name: gate-refold-skip
# An edit that changes no BRUSH skips the brush replay + full re-mesh (was 705 ms
# at 2000 brushes, on every fold, deleting a teleporter included). Skipped only
# when the brush records AND the store fingerprint are unchanged, so drift behind
# the journal's back still gets a full fold.
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . res://scenes/test/gate_refold_skip.tscn"
timeout: 600
- name: gate-map-change-keys
# A map change must not kill the builder keys: the old map's _exit_tree pop ran
# after the new map's _ready push and removed `builder` (N / Shift+Space / Delete
# dead). Contexts are now held per owner. Real map_test, change_map's order.
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . --script res://scenes/test/gate_map_change_keys.gd"
timeout: 300
- name: gate-pick-level-only
# A pick names a brush only when the ray hit the LEVEL's body: a scene box's
# shape 0 used to name the level's brush 0 (an unrelated brush that click
# selected and Delete removed). Real physics on map_test.
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . --script res://scenes/test/gate_pick_level_only.gd"
timeout: 300
- name: gate-delete-selection
# Delete acts on the SELECTION (objects, then brushes; the aimed brush only when
# nothing is selected). One piece of a parametric shape deletes the whole shape
# (shape.remove). One gesture each; Ctrl+Z restores. Real map_test + journal.
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . res://scenes/test/gate_delete_selection.tscn"
timeout: 300
- name: gate-scene-boxes-base
# A level map's scene boxes (StaticBody3D + one BoxShape3D) become BASE brushes at
# load (keys i:1:<n> in node-name order) so they can be picked, moved and deleted
# through the journal; rotation kept; non-collision children (HubLabel) survive.
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . res://scenes/test/gate_scene_boxes_base.tscn"
timeout: 300
- name: gate-home-reset
# Home reset to the release starter (the flatten pattern): seeded, edited, flattened,
# edited again, reset through the History panel -> exactly the starter's content +
# marker; player position and level source kept; backup holds every erased key;
# refused outside Home and for a non-admin.
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . res://scenes/test/gate_home_reset.tscn"
timeout: 300
- name: gate-nav-follows-edits
# The bot nav graph is rebuilt after level edits (debounced): built once at load, not
# during an edit burst, once after it; a platform joins the navigable set, deleting it
# restores the original graph.
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . res://scenes/test/gate_nav_follows_edits.tscn"
timeout: 300
- name: gate-step-up
# Player step-up (docs/player-step-up-design.md): ledges <= 0.395 m walked up with
# no airborne frame; 0.45-0.79 m one single-leg hop each (STEP_HOP, never FALL/LAND,
# lead leg alternating up a flight, derived on a remote copy); 0.85 m blocked; a
# 0.224 m flight up and down never airborne; drawn height continuous; no ramp lag.
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . res://scenes/test/gate_step_up.tscn"
timeout: 300
- name: gate-shape-editor
# Parametric shapes in the editor: the box tool creates one (ONE shape.create); a
# click on any piece selects the shape (face mode too — pieces are read-only); the
# Shape panel's param change is one shape.set; move/scale/rotate write shape.xform /
# shape.size; Make Real hands the same geometry over as plain brushes; undo restores.
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . res://scenes/test/gate_shape_editor.tscn"
timeout: 300
- name: gate-extrude-shape-prompt
# E on a parametric shape's piece asks "Make Real, then extrude / Cancel" (pieces
# are read-only): Cancel writes nothing; Make Real converts and starts Extrude; the
# commit pushes the TILTED face along its own normal (#109, 2026-10-13).
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . res://scenes/test/gate_extrude_shape_prompt.tscn"
timeout: 300
- name: gate-rotate-cursor
# Rotating about the 3D cursor keeps each thing's offset (it orbits, never collapses
# onto the cursor): a plain brush, a parametric shape, and a second turn of the shape.
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . res://scenes/test/gate_rotate_cursor.tscn"
timeout: 300
- name: gate-element-rotate
# R on a vertex / edge / face turns THAT element, not the brush: a corner lands at
# R(v - p) + p round the 3D cursor, an edge's two endpoints likewise, a face's plane
# turns about its centroid; the rest of the cube does not move (#109, 2026-10-13).
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . res://scenes/test/gate_element_rotate.tscn"
timeout: 300
- name: gate-knife-local
# The knife's axis key twice cuts on the brush's OWN axis (Blender X X): world X,
# then the turned cube's own X, then world again; a local cut lands on the cube's grid.
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . res://scenes/test/gate_knife_local.tscn"
timeout: 300
- name: gate-axis-local
# X X = the thing's own axis in Move, Rotate, element Move and Scale (shapes): a
# turned cube moves along / turns about its own axes; a turned shape scales its own X.
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . res://scenes/test/gate_axis_local.tscn"
timeout: 300
- name: gate-circle-select
# C = Blender's circle select: LMB drag paints brushes in, Shift paints out, the
# wheel resizes the ring, RMB leaves with the selection kept (#109 step 6).
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . res://scenes/test/gate_circle_select.tscn"
timeout: 300
- name: gate-select-mode-menu
# Ctrl+Tab = Blender's select-mode menu (vertex / edge / face / brush), the same
# actions as 1 2 3 4 with their keys shown; picking one sets the mode (#109 step 6).
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . res://scenes/test/gate_select_mode_menu.tscn"
timeout: 300
- name: gate-individual-origins
# Ctrl+. = Blender's Individual Origins pivot: rotate / scale turn each unit (a brush,
# or a shape's pieces as one) about its own centre with its own grid step, models
# about their own origins; one undo (#109 build tools).
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . res://scenes/test/gate_individual_origins.tscn"
timeout: 300
- name: gate-shape-parts
# A shape's parts (#109 "select, prompt on edit"): vertex / edge / face mode select
# the part; G / R / the [ ] nudge / the knife ask "Make Real, then go on"; Make Real
# carries the selected part onto the plain brush and the tool runs on it.
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . res://scenes/test/gate_shape_parts.tscn"
timeout: 300
- name: gate-proportional
# Proportional editing (O, #109): Shift+O falloff, Alt+O connected; G moves the units
# near the selection by a falloff of their distance (brushes, a shape as one, models),
# one gesture; PgUp / PgDn size the radius.
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . --script res://scenes/test/gate_proportional.gd"
timeout: 300
- name: gate-proportional-vertex
# Proportional, element level (#109): G / [ ] on a vertex — nearby corners follow by
# the falloff, re-hulled, on neighbouring brushes too unless Alt+O; one undo.
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . res://scenes/test/gate_proportional_vertex.tscn"
timeout: 300
- name: gate-mirror
# Mirror (Ctrl+M, #109): X / Y / Z about the editor pivot; brushes reflect exactly
# (individual origins too), a shape asks Make Real first, an object mirrors its
# placement (position + facing, never a negative scale); one undo.
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . --script res://scenes/test/gate_mirror.gd"
timeout: 300
- name: gate-creature-tiers
# Creature SIMULATION TIERS + SLEEP (CreatureScheduler, #114): by the NEAREST player
# in the mesh — near 60 Hz, mid 10 Hz, far 2 Hz, beyond asleep (zero ticks, frozen);
# each tick gets the time it missed; a creature by a second player stays near; a far
# hopper keeps its raised ground; only ticked creatures are synced.
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . res://scenes/test/gate_creature_tiers.tscn"
timeout: 300
- name: gate-op-journal
# LIVE CUTS through the journal (docs/cutting-and-operations.md §9a): op.create
# opens a door in a box shape, the cutter is not solid; moving the cutter moves
# the door, resizing the target re-cuts it; op.set -> intersect; undo restores
# the wall and a solid cutter; a second peer folds the same pieces; flatten refuses.
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . res://scenes/test/gate_op_journal.tscn"
timeout: 300
- name: gate-op-editor
# Live cuts in the editor: _csg subtract with a shape cutter writes ONE op.create;
# the crosshair picks the wire cutter; plain brushes still cut destructively;
# Make Real refuses on a cutter, bakes the target and retires the op; Ctrl+Z restores.
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . res://scenes/test/gate_op_editor.tscn"
timeout: 300
- name: gate-op-inset
# #120 L1 §12.3: inset = a pocket `depth` deep where the cutter meets the skin;
# outset = a boss `depth` proud, never overlapping; depth edits re-cut; undo.
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . res://scenes/test/gate_op_inset.tscn"
timeout: 240
- name: gate-stack-panel
# The Stack panel (cutting-and-operations.md §9b): generator + one row per live cut in
# order; the eye switches a cut off (its cutter stays wire), the kind dropdown writes
# op.set, ✕ op.remove — one Ctrl+Z each; a selected cutter lists what it cuts.
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . res://scenes/test/gate_stack_panel.tscn"
timeout: 300
- name: gate-fly-modal
# Keymap step 1 (keymap-blender-279.md §2a): no permanent WASD — the build camera moves
# only while flying (RMB held / Shift+F), the exclusive `fly` context suppresses every
# command then; Shift+F Esc returns to the start view, Enter keeps; Ctrl+Numpad CSG keys.
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . --script res://scenes/test/gate_fly_modal.gd"
timeout: 300
- name: gate-keymap-blender
# Keymap steps 2-3 (keymap-blender-279.md §1, §5): our build-mode keys vs Blender 2.7x's
# OWN key-map (tools/keymap/blender_279_left.json, generated by blender_dump.py), command
# by command (tools/keymap/command_map.json); a stale exception fails too.
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . --script res://scenes/test/gate_keymap_blender.gd"
timeout: 300
- name: gate-keymap-step3
# Keymap step 3's new commands through their keys: A / Ctrl+I / B selection, Shift+A add
# menu -> create tool, U texture menu, N sidebar, Tab element/brush, Z wireframe, Shift+R.
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . res://scenes/test/gate_keymap_step3.tscn"
timeout: 300
- name: gate-help-contexts
# Keymap step 4 (§4a): F1 lists the keys of the contexts active NOW (snapshot taken
# before the search box takes focus), "show every context" marks the rest, Blender
# names on mapped rows; the status-bar context strip (BUILD / FLYING / tool).
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . --script res://scenes/test/gate_help_contexts.gd"
timeout: 300
- name: gate-keymap-layouts
# Keymap step 5 (§6): letters bind by CHARACTER, everything else by POSITION — AZERTY,
# Dvorak and German events shaped as each layout sends them.
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . --script res://scenes/test/gate_keymap_layouts.gd"
timeout: 300
- name: gate-view-commands
# Keymap step 6.1 (§6a): Home view all, Shift+C + cursor to origin, Alt+Home centre on
# the 3D cursor; Home yields to UV fit while faces are selected.
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . res://scenes/test/gate_view_commands.tscn"
timeout: 300
- name: gate-pivot
# Keymap step 6.2: the pivot keys (, Ctrl+, . Alt+.) and rotate / scale about it.
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . res://scenes/test/gate_pivot.tscn"
timeout: 300
- name: gate-select-linked
# Keymap steps 6.3-6.4: Ctrl+L select linked (touching, transitively), Shift+G select
# grouped, L's unit is the whole shape; X = the confirming delete popup.
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . res://scenes/test/gate_select_linked.tscn"
timeout: 300
- name: gate-overlays
# Keymap step 6.5 (§6a.5): one switch per overlay on its key + the Shift+Alt+Z master
# (each comes back as it was); outlines off keeps clearance / cutters; states survive
# a mode change; the Outliner eye agrees; the top bar's Overlays toggle + ▾ rows.
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . res://scenes/test/gate_overlays.tscn"
timeout: 300
- name: gate-shape-hotspot
# Hotspot on a parametric shape survives its edits: Fit / Re-roll write the SHAPE's tex
# (no face.tex on renamed pieces); lengthening the shape keeps the face on the same
# strip (dc_face_hash ignores @<ver>); Pin on a whole shape is refused; undo works.
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . --script res://scenes/test/gate_shape_hotspot.gd"
timeout: 300
- name: gate-q-menu
# The Q menu and H = hide (cutting-and-operations.md §9c): rows by selection
# (nothing / shape / cutter / two things), a pick runs its op (keyless actions reach
# the map as InputEventAction); H / Shift+H / Alt+H through the Outliner's rows,
# ungrouped brushes by their member id. Saves under its OWN outliner key.
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . res://scenes/test/gate_q_menu.tscn"
timeout: 300
# gate-handspring SKIPPED for now (user, 2026-10-07): the handspring is in-progress work,
# and the committed version fails check B ("1 of 5 top frames tucked"). Re-enable with
# the handspring work. Run by hand meanwhile:
# HYPERTPS_DAEMON_TCP=127.0.0.1:1 $GODOT --headless --path . res://scenes/test/gate_handspring.tscn
- name: gate-faces-to-brushes
# Faces of different brushes -> new brushes: Alt+J hulls them into one (fills the
# gap between facing walls), Alt+E extrudes each one grid step; the slab is a working
# CSG cutter. Shift+G duplicates brushes, shapes and objects and starts Move.
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . res://scenes/test/gate_faces_to_brushes.tscn"
timeout: 300
- name: gate-level-collision-scale
# DCLevel collision at 2000 brushes: the body joins the physics space AFTER its shapes
# (a Jolt body in a space rebuilt its compound on every add — 1149 ms of a 1172 ms
# rebuild at 2480 brushes); picking's shape -> brush map still exact on 25 rays.
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . res://scenes/test/gate_level_collision_scale.tscn"
timeout: 300
- name: gate-editor-highlights
# The crosshair's auto highlight is dimmed; leaving build mode hides every highlight
# (selection outlines, element, face tint, aim) and keeps the selection for return.
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . res://scenes/test/gate_editor_highlights.tscn"
timeout: 300
- name: gate-journal-index
# The journal INDEX (LevelPersistence): parsed once, updated per key from
# state_changed. After 210 seeded random steps (records, objects, undo/redo/seek,
# a peer's record, erases, source changes, map_changing) it must answer exactly what
# a from-scratch rebuild answers; an object move parses ONE record and skips the
# brush fold; a 40-record gesture undoes and redoes as one.
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . res://scenes/test/gate_journal_index.tscn"
timeout: 300
- name: gate-outliner-delete
# Outliner Delete acts on anything saved in the room (objects, a group's brushes) as
# ONE gesture; plain scene nodes are refused; a group row opens to list its brushes.
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . res://scenes/test/gate_outliner_delete.tscn"
- name: gate-outliner-sync
# The tree never disagrees with the viewport: a group row the viewport moved away
# from is dropped, so the outliner's Delete cannot take the whole group (#120 audit).
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . res://scenes/test/gate_outliner_sync.tscn"
- name: gate-pcg
# Pcg32 (the fold's randomness) gives pcg-c-basic's numbers exactly, on every peer:
# golden values from an independent reference, seeding via SHA-256 (#120 F4).
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . res://scenes/test/gate_pcg.tscn"
- name: gate-face-primitives
# FaceKit: a face's polygon (ordered, convex), prisms over it that share the face
# exactly, the hull of two sections (watertight segments), grid cells to rectangles.
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . res://scenes/test/gate_face_primitives.tscn"
- name: gate-collection-fold
# col.* records -> the room's collection tree: per-field last-write-wins, cycles
# ignored and reported, remove keeping contents vs with hierarchy, links (#120 F1).
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . res://scenes/test/gate_collection_fold.tscn"
- name: gate-collection-persist
# Collections are room state: col.* through the journal come back as the tree, never
# re-fold the level, never reach DCLevel (nothing skipped), and undo (#120 F1).
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . res://scenes/test/gate_collection_persist.tscn"
- name: gate-collection-edit
# Editing collections, one gesture each: new, from selection, link (Shift+M), move
# (M), unlink (Ctrl+Alt+G), rename, delete keeping contents, Delete Hierarchy (#120 F1).
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . res://scenes/test/gate_collection_edit.tscn"
- name: gate-brush-regroup
# Brush objects (needs dc-terrain BrushRegroup): a loose brush becomes its own object
# on Ctrl+G, a later split stays in it, undo restores it, Ctrl+J joins, P separates
# (keeping its collections), shape pieces are refused (#120 F1).
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . res://scenes/test/gate_brush_regroup.tscn"
- name: gate-collection-keys
# Ctrl+G, M / Shift+M / Ctrl+Alt+G pickers, Ctrl+J, P and the Object menu, through
# Keymap.run (the menu's path) (#120 F1).
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . res://scenes/test/gate_collection_keys.tscn"
- name: gate-meta-collection
# Several collection rows select all their brushes; Ctrl+G / + Collection wraps the
# selected collections in a new parent where they were, subtrees intact (2026-10-18).
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . res://scenes/test/gate_meta_collection.tscn"
- name: gate-collection-focus
# M and Ctrl+G through Viewport.push_input, the organizer's Tree focused or not; the
# new collection is named in a prompt first (2026-10-18).
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . --script res://scenes/test/gate_collection_focus.gd"
- name: gate-duplicate-structure
# Shift+D keeps structure: one brush object's copies form one new object, a copy keeps
# its parent (or the parent's copy), every copy joins its source's collections (#120 F1).
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . res://scenes/test/gate_duplicate_structure.tscn"
- name: gate-cable-gen
# The cable generator (#120 L3 §11.1): segments = hulls of profile rings (static
# DCLevel.hull_of), watertight, angle / length rules, square + tilt, taper, strip,
# connectors, rings, closed curves, determinism.
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . res://scenes/test/gate_cable_gen.tscn"
- name: gate-cable-journal
# Cables in the fold (#120 L3 §11.2): add_cable is one gesture, curve edits / params /
# removal re-fold the pieces, arrival order, mirror, cuts, flatten keeps the curve.
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . res://scenes/test/gate_cable_journal.tscn"
- name: gate-cable-tool
# The cable tools (#120 L3 §11.3): Add → Cable, slack sag, G / R move the curve, X
# removes the curve with the cable unless something else reads it; clean hulls at 320 m.
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . res://scenes/test/gate_cable_tool.tscn"
- name: gate-cable-profile
# Custom cable profiles (#120 L3 §14.2): a closed curve's convex outline as the
# cross-section, live; the Shape panel's Profile curve row; fallback to round ("r" names).
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . res://scenes/test/gate_cable_profile.tscn"
- name: gate-cable-drop
# Drop to the ground (#120 L3 §14.3): CurveDrop on a fake floor; real physics with the
# cable's own pieces skipped; Ctrl+Shift+D tool, one gesture, Adjust Last.
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . res://scenes/test/gate_cable_drop.tscn"
- name: gate-edge-set
# The edge set (#120 L3 §14.4): Shift-click edges across brushes, held as brush key +
# two plane keys, resolved from the planes, live brushes only, highlighted.
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . res://scenes/test/gate_edge_set.tscn"
- name: gate-cable-edges
# Cables from edges (#120 L3 §14.5): chains (stop at a 3-way vertex, close a loop),
# vertices / centres, offset along the mean normal, corners; Alt+C, one gesture.
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . res://scenes/test/gate_cable_edges.tscn"
- name: gate-cable-bundle
# A bundle between two face sets (#120 L3 §14.6): islands, seeded ends on the faces,
# free end handles, slack; N cables + a Bundle collection in one gesture; Shift+Ctrl+C.
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . res://scenes/test/gate_cable_bundle.tscn"
- name: gate-brush-narrow-reads
# DCLevel.get_brushes_bounds / count_clearance answer bit-for-bit what the get_brush
# loops they replaced did (hyper-games #127: the organizer's per-fold reads).
run: "/storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . --script res://scenes/test/gate_brush_narrow_reads.gd"
- name: gate-asset-doc
# #120 L4 step 1: a collection's contents as an asset blob — round trip, canonical
# bytes, local dots, cuts carried with their cutter, decode refuses untrusted input.
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . res://scenes/test/gate_asset_doc.tscn"
- name: gate-asset-fold
# #120 L4 step 2: placed assets fold as live instances — at their pivot the originals'
# windings bit-identical; moved / turned / undone / missing blob / tail fold.
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . res://scenes/test/gate_asset_fold.tscn"
- name: gate-asset-library
# #120 L4 step 3: Save as asset into the room's + personal libraries; a re-save moves
# my placements, others' wait for Update placements; personal published on place; fetch.
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . res://scenes/test/gate_asset_library.tscn"
- name: gate-asset-edit
# #120 L4 step 4: a placed asset is one thing in the editor — select, G/R/X/Shift+D,
# read-only parts, Make Real (same windings), outliner rows; no edit names a synthetic piece.
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . res://scenes/test/gate_asset_edit.tscn"
- name: gate-asset-browser
# #120 L4 step 5: the Assets strip — tiles, filters, Save to switches, Update N, the
# drop frame (floor / wall / ceiling), the 3D view armed only during a tile's drag.
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . res://scenes/test/gate_asset_browser.tscn"
- name: gate-asset-thumb
# #120 L4 step 6: a save renders a 128 px thumbnail of the collection alone (real
# pixels), named by the overlay's hash, in both libraries and on the tile; a late one
# never clobbers a newer save. WINDOWED: needs a device (the headless dummy draws nothing).
run: "DISPLAY=:0 HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --path . res://scenes/test/gate_asset_thumb.tscn"
- name: gate-curve-overlay
# Curves (wire, points, held handles) are an editor overlay: hidden in play mode, the
# Curves switch (Shift+Alt+K) and the master switch hide them in build (2026-10-18).
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . res://scenes/test/gate_curve_overlay.tscn"
- name: gate-curve-member
# #120 §16 step 1: a curve is a collection member ("c:<dot>"): Ctrl+G / M / Shift+M /
# Ctrl+Alt+G, Delete Hierarchy keeps a used curve, flatten remaps, assets carry it.
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . res://scenes/test/gate_curve_member.tscn"
- name: gate-outliner-curves
# #120 §16 steps 2-5: curve rows (a cable's own curve under the cable), rename,
# select both ways, X (refused while used), drawn selected, hide, Shift+D, G / R,
# click the wire to pick (nearer wins over the brush behind it).
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . res://scenes/test/gate_outliner_curves.tscn"
- name: gate-curve-g-mode
# #120 §17 step 1: G on a selected curve by select mode — vertex mode edits the point
# nearest the pointer, brush mode moves the whole curve; a mode switch keeps the curve.
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . res://scenes/test/gate_curve_g_mode.tscn"
- name: gate-modal-drag
# #120 §17 step 2: the mouse drags a tool's focused value (Blender's): px x step / 25,
# Shift finer, Ctrl snap, accumulates on integers; Rotate drags, Move does not.
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . res://scenes/test/gate_modal_drag.tscn"
- name: gate-shape-handles
# #120 §17 step 3: a selected shape's six face dots; press-drag-release resizes one
# side (the opposite stays), grid-snapped, one gesture; Esc nothing; Ctrl+Z restores.
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . res://scenes/test/gate_shape_handles.tscn"
- name: gate-tool-handles
# #120 §17 step 4: a tool's dots (BoxCutter depth, cable slack): a press on one grabs it
# (no confirm), the drag sets the value, the release lets go; a press elsewhere confirms.
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . res://scenes/test/gate_tool_handles.tscn"
- name: gate-union-select
# #120 §17 step 5a: objects, brushes and curves held together — Shift adds across
# kinds, A selects all three (and toggles off), the outliner lights every row.
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . res://scenes/test/gate_union_select.tscn"
- name: gate-union-ops
# #120 §17 step 5b: X and H on objects + brushes + curves together — one gesture, one
# Ctrl+Z; a cable's curve selected with its cable goes with it (not refused).
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . res://scenes/test/gate_union_ops.tscn"
- name: gate-union-move
# #120 §17 step 5c: G and Shift+D on objects + brushes + curves together — one delta,
# one gesture, Esc restores all; Shift+D copies every kind and Moves the copies.
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . res://scenes/test/gate_union_move.tscn"
- name: gate-union-rotate
# #120 §17 step 5d: R on objects + brushes + curves together — one pivot (their
# combined centre), one gesture; Esc restores all.
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . res://scenes/test/gate_union_rotate.tscn"
- name: gate-face-set-live
# #129 deferred bug: the face set (Alt+J / Alt+E / cable bundle) drops a deleted
# brush's face (a tombstone brush_by_key still returns), as the edge set does.
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . res://scenes/test/gate_face_set_live.tscn"
- name: gate-curve-point-handles
# #129 deferred bug: set_curve_point moving `p` re-derives auto / vector handles (the
# moved point's and its neighbours'), one gesture; free / aligned keep theirs.
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . res://scenes/test/gate_curve_point_handles.tscn"
- name: gate-duplicate-linked
# Alt+D (Blender's Duplicate Linked): the copy shares the shape's data (an edit on
# either reaches both), keeps its own place / size / modifiers; a link of a link names
# the root; the root deleted, links still share; flatten keeps the set linked.
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . res://scenes/test/gate_duplicate_linked.tscn"
- name: gate-make-parametric
# Any brush -> a parametric shape: box when it is its own box, else hull (its planes
# in the unit box, per-face textures kept); resizable; collections kept; undo (#120 F2).
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . res://scenes/test/gate_make_parametric.tscn"
- name: gate-assign-generator
# Box -> stairs with the box as passage; params kept across a switch; Remove generator;
# hull round trip; undo; Shift+D and flatten keep the passage (#120 F2, #109).
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . res://scenes/test/gate_assign_generator.tscn"
- name: gate-mod-copies
# Copy modifiers on a shape: mirror / array / radial, a stack, edits rename the copies,
# on / off / remove / undo, determinism, a cut mirrored with its target, Make Real
# bakes the copies, flatten keeps the modifiers (#120 F2c).
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . res://scenes/test/gate_mod_copies.tscn"
- name: gate-mod-curve
# Follow-curve: the object and its copies onto a curve (rigid, convex), fit, a curve
# edit moves them (only a followed curve re-folds), curve removed -> back, determinism,
# a cut carried, the nearest curve, flatten remaps the curve (#120 F2 + F5).
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . res://scenes/test/gate_mod_curve.tscn"
- name: gate-mod-chamfer
# The chamfer modifier (dc-terrain BrushChamfer, after the cuts): box, width edit,
# on / off / undo, no groove at a mirror seam, cut pieces chamfered too (#120 F2d).
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . res://scenes/test/gate_mod_chamfer.tscn"
- name: gate-mod-dice
# #120 L1 §12.4: dice splits a shape into even grid cells (convex, no overlap), BEFORE
# the copies whatever the record order; a cut reaches the cells; stable names.
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . res://scenes/test/gate_mod_dice.tscn"
timeout: 240
- name: gate-param-prism
# #120 L2 §13.1: the prism cutter — one convex piece, taper / shear, ngon, concave -> hull.
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . --script res://scenes/test/gate_param_prism.gd"
timeout: 120
- name: gate-cut-through
# #120 L2 §13.3: through-depth = the targets' deepest reach below the plane + a step.
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . --script res://scenes/test/gate_cut_through.gd"
timeout: 120
- name: gate-box-cutter
# #120 L2 §13.2: BoxCutter — rect on a face, through by default, X / I modes, E depth,
# one gesture (prism shape + live op), one Ctrl+Z.
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . res://scenes/test/gate_box_cutter.tscn"
timeout: 240
- name: gate-q-stack
# #120 L2 §13.4–13.5: Change cut, Extract (+ BoxCutter Y), Apply up to here (later cuts
# stay live on the baked pieces), the Q rows + keys, the LOCAL stack scroll preview.
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . res://scenes/test/gate_q_stack.tscn"
timeout: 300
- name: gate-mod-keys
# #120 L1 §12.5: Shift+Ctrl+M opens Add modifier with letters; G / D add grid / dice.
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . res://scenes/test/gate_mod_keys.tscn"
timeout: 240
- name: gate-modal-controls
# One modal convention: a tool's key table is its status line and its H overlay; bound
# values step with [ ] (Shift finer) and Tab; Mirror migrated, through the real key
# path, with a summary card on confirm (#120 F3).
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . res://scenes/test/gate_modal_controls.tscn"
- name: gate-curve-math
# Curve evaluation (pure): Bezier vs analytic (line, quarter circle), arc length,
# rotation-minimising frames (no flips on a helix), tilt, radius, auto handles off the
# surface normal, closed curves, determinism (#120 F5).
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . res://scenes/test/gate_curve_math.tscn"
- name: gate-curve-journal
# Curves are room state: curve.create / set (whole or one point) / remove through the
# journal, never re-folding the level; undo; arrival order; the view; flatten (#120 F5).
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . res://scenes/test/gate_curve_journal.tscn"
- name: gate-curve-tool
# The curve tool: Add menu, P places points on surfaces (lifted along the normal, which
# each point keeps), Backspace, ] lift, C closed, Enter = one curve.create, Esc
# writes nothing (#120 F5).
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . res://scenes/test/gate_curve_tool.tscn"
timeout: 300
- name: gate-scene-objects-base
# A level map's teleporters and spawn points are BASE objects ("scene:<Name>"):
# movable, relabelable and deletable through the journal; Ctrl+Z restores the
# authored transform/props; a deleted one is detached, so undo brings the same node.
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . res://scenes/test/gate_scene_objects_base.tscn"
timeout: 300
- name: gate-texture-strip
# The texture strip (#94 P1 §9) on a real map_flat: a BOTTOM-row dock panel,
# search / pack filter, a click sets the current texture, U applies it to a
# brush or face selection LOCALLY and as one face.tex record per brush (texture
# only — alignment kept; untextured faces become paraxial), a refold agrees,
# Y picks a face's texture, U/Y bound with no conflicts.
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . --script res://scenes/test/gate_texture_strip.gd"
timeout: 300
- name: gate-pack-texture
# Texture packs reach the level (#94 P1 step 3): pack.json catalog (counts per
# pack), TexturePacks.build at 1024 and 512, missing checker, flat-normal / plain
# ORM defaults, Settings texture_res follows the preset, and LevelPackMaterial on
# a real DCLevel through the journal: on-demand load, faces -> their layer,
# unknown id -> missing layer, 512 switch keeps layers. Scene gate (autoloads).
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . res://scenes/test/pack_texture_gate.tscn"
timeout: 300
- name: gate-presence-null-identity
# presence.gd stored identity fields with str(): str(null) is "<null>", so a
# fresh (unnamed) keet identity was announced with name "<null>", and a null
# memberId passed the `== ""` guard and announced under presence:<null>. (#89)
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . --script res://scenes/test/gate_presence_null_identity.gd"
timeout: 300
- name: gate-rpc-timeout
# KeetUdsClient released a callback only on a reply or a disconnect, so a daemon
# that accepted a call and never answered left the caller waiting for ever (the
# menu sat on "identity" silently). A local TCP server that accepts and never
# replies reproduces it; calls now fail with "Timed out" after rpc_timeout_sec. (#89)
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . --script res://scenes/test/gate_rpc_timeout.gd"
timeout: 300
- name: gate-map-visibility
# Player nodes hang off Game, not off the map, so every peer's body existed in
# every map: a peer parked in map_flat was visible from map_test and followed
# you through a teleporter. Nothing filtered by map because change_map assumed
# the whole room moved together — and it never did. world:settings:current_map
# is read once at game start and nothing subscribes to it, so a live peer
# never follows someone else's map change.
run: "/storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . --script res://scenes/test/gate_map_visibility.gd"
timeout: 300
- name: gate-room-state-seed
# #131: a timed-out room-state load is UNKNOWN, not absent — the default world is never
# seeded then (it overwrote a slow room's saved map as admin); the world restores late
# once state arrives; the timeout reports only errors from its own wait.
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . --script res://scenes/test/gate_room_state_seed.gd"
- name: gate-room-state-fault
# The room-state timeout used to say "(daemon down, room not opening)" — two
# candidates, neither distinguished. It fired once on stortron after a pgh app
# update and four controlled attempts failed to reproduce it, so the single
# occurrence had to carry its own evidence and did not.
#
# Gated as a PURE mapping from observations to message, which is the only part
# that can be wrong in a way that costs anything, plus a structural check that
# the timeout path actually calls it.
run: "/storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . --script res://scenes/test/gate_room_state_fault.gd"
timeout: 300
- name: gate-nav-debug
# DCTerrain.debug_nav_chunk — the instrument from the chunk/portal nav design
# (dc-terrain docs/plans/2026-09-15). Makes the portal graph visible BEFORE
# anything paths over it, because a pathfinder on a wrong graph produces bad
# routes that look like a bad pathfinder.
#
# ⛔ Its load-bearing assertion is that CARVING CHANGES THE ANSWER. A nav view
# built from the generator alone would be identical before and after an edit and
# would look healthy while being blind to every change in the world —
# DCCollisionSDF has exactly that bug. The first version of this gate guessed a
# method name, skipped that check, and reported PASS.
#
# It also fails if the GDExtension .so is stale, which is a known trap: the
# symlinked binary is untracked and CI never rebuilds it. All three targets
# (editor, template_debug, template_release) must be built.
run: "/storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . --script res://scenes/test/gate_nav_debug.gd"
timeout: 300
- name: gate-pathwalker
# A bot given a route walks it: advances through waypoints in order, converges
# rather than orbiting, appends the destination so it does not stop in the last
# doorway, and does not move at all when there is no route.
#
# Driven through a STUB terrain on purpose. The search has its own gate in
# libdc; driving the real DCTerrain here would need a meshed world and would
# leave the thing under test — does the bot converge or orbit — dependent on
# whatever terrain happened to generate.
run: "/storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . --script res://scenes/test/gate_pathwalker.gd"
timeout: 300
- name: gate-bot-declaration-survives
# A map change ends the BOT, not its DECLARATION. despawn() erased the durable
# registry row for every position body, so leaving a map deleted the
# declarations of the bots that belong to it and re-entering had nothing to
# revive. The position row is still erased, which is what other peers render.
run: "/storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . --script res://scenes/test/gate_bot_declaration_survives.gd"
timeout: 300
- name: gate-bot-revive
# Entering a map must bring its DECLARED bots back to life, not just to sight.
# Teardown was wired (map_changing despawns map-bound bots) and setup never
# was, so a declared bot could never run again after its first map change --
# the map with the nav volume was the one no bot lived in. Asserts the brain
# actually RAN (beacon writes state:bot:beacon:alive) and that a row arriving
# before its map exists is held and retried rather than dropped.
run: "/storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . --script res://scenes/test/gate_bot_revive.gd"
timeout: 300
- name: gate-poll-no-reemit
# The 3 s room poll emits only what changed. Step 4 stages the CSG full-rebuild
# race (hyper-games #108): a poll sent BEFORE a local write but answered after it
# must not report that key erased, which re-folded the whole terrain journal after
# the player's own carve. A poll in flight during a local erase must not bring the
# key back either.
run: "HYPERTPS_DAEMON_TCP=127.0.0.1:1 /storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . --script res://scenes/test/gate_poll_no_reemit.gd"
timeout: 120
- name: gate-durable-load-order
# A bulk durable load must hand handlers a COMPLETE snapshot. Store-and-emit
# in one pass gave them a half-filled mirror, so AdminFacade could not see the
# admin:result:<seq> marker for the very command it was being handed and
# re-executed the room's whole command history on every cold load.
#
# The gate registers a bot as the side effect, because the marker's VALUE is
# destroyed by the load itself and a state_changed observer runs too late.
run: "/storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . --script res://scenes/test/gate_durable_load_order.gd"
timeout: 300
- name: gate-nav-end-to-end
# THE CHAIN, on map_flat: real map -> real box colliders -> nav graph -> route
# -> a bot that walks it. Every link had its own gate and the chain did not,
# which is the gap this closes.
#
# map_flat on purpose: it has NO DCTerrain and no density field anywhere, so if
# navigation worked only on terrain it would work nowhere the bots actually are.
run: "/storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . --script res://scenes/test/gate_nav_end_to_end.gd"
timeout: 400
- name: gate-level-nav
# The STARTING map (map_test) gives bots navigation: map_dc_level builds a
# DCNavVolume for every level map (it had none -- bots walked through walls and
# the A* brain stood still), thin floors are found, and a route around Wall1
# goes AROUND it (intra-region cell routing). Verified red before the change.
run: "/storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . --script res://scenes/test/gate_level_nav.gd"
timeout: 300
- name: gate-walker-acquire
# walker.gd looked its player up once in _bot_init and gave up for ever.
# hyper-games #77 item 4. Its existing gate, gate_walker_bot, builds its own
# Game/Players and attaches stub_local_player.gd, whose is_local_player()
# returns true unconditionally — it asserts against a tree constructed to
# satisfy the code under test and cannot see this. THIS gate instances the
# real player_procedural.tscn and uses the real PlayerPosition autoload.
# Verified red against the pre-fix walker before it was added.
run: "/storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64 --headless --path . --script res://scenes/test/gate_walker_acquire.gd"
timeout: 300
- name: export-godot
# Export with a REAL Vulkan RenderingDevice (NOT --headless) so the Shader
# Baker (export_presets.cfg: shader_baker/enabled=true) actually runs. The
# baker bakes the SPIR-V shader cache into the PCK as res://.godot/shader_cache,
# so installed clients load warm shaders instead of cold-recompiling on first
# launch (incl. after every OTA, since each version ships its own bake).
# `--headless` uses the dummy renderer → no RenderingDevice → _is_active()
# is false → the baker silently bakes nothing. The build box (tabertron) has
# an RTX GPU on DISPLAY=:0. A failed export aborts the deploy safely: pgh-publish
# never runs, so the live version is untouched.
# NOTE: godot 4.6 segfaults on EXIT (dangling GDExtension atexit handler) — harmless
# AFTER the .pck is written, but a `test -f` on the binary alone would mask a crash
# that happened BEFORE the pack finished, shipping a truncated .pck. So validate the
# .pck exists AND is a plausible size (real pack ~391MB). See hyper-games #1.
# Exports with the godot-rtx EDITOR against the godot-rtx TEMPLATE
# (export_presets.cfg custom_template/release). Both must come from the same
# build or the engine version-mismatches. The template is a build artifact of
# nvidia-rtx-godot and is NOT in git, so assert it exists FIRST: without this
# test a missing template silently falls back to the installed 4.6.stable
# templates and ships a build with no AreaLight3D, which looks identical to a
# successful deploy.
run: |
GODOT=/storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64
TEMPLATE=/storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.template_release.x86_64
test -x "$GODOT" || { echo "ERROR: godot-rtx editor missing at $GODOT"; exit 1; }
test -f "$TEMPLATE" || { echo "ERROR: godot-rtx export template missing at $TEMPLATE — build it with 'scons platform=linuxbsd target=template_release'"; exit 1; }
# $TEMPLATE is only an ASSERTION -- the export uses whatever
# export_presets.cfg custom_template/release names. Those drifted apart on
# 2026-09-21: ci.yaml moved to the 4.8 engine while the preset still named the
# 4.7 template, so the export ran a 4.8 EDITOR against a 4.7 TEMPLATE and
# shipped a 4.8 .pck next to a 4.7 binary. It published green; the failure
# only appeared on a user's machine as
# "Pack created with a newer version of the engine: 4.8.0"
# Assert they agree, so the pair can never silently diverge again.
grep -qF "custom_template/release=\"$TEMPLATE\"" export_presets.cfg || {
echo "ERROR: export_presets.cfg custom_template/release does not match \$TEMPLATE ($TEMPLATE)."
echo " The export would use the preset's template, not this one. Preset says:"
grep -n 'custom_template/release' export_presets.cfg
exit 1; }
echo "engine: $("$GODOT" --version)"
mkdir -p build/linux
DISPLAY=:0 "$GODOT" --rendering-driver vulkan --export-release "Linux" build/linux/hypertps
test -f build/linux/hypertps && test "$(stat -c%s build/linux/hypertps.pck 2>/dev/null || echo 0)" -gt 100000000
timeout: 600
- name: copy-ngx-dlss
# DLSS needs its RUNTIME snippet libraries at run time; libnvsdk_ngx.a is
# link-time only, so a binary can carry NGX symbols and still fail to create
# the feature. dlss.cpp:679 searches exactly two directories:
# 1) the executable's own dir 2) <exe_dir>/../thirdparty/ngx/lib/linux_x86_64
# The editor satisfies (1) because the .so sit in bin/ next to it. A shipped
# game satisfies NEITHER unless we put them beside hypertps — which is why
# every exported build until now failed with
# [NGX DLSS] DLSS-SR feature creation failed: 0xbad0000b
# = NVSDK_NGX_Result_FAIL_UnableToInitializeFeature ("the library for that
# feature could not be found"). Both are needed: -dlss is SR, -dlssd is Ray
# Reconstruction, which is the denoiser path tracing uses.
# Filenames are copied verbatim — the versioned names are what NGX matches
# in the editor case that demonstrably works.
run: |
NGX=/storage/projects/code/godot/nvidia-rtx-godot-v3/thirdparty/ngx/lib/linux_x86_64
for so in libnvidia-ngx-dlss.so.310.5.3 libnvidia-ngx-dlssd.so.310.5.3; do
test -f "$NGX/$so" || { echo "ERROR: NGX runtime lib missing at $NGX/$so — run 'python misc/scripts/install_dlss_linux.py' in nvidia-rtx-godot"; exit 1; }
cp "$NGX/$so" build/linux/
done
ls -la build/linux/libnvidia-ngx-*.so*
- name: copy-gdextension
run: |
mkdir -p build/linux/bin/linux
# *.so* (not *.so) so versioned runtime libs like libomp.so.5 are included —
# libomp.so.5 ends in .so.5 and was silently dropped by a bare *.so glob.
for f in bin/linux/*.so*; do
if [ -L "$f" ]; then
cp "$(readlink -f "$f")" "build/linux/bin/linux/$(basename "$f")"
else
cp "$f" "build/linux/bin/linux/"
fi
done
# dc_terrain GPU compute shaders — shipped next to libdc_terrain…so so the
# extension resolves them via dladdr at runtime (relative to its own .so),
# not a hardcoded dev path. Without these the GPU mesh pipeline fails on
# other machines and CPU-falls-back to dual-contouring → ~4fps. -rL derefs
# the bin/linux/shaders symlink into the real shader tree (incl generators/).
if [ -e bin/linux/shaders ]; then
cp -rL bin/linux/shaders build/linux/bin/linux/shaders
echo "Shipped dc_terrain shaders: $(find build/linux/bin/linux/shaders -name '*.glsl' | wc -l) .glsl files"
else
echo "WARNING: bin/linux/shaders missing — dc_terrain GPU meshing will CPU-fall-back"
fi
- name: verify-gdextension
run: |
echo "GDExtension .so files in build:"
ls -la build/linux/bin/linux/*.so*
file build/linux/bin/linux/*.so* | grep -v "ELF" && echo "ERROR: non-ELF .so files!" && exit 1 || echo "All .so files are ELF binaries"
# GLIBC CEILING: 2.35, ashatron's. A plain `scons` on this host links against
# its glibc 2.39 and produces a library that needs GLIBC_2.38 -- it builds,
# every gate here passes, and on ashatron the extension silently does not
# load. dc-terrain and hyperswarm-peer ship ZIG-built (tools/zig-cc, target
# glibc 2.28, libomp.so.5); 2026-09-29 a plain rebuild of dc-terrain reached
# a published release this way. Build them with:
# CC=$PWD/tools/zig-cc CXX=$PWD/tools/zig-cxx scons platform=linux target=template_release
# proc_nodes is zig-built the same way since 0.1230.0 (it shipped GLIBC_2.38
# before, and loads: proc_nodes.gdextension is in the export include_filter).
# THE ENGINE TOO: build/linux/hypertps is the export template, and a template
# built on this host needs GLIBC_2.38 -- ashatron refused to start 0.1229.0
# ("libm.so.6: version `GLIBC_2.38' not found"). It is built in an ubuntu:22.04
# container (glibc 2.35) from a `git archive` of the engine branch.
# blender_bridge alone WARNs: its .so ships but never loads, because
# bin/blender_bridge.gdextension is in the export exclude_filter.
FAIL=0
for so in build/linux/hypertps build/linux/bin/linux/*template_release*.so; do
need=$(objdump -T "$so" | grep -oE 'GLIBC_[0-9]+\.[0-9]+' | sort -V | tail -1)
ok=$(printf '%s\nGLIBC_2.35\n' "$need" | sort -V | tail -1)
if [ "$ok" = "GLIBC_2.35" ]; then echo " glibc ok $need $(basename "$so")"; continue; fi
case "$(basename "$so")" in
libblender_bridge.*) echo " WARN $need > 2.35 (not loaded: blender_bridge.gdextension is excluded): $(basename "$so")" ;;
hypertps) echo " ERROR $need > 2.35: the engine template -- rebuild it in the ubuntu:22.04 container"; FAIL=1 ;;
*) echo " ERROR $need > 2.35: $(basename "$so") -- rebuild it through tools/zig-cc"; FAIL=1 ;;
esac
done
[ "$FAIL" = 0 ] || exit 1
- name: gate-flatten-node-modules
# The launcher flattens pgh's node_modules on first start. It used to leave
# every nested `../../<hash>` link dangling (1310/1310), so each package
# resolved the APP's top-level deps instead of its own: 0.1217.0 aborted in
# keet-core on blind-peering 2.1.2 (needs ^2.5.0). Nested links must land on
# the one path their hash loads from.
run: node scripts/test/flatten-node-modules.test.cjs
timeout: 60
- name: install-public-deps
# `npm ci` — strictly installs from package-lock.json, refuses if lockfile
# disagrees with package.json. Catches transitive drift at CI time instead
# of crash time on user devices. Lockfile MUST be committed alongside
# package.json. --omit=dev (modern replacement for --production) skips
# devDependencies. --ignore-scripts blocks postinstall hooks.
run: npm ci --omit=dev --ignore-scripts
timeout: 600
- name: overlay-private-deps
run: rsync -a --ignore-existing /storage/projects/code/hypercore-kb/hyper-fleeky/keet-cli/node_modules/ node_modules/
- name: overlay-keet-core
# 2026-05-11: @holepunchto/keet-core-hyperdb 1.22.0 was retired
# upstream — its bundle/abi*.bundle.js files reference targets not
# actually inlined, causing the cold-install crash in keet-daemon
# (hyper-fleeky-dev#20). Replacement is @holepunchto/keet-core
# 4.14.4 (no eager bundle loading; bundle protocol is migration-
# only). We overlay the new package set from holepunchto-deps
# directly because keet-cli (the rsync source above) hasn't been
# bumped yet and stays on 1.22.0 as legacy fallback. The new
# package set + keet-daemon's matching import swap together
# resolve the bundle crash. Audit at hyper-fleeky/keet-audit-4.x/.
run: |
DEPS=/storage/projects/code/hypercore-kb/hyper-fleeky/holepunchto-deps/@holepunchto
rm -rf node_modules/@holepunchto/keet-core-hyperdb
# 2026-06-29: keet-core 4.17.3 (Keet 4.17.2). Overlay the FULL @holepunchto
# set from holepunchto-deps — keet-cli's rsync (overlay-private-deps) only
# provides STALE siblings and is missing keet-room-registry (a NEW keet-core
# 4.17.x dep), so we must overlay every keet-core sibling, not just 3.
# 2026-09-30: keet-core 4.22.28 (Keet 4.22.5) adds @holepunchto/call-swarm (calls moved
# out of keet-core api/v1/calls).
# 2026-10-11: keet-core 4.23.8 (Keet 4.23.0) — no new @holepunchto package. Its new deps
# (ble-swarm, bare-hrtime, speedometer) ship inside keet-daemon 1.1.27's own node_modules,
# which copy-keet-daemon brings along. Lockstep reason: the network's autoupgrade is 37
# and 4.22.28's ceiling was 35 — every new room interrupted (room.invite: null anchor).
PKGS="keet-core call-swarm keet-room-registry keet-call keet-unread-anchors keet-core-api keet-core-schemas keet-indexing keet-push keet-registry keet-registry-client keet-registry-schema hyperdiscovery hyperindex keet-default-config keet-debug-helper legacy-keet-core-schemas"
for pkg in $PKGS; do
if [ -d "$DEPS/$pkg" ]; then
rm -rf "node_modules/@holepunchto/$pkg"
cp -r "$DEPS/$pkg" "node_modules/@holepunchto/$pkg"
else
echo " WARN: $pkg not in holepunchto-deps"
fi
done
echo "Overlay versions:"
for pkg in $PKGS; do
[ -d "node_modules/@holepunchto/$pkg" ] || continue
v=$(node -e "console.log(require('./node_modules/@holepunchto/$pkg/package.json').version)")
echo " @holepunchto/$pkg: $v"
done
- name: copy-keet-daemon
run: rm -rf node_modules/keet-daemon && cp -r /storage/projects/code/hypercore-kb/hyper-fleeky/packages/keet-daemon node_modules/keet-daemon
- name: verify-deps
run: |
FAIL=0
for mod in keet-daemon framed-stream b4a @holepunchto/keet-core hyperswarm; do
if [ ! -d "node_modules/$mod" ]; then echo "MISSING: $mod"; FAIL=1; fi
done
test $FAIL -eq 0 && echo "All critical deps present" || exit 1
- name: tune-rocksdb-memory
run: sed -i 's/maxWriteBufferNumber = 2/maxWriteBufferNumber = 1/' node_modules/rocksdb-native/lib/column-family.js && sed -i 's/maxOpenFiles = -1/maxOpenFiles = 256/' node_modules/rocksdb-native/lib/state.js
- name: stage-fat-tree
run: |
# Multi-arch layout: host-independent files at top level, platform
# binaries under by-arch/<host>/build/<platformDir>/. The rootTree
# references all arches; pgh app install's skipPath drops foreign
# by-arch/<other-host>/ during materialize so each user only fetches
# their own host's binaries.
RAW=$(uname -m)
case "$RAW" in x86_64) ARCH=x64 ;; aarch64) ARCH=arm64 ;; *) ARCH="$RAW" ;; esac
HOST="linux-${ARCH}"
STAGE=stage
rm -rf "$STAGE"
mkdir -p "$STAGE/by-arch/${HOST}/build/linux/bin/linux"
# Host-independent at top level
cp index.js package.json version.txt "$STAGE/"
cp -r daemon "$STAGE/"
cp -r lib "$STAGE/"
cp -r scripts "$STAGE/"
cp -r node_modules "$STAGE/"
# Host-specific: godot binary + pck + native GDExtension .so
cp build/linux/hypertps "$STAGE/by-arch/${HOST}/build/linux/"
cp build/linux/hypertps.pck "$STAGE/by-arch/${HOST}/build/linux/"
# NGX DLSS runtime libs must land BESIDE hypertps, not in bin/linux/ —
# dlss.cpp searches the executable's own dir (see copy-ngx-dlss).
cp build/linux/libnvidia-ngx-*.so* "$STAGE/by-arch/${HOST}/build/linux/"
cp -L build/linux/bin/linux/*.so* "$STAGE/by-arch/${HOST}/build/linux/bin/linux/"
# dc_terrain GPU compute shaders, alongside the .so (dladdr-resolved at runtime)
if [ -d build/linux/bin/linux/shaders ]; then
cp -rL build/linux/bin/linux/shaders "$STAGE/by-arch/${HOST}/build/linux/bin/linux/shaders"
fi
- name: strip-foreign-prebuilds
run: |
RAW=$(uname -m)
case "$RAW" in x86_64) ARCH=x64 ;; aarch64) ARCH=arm64 ;; *) ARCH="$RAW" ;; esac
HOST="linux-${ARCH}"
TARGET="stage/node_modules"
if [ -d "$TARGET" ]; then
BEFORE=$(du -sm "$TARGET" | cut -f1)
find "$TARGET" -path "*/prebuilds/*" -mindepth 1 -maxdepth 1 -type d ! -name "$HOST" -exec rm -rf {} +
AFTER=$(du -sm "$TARGET" | cut -f1)
echo "Stripped foreign prebuilds: ${BEFORE}MB -> ${AFTER}MB (saved $((BEFORE - AFTER))MB)"
fi
- name: pgh-publish
run: |
cd stage
pgh publish . --accept-conflict "automated CI publish; dep version splits + non-deterministic builds inherited from per-app lockfiles across the pgh ecosystem"
timeout: 300
- name: bundle-windows
# Windows is on the SAME engine as every Linux step: nvidia-rtx-godot-v3
# (4.8.dev, linux-rtx-v3-dev @ b7b5b330f3). Both paths below must move
# together -- this step exports Windows using the LINUX editor against the
# WINDOWS template, and as export-godot states, a mismatched pair
# version-mismatches. That failure would surface ONLY on a tagged release,
# because every other build takes the stub branch below and never exports.
#
# The 4.8 Windows template was cross-built 2026-09-21 (14m09s, clean):
# cd /storage/projects/code/godot/nvidia-rtx-godot-v3
# scons platform=windows target=template_release use_mingw=yes d3d12=no \
# winrt=no accesskit=no angle=no
# That build needs df60317884 ("windows: _fread_nolock is MSVC-only"), which
# the port carries: NVIDIA's 4.8 base uses _fread_nolock, MSVC-only, which
# breaks under mingw.
# use_ngx_dlss is linuxbsd-only in SConstruct, so the Windows build has no NGX
# DLSS -- same as on v2, not a regression from this migration.
#
# The standalone Windows build, for TAGGED RELEASES ONLY.
#
# It is 450MB zipped and the release dir stores plain files with no
# content-addressing or dedup, so one per master push would be ~450MB of
# permanent disk each — the accumulation problem that produced a 9.5GB
# pear-runtime store, in a different place.
#
# WHY A STUB ON EVERY OTHER BUILD, rather than skipping the step:
# collectArtifacts THROWS "artifact not found after build" on a missing path
# (ci-runner.js), and the artifacts list below cannot be conditional. So the
# path always exists; on a non-tag build it is a ~200 byte zip that says so,
# which is honest about what it is and costs nothing.
#
# CHANNEL is set by the runner from the channels map below, so "production"
# means this build came from a v* tag.
#
# Prerequisites are BUILD ARTIFACTS ON THIS HOST, not built here: the fork's
# Windows template (~9min) and the three Windows GDExtension DLLs (dc-terrain,
# hyperswarm-peer, proc-nodes). The script
# fails loudly if the export is missing; the same assert-don't-build pattern
# ci.yaml already uses for the Linux template.
run: |
mkdir -p dist
rm -f dist/hypertps-win64.zip
if [ "$CHANNEL" = "production" ]; then
GODOT=/storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.linuxbsd.editor.x86_64
TEMPLATE=/storage/projects/code/godot/nvidia-rtx-godot-v3/bin/godot.windows.template_release.x86_64.exe
test -x "$GODOT" || { echo "ERROR: godot-rtx editor missing at $GODOT"; exit 1; }
test -f "$TEMPLATE" || { echo "ERROR: WINDOWS export template missing at $TEMPLATE — build it with 'scons platform=windows target=template_release use_mingw=yes d3d12=no winrt=no accesskit=no angle=no'"; exit 1; }
# The EXPORT HAPPENS HERE, not in a prior step: build/ is gitignored, so
# the git-archive extraction CI runs in has no build/windows/ at all and
# the bundle script's precondition would fail. Same reasoning as
# export-godot above — DISPLAY=:0 and a real Vulkan device, so the shader
# baker actually runs rather than silently baking nothing.
mkdir -p build/windows
DISPLAY=:0 "$GODOT" --rendering-driver vulkan --export-release "Windows" "$PWD/build/windows/hypertps.exe"
test -f build/windows/hypertps.exe || { echo "ERROR: windows export produced no exe"; exit 1; }
test "$(stat -c%s build/windows/hypertps.pck 2>/dev/null || echo 0)" -gt 100000000 || { echo "ERROR: windows .pck missing or implausibly small"; exit 1; }
# No GDExtension DLL may be packed INTO the .pck: FileAccess::exists() then
# finds the packed copy of the relative bin/windows/<dll>, the exe-dir
# fallback never runs, and LoadLibraryExW fails with Error 126 for every
# extension (v0.1207.0). The Windows bundle keeps DLLs beside the exe, so a
# packed one is always wrong here. (Linux packs its .so too, but its install
# tree ships a real bin/linux/ beside the binary, so the paths line up.)
python3 tools/pck-ls.py build/windows/hypertps.pck --forbid '*.dll' || { echo "ERROR: a DLL is packed into the Windows .pck — check export_presets.cfg include_filter"; exit 1; }
./tools/build-windows-bundle.sh "$PWD/dist/hypertps-win64"
(cd dist && zip -rq hypertps-win64.zip hypertps-win64)
else
mkdir -p dist/_stub
echo "The Windows bundle is built for tagged releases only (v*). This build was CHANNEL='${CHANNEL:-none}'." > dist/_stub/NOTE.txt
(cd dist/_stub && zip -q ../hypertps-win64.zip NOTE.txt)
fi
ls -la dist/hypertps-win64.zip
timeout: 1800
# Files only — collectArtifacts has no dir walk and no glob, which is why the
# bundle is a single zip rather than a tree.
artifacts:
- dist/hypertps-win64.zip
# Tags publish to `production`; branches stay on the default (none). This is what
# gates the Windows bundle above, and what pins a version against GC: the release
# manager's gc() keeps every version a channel points at.
channels:
production:
tags: ["v*"]