Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
161 commits
Select commit Hold shift + click to select a range
7fcbbec
Replace hardcoded unit-name lists in gadgets with customparams (#9019)
Ruwetuin Sep 1, 2026
70a0e59
moved loc-graph.yml file to correct folder (#9025)
Ruwetuin Sep 1, 2026
7bc242b
buildsquare/easyfacing: also use DrawWorld to overlay the effect so i…
Ruwetuin Sep 1, 2026
a52b9f8
selected units: dont highlight hovered units/features when you are pl…
Ruwetuin Sep 1, 2026
829ea6b
Terraform Brush v1.12 (#8847)
PtaQQ Sep 1, 2026
9418383
gui_pip: remove unreferenced local functions (#9034)
sparr Sep 1, 2026
7f7ca18
Remove unreferenced local functions (#9033)
sparr Sep 1, 2026
77f4c52
Fix Gaia storage going negative after ruins spawn during loading (#9028)
Egzothicki Sep 1, 2026
c642aaa
Fix widget selector filter input when opened via chat command. (#9020)
Rysicaa Sep 1, 2026
4f15235
Name the resource spot builder's unit tables (#9009)
sparr Sep 1, 2026
711aa86
Update Lua libary submodule
github-actions[bot] Sep 2, 2026
e4e3e3e
flowui: unit outline chamfered corner insides as well (#9052)
Ruwetuin Sep 2, 2026
6403e19
deferred lights: performance optimization (#9050)
Vandomas Sep 2, 2026
2670fb5
Stop pre placed units from blocking start position placement (#9030)
Egzothicki Sep 2, 2026
24c5130
type check: stop penalizing code deletions by requiring they always r…
sparr Sep 2, 2026
610be21
Fix corpse link when death animations displace wrecks. (#8826)
Rysicaa Sep 2, 2026
2c08f19
New Raptor Music from West Basinger (#8993)
Damgam Sep 2, 2026
b109245
Eliminate AllyTeamID alias made redundant by upstream (#9001)
sparr Sep 2, 2026
9189d44
Use integer and new ID aliases to replace number and integer types (#…
sparr Sep 2, 2026
d9004f5
Fix waypoint drag lines being occluded by terrain (#9045)
eun-ice Sep 2, 2026
5e0b989
Fix builder priority stall budget (#9021)
Rysicaa Sep 2, 2026
31abb4a
Correct VTOL casing to uppercase (#9017)
PrivacyIsARight Sep 2, 2026
44e4181
Normalize anti air strings to Anti-Air (#9040)
PrivacyIsARight Sep 2, 2026
ed27766
Draw no-rush zones from the startbox polygon (#8900)
burnhamrobertp Sep 2, 2026
5233a29
Aliases for the object-or-position unions (#9002)
sparr Sep 2, 2026
5b18c45
Update Lua libary submodule
github-actions[bot] Sep 3, 2026
11ccd51
Fix low-prio builder energy budget using actual team pull.
Sep 1, 2026
80841d4
Document metal and energy accounting in builder priority gadget.
Sep 1, 2026
6fe1af1
trim trailing whitespace for stylua
Sep 1, 2026
90e7e60
var rename
Sep 3, 2026
3b61544
use fully accurate accounting on energy pull
Sep 3, 2026
281eab9
style
Sep 3, 2026
fbe65a2
renamed copilot-instructions.md -> agents.md, + mission api update (#…
Ruwetuin Sep 3, 2026
5ffdea9
chat: flush the unit share summary by sim frame (#9058)
Vandomas Sep 3, 2026
bad136a
fix: advanced metal fortifier exploding on completion (#9064)
efrec Sep 3, 2026
878bc32
Remove locals that are written but never read (#9043)
sparr Sep 3, 2026
826a027
Remove unused local declarations in luaui (#9018)
sparr Sep 3, 2026
ff6e5b1
api_unit_tracker_gl4: remove dead callin handle cache and unused help…
sparr Sep 3, 2026
46326ec
nano particles: default enable NanoParticlesGroundClamp (#9067)
Ruwetuin Sep 3, 2026
9010658
stack hack fix: performance optimization (#9068)
Ruwetuin Sep 3, 2026
060bfb7
Nano particles performance optimization (#9069)
Ruwetuin Sep 3, 2026
14aefe0
remove loc-graph (#9070)
Ruwetuin Sep 3, 2026
35b4255
Use new ID aliases in type annotations (#9011)
sparr Sep 3, 2026
a5aa0b2
Fix typos in interface.json (#9071)
PrivacyIsARight Sep 4, 2026
7203e36
prevent nanoframe blocking/stack hack fix: performance optimization (…
Ruwetuin Sep 4, 2026
50a434d
Made tweakunits load before tweakdefs and extended available tweakuni…
goldjee Sep 4, 2026
de228de
chat: keep color codes whole when wrapping lines (#9057)
Vandomas Sep 4, 2026
9a5fe4b
Terraform Brush 1.13: tileset performance controls, biome library, pa…
PtaQQ Sep 4, 2026
df621e4
shield effects: performance optimization (#9091)
Ruwetuin Sep 4, 2026
46c3f03
Fix more typos (#9088)
PrivacyIsARight Sep 4, 2026
67af78a
Fix even more typos (#9089)
PrivacyIsARight Sep 4, 2026
fb42c27
ai namer: add Eunice3x to contributor names (#9086)
PtaQQ Sep 4, 2026
9a3765f
Display attack range arc for missile ships (#9083)
Rysicaa Sep 4, 2026
6a4f503
shield effects: scavenger shields get purple tint (#9094)
Ruwetuin Sep 5, 2026
723a05d
clone tool/feature placer/terraform brush/map project units: dont use…
Ruwetuin Sep 5, 2026
db7ca67
Update Lua libary submodule
github-actions[bot] Sep 5, 2026
5069526
carrier spawner: performance optimization + dronecarrier plasma proje…
Ruwetuin Sep 5, 2026
3ccf25e
Fix visibility typo (#9098)
PrivacyIsARight Sep 5, 2026
651d11f
scav/raptor spawner defense: scavs also get target-picking optimizati…
Ruwetuin Sep 5, 2026
876508e
Cortex Commander fire laser from the barrel muzzle (#9082)
Egzothicki Sep 5, 2026
dd755c0
Remove unused local declarations in luarules, defs and shared code (#…
sparr Sep 5, 2026
4dbf617
nano particles: fix (#9102)
Ruwetuin Sep 5, 2026
84a8955
Fix received typos (#9101)
PrivacyIsARight Sep 5, 2026
a1e2b76
polygon startbox/norush timer render performance improved (#9118)
Ruwetuin Sep 6, 2026
262fb0a
no rush mode: lava damage gets applied again (#9119)
Ruwetuin Sep 6, 2026
6ae3686
Hide wind and tidal animations with the topbar indicators (#9115)
burnhamrobertp Sep 6, 2026
5bcea5c
ecostats: fix background size difference issue (#9121)
Ruwetuin Sep 6, 2026
bd08aac
spectator hud: added safeguards (#9122)
Ruwetuin Sep 6, 2026
efc329a
fix: attached units customparams (#9123)
efrec Sep 6, 2026
e6a1a2d
autocolors: assure same team colors for non ffa games as well (#9126)
Ruwetuin Sep 6, 2026
8bc7d88
fix: Set Target issues with CAI, other cases where current target sli…
efrec Sep 6, 2026
3c7bf36
fix: file was renamed to unit_attachments.lua (#9131)
efrec Sep 7, 2026
717bb9b
fix for empty cobScriptTurrets tbl (#9132)
efrec Sep 7, 2026
a6f30a7
Fix CommandInsert appending instead of inserting with Shift+Space (#9…
frkns Sep 7, 2026
fb1a21a
fixes long stop message crashign the vote widget (#9135)
Rysicaa Sep 7, 2026
85d7f23
pip: also show screen mode overlays (height, traversability, resource…
Ruwetuin Sep 7, 2026
5d829ed
Per-anchor curvature for startboxes in Terraformer (#8857)
burnhamrobertp Sep 7, 2026
336efea
attackrange: increased performance (#9138)
Ruwetuin Sep 7, 2026
5378ad9
defenserange: increased performance (#9139)
Ruwetuin Sep 7, 2026
878519b
Update Lua libary submodule
github-actions[bot] Sep 8, 2026
ae4b18d
Fixed Jet texture having seams on the sides. (#9145)
Damgam Sep 8, 2026
b0f3e77
Bugfix: Make sortStable stable (#9146)
sorenmarkert Sep 8, 2026
9c032ed
Bugfix/table sort stable (#9148)
sorenmarkert Sep 8, 2026
fafeae4
Don't lua check mapgenerator/** templates, (#9147)
sorenmarkert Sep 8, 2026
7089c3c
Disable comm spawn and faction picker via start script (#9031)
sorenmarkert Sep 8, 2026
d94b0b2
Switch rapid deployment to trigger based builder (#9107)
p2004a Sep 8, 2026
df256d5
add LoadFile, Json at load time in spec_helper (#9156)
efrec Sep 8, 2026
59baf89
Update Lua libary submodule
github-actions[bot] Sep 9, 2026
8779d1e
Fix Cancel Target order menu visibility (#8997)
eun-ice Sep 9, 2026
651e57a
Fix aggressive typos (#9103)
PrivacyIsARight Sep 9, 2026
cf83f3e
Fix doesn't typos (#9104)
PrivacyIsARight Sep 9, 2026
028dae0
Fix don't typos (#9108)
PrivacyIsARight Sep 9, 2026
ebec149
Stop unused start boxes moving FFA and duel spawns (#9159)
burnhamrobertp Sep 9, 2026
56b19de
Give unboxed allyteams a whole-map start box (#9137)
burnhamrobertp Sep 9, 2026
73b7e6b
cus gl4: shadow pass tex perf improvement (#9161)
Ruwetuin Sep 9, 2026
fe2e1c4
Zombies - Swarming Update (#8949)
SethDGamre Sep 9, 2026
da09265
Terraform Brush 1.14: sculpting cadence, INFLUENCE bands, editor sand…
PtaQQ Sep 9, 2026
66747d8
feat: add a debug view for target priority (#9106)
efrec Sep 9, 2026
e9e79c9
In-game keybind editor (#8153)
burnhamrobertp Sep 9, 2026
def1c85
gui_savegame: remove the unreachable load and delete portions (#9037)
sparr Sep 9, 2026
6f5bc8b
keybind editor: performance improvements + improved styling (#9169)
Ruwetuin Sep 9, 2026
747fc47
Use `pr-`/`br-` instead of `pr:`/`br:` for rapid branches (#9172)
p2004a Sep 9, 2026
5638ca8
keybind editor: more style tweaks (#9175)
Ruwetuin Sep 9, 2026
8a54f12
Update Lua libary submodule
github-actions[bot] Sep 10, 2026
f6f15d9
keybind editor: styling tweaks (#9180)
Ruwetuin Sep 10, 2026
85921a7
widget auto reloader: monitor the include files widgets use as well (…
Ruwetuin Sep 10, 2026
f992834
Fix keybind editor migration, duplicate names and meta capture (#9182)
burnhamrobertp Sep 10, 2026
c82c12d
fix: startbox spec from leaks a zlib stub into later specs (#9189)
efrec Sep 10, 2026
c71941d
changelog: changed style to be more similar to keybind editor (#9191)
Ruwetuin Sep 10, 2026
a58a549
changelog: use .md file instead of .txt (#9192)
Ruwetuin Sep 10, 2026
406e43f
topbar buttons clicking: fix new window widget not opening when anoth…
Ruwetuin Sep 10, 2026
a6beec3
Add stockpile support to State Prefs V2 (#8822)
Rysicaa Sep 10, 2026
f7eed43
Update Lua libary submodule
github-actions[bot] Sep 11, 2026
e6bd069
gameinfo: style change + added categories sidebar + tweakunits show a…
Ruwetuin Sep 11, 2026
208f3e5
dev helper:s add filter 'rework' (#9204)
Ruwetuin Sep 11, 2026
546ce61
added configsetting WindowsHideInterface to hide the GUI exept the ce…
Ruwetuin Sep 11, 2026
9067b49
Fix console color wrapping (#9199)
PrivacyIsARight Sep 11, 2026
0d73feb
pip: fix minimized pip resolution change position/sizing issue (#9209)
Ruwetuin Sep 11, 2026
a635c1b
top bar buttons: extracted into separate widget (#9211)
Ruwetuin Sep 11, 2026
85751f8
widgethandler: add show/hide interface (#9212)
Ruwetuin Sep 11, 2026
71f413a
fix: gui_top_bar buttons eat clicks, click releases (#9213)
efrec Sep 12, 2026
2af2ee1
Fix enemy fire-state display in godmode (#9200)
eun-ice Sep 12, 2026
fd3aa82
adjusted scrollbar behavior for gameinfo, keybinds, changelog (#9219)
Ruwetuin Sep 12, 2026
2b4b413
extracted search algo's into new include file (#9220)
Ruwetuin Sep 12, 2026
d1c0b23
gameinfo/keybind editor: refinements (#9221)
Ruwetuin Sep 12, 2026
e0bead8
widget selector: overhaul (#9224)
Ruwetuin Sep 12, 2026
5a82df3
widget selector: add 'Reset' button to next to widgets that clear the…
Ruwetuin Sep 12, 2026
4114477
widget selector/profile: added profiler + extracted profiling wrappin…
Ruwetuin Sep 12, 2026
a9f5ae5
Quick Start - remove debug echo's (#9230)
SethDGamre Sep 12, 2026
c21f3e0
widget selected: added show data button per widget (#9231)
Ruwetuin Sep 12, 2026
3110cde
widget selector: add if its an rml widget and also if errored + added…
Ruwetuin Sep 12, 2026
28e1222
widget selector: removed unload widgets button + fix (#9233)
Ruwetuin Sep 13, 2026
1d267c2
gameinfo: add clickable unitpic grid of all the changed tweakunits (#…
Ruwetuin Sep 13, 2026
fe695c6
widget selector: added errors button + errors only toggle (#9236)
Ruwetuin Sep 13, 2026
7bd7844
widget selector: category coloring + added 'uses' / 'used by' info (#…
Ruwetuin Sep 13, 2026
00475f5
Fix godmode exploit (#9238)
TTNO1 Sep 14, 2026
eead358
keybind editor: refinements (#9240)
Ruwetuin Sep 14, 2026
29a15b3
keybind editor: added cmd icons, highlight conflicts, added im/export…
Ruwetuin Sep 14, 2026
5e3f3f6
Restore the space meta key on the shipped keymaps (#9244)
burnhamrobertp Sep 14, 2026
d514c9d
Remove yardmap from mobile unit defs (#9243)
burnhamrobertp Sep 14, 2026
87b325d
keybind editor: added keyboard layout button (#9248)
Ruwetuin Sep 14, 2026
e33bdce
Airjets extension (#9207)
Beherith Sep 14, 2026
bf7395d
Add printf support to CUS_GL4 (#9061)
Beherith Sep 14, 2026
d25ab7b
Update Lua libary submodule
github-actions[bot] Sep 15, 2026
7e29420
Make ruins not spawn anything the user blocks (#9210)
PrivacyIsARight Sep 15, 2026
a483e29
Migrate keybinds that keyload a bind file we no longer ship (#9249)
burnhamrobertp Sep 15, 2026
9a0c491
widget selector: add favorites icon/button and category +default enab…
Ruwetuin Sep 15, 2026
b598bf6
keyboard editor: enlarged keyboard text (#9256)
Ruwetuin Sep 15, 2026
206ce72
ci: run the unit tests on every branch, not just slash-free ones (#9260)
efrec Sep 16, 2026
cc191c1
Legion bugfixes and Alaris adjustment (#9054)
Zecrus2 Sep 16, 2026
5d717d7
Legion: QA pass, range rings, blueprints, stale folder references, et…
efrec Sep 16, 2026
e0cd351
FactoryQ Manager: contextual load hotkeys + toggle action (v1.7) (#8047)
MadeByGabe Sep 16, 2026
bc880e1
fix?: quotas with repeat mode (#9222)
efrec Sep 16, 2026
17567e3
Fix command modifiers being lost when dragging waypoints (#9201)
eun-ice Sep 16, 2026
3df3cc9
docs: correct the CI gate facts in agents.md, add spec instructions (…
efrec Sep 16, 2026
e1f046a
Fix Advanced Metal Fortifier ignoring Set Target and Attack commands …
Egzothicki Sep 16, 2026
4ccb3a9
teamstats: overhaul + adding categories + added teamstats api gadget …
Ruwetuin Sep 16, 2026
2442deb
Attack command: Fix ground Area Attack target cycling (#8773)
eun-ice Sep 16, 2026
555c011
Merge branch 'master' into fix/builder-priority-constructor-energy-bu…
efrec Sep 16, 2026
26a8576
silence emmylua
Sep 16, 2026
5c09f0f
Merge branch 'fix/builder-priority-constructor-energy-budget' of http…
Sep 16, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
87 changes: 52 additions & 35 deletions .github/copilot-instructions.md → .github/agents.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Beyond All Reason - Copilot Instructions
# Beyond All Reason - Agent Instructions

Mixed script types: LuaUI widgets, LuaRules gadgets, BOS animation scripts, shaders, RmlUi documents, busted specs.

Expand All @@ -17,10 +17,27 @@ Before editing code:

Keep patches narrowly scoped and easy to review. Large cross-subsystem refactors need an explicit request.

Before you hand work over:

Four checks decide whether a change is mergeable. Three of them run Lua tooling pinned to a version you can install
locally and reproduce exactly. Run the ones your changes touch.

| Check | Tool and pin | What it scopes | Local command |
| --- | --- | --- | --- |
| `busted` | lux-cli 0.28.3 | Whole spec suite, never a diff | `lx --lua-version 5.1 test` |
| `stylua` | StyLua 2.5.2 | Only hunks overlapping lines you wrote | `stylua --check --respect-ignores <files>` |
| `emmylua_check` | emmylua 0.24.0 | Whole tree, compared against the base | `emmylua_check -c .emmyrc.json .` |
| `docker compose up (headless)` | stock engine | Whole game, headless | `tools/headless_testing/README.md` |

## Keeping These Instructions Current

- Update the affected section in the same pull request whenever a change alters a convention, tool, workflow,
directory, or command described here.
Instruction files: this one, `.github/RmlUi-instructions.md`, `.github/spec-instructions.md`, and
(only on the `mission-api/dev` branch) `luarules/mission_api/mission-api-instructions.md`.
The rules below apply to all of them.

- Update the affected file and section in the same pull request whenever a change alters a convention, tool,
workflow, directory, or command they describe. A change to a subsystem covered by its own file updates that file,
not this one.
- Verify a claim before writing it down (run the command, read the config, count the occurrences).
- Delete guidance that no longer matches the repository instead of layering exceptions on top of it.

Expand Down Expand Up @@ -109,12 +126,13 @@ Match the validator to the file type. If unsure of a tool's scope, inspect the p
- lux 0.28.x appends duplicate `dependencies` and `entrypoints` entries to `lux.lock` on every cold sync (a run with
no `.lux/` tree), growing the file by ~13 lines each time without ever converging. Tests still pass. Do not commit
that churn: `git checkout -- lux.lock` afterwards, and only commit a lockfile change you made deliberately.
- Lint: `luacheck` 1.2.0 with `.luacheckrc`; CI reports only lines changed in the PR (`.github/workflows/lint.yml`).
- Format: StyLua with `.stylua.toml` (tabs, indent width 4, 120 columns, CRLF, sorted requires) and `.styluaignore`;
`.editorconfig` mirrors the indent and whitespace rules. `.github/workflows/format_check.yml` pins stylua 2.5.2 and
runs it with `--check --respect-ignores`; install that version locally rather than relying on `lx fmt`.
- Types: EmmyLua analyzer via `.emmyrc.json`, stubs in `types/`, engine definitions from the `recoil-lua-library`
submodule. The codebase is at zero type errors — keep it there.
- Lint: `luacheck` 1.2.0 with `.luacheckrc`. Treat `.luacheckrc` as a style record, not as a gate.
- Format: StyLua 2.5.2 with `.stylua.toml` (tabs, indent width 4, 120 columns, CRLF, sorted requires) and
`.styluaignore`; `.editorconfig` mirrors the indent and whitespace rules. The gate fails only on changed hunks.
- Types: EmmyLua analyzer 0.24.0 via `.emmyrc.json`, stubs in `types/`, engine definitions from the
`recoil-lua-library` submodule. This is not a zero-errors gate. A new error blocks anywhere, and warnings run on
two budgets: fifteen net-new warnings per thousand changed lines, and thirty total warnings per thousand lines in
the files you changed. A change with no type errors still fails when it adds warnings faster than that per line.
- Integration tests: headless engine via `docker compose -f tools/headless_testing/docker-compose.yml`
(`.github/workflows/test_integration.yml`). They can also be run without docker against an engine already
downloaded by an installed BAR client — see `tools/headless_testing/README.md`.
Expand All @@ -125,52 +143,43 @@ Match the validator to the file type. If unsure of a tool's scope, inspect the p
### Commands

```sh
lx --lua-version 5.1 test # full busted suite (what CI runs)
lx --lua-version 5.1 test -- spec/common/lib_spline_spec.lua # single spec file
lx --lua-version 5.1 test -- --output=plainTerminal # extra flags after `--` reach busted
lx lint # luacheck over the project; provisions luacheck itself
lx exec luacheck -- path/to/file.lua # lint one file
stylua --respect-ignores path/to/file.lua # format one file
stylua --check --respect-ignores path/to/file.lua # verify formatting without rewriting
lx --lua-version 5.1 test # full busted suite (what CI runs); needs lux-cli 0.28.3
busted --output=plainTerminal # same suite, when the .lux tree is already synced
busted spec/common/lib_spline_spec.lua # single spec file
stylua --check --respect-ignores path/to/file.lua # what the format gate sees; needs stylua 2.5.2
stylua path/to/file.lua # format one file (`lx fmt` reformats the whole codebase)
emmylua_check -c .emmyrc.json . # what the type gate sees; needs emmylua 0.24.0
```

Always drive busted and luacheck through `lx`. It is the only invocation that resolves against the project's
`.lux/` tree and `lux.lock`; a system-wide `busted` or `luacheck` on `PATH` silently resolves a different dependency
set, and the lux-generated wrappers under `.lux/` and `~/.local/share/lux/` are not runnable directly (they need the
`LUA_INIT` loader that `lx` sets).
These mean nothing without the pinned versions, and the versions are not installed for you.

StyLua is the exception: it is a standalone binary, not a rock, so `lx exec stylua` fails and `lx fmt` is a silent
no-op on lux 0.28.x (exit 0, nothing reformatted). Call `stylua` directly, pinned to the version in
`.github/workflows/format_check.yml` (2.5.2). Pass `--respect-ignores` whenever you name explicit paths, or
`.styluaignore` is bypassed and generated files such as the atlases get reformatted.

Scope `luacheck` and `stylua` to the files you touched; repository-wide runs create large unrelated diffs. `lx lint`
reports pre-existing warnings across the tree, so compare against the baseline rather than assuming your change
caused them.
Scope `stylua` to the files you touched; repository-wide runs create large unrelated diffs. The type check has no
scoped mode, so read its output against the baseline rather than assuming your change caused everything in it.

### By file type

- `.lua` (LuaUI/LuaRules/AI/common): relevant `spec/` tests, clean luacheck and StyLua, plus in-engine runtime
verification (LuaUI reload). Do not reach for BOS tooling.
- `spec/**/*_spec.lua`: `lx --lua-version 5.1 test`, per the commands above. Keep `lux.lock` and `.emmyrc.json` in
sync when dependencies resolve to new versions.
- `spec/**/*_spec.lua`: busted, per the commands above. Keep `lux.lock` and `.emmyrc.json` in sync when dependencies
resolve to new versions.
- Definition and gamedata changes: `spec/gamedata/unitdefs_spec.lua` covers def loading.
- `.bos`: `BARScriptCompiler.exe` (external tool).
- Shaders: compile path plus runtime fallback behavior where applicable.
- `.rml` / `.rcss`: in-engine UI behavior and performance-sensitive interactions.

## Tests

Read `.github/spec-instructions.md` before adding or changing anything under `spec/`.

- Add or update unit tests for the behavior you change, not only for shared logic in `common/` and `modules/`: new
logic arrives with tests, changed logic has its tests updated, and a bug fix gets a test that fails without it.
- A new test must fail against the commit before yours and pass against yours. A test that passes both is asserting
something nobody changed, and it goes stale without anyone noticing. Never write pending tests.
- When rendering or engine callins make code hard to test, extract the decision-making part into a testable function
and cover that. Only genuinely rendering-bound behavior stays manual and in-engine.
- Tests live in `spec/`, mirroring source layout (`spec/common/`, `spec/luaui/Widgets/`, `spec/gamedata/`) and named
`*_spec.lua`. `.busted` sets `pattern = "_spec"` and `ROOT = spec/`, and puts `common/`, `luarules/`, `luaui/`, and
`spec/` on `package.path`, so require modules by their repo-relative path.
- `spec/spec_helper.lua` mocks the engine surface (`Spring`, `LOG`, `GG`, `unpack`) — extend it instead of re-mocking
per file. Build engine state with `spec/builders/` (`spring_synced_builder`, `unit_def_builder`, and friends)
rather than hand-rolled tables.

## Compatibility and Data Ownership

Expand All @@ -192,8 +201,9 @@ caused them.
(`.github/PULL_REQUEST_GUIDELINES.md`).
- Fill in the "Test steps" checklist in `.github/PULL_REQUEST_TEMPLATE.md`, and attach before/after media for visible
changes.
- Player-visible balance and gameplay changes get a `changelog.txt` entry under the current `# Month` heading, in the
existing style: `• [Unit] 1500 -> 1400 health`. Internal refactors and tooling changes do not.
- Player-visible balance and gameplay changes get a `changelog.md` entry under the current `# Month` heading, in the
existing style: `- [Unit] 1500 -> 1400 health`, with sub-points as nested list items. The file is Markdown and is
rendered in-game by `gui_changelog_info.lua`. Internal refactors and tooling changes do not.
- Style expectations beyond this file live in `CONTRIBUTING.md` (engine-call overhead, caching Defs lookups, correct
iterators, comments explain "why" not "what", no dead code).

Expand All @@ -207,6 +217,13 @@ caused them.
- Only add strings to `language/en/`; the community handles other languages through Transifex
(`language/transifex.yml`).

## Mission API

The data-driven mission runtime has load-order and dispatch conventions of its own. Read
`luarules/mission_api/mission-api-instructions.md` before editing `luarules/mission_api/`,
`luarules/gadgets/api_missions*.lua`, `singleplayer/`, or `spec/mission_api/`. That file, and most of what it
describes, lives on `mission-api/dev` rather than `master`.

## RmlUi

- Follow RmlUi syntax and semantics, but optimize for performance: avoid unnecessary DOM updates, reflows, excessive
Expand Down
2 changes: 2 additions & 0 deletions .github/scripts/emmylua_compare.py
Original file line number Diff line number Diff line change
Expand Up @@ -616,6 +616,8 @@ def main():
delta_ceiling = int(
args.warn_total_per_kloc * args.changed_file_lines_delta / 1000.0
)
# A negative delta would mean shrinking a file requires fixing type warnings.
delta_ceiling = max(0, delta_ceiling)
budget = {
"added": added,
"resolved": resolved,
Expand Down
87 changes: 87 additions & 0 deletions .github/spec-instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# Spec Instructions — busted unit tests

This file covers everything under `spec/`. Read it before adding or changing a spec, a builder, or
`spec/spec_helper.lua`. Subsystem conventions that sit on top of these rules live with their subsystem, currently
`luarules/mission_api/mission-api-instructions.md`.

The rules here exist because a spec is read far more often than it is written, and usually by someone who did not
write it and is trying to work out whether a red test means their change is wrong.

## The gates that judge a spec

The gate table, the pinned versions and the handoff rule live in `.github/agents.md` under "Before You Hand Work
Over". Three things about those gates are specific to `spec/` and are easy to get wrong.

Two exemption lists apply here and they do not agree with each other. `.styluaignore` exempts nothing under `spec/`,
so every spec and every builder is format-gated. `.emmyrc.json` exempts `spec/spec_helper.lua` and
`spec/builders/**` from type checking, and nothing else. So a builder is formatted but never type-checked, while the
spec beside it is both. Never assume a sibling directory carries the same exemptions as yours.

Because the builders are unchecked, a mistake in one surfaces as a confusing type warning in each spec that uses it
rather than as an error where the mistake is. When warnings appear in a spec you did not expect to touch types,
suspect the builder first.

Do not read an absent check as a passing one. `gh pr checks <n>` lists which gates actually ran, and a gate that
never fired looks exactly like one that passed. Run the suite yourself before you hand anything over, particularly
for work based on a long-lived branch, where a spec can go stale for days without anyone seeing red.

## What a spec has to be

**Readable without a tour.** Someone looking at one failing assertion must be able to decide whether the expected
value is right by reading the spec file and at most one helper. If understanding your test means opening three
builders and a subsystem helper, inline the fixture instead.

**Indented with tabs.** The repo uses tabs and StyLua enforces them on every line you write. A space-indented spec
fails the format gate on almost every line of the file, which buries whatever else the check found.

**Honest about what it proves.** A new test must fail against the commit before yours and pass against yours. If it
passes both, it is asserting something nobody changed, and it will go stale without anyone noticing.

**About behavior the code actually promises.** Read the implementation before you write the assertion. A test that
invents a rule the module never implemented is worse than no test, because it fails later for a reason that has
nothing to do with the change that tripped it, and the person who hits it has no way to tell which side is wrong.

**Self-consistent.** Before adding a case, read the neighbouring cases in the same file. Two tests in one spec that
imply contradictory rules mean at least one of them is wrong.

## What a spec must not do

Do not re-implement production logic inside the test harness. A builder that mirrors a production module is a second
copy that drifts, and every spec that trusts it inherits the drift. Call the production module instead. A comment of
the form `Mirrors <production file>` in anything under `spec/builders/` is a defect, not documentation.

Do not assert only that a stub was called. `assert.equal(1, #calls.doThing)` passes when `doThing` does nothing. Wire
the real module into the mock and assert the state it should have produced.

Do not import the whole builder barrel. Require the builders you use by path rather than including
`spec/builders/index.lua`, which pulls in every builder on every spec file that touches it.

Do not mix setup conventions inside one directory. Whatever a `spec/<subsystem>/` directory does, hand-assigned
globals, a subsystem helper, or a builder, every spec in it does the same thing, so that fixing one teaches you how
to fix the next.

Do not write specs for behavior that is not implemented. Open an issue instead. A green suite is supposed to mean
the code works.

## How much to write

New logic arrives with tests, changed logic has its tests updated, and a bug fix gets a test that fails without it.
That mandate has no upper bound in it, so apply one yourself.

Specs that add more than twice the lines of the implementation they cover need a sentence in the pull request saying
why. Sometimes the answer is good, because a validation layer really does need a case per rule. More often it means
the same machinery is being driven from several directions, and two specs are covering one behavior.

Test infrastructure changes, meaning `spec/builders/` and `spec/spec_helper.lua`, land in their own pull request
ahead of the feature that needs them. They are the files every other spec depends on, and they are impossible to
review inside a large feature diff.

## Where specs live

Specs mirror the source tree under `spec/` and are named `*_spec.lua`. `.busted` sets `pattern = "_spec"` and
`ROOT = spec/`, and puts `common/`, `luarules/`, `luaui/` and `spec/` on `package.path`, so require modules by their
repository-relative path.

`spec/spec_helper.lua` mocks the engine surface, currently `Spring`, `LOG`, `GG` and `unpack`. Extend it rather than
re-mocking per file, and keep in mind that it is excluded from type checking, so mistakes in it surface as confusing
type warnings in the specs that use it rather than as errors in the helper.
38 changes: 0 additions & 38 deletions .github/workflows/quick_deploy.yml

This file was deleted.

51 changes: 51 additions & 0 deletions .github/workflows/rapid-build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# Builds rapid package and deploys to CDN.
#
# Automatically builds `byar:test` version on push to `stable` branch and
# allows to create `byar:pr:{number}` and `byar:br:{branch_name}` custom
# rapid branches via manual workflow trigger.
#
# The policy controling what are the allowed rapid branches based on triggers
# is configured in
# https://github.com/beyond-all-reason/rapid-hosting/blob/main/playbook/group_vars/prod/vars.yml
name: Rapid build
on:
push:
branches:
- stable
workflow_dispatch:
inputs:
pr-or-branch:
description: PR number or branch name to build
required: true
type: string
concurrency:
group: rapid-build
jobs:
build:
if: github.event_name == 'workflow_dispatch' || github.repository == 'beyond-all-reason/Beyond-All-Reason'
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write
steps:
- name: Resolve the dispatch input
id: resolve
if: github.event_name == 'workflow_dispatch'
env:
GH_TOKEN: ${{ github.token }}
PR_OR_BRANCH: ${{ inputs.pr-or-branch }}
run: |
if [ -z "${PR_OR_BRANCH//[0-9]/}" ]; then
commit="$(gh api "repos/$GITHUB_REPOSITORY/pulls/$PR_OR_BRANCH" --jq .head.sha)"
echo "branch=pr-$PR_OR_BRANCH" >> "$GITHUB_OUTPUT"
else
commit="$(gh api "repos/$GITHUB_REPOSITORY/git/ref/heads/$PR_OR_BRANCH" --jq .object.sha)"
echo "branch=br-$PR_OR_BRANCH" >> "$GITHUB_OUTPUT"
fi
echo "commit=$commit" >> "$GITHUB_OUTPUT"
- uses: beyond-all-reason/rapid-hosting/action@main
with:
url: https://repos.beyondallreason.dev/build
repo: byar
branch: ${{ steps.resolve.outputs.branch || 'test' }}
commit: ${{ steps.resolve.outputs.commit || github.sha }}
4 changes: 2 additions & 2 deletions .github/workflows/test_unit.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ name: Run Unit Tests

on:
push:
branches: ['*']
branches: ['**'] # needs to tolerate a slash but not run on tag pushes
pull_request:
branches: ['*']
branches: ['**'] # needs to tolerate a slash but not run on tag pushes

jobs:
busted:
Expand Down
Loading
Loading