diff --git a/README.md b/README.md index 60f7056..c370509 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ A collection of public, reusable agent skills, cheatsheets, and operational work | **C# / .NET** | [`csharp-dotnet.md`](skills-public/code-languages/csharp-dotnet.md) | `TreatWarningsAsErrors`, nullable enabled, no `.Result`, trimming/single-file publish caveats, SDK shims | | **C / C++** | [`c-cpp.md`](skills-public/code-languages/c-cpp.md) | full warning set + `-Werror`, sanitizers, RAII/Rule of Zero, target-based CMake, FFI ownership | | **Lua** | [`lua.md`](skills-public/code-languages/lua.md) | pin the dialect first, `luac -p` + luacheck as the only gate, locals over globals, `pcall` at the boundary, `#t`/pattern traps, embedded & HavokScript hosts | -| **GSC** | [`gsc.md`](skills-public/code-languages/gsc.md) | no compiler or test harness, `isdefined` on everything external, `endon` before the first wait, per-frame budget, precache at load, level-state side effects | +| **GSC** | [`gsc.md`](skills-public/code-languages/gsc.md) | compile off-engine with `gsc-tool --mode comp`, `isdefined` on everything external, `endon` before the first wait, per-frame budget, precache at load, level-state side effects | Each language file extends `coding-dev.md` rather than repeating it. A project's own `AGENTS.md` / `CLAUDE.md` / `.rules` always overrides both. diff --git a/code-languages/gsc.md b/code-languages/gsc.md index 4f571ed..1fc7fef 100644 --- a/code-languages/gsc.md +++ b/code-languages/gsc.md @@ -4,7 +4,7 @@ description: >- Use this skill when writing, reviewing, or debugging GSC (Game Script / Call of Duty script) or CSC — including entity-vs-level scope, threads and notify/wait semantics, endon/waittill discipline, undefined-value guards, the no-OOP/no-exceptions reality, asset precaching, - per-frame cost, and the fact that GSC has no compiler, linter, or test harness worth the name. + per-frame cost, block scoping, and compiling off-engine with gsc-tool. --- # GSC — Rules & Preferences @@ -13,10 +13,10 @@ Extends [`coding-dev.md`](skills-public/coding-dev.md). Everything there applies GSC is the Call of Duty engine's own scripting language (`.gsc` server-side, `.csc` client-side). It is **not Lua, not JavaScript, and not C** despite resembling all three. It has -no type system, no exceptions, no classes, no closures in most dialects, and — in a modded -install — **no diagnostics beyond the in-game console**. That last fact drives most of what -follows: the language will not tell you that you are wrong, so the code has to be written so -that it cannot be. +no type system, no exceptions, no classes, no closures in most dialects, and no test harness. +It can be compiled off-engine (see §1), which catches syntax and scope errors — but nothing +checks a type, a field that was never set, or a builtin that does not exist in the target title. +So the code still has to be written so that it cannot be wrong. > [!IMPORTANT] > **Establish the dialect before writing anything.** IW3/IW4 (MW/MW2), IW5 (MW3), T5/T6 (Black @@ -27,18 +27,38 @@ that it cannot be. --- -## 1. There Is No Gate — Build One +## 1. The Gate — `gsc-tool`, Then The Game -GSC has no `gsc -Wall`. What exists: +**There is an off-engine GSC compiler: `gsc-tool`.** Reach for it first; it is +easy to assume none exists and verify by loading a map instead. + +```bash +gsc-tool --mode comp --game --system pc --dry path/to/script.gsc +``` + +It exits non-zero and prints the engine's own diagnostic, e.g. +`[ERROR]:compiler:script:97:25: local variable 'previous_count' not found`. + +- **Use `--mode comp`, not `--mode parse`.** `parse` is syntax-only and happily + accepts code the engine's compiler rejects, including the block-scope error + above. `comp` with `--dry` writes nothing and does the real thing. +- **`--game` must match the title**, and it is not the marketing name: MW2 + Campaign Remastered is `h2`, MWR is `h1`. The choices are `iw5 iw6 iw7 iw8 + iw9 s1 s2 s4 h1 h2 t6 t7 t8 t9 jup`. Wrong title, wrong builtin table. +- Prebuilt binaries ship on its GitHub releases; there is no distro package. +- Many mod trees already vendor it (Overlord has it under `deps/gsc-tool`). + +What that still leaves: | Check | How | | :--- | :--- | -| **Syntax** | The engine's own compile at load. Read the console; a parse error names the file and line and then stops the whole script loading. | +| **Syntax + semantics** | `gsc-tool --mode comp`. Put it in the build gate. | +| **Builtin exists for this title** | `gsc-tool`'s own engine tables, e.g. `src/gsc/engine/h2_meth.cpp`. Grep before using a builtin you remember from another title. | | **Runtime errors** | In-game console / `developer 1`. Many mod loaders print a script stack trace. | -| **Static lint** | Third-party only, and dialect-specific. Use one if it exists for the target title; otherwise there is none. | | **Tests** | None off-host. Behaviour is verified by playing the map. | -Therefore: +So a compile is cheap and mandatory, and **behaviour is still unverified until +the map runs.** Both halves matter: - **`developer 1` (or the loader's equivalent) is mandatory during development.** Without it the engine suppresses script warnings and an undefined-value error looks like "the feature did @@ -48,7 +68,7 @@ Therefore: - **Instrument with `iprintln` / `iprintlnbold` / `println`** on the success *and* the failure path. A function that silently returns on a guard is indistinguishable from one that never ran. - **Say plainly in your report that GSC behaviour is unverified** unless you actually loaded the - map and watched it. There is no test suite to hide behind. + map and watched it. A clean `gsc-tool` compile proves the script loads, not that it works. --- @@ -211,6 +231,11 @@ init() functions over reimplementing them. `maps\_utility` and `common_scripts\utility` already have flag, wait, and array helpers. - **`/* */` comments do not nest.** Commenting out a block that contains one breaks the file. +- **A local declared inside a block is not visible after it.** Writing it in one `if` and reading + it in a second does not compile — declare it unconditionally at function scope. Assigning + `undefined` to an entity field unsets it, which is usually the right "restore nothing". +- **No file-scope constants.** `MY_CONST = 0.2;` outside a function is not valid in most + dialects; use a local at the point of use or a `level.` field set during init. - File and function size limits from the general rules apply, and matter more here than usual: there is no debugger worth the name, so a 300-line function is unanalysable. - **Delete dead code.** With no linter to flag it, commented-out GSC accumulates forever.