Release 1.1.2 — the default path works end to end

Fix release for 1.1.1: the minimal path end to end, engine commands that
run on Godot, Unity and Unreal, hook warnings you can see, and verdicts
that say when they could not check. Details in CHANGELOG.md; upgrade
steps in UPGRADING.md (v1.1.1 → v1.1.2).

Fixes #67
Fixes #131

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WuJn2FdynEybfL5KC697nJ
This commit is contained in:
Donchitos
2026-09-29 21:32:19 +10:00
co-authored by Claude Opus 5.5
parent 7ed2c3e9c4
commit b21fa0f7f2
383 changed files with 22888 additions and 10618 deletions
@@ -3,7 +3,6 @@
## Agent Summary
Domain: Addressable Asset System — groups, async loading/unloading, handle lifecycle management, memory budgeting, content catalogs, and remote content delivery.
Does NOT own: rendering systems (engine-programmer), game logic that uses the loaded assets (gameplay-programmer).
Model tier: Sonnet (default).
No gate IDs assigned.
---
@@ -11,8 +10,8 @@ No gate IDs assigned.
## Static Assertions (Structural)
- [ ] `description:` field is present and domain-specific (references Addressables / asset loading / content catalogs / remote delivery)
- [ ] `allowed-tools:` list includes Read, Write, Edit, Bash, Glob, Grep
- [ ] Model tier is Sonnet (default for specialists)
- [ ] `tools:` list includes Read, Write, Edit, Bash, Glob, Grep
- [ ] Model tier is `sonnet` — frontmatter `model:` reads exactly `sonnet` (tiers: `.claude/docs/model-tiers.md`)
- [ ] Agent definition does not claim authority over rendering systems or gameplay using the loaded assets
---
@@ -26,8 +25,8 @@ No gate IDs assigned.
- Stores the returned `AsyncOperationHandle<Texture2D>` in the requesting object
- On character destruction (`OnDestroy()`), calls `Addressables.Release(handle)` with the stored handle
- Does NOT use `Resources.Load()` as the loading mechanism
- Notes that releasing with a null or uninitialized handle causes errors — includes a validity check
- Notes the difference between releasing the handle vs. releasing the asset (handle release is correct)
- Checks that the load succeeded (`handle.Status`) before using `Result`, and never releases a handle it does not hold (e.g., a character destroyed before its load was started)
- Uses `Addressables.Release(handle)` for loaded assets and reserves `Addressables.ReleaseInstance()` for `InstantiateAsync` results — does not mix the two
### Case 2: Out-of-domain redirect
**Input:** "Implement the rendering system that applies the loaded texture to the character mesh."
@@ -35,37 +34,39 @@ No gate IDs assigned.
- Does NOT produce rendering or mesh material assignment code
- Explicitly states that rendering system implementation belongs to `engine-programmer`
- Redirects the request to `engine-programmer`
- May describe the asset type and API surface it will provide (e.g., `Texture2D` reference once the handle completes) as a handoff spec
- Hands off the asset contract instead: the `Texture2D` comes from the handle's `Result` once the load succeeds, and stays valid only until the owning handle is released, so the consumer must not hold it past that point
### Case 3: Memory leak — un-released handle
**Input:** "Memory usage keeps climbing after each level load. We use Addressables to load level assets."
**Expected behavior:**
- Diagnoses the likely cause: `AsyncOperationHandle` objects not being released after use
- Identifies the handle leak pattern: loading assets into a local variable, losing reference, never calling `Addressables.Release()`
- Produces an auditing approach: search for all `LoadAssetAsync` / `LoadSceneAsync` calls and verify matching `Release()` calls
- Provides a corrected pattern using a tracked handle list (`List<AsyncOperationHandle>`) with a `ReleaseAll()` cleanup method
- Does NOT assume the leak is elsewhere without evidence
- Produces an auditing approach: pair every `LoadAssetAsync` with `Release()`, every `InstantiateAsync` with `ReleaseInstance()`, and every `LoadSceneAsync` with `UnloadSceneAsync()`
- Provides a corrected pattern: every active handle kept in a tracked collection and released when the level unloads, so nothing accumulates across level transitions
- Confirms the diagnosis with the Addressables Event Viewer (per-asset reference counts) or the Memory Profiler before concluding — does not assume the leak is elsewhere without evidence
### Case 4: Remote content delivery — catalog versioning
**Input:** "We need to support downloadable content updates without requiring a full app re-install."
**Expected behavior:**
- Produces the remote catalog update pattern:
- Produces the remote catalog update pattern documented in `docs/engine-reference/unity/plugins/addressables.md`:
- `Addressables.CheckForCatalogUpdates()` on startup
- `Addressables.UpdateCatalogs()` for detected updates
- `Addressables.DownloadDependenciesAsync()` to pre-warm the updated content
- Notes catalog hash checking for change detection
- Addresses the edge case: what happens if a player starts a session, the catalog updates mid-session — defines behavior (complete current session on old catalog, reload on next launch)
- `Addressables.GetDownloadSizeAsync()` before downloading, then `Addressables.DownloadDependenciesAsync()` to pre-download the updated content
- Re-downloads only changed bundles; catalogs are versioned so clients fall back to cached content, and download failures retry with exponential backoff while showing progress
- Raises the mid-session case (a catalog update lands while a session is running) and asks the user which behavior they want, rather than silently picking one
- Plans the update-path tests: fresh install, V1 to V2, and V1 to V3 skipping V2
- Does NOT design the server-side CDN infrastructure (defers to devops-engineer)
### Case 5: Context pass — platform memory constraints
**Input:** Platform context: Nintendo Switch target, 4GB RAM, practical asset memory ceiling 512MB. Request: "Design the Addressables loading strategy for a large open-world level."
**Expected behavior:**
- References the 512MB memory ceiling from the provided context
- References the 512MB memory ceiling from the provided context, and uses it in place of its own default console budget
- Designs a streaming strategy:
- Divide the world into addressable zones loaded/unloaded based on player proximity
- Defines a memory budget per active zone (e.g., 128MB, max 4 zones active)
- Specifies async pre-load trigger distance and unload distance (hysteresis)
- Notes Switch-specific constraints: slower load times from SD card, recommend pre-warming adjacent zones
- Additive scene streaming through `Addressables.LoadSceneAsync()` / `Addressables.UnloadSceneAsync()`, with groups organized per zone by loading context
- A memory budget per active zone whose total stays under the 512MB ceiling, with zones unloaded on transition rather than accumulated
- Essential zone content loaded first, optional content streamed after
- Defers where zone boundaries fall to `level-designer`
- Flags storage I/O speed on the target hardware as a load-time risk to verify on device, without quoting platform load speeds from memory
- Does NOT produce a loading strategy that would exceed the stated 512MB ceiling without flagging it
---
@@ -76,8 +77,9 @@ No gate IDs assigned.
- [ ] Redirects rendering and gameplay asset-use code to engine-programmer and gameplay-programmer
- [ ] Returns structured output (loading patterns, handle lifecycle code, streaming zone designs)
- [ ] Always pairs `LoadAssetAsync` with a corresponding `Release()` — flags handle leaks as a memory bug
- [ ] Designs loading strategies against provided memory ceilings
- [ ] Does not design CDN/server infrastructure — defers to devops-engineer for server side
- [ ] Designs loading strategies against provided memory ceilings, which override its default per-platform budgets
- [ ] Specifies only the client-side contract (remote load path / URL structure, catalog versioning, cache-header requirement) and defers CDN hosting and the delivery pipeline to devops-engineer
- [ ] Asks "May I write this to [filepath]?" naming the file before writing
---