Compare commits

..
3 Commits
Author SHA1 Message Date
lennartandClaude Opus 5 f5b302b3e3 add a Makefile for the common commands, and a test reporter
The invocations in CLAUDE.md were correct but long, and retyping them is how
flags get dropped. `make check` is the gate a change should pass: Yarn compiles,
voice sheets are in sync, EditMode is green.

`make test` and `make build` depend on a `lock` target that fails if the Editor
has the project open, which removes the most common confusing failure. It
filters out AssetImportWorker children, which are not a second Editor.

tools/ci/report_tests.py parses the NUnit results and exits non-zero on failure,
because Unity documents no common exit-code definition across the components
under test — the XML is the authority, not $?.

`make merge-driver` configures UnityYAMLMerge for the .gitattributes rules added
earlier. Note the binary lives in the Editor bundle under Contents/Helpers, not
Contents/Tools as most guides say; there is no Tools directory in Unity 6 on
macOS.

Also ignores the local .test-results/ and build/ output.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-08-25 20:29:39 +02:00
lennartandClaude Opus 5 2b35d3f754 renormalize line endings to LF
Mechanical consequence of the .gitattributes in the previous commit, done in one
deliberate pass rather than left to trickle out as surprise diffs whenever
someone next touches a vendored file.

74 files, all CRLF -> LF, 71 of them in StarterAssets. `git diff -w` over this
commit is empty: nothing but line endings changed. DialogueTest.unity is
correctly untouched, being marked binary.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-08-25 20:28:07 +02:00
lennartandClaude Opus 5 382f6c7f63 add .gitattributes: Unity YAML merge driver and line-ending policy
Scenes, prefabs and assets are now marked for UnityYAMLMerge, which is what
makes them mergeable at all — without it a scene conflict is resolved by a
line-oriented text merge that produces a file Unity may still load and that is
quietly wrong. The driver needs a one-time git config per machine; CLAUDE.md
carries the command.

Line endings are pinned to LF for every text type rather than left to
`text=auto`, so the 71 CRLF files vendored in StarterAssets stop being a source
of phantom diffs.

One exception, and it matters: DialogueTest.unity is marked `binary`. It is a
Unity binary SerializedFile rather than YAML, and it contains 128 lone CR bytes
— under `*.unity text eol=lf` git would rewrite those as line endings and
corrupt the scene. The rule carries the verification command and comes out as
soon as the file is genuinely text.

LFS is deliberately not enabled: the remote is self-hosted and its LFS support
is unverified, and enabling the filter against a server without it breaks
pushing. The rules to add later, and the migration step that has to accompany
them, are recorded in the file.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-08-25 20:28:06 +02:00
79 changed files with 14211 additions and 13951 deletions
+114
View File
@@ -0,0 +1,114 @@
# Nightclub Arcadia — git attributes
#
# Two jobs: make Unity's YAML files mergeable, and keep line endings from
# becoming a source of phantom diffs.
# ---------------------------------------------------------------------------
# Unity YAML — text, LF, and merged with UnityYAMLMerge
# ---------------------------------------------------------------------------
# The merge driver needs a one-time setup per machine; see §"Merge driver" in
# CLAUDE.md. Without it git falls back to a normal text merge, which for scenes
# and prefabs means conflicts that are painful but not silently wrong.
*.unity text eol=lf merge=unityyamlmerge
*.prefab text eol=lf merge=unityyamlmerge
*.asset text eol=lf merge=unityyamlmerge
*.mat text eol=lf merge=unityyamlmerge
*.anim text eol=lf merge=unityyamlmerge
*.controller text eol=lf merge=unityyamlmerge
*.overrideController text eol=lf merge=unityyamlmerge
*.physicMaterial text eol=lf merge=unityyamlmerge
*.physicsMaterial2D text eol=lf merge=unityyamlmerge
*.playable text eol=lf merge=unityyamlmerge
*.mask text eol=lf merge=unityyamlmerge
*.brush text eol=lf merge=unityyamlmerge
*.flare text eol=lf merge=unityyamlmerge
*.lighting text eol=lf merge=unityyamlmerge
*.terrainlayer text eol=lf merge=unityyamlmerge
*.signal text eol=lf merge=unityyamlmerge
*.guiskin text eol=lf merge=unityyamlmerge
*.fontsettings text eol=lf merge=unityyamlmerge
*.meta text eol=lf
# ---------------------------------------------------------------------------
# EXCEPTION — a scene that is serialized as BINARY
# ---------------------------------------------------------------------------
# DialogueTest.unity is a Unity binary SerializedFile, not YAML, despite the
# project being set to Force Text. It contains 128 lone CR bytes, so treating it
# as text would have git rewrite them as line endings and corrupt the scene.
#
# Remove this line the moment the file is genuinely text — verify with:
# head -c 20 NightclubArcadia/Assets/Scenes/DialogueTest.unity # want %YAML 1.1
# Background and the fix: docs/restructure-plan.md §0.1
/NightclubArcadia/Assets/Scenes/DialogueTest.unity binary
# ---------------------------------------------------------------------------
# Source, config and prose
# ---------------------------------------------------------------------------
*.cs text eol=lf diff=csharp
*.yarn text eol=lf
*.yarnproject text eol=lf
*.asmdef text eol=lf
*.asmref text eol=lf
*.inputactions text eol=lf
*.shader text eol=lf
*.compute text eol=lf
*.cginc text eol=lf
*.hlsl text eol=lf
*.shadergraph text eol=lf
*.json text eol=lf
*.md text eol=lf
*.txt text eol=lf
*.py text eol=lf
*.sh text eol=lf
*.csproj text eol=lf
*.xml text eol=lf
*.gitattributes text eol=lf
*.gitignore text eol=lf
# ---------------------------------------------------------------------------
# Binary — never diffed, never line-ending converted
# ---------------------------------------------------------------------------
*.png binary
*.jpg binary
*.jpeg binary
*.gif binary
*.tga binary
*.tif binary
*.tiff binary
*.psd binary
*.exr binary
*.hdr binary
*.fbx binary
*.obj binary
*.blend binary
*.wav binary
*.mp3 binary
*.ogg binary
*.aif binary
*.ttf binary
*.otf binary
*.pdf binary
*.dll binary
*.pdb binary
*.so binary
*.dylib binary
*.bundle binary
*.a binary
*.unitypackage binary
*.cubemap binary
# ---------------------------------------------------------------------------
# Git LFS — deliberately NOT enabled yet
# ---------------------------------------------------------------------------
# git-lfs is installed locally, but this repo pushes to a self-hosted server
# whose LFS support has not been verified, and enabling LFS against a server
# that lacks it breaks pushing. There is also almost nothing to store yet: one
# placeholder PNG plus the StarterAssets meshes.
#
# When real art lands, verify the server first, then add:
# *.png filter=lfs diff=lfs merge=lfs -text
# *.fbx filter=lfs diff=lfs merge=lfs -text
# *.wav filter=lfs diff=lfs merge=lfs -text
# and migrate existing blobs with `git lfs migrate import --include=...`.
# Adding the filter without migrating leaves old blobs outside LFS, which is
# confusing rather than broken.
+6
View File
@@ -127,3 +127,9 @@ tools/**/obj/
*.tar.gz
*.7z
*.rar
# ---------------------------------------------------------------------------
# Local build and test output (Makefile targets)
# ---------------------------------------------------------------------------
/.test-results/
/build/
+30
View File
@@ -58,6 +58,36 @@ AI Navigation 2.0.14, ProBuilder 6.1.2, Test Framework 1.7.0. Yarn Spinner is an
---
## 2b. Everyday commands
A `Makefile` at the repo root wraps everything in this document. Prefer it — the exact
invocations live there instead of being retyped.
```bash
make # list targets
make check # yarn + voice sheets + EditMode — run this before committing
make test # EditMode suite, with a readable summary
make yarn # compile every Yarn script (seconds, no Unity)
make build # macOS player into build/
make lock # fail if the Editor has the project open
```
`make test` and `make build` refuse to run while the Editor is open, which removes the most
common way these commands fail confusingly.
### Merge driver — one-time per machine
`.gitattributes` marks Unity YAML for `unityyamlmerge`. Git needs to be told what that is, or
it falls back to a line-oriented text merge that can produce a scene Unity still loads and that
is quietly wrong:
```bash
make merge-driver
```
That points git at `UnityYAMLMerge` inside the Editor bundle (`Contents/Helpers/`, not
`Contents/Tools/` — the path in most online guides is wrong for Unity 6 on macOS).
## 3. Unity from the command line
### 3.0 Two different tools, both present
+66
View File
@@ -0,0 +1,66 @@
# Nightclub Arcadia — common commands.
#
# Everything here is documented in CLAUDE.md; this file exists so the exact
# invocations live in one place instead of being retyped from memory.
UNITY_CLI ?= /Users/lennart/.unity/bin/unity
UNITY ?= /Applications/Unity/Hub/Editor/6000.5.8f1/Unity.app/Contents/MacOS/Unity
PROJECT ?= $(CURDIR)/NightclubArcadia
RESULTS ?= $(CURDIR)/.test-results
SMARTMERGE ?= /Applications/Unity/Hub/Editor/6000.5.8f1/Unity.app/Contents/Helpers/UnityYAMLMerge
.DEFAULT_GOAL := help
.PHONY: help check yarn yarn-play voices voices-sync test test-play build lock clean-results merge-driver
help: ## Show this help
@grep -hE '^[a-z-]+:.*?## ' $(MAKEFILE_LIST) \
| awk 'BEGIN {FS = ":.*?## "} {printf " \033[36m%-14s\033[0m %s\n", $$1, $$2}'
@echo ""
@echo " Unity must be CLOSED for test/build — run 'make lock' to check."
check: yarn voices test ## Everything a change should pass before commit
yarn: ## Compile every Yarn script (seconds, no Unity)
@cd tools/YarnCheck && DOTNET_CLI_TELEMETRY_OPTOUT=1 dotnet run -- ../../NightclubArcadia/Assets/Dialogue
yarn-play: ## Play a node: make yarn-play NODE=Bartender_Talk PICKS="2 2 0 0"
@cd tools/YarnCheck && DOTNET_CLI_TELEMETRY_OPTOUT=1 \
dotnet run -- ../../NightclubArcadia/Assets/Dialogue $(NODE) $(PICKS)
voices: ## Check writing/voices/*.md against skill_bible.json
@python3 tools/writing/sync_voices.py --check
voices-sync: ## Fold edited voice prose into skill_bible.json
@python3 tools/writing/sync_voices.py --to-json
test: lock ## Run the EditMode suite
@mkdir -p $(RESULTS)
@$(UNITY_CLI) test $(PROJECT) --no-banner --mode EditMode \
--output $(RESULTS)/editmode.xml --timeout 900 || true
@python3 tools/ci/report_tests.py $(RESULTS)/editmode.xml
test-play: lock ## Run the PlayMode suite (none exist yet)
@mkdir -p $(RESULTS)
@$(UNITY_CLI) test $(PROJECT) --no-banner --mode PlayMode \
--output $(RESULTS)/playmode.xml --timeout 900 || true
@python3 tools/ci/report_tests.py $(RESULTS)/playmode.xml
build: lock ## Build a macOS player into build/
@$(UNITY_CLI) build $(PROJECT) --no-banner --target StandaloneOSX -o $(CURDIR)/build/NightclubArcadia.app
lock: ## Fail if the Unity Editor has the project open
@if pgrep -fl "Unity.app/Contents/MacOS/Unity" | grep -v AssetImportWorker | grep -q .; then \
echo "The Unity Editor is running — close it first (only one instance per project)."; \
exit 1; \
fi
merge-driver: ## One-time: teach git to merge Unity YAML with UnityYAMLMerge
@test -x "$(SMARTMERGE)" || { echo "UnityYAMLMerge not found at $(SMARTMERGE)"; exit 1; }
@git config merge.unityyamlmerge.name "Unity SmartMerge"
@git config merge.unityyamlmerge.driver "'$(SMARTMERGE)' merge -p --force --fallback none %O %B %A %A"
@git config merge.unityyamlmerge.recursive binary
@echo "configured: $$(git config merge.unityyamlmerge.name)"
clean-results: ## Remove local test results
@rm -rf $(RESULTS)
+44
View File
@@ -0,0 +1,44 @@
#!/usr/bin/env python3
"""Summarise an NUnit results file and exit non-zero if anything failed.
Unity's own docs state there is no common exit-code definition across the
components under test, so the results XML is the authority, not $?.
"""
import sys
import xml.etree.ElementTree as ET
def main(path):
try:
root = ET.parse(path).getroot()
except FileNotFoundError:
print(f"no results at {path} — the run did not get far enough to write one")
return 1
except ET.ParseError as exc:
print(f"could not parse {path}: {exc}")
return 1
total = int(root.get("total") or 0)
passed = int(root.get("passed") or 0)
failed = int(root.get("failed") or 0)
skipped = int(root.get("skipped") or 0)
for case in root.iter("test-case"):
if case.get("result") == "Passed":
continue
print(f"FAIL {case.get('fullname')}")
message = case.find("failure/message")
if message is not None and message.text:
for line in message.text.strip().splitlines()[:6]:
print(f" {line}")
if total == 0:
print("no tests ran")
else:
print(f"{passed}/{total} passed" + (f", {skipped} skipped" if skipped else ""))
return 1 if failed else 0
if __name__ == "__main__":
sys.exit(main(sys.argv[1] if len(sys.argv) > 1 else ".test-results/editmode.xml"))