diff --git a/AGENTS.md b/AGENTS.md index 613988c..b3390ea 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -15,6 +15,7 @@ This repo builds the Grill CLI and project setup tooling. The generated map-proj - Local helpers in `config/ProjectConfigModels.kt` should stay thin: typealiases plus immutable copy helpers for shared records. - `YamlHelper.dumpProjectConfig` intentionally serializes a pruned YAML map instead of the shared records directly. This preserves the old user-facing `wurst.build` behavior by omitting null/default nested fields. - `wbschema.json` should stay lenient and aligned with the shared config parser, especially for `scriptMode`, `wc3Patch`, and nullable legacy fields. +- Compiler-owned agent references belong in `~/.wurst/wurst-compiler/agent-docs/`. Generated project notes should prefer those version-matched local files when present and retain an online fallback until compiler releases ship them. ## WC3 Patch And Core JASS diff --git a/src/main/kotlin/file/SetupApp.kt b/src/main/kotlin/file/SetupApp.kt index 4b191c8..d19f63e 100644 --- a/src/main/kotlin/file/SetupApp.kt +++ b/src/main/kotlin/file/SetupApp.kt @@ -32,7 +32,7 @@ object SetupApp { private data class WurstProcessResult(val exitCode: Int, val output: List) - internal const val AGENTS_TEMPLATE_VERSION = "2026-08-05" + internal const val AGENTS_TEMPLATE_VERSION = "2026-08-08" private const val AGENTS_TEMPLATE_MARKER_PREFIX = "" private const val AGENTS_TEMPLATE_SOURCE_HINT = "WurstScript Warcraft III map project notes" @@ -936,6 +936,17 @@ object SetupApp { if (markerLine == AGENTS_TEMPLATE_MARKER) { return null } + if (markerLine != null) { + val markerVersion = markerLine + .removePrefix(AGENTS_TEMPLATE_MARKER_PREFIX) + .removeSuffix("-->") + .trim() + // Template versions are ISO dates, so lexical ordering is chronological. A newer + // downloaded template is valid even when this older Grill binary cannot recognize it. + if (markerVersion > AGENTS_TEMPLATE_VERSION) { + return null + } + } if (markerLine != null) { return "AGENTS.md was generated from an older WurstSetup template ($markerLine). Consider refreshing it from templates/AGENTS.md and re-applying project-local notes." } diff --git a/src/main/kotlin/global/InstallationManager.kt b/src/main/kotlin/global/InstallationManager.kt index 0f0ba81..ce1e24e 100644 --- a/src/main/kotlin/global/InstallationManager.kt +++ b/src/main/kotlin/global/InstallationManager.kt @@ -9,6 +9,8 @@ import net.NetStatus import java.nio.file.Files import java.nio.file.Path import java.nio.file.Paths +import java.nio.file.StandardCopyOption +import java.util.jar.JarFile import java.util.regex.Pattern @@ -19,6 +21,7 @@ object InstallationManager { private val log = KotlinLogging.logger {} private const val FOLDER_PATH = ".wurst" private const val COMPILER_FILE_NAME = "wurstscript.jar" + private const val LANGUAGE_AGENT_DOC_ENTRY = "agent-docs/WURST_LANGUAGE.md" private const val GRILL_JAR_NAME = "grill.jar" private const val LEGACY_GRILL_JAR_NAME = "WurstSetup.jar" @@ -49,6 +52,7 @@ object InstallationManager { log.info("verifyInstallation: detectedCompilerJar=$detectedCompilerJar exists=${detectedCompilerJar?.let { Files.exists(it) }}") if (detectedCompilerJar != null) { log.info("Found installation at $detectedCompilerJar") + ensureCompilerAgentDocs(detectedCompilerJar) status = InstallationStatus.INSTALLED_UNKNOWN try { if (!Files.isWritable(detectedCompilerJar)) { @@ -94,9 +98,11 @@ object InstallationManager { log.info("\tšŸ“¦ Extracting..") ZipArchiveExtractor.extractArchive(it, installDir) Files.delete(it) - if (detectCompilerJar() == null) { + val compilerJar = detectCompilerJar() + if (compilerJar == null) { log.error("āŒ Compiler not found after extraction.") } else { + ensureCompilerAgentDocs(compilerJar) if (isFreshInstall) { wurstConfig = WurstConfigData() } ensureGrillJarInstalled() setLaunchersExecutable() @@ -193,6 +199,25 @@ object InstallationManager { } } + private fun ensureCompilerAgentDocs(compilerJar: Path) { + try { + JarFile(compilerJar.toFile()).use { jar -> + val entry = jar.getJarEntry(LANGUAGE_AGENT_DOC_ENTRY) ?: return + val docsDir = compilerDir.resolve("agent-docs") + Files.createDirectories(docsDir) + jar.getInputStream(entry).use { input -> + Files.copy( + input, + docsDir.resolve("WURST_LANGUAGE.md"), + StandardCopyOption.REPLACE_EXISTING + ) + } + } + } catch (e: Exception) { + log.warn("Could not extract compiler agent docs: ${e.message}") + } + } + private fun resolveOwnJar(): Path? { return try { val url = InstallationManager::class.java.protectionDomain.codeSource.location diff --git a/src/test/kotlin/AgentsTemplateTests.kt b/src/test/kotlin/AgentsTemplateTests.kt new file mode 100644 index 0000000..45e9dac --- /dev/null +++ b/src/test/kotlin/AgentsTemplateTests.kt @@ -0,0 +1,37 @@ +import file.SetupApp +import org.testng.Assert +import org.testng.annotations.Test +import java.nio.file.Files +import java.nio.file.Paths + +class AgentsTemplateTests { + private val templatePath = Paths.get("templates", "AGENTS.md") + + @Test + fun testTemplateStaysTokenLean() { + val content = Files.readString(templatePath) + val wordCount = Regex("""\S+""").findAll(content).count() + + Assert.assertTrue(wordCount <= 900, "AGENTS template grew to $wordCount words (limit: 900)") + Assert.assertTrue(content.length <= 7000, "AGENTS template grew to ${content.length} characters (limit: 7000)") + } + + @Test + fun testLanguageDocsPreferCompilerMatchedLocalReference() { + val content = Files.readString(templatePath) + val localReference = "~/.wurst/wurst-compiler/agent-docs/WURST_LANGUAGE.md" + val onlineFallback = "https://wurstlang.org/manual.html" + val localIndex = content.indexOf(localReference) + val onlineIndex = content.indexOf(onlineFallback) + + Assert.assertTrue(localIndex >= 0, "Missing compiler-matched local language reference") + Assert.assertTrue(onlineIndex > localIndex, "Online manual must remain a fallback after the local reference") + } + + @Test + fun testNewerTemplateDoesNotLookStaleToOlderGrill() { + val newerMarked = "\n# AGENTS.md\n" + + Assert.assertNull(SetupApp.agentsTemplateWarning(newerMarked)) + } +} diff --git a/templates/AGENTS.md b/templates/AGENTS.md index b0ca0ad..0fc22a9 100644 --- a/templates/AGENTS.md +++ b/templates/AGENTS.md @@ -1,251 +1,73 @@ - + # AGENTS.md - WurstScript Map Project Notes WurstScript Warcraft III map project notes for editing `.wurst` code, dependencies, generated objects, tests, or map build logic. -## Read More On Demand +## Read On Demand -This file is the working set; pull deeper docs into context only when the task needs them: +Keep this file in context. Read deeper references only when the task needs them: -- **Stdlib APIs**: grep `_build/dependencies/wurstStdlib2/wurst/` for wrappers and packages before writing a native call or new infrastructure. -- **Dependency guides**: before editing code that uses a dependency, check `_build/dependencies//` for its own `AGENTS.md` or usage guides and read them first (e.g. `wurst-table-layout` ships `AGENTS.md`, `AI_USAGE.md`, and `WC3_FRAMEHANDLE_GUIDE.md` — required reading before UI work). -- **Language details**: https://wurstlang.org/manual.html (full manual: generics, closures, modules, compiletime, operators). -- When unsure about syntax or local APIs, inspect nearby working code before guessing. +- **Language semantics**: read `~/.wurst/wurst-compiler/agent-docs/WURST_LANGUAGE.md` when installed so the reference matches the local compiler; otherwise use https://wurstlang.org/manual.html. +- **Stdlib APIs**: search `_build/dependencies/wurstStdlib2/wurst/` before writing a native call or new infrastructure. Read its `AGENTS.md` when present. +- **Other dependencies**: before changing code that uses one, inspect `_build/dependencies//` and read its `AGENTS.md` or usage guides first. +- **Project conventions**: inspect nearby working code and project-local notes before guessing syntax, APIs, or style. -## Working Rules +## Source And Scope -- Prefer simple, maintainable code. Fix root causes; avoid brittle workarounds, duplicated branches, and special-case patches. -- Keep packages focused and below ~500 lines; split by feature, responsibility, or data type. -- Make changes in the source package, not generated output. Do not edit `_build/` as source-of-truth; patch upstream dependency repos instead of copied dependency code. -- Keep tests narrow. Add/update tests for behavior, parsing, compiletime generation, or shared utilities. -- Avoid broad refactors unless they directly reduce risk or complexity for the requested change. -- Fix compiler warnings unless they are intentionally suppressed. +- Change source packages, configuration, and tests; never treat `_build/` or downloaded dependencies as source-of-truth. Patch an upstream dependency repository instead of its installed copy. +- Prefer small, maintainable changes that address the root cause. Avoid unrelated refactors, duplicated branches, and special-case patches. +- Keep packages focused and below roughly 500 lines; split by feature, responsibility, or data type when useful. +- Add or update narrow tests for changed behavior, parsing, compiletime generation, or shared utilities. +- Fix relevant compiler warnings unless intentionally suppressed and explained. -## Stdlib-First: No Raw JASS Natives (Mandatory) +## Stdlib First -The most important coding rule: use the WurstScript stdlib and library APIs, never ported JASS. The goal is clean, reusable Wurst — not a JASS transliteration. Never call a raw `common.j`/`Blizzard.j` native when a wrapper or extension function exists (there is one for almost every native); grep the stdlib first. The only bar for a raw native is that you searched and confirmed no wrapper exists — then add a one-line comment saying so. Code that reads like JASS (manual handle juggling, native calls, global trigger callbacks, op-limit chunking) is wrong here even if it compiles. +Use Wurst stdlib and dependency APIs instead of ported JASS or hand-built engine infrastructure. Search the stdlib before calling a raw `common.j`/`Blizzard.j` native; if no wrapper exists, add a one-line comment recording that search. The normal `CreateTrigger()..register...()..addAction() ->` cascade is an accepted Wurst idiom. -Use the stdlib API, not a raw native, for at least: - -- Timers → `ClosureTimers` (`doAfter`, `doPeriodically`); never `CreateTimer`/`TimerStart`/`PauseTimer`/`DestroyTimer`. -- Printing → `print` / `printTimed` / `p.print`; never `DisplayText*ToPlayer`/`...ToForce`. -- Player state → `Player` extensions (`p.addGold`, `p.getId`, ...); prefer `players[i]` over `Player(i)`. -- Unit inspection → `Unit` extensions (`u.getTypeId()`, `u.getOwner()`, `u.getAbilityLevel(id)`, ...). -- Hashtables → `Hashtable` extensions (`ht.saveInt`/`loadInt`/`flushChild`/...). -- Group iteration → `ClosureForGroups` (`forUnitsInRange`, `forUnitsInRect`) + `GroupUtils` (`getGroup()`/`group.release()`), not `GroupEnum*` + `ForGroup` globals. - -The `CreateTrigger()..register...()..addAction() ->` cascade is the accepted idiom and is fine. - -Likewise, do not reinvent stdlib infrastructure — it is battle-tested against WC3 edge cases (recycling, op-limits, cleanup, desync) that hand-rolled versions get wrong. Grep for an existing system before building one: - -- Dummy spell casting → `DummyCaster` / `InstantDummyCaster` (unit pooling: `DummyRecycler`). -- Triggered damage → `DummyDamage` to deal, `DamageEvent` to detect/modify. -- Events → `ClosureEvents` (`EventListener.add(...)`) / `RegisterEvents`; no custom global-trigger dispatcher or event bus. -- Knockback / FX / sound / interpolation / orders → `Knockback3`, `Fx`, `SoundUtils`/`Sounds`, `Interpolation`, `Orders`/`OrderStringFactory`. -- Collections → `LinkedList`, `HashMap`, `HashList`. - -If stdlib almost fits, wrap the stdlib type thinly and note why in a comment. Reinventing this is treated as a defect even if tests pass. - -## Agent Workflow - -```bash -grill install # install/update dependencies -grill typecheck --quiet # after Wurst changes -grill test --quiet -``` - -If quiet output reports a failure, rerun narrowly using the failed file, line, package, or test name (`grill typecheck`, `grill test PackageOrTestName`). Avoid full noisy reruns unless there is no target. - -For build changes: `grill build ExampleMap.w3x --quiet`. Builds default to production mode (compiletime `isProductionBuild()` returns `true`); add `--dev` only when validating behavior that needs `isProductionBuild() == false`. To dump a map's object-editor data to Wurst source: `grill exportobjects `. - -Done means relevant errors/warnings are fixed or explicitly explained. +Do not reimplement systems already provided by packages such as `ClosureTimers`, `ClosureEvents`, `ClosureForGroups`, `GroupUtils`, `DummyCaster`, `DamageEvent`, `Fx`, `SoundUtils`, `Orders`, or the stdlib collections. If an API nearly fits, prefer a thin wrapper and document the remaining mismatch. ## Project Configuration -`wurst.build` is the root YAML config. Key fields: `projectName`, `dependencies` (Git URLs managed by `grill`), and `buildMapData` (metadata written to the output `.w3x`). The default dependency is usually `wurstStdlib2`. - -## Lua vs Jass +`wurst.build` is the authoritative project YAML: -Maps target Lua or Jass via World Editor settings. Check the target before adding/removing `execute()` or timer chunking: +- `scriptMode` (`LUA` or `JASS`) selects compiler output. +- `wc3Patch` selects compatible core JASS and the stdlib era. +- `dependencies` lists Git URLs managed by `grill`; the default is usually `wurstStdlib2`. +- `buildMapData` controls metadata written to the output `.w3x`. -- **Lua**: no practical op-limit; long loops and deep calls are fine. Do not add `execute()` as an op-limit workaround. Use timers only for real asynchronous delay. -- **Jass**: the VM has an operation limit per thread; `execute()` resets it by starting a new thread. Heavy work may need chunking across ticks. +Read `scriptMode` before adding or removing `execute()` or timer chunking. Do not infer the build/typecheck target from the locally installed Warcraft III client; client compatibility is a separate launch concern. -## Wurst Essentials +- **Lua**: no practical op-limit. Do not add `execute()` as an op-limit workaround; use timers for actual asynchronous delay. +- **Jass**: the VM has an operation limit per thread. Heavy work may require `execute()` or chunking across ticks. -Every `.wurst` file starts with a package; blocks are indentation-based (tabs or 4 spaces, never mixed): +## High-Risk Wurst Semantics -```wurst -package MyPackage -import Wurstunit +- Closures capture locals by value. Assigning inside a callback does not update the captured outer local. Keep creation and follow-up handlers in the same closure, store shared mutable state on an owning class, or use `reference(value)` and destroy it when finished. +- Wurst class lifetime remains explicit for Lua output. Objects created with `new`, stored closures/listeners, references, and owned collections usually need `destroy`; owners should clear stale references after destruction and must avoid double-destroy. +- WC3 `int` is signed 32-bit and overflows silently. Promote before multiplication (`worth.toReal() * count`), never after an integer expression has already overflowed. +- Lambdas require a known target type. Lambdas used as `code` cannot accept parameters or capture locals. +- Every `.wurst` source belongs to a package and uses indentation-defined blocks. Package exports require `public`; imports are not re-exported unless declared `import public`. -init - print("loaded") -``` - -```wurst -let immutable = 5 -var mutable = 10 -constant int SOME_ID = 'A000' -int array values = [1, 2, 3] - -function max(int a, int b) returns int - if a > b - return a - return b -``` +## Compiletime Objects -Use `let` unless mutation is needed. Put locals near first use. Prefer type inference. Do not write Jass-style `takes` / `returns nothing`. +Use compiletime generation and stable ID helpers for object-editor data. New generated objects must use real melee objects as bases, never other custom objects. Melee bases carry abilities, costs, upgrades, requirements, stock, food, race, classification, art, sound, and tooltip fields; explicitly clear inherited side effects for the object family. Regression tests should assert dangerous fields are absent as well as intended fields being present. -Control flow: `if`/`else if`/`else`, `switch x` + `case`/`default`, `while`, `for i = 0 to 10`, `for i = 10 downto 0`, `for u in group` / `for u from group`. `continue` skips an iteration; `skip` is a no-op statement. Statements end at newline; continue after `(`, `[`, operators, or before `.`, `..`, `)`, `]`, `begin`. +## Task-Specific References -Operators: `+`, `-`, `*`, `/` (real division, even on two ints), `div` (integer division), `%`, `mod`, `and`, `or`, `not`, `==`, `!=`, `<`, `<=`, `>`, `>=`, ternary `cond ? a : b`. +- For custom UI, read the UI dependency's guides before editing. In particular, `wurst-table-layout` provides `AGENTS.md`, `AI_USAGE.md`, and `WC3_FRAMEHANDLE_GUIDE.md`; its rules own frame lifecycle, parenting, safe-area, and multiplayer behavior. +- For unfamiliar stdlib or dependency APIs, search declarations and nearby usage rather than inventing signatures. +- For map object data, determine the authoritative compiletime source before changing generated output. -Null-safe member access with `?.` skips the access (including argument evaluation) when the receiver is null; the receiver is evaluated once: - -```wurst -target?.kill() // no-op when target is null -let owner = target?.getOwner() // null when target is null; chains: a?.next?.next -``` - -The receiver type must be nullable (class/interface/string/handle — not `int`/`real`/`boolean`). If the member's own type cannot represent null (e.g. `getCount()` returning `int`), the `?.` call is only valid as a standalone statement, not as a value — use an explicit `if x != null` there. `?.` is not assignable (`a?.x = 5` is invalid). - -## Packages and API Shape - -- Package members are private by default; use `public` for exports. Class members are public by default; restrict with `private`/`protected`. -- Every package implicitly imports `Wurst` unless `NoWurst` is imported. `import public` re-exports names; plain `import` does not. -- Package initialization is top-to-bottom; imports initialize before importers. Avoid `initlater` unless breaking an unavoidable init cycle. - -Naming: packages/classes `UpperCamelCase`; tuples, functions, members, locals `lowerCamelCase`; top-level constants `UPPER_SNAKE_CASE`. - -## Preferred Wurst Style - -Use cascade syntax for setup and extension functions for readable APIs: - -```wurst -CreateTrigger() - ..registerAnyUnitEvent(EVENT_PLAYER_UNIT_ISSUED_ORDER) - ..addCondition(Condition(function cond)) - ..addAction(function action) - -public function unit.getX2() returns real - return GetUnitX(this) -``` - -Prefer `target?.damage(50.)` over `if target != null` + `target.damage(50.)` when the null case simply does nothing; keep the explicit check when the null case needs handling or the accessed value's type cannot be null. - -Prefer `vec2` tuples over `location` handles unless required. Prefer polymorphism/data modeling over large `instanceof`/`typeId` chains. Avoid unchecked `castTo` unless proven safe. - -Lambdas need a target type — standalone inference does not work: - -```wurst -Predicate even = x -> x mod 2 == 0 - -doAfter(1.) -> - print("later") -``` +## Validate -Important closure rule: locals captured by a closure are captured by value. Treat captured locals as read-only shared state: assigning to one inside a callback does not update the outer local that was captured. For shared mutable state, keep the state in a class instance or use `reference(value)`, then mutate `.val` and destroy the reference when finished. Lambdas used as `code` cannot take parameters or capture locals. - -## Classes, Tuples, Generics - -`new` objects generally need `destroy`. Tuples are value types and must not be destroyed. `super(...)` must be the first constructor statement; overridden methods require `override`. Interfaces declare required methods; modules (`use`) inject reusable members. - -Prefer `T:` generics for performance-sensitive or instance-heavy containers (`class Box`); old `T` generics erase through integer casts and can share storage. - -## Compiletime and Objects - -Use compiletime generation for object-editor data. Prefer wrappers and ID generators so IDs stay stable and collision-free; avoid hardcoded new object IDs unless existing code intentionally does so. - -```wurst -let value = compiletime(fac(5)) - -@compiletime function createSpell() - new AbilityDefinitionMountainKingThunderBolt(SPELL_ID) - ..setName("Wurst Bolt") - ..presetDamage(lvl -> 400. + lvl * 100.) -``` - -Generated objects must use real melee objects as bases, never other custom objects (custom bases compile into invalid or order-dependent data). Melee bases carry baggage — repair costs, upgrades/tech requirements, stock/bounty/food/race/classification fields, default abilities, art/sound/tooltips — so audit and explicitly clear inherited side effects per object family (prefer local helper presets that null known-dangerous fields, then layer intended fields). Regression tests for generated objects should assert the *absence* of known inherited side effects, not only the presence of new fields. - -## Production Pitfalls - -Recurring real-world failure modes; treat as a pre-edit checklist for non-trivial changes. - -### Integer overflow - -WC3 `int` is 32-bit signed and wraps silently at ~2.1 billion. Easy to hit when multiplying or summing large game quantities (gold/worth totals, damage products, accumulated stats — aggregate worths routinely exceed ~46k, the square root of int-max). - -- Promote to `real` BEFORE multiplying: `a.toReal() * b`, never `(a * b).toReal()` (already overflowed). -- Same for running sums of products: `total += worth.toReal() * count * mult`. -- `/` is real division so it does not overflow, but its operands still can. Prefer `real` accumulators that fan in many large terms. - -### Closure capture is by value - -If a closure assigns to a local from an outer scope, the outer local is not updated. Do not assign a value to an outer local inside a callback and use that outer local afterwards — declare it inside the closure, or register follow-up handlers inside the same callback that creates the value: - -```wurst -// BUG: clicked is still null outside the build closure -framehandle clicked = null -dialog.build() -> - clicked = textButton("OK", 0.08, 0.024) -clicked.onClick() -> - doThing() - -// OK: keep creation and handler in the same closure -dialog.build() -> - let clicked = textButton("OK", 0.08, 0.024) - clicked.onClick() -> - doThing() -``` - -When a value genuinely must cross closure boundaries, use `reference(value)`, access `.val`, and `destroy` the reference when the owner is done — but prefer restructuring to avoid it. - -### Wurst object lifetime is manual - -Lua output is garbage-collected at the runtime level, but Wurst class lifetimes and destructors are still explicit. Objects created with `new`, stored closures/listeners, timers/callbacks, references, and collections usually need `destroy` when their owner is done — do not rely on "Lua will GC it" if an `ondestroy` cleans up state, callbacks, or nested objects. Conversely, do not double-destroy: instance ids can be reused and there is no generic "is destroyed?" check, so owners must null their own stale references: - -```wurst -if watcher != null - destroy watcher - watcher = null -``` - -### Custom UI work - -If the project uses `wurst-table-layout` / `TableUi`, read that dependency's `AGENTS.md`, `AI_USAGE.md`, and `WC3_FRAMEHANDLE_GUIDE.md` before editing UI (see Read More On Demand). Hard rules that hold regardless: - -- Load TOC files in `init` if needed, but do no actual frame work (create/move/size/show/reparent) during blocking map-load init — delay it with `doAfter(0.)` or later. -- Build frames under their eventual parent (`withParent(...)` or inside `dialogFrame(...).build() ->`); re-parenting after creation can desync visual and clickable areas. -- Keep root panels/dialogs in the 4:3 safe band with `placeSafe(...)` and declared dimensions. Never size/place UI from `BlzGetLocalClientWidth()/Height()` without guarding against zero/invalid values (minimized clients). -- Prefer building reusable hidden frame trees after map load, then show/hide/update them; do not create complex frames on demand mid-game or destroy/recreate framehandles during cleanup. -- Do not move or resize Blizzard default frames (chat/messages) to make room — bad coordinates and default-frame refreshes can crash/desync. -- Prefer table-wide defaults (e.g. `layout.defaultHalign(Align.CENTER)`) over per-row alignment calls. - -## Tests - -```wurst -package MyTests -import Wurstunit - -@Test public function multiplicationWorks() - 12.assertEquals(3 * 4) +```bash +grill install +grill typecheck --quiet +grill test --quiet ``` -Tests should be small, deterministic, self-contained, and assertion-driven. If quiet output lists a failed package/test, rerun that target before expanding scope. - -## Formatting - -- spaces around binary operators: `a + b`; no space before call parentheses: `foo(1)` -- no spaces around `.`, `..` or `?.`; no spaces after `(`/`[` or before `)`/`]` -- comments use `// Comment`; doc comments `/** ... */` appear in autocomplete -- avoid manual horizontal alignment; prefix intentionally unused variables with `_` +If a quiet check fails, rerun the smallest relevant package, file, or test without `--quiet`. Avoid broad noisy reruns when the failure supplies a useful target. -## Quick Pitfall Checklist +For map build changes, run `grill build ExampleMap.w3x --quiet`. Builds are production mode by default; add `--dev` only when validating code that requires `isProductionBuild() == false`. Use `grill exportobjects ` to dump object-editor data to Wurst source. -- Wurst code must be inside `package`; indentation defines blocks. -- `array.length` is only the initial length. -- Varargs are limited by Jass's 31-argument limit. -- Closures capture locals by value: do not expect callback assignments to update the outer local; use a class or `reference(value)` for intentional shared mutation. -- Lambdas need a known target type; `code` lambdas cannot capture locals. -- `new` objects and stored closures usually need `destroy` (see Production Pitfalls). +Done means the focused checks pass and relevant errors or warnings are fixed or explicitly explained. Runtime/UI behavior that static checks cannot prove still requires the smallest suitable Warcraft III or e2e verification.