#!/usr/bin/env bash
# dev-editor — open the SOURCE TREE in the Godot editor.
#
# scripts/dev-editor.sh # open the project
# scripts/dev-editor.sh scenes/test/proc_bridge_test.tscn # open one scene
# scripts/dev-editor.sh res://scenes/game/game.tscn
# scripts/dev-editor.sh <scene> -- --verbose # extra godot args
# scripts/dev-editor.sh --no-daemon # no keet-daemon
#
# WHY THIS EXISTS, and why it is NOT dev-run.sh. dev-run.sh RUNS the game (plus
# keet-daemon, which the game cannot do without). This opens the EDITOR, which
# is a different thing and is required for any @tool scene -- every path in
# scenes/test/proc_bridge_test.gd sits behind Engine.is_editor_hint(), so
# running the project executes none of it and the Blender bridge never starts.
#
# THE keet-daemon IS STARTED (or reused) HERE TOO, since 2026-10-05. It used to be
# skipped on the grounds that editor autoloads do not run -- true of the editor,
# but F5 RUNS THE GAME from it, and the game with no daemon keeps every durable
# write in memory and loses it on quit. Home is a keet room (docs/home-world-
# design.md), so the daemon always runs. Only a daemon started here is stopped
# on exit; --no-daemon skips it. Same code as dev-run.sh: scripts/daemon.sh.
#
# THE ENGINE IS THE FORK, NOT SYSTEM godot4 (4.6.stable) -- same binary as
# dev-run.sh, because both source scripts/engine.sh.
#
# FIRST LAUNCH AFTER A CLEAN CHECKOUT reimports the whole project and can sit on
# "Scanning file structure..." for minutes with no window. That is not a hang.
# Output is passed straight through so you can watch it happen.
#
# THE EDITOR REWRITES project.godot ON EXIT, and it does not round-trip: it
# strips every comment and reorders keys. Opening the editor once silently
# deleted the 30-line block documenting the Jolt swap. Nothing warned.
#
# Warning is not enough, because losing that block is not acceptable and a
# warning still leaves you to clean up. So this compares the SETTINGS -- the
# key=value pairs, ignoring comments, blank lines and order -- before and after:
#
# settings identical -> your file is restored verbatim, comments intact.
# The rewrite carried no information, so nothing is lost
# by discarding it.
# settings changed -> the editor's file is KEPT, because you meant that,
# and the changed keys are printed along with the
# pre-launch copy so you can put the comments back.
set -uo pipefail
REPO="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
. "$REPO/scripts/engine.sh"
DAEMON_TAG=dev-editor
. "$REPO/scripts/daemon.sh"
# NVIDIA's shader disk cache defaults to a 128 MB cap and stores nothing new once
# full; every launch then recompiles proc-nodes' and dc-terrain's ~115k-char
# shaders in the driver (6.8-7.3 s each, measured). See index.js.
export __GL_SHADER_DISK_CACHE_SIZE="${__GL_SHADER_DISK_CACHE_SIZE:-4294967296}"
_die() { echo "dev-editor: $*" >&2; exit 1; }
[ -x "$GODOT" ] || _die "engine missing at $GODOT (set HYPER_TPS_GODOT)"
[ -f "$REPO/project.godot" ] || _die "no project.godot in $REPO"
scene=""
godot_args=()
seen_sep=0
want_daemon=1
for a in "$@"; do
if [ "$seen_sep" = 1 ]; then godot_args+=("$a"); continue; fi
case "$a" in
--) seen_sep=1 ;;
-h|--help) sed -n '2,30p' "${BASH_SOURCE[0]}"; exit 0 ;;
--no-daemon) want_daemon=0 ;;
-*) godot_args+=("$a") ;;
*) [ -z "$scene" ] || _die "more than one scene given: '$scene' and '$a'"
scene="$a" ;;
esac
done
# Normalise a scene argument to res://. Godot resolves a bare relative path
# against its own cwd, not --path, so passing "scenes/foo.tscn" from anywhere
# but the repo root silently opens nothing.
if [ -n "$scene" ]; then
case "$scene" in
res://*) rel="${scene#res://}" ;;
/*) case "$scene" in
"$REPO"/*) rel="${scene#"$REPO"/}" ;;
*) _die "scene is outside the project: $scene" ;;
esac ;;
*) rel="$scene" ;;
esac
[ -f "$REPO/$rel" ] || _die "no such scene: $REPO/$rel"
scene="res://$rel"
fi
# A dangling GDExtension symlink does not fail loudly -- the class simply does
# not register, and the first symptom is a scene that will not instantiate. One
# of these was broken for months, so it is worth the two lines.
for so in "$REPO"/bin/linux/*.so; do
[ -e "$so" ] || echo "dev-editor: WARNING broken symlink: $so" >&2
done
if [ "$want_daemon" = 1 ]; then
[ -x "$BARE" ] || _die "bare missing (set HYPER_TPS_BARE), or pass --no-daemon"
ensure_daemon
# EXIT covers the normal end (`exit $rc` below) and a Ctrl-C alike.
trap stop_daemon_if_started EXIT
fi
SNAP="$(mktemp -t project.godot.pre-editor.XXXXXX)"
cp "$REPO/project.godot" "$SNAP"
echo "dev-editor: $(basename "$GODOT") -e ${scene:-<project>} --path $REPO"
DISPLAY="${DISPLAY:-:0}" "$GODOT" --path "$REPO" -e ${scene:+"$scene"} \
--rendering-driver vulkan "${godot_args[@]+"${godot_args[@]}"}"
rc=$?
if cmp -s "$SNAP" "$REPO/project.godot"; then
rm -f "$SNAP"
else
# Compare settings only: strip comments and blanks, keep section headers so a
# key is qualified by its section, and sort so reordering does not count.
_settings() {
sed -e 's/[[:space:]]*$//' -e '/^;/d' -e '/^[[:space:]]*$/d' "$1" | sort
}
if diff -q <(_settings "$SNAP") <(_settings "$REPO/project.godot") >/dev/null; then
cp "$SNAP" "$REPO/project.godot"
rm -f "$SNAP"
echo "" >&2
echo "dev-editor: the editor rewrote project.godot but changed no setting —" >&2
echo " it only stripped comments and reordered keys. Your file is restored." >&2
else
echo "" >&2
echo "dev-editor: the editor CHANGED project.godot settings. Keeping its version." >&2
diff <(_settings "$SNAP") <(_settings "$REPO/project.godot") \
| grep -E '^[<>]' | sed 's/^/ /' >&2
echo " Comments were stripped in the rewrite. Pre-launch copy: $SNAP" >&2
echo " Diff: git -C '$REPO' diff project.godot" >&2
fi
fi
exit $rc