Transmission #064: PDD scaffold — Unity Jersey /v1 client migration

Opened a Prompt-Driven Development project to design replacing the retired Javalin-era Unity integration with the Jersey/OpenAPI Surface at server.helerion.com/v1: generated C# client (ideally delete-and-regenerate on Unity compile), first-run character creation (name-only for now), persistence of returned character id, startup fetch by id, inventory updates via JSON Patch on character items, and loot sourced from embedded Item.drops instead of separate loot endpoints. Planning artifacts live under .agents/planning/2026-05-16-unity-jersey-api-migration/ (rough-idea.md, idea-honing.md, plus research/, design/, implementation/ for subsequent steps).

Preliminary research (owner opted in first)

Captured repo anchors and codegen implications: ApiUtils + CharactersApiIntegrationTest show /v1 base URIs and replace /items JSON Patch persistence; HelerionGameData, HelerionHttp, CharacterSession are the purge surface for Jersey alignment; openapi.yaml Item.drops + ItemComponent replace per-harvestable GET /api/loot-table/* traffic. Notes live in research/research-plan.md, research/existing-codebase.md, research/openapi-and-codegen.md, and research/synthesis.md (incl. mermaid + design gaps).

Requirements clarification underway

Entered PDD Step 3; first open question (inventory PATCH strategy: merge locally + REPLACE /items vs read-modify-write vs finer-grained ops) recorded in idea-honing.md pending owner answer.

Update: REQ inventory sync prefers JSON Patch add on /items (fine-grained ops) vs full-array replace. idea-honing.md Q1 answered. Open: failed PATCH UX (Q2).

REQ failed PATCH UX: toast/banner (C) on top of persisted retry queue (B); idea-honing.md Q2 answered; open 400-reconcile behavior (Q3).

REQ PATCH 400: GET + reconcile queued ops (A), then halt auto PATCH/backoff and persistent error until explicit recovery; idea-honing.md Q3 answered; open **startup gate before character (Q4).

REQ startup hard gate: /v1 idle until POST /characters succeeds and character id persists locally (Q4 = A).

REQ Item drops (Q5=A): per drop line run quantity independent Bernoulli trials; p = clamp(chance,0,1) or 1 if chance missing.

Requirements closed

Planning owner marked requirements clarification complete (idea-honing.md Q1–Q5, closure ) .

Detailed design

Drafted design/detailed-design.md: REQ rollup, architecture/components, mermaid flows, sync/error handling.

Owner picked option 1: accept detailed design as-is.

Implementation plan + summary (PDD Steps 78)

Authored implementation/plan . md (nine demoable steps + opening checkbox ) matching SOP checklist rules ; logged roll-up in summary .

Step 1 kickoff (OpenAPI C# codegen)

/v1 migration Step 1: regenerated client output under Assets/Generated/Helerion.OpenApi/ is driven by scripts/regenerate-helerion-openapi-client.sh (OpenAPI Generator 7.22.0 via OPENAPI_GENERATOR_VERSION, not a nonexistent @openapitools/openapi-generator-cli@7.22.0 npm dist-tag). End-to-end regen completes locally (exit 0). Owner confirmed Helerion.OpenApi compiles in Unity (batchmode smoke still fails if another Editor holds the lock; Hub 6000.3.10f1 on the agent host). Il2CPP/console reminder: generated ApiClient references System.Net.Http beside UnityWebRequest.

Step 2 landing (CharacterSession)

Added Assets/Scripts/Helerion/CharacterSession.cs: PlayerPrefs key helerion_character_id, warm hydrate via CharactersApi.GetCharacterAsync, onboarding path CreateHeroCoroutine(string) (POST /characters then GET). Exposes SessionReady, HydratedCharacter, AwaitingHeroCreation, LastError, SessionReadyChanged. Unity codegen requires async Tasks only (ISynchronousClient methods throw). 404 clears stored id. Editor Dev menus: clear prefs, create DevBootstrap. Checklist Steps 12 [x] in implementation/plan.md.

Step 3 — name overlay + session gate

HelerionCharacterOnboardingUI (Assets/Scripts/Helerion/) builds a screen-space uGUI card (LegacyRuntime font, overlay sort 280) when AwaitingHeroCreation, submits through CreateHeroCoroutine. CharacterSession now [DefaultExecutionOrder(-200)] with optional syncJerseyV1BaseFromHelerionGameData so jerseyV1BaseUrl tracks HelerionGameData.apiBaseUrl + /v1 on the shared Helerion server object; WaitUntilSessionReadyIfSessionPresent() gates HelerionGameData.LoadCoroutine (REQ: no /v1 catalogue traffic before SessionReady). ToastService for POST/hydrate errors. Step 3 checklist [x].

Fix: BuildUi accidentally left unclosed, nesting OnSubmitClicked inside it (CS0106 / CS1513). Restored _submitLabel wiring and the closing } before OnSubmitClicked.

Step 4 — catalog + embedded drops (Jersey GET /items)

HelerionGameData now calls ItemsApi.ListItemsAsync on {apiBaseUrl}/v1/items (async Task + coroutine wait) and builds definitions from OpenAPI Item / ItemType (maps HARVESTABLE instead of legacy HARVESTABLE_RESOURCE string). Per-harvestable GET /api/loot-table/{id} removed; loot uses Item.drops (ItemComponent rows) via HelerionHarvestableDropRoll (per-line quantity Bernoulli trials, clamp(chance,0,1)). Assets/Tests/Editor/HelerionHarvestableDropRollTests.cs covers aggregation + clamp. Pickups/loot CharacterSession.RequestServerMirrorAfterLocalPickup (PATCH characters/{id} add /items/-). Checklist Step 4 [x].

Step 5 — JSON Patch transport ( CharactersApi )

CharacterSession: PatchReplaceItemsCoroutine (replace /items) and PatchAddItemCoroutine (add /items/-) via PatchCharacterAsync + application/json-patch+json generator path; refreshes HydratedCharacter. HelerionCharacterPatchSmoke (inspector context menus, Editor play mode) exercises both against TryGetFirstCatalogItemId. Server: CharactersApiIntegrationTest.patchCharacter_addItem_appendsToItems. Checklist Step 5 [x].

Server Docker image — publish script + release workflow

server/docker/scripts/publish-server.sh must docker build with context server/ and -f docker/Dockerfile. Using docker/ alone as context broke COPY build/libs/helerion-server.jar (path is relative to server/, not server/docker/). .github/workflows/release.yml still pointed at server/Dockerfile after the file moved to server/docker/Dockerfile; it now passes -f server/docker/Dockerfile while keeping server/ as context — same semantics as local publish.

Jersey ReDoc (spec URL correctness)

Earlier fix removed UriInfo-built internal http://internal:8082/... spec-url (browser unreachable behind nginx-proxy). Using bare relatives yaml / json then broke RFC 3986 resolution when the page URL is /v1/docs (no trailing slash): yaml became /v1/yaml (404). ApiDocsResource now uses path-absolutes /v1/docs/yaml and /v1/docs/json (API_ROOT_PATH aligns with @ApplicationPath("v1") on JerseyResourceConfig). Bundled classpath openapi.yaml unchanged — not a copy failure.

Debug session 64b7d7 (character-create failure)

Unity Editor-only AgentLog (NDJSON) on CharacterSession create/hydrate paths (EmitDebugAgentLog for HelerionCharacterOnboardingUI null session); ingests append to .cursor/debug-64b7d7 . log to trace Jersey base vs HelerionGameData, POST vs GET faults. Runtime ingest showed httpcode=201 plus Newtonsoft failure on alignment:null mapped to int. openapi left unchanged (owner constraint): CustomJsonCodec in Assets/Generated/Helerion.OpenApi/Client/ApiClient.cs (CoerceNullableAlignmentJson) rewrites alignment:null0 before Character deserialize. CreateHeroCoroutine now calls SyncJerseyUrlFromGameDataIfEnabled() before EnsureApi (BootstrapCoroutine/patch parity). Re-verify NDJSON createOk + hydrate ok, then remove AgentLog when satisfied.

List / GET items: Prod JSON includes value:null (e.g. harvestables**) while codegen Item.Value int ListItems 200 → UnexpectedResponseException. CustomJsonCodec CoerceItemListNumericNullsJson rewires value:null 0 on each array element (openapi unchanged**)**.

Helerion inventory (HelerionInventoryBridge removed **)

Deployed Jersey-only App (**no Javalin POST /api/inventories* ) explain 405 against stale Unity URLs****; **standalone** **HelerionInventoryBridge** **removed****; pickup + loot enqueue CharacterSession.RequestServerMirrorAfterLocalPickup (runs PatchAddItemCoroutine) **. SampleScene HelerionServer Inspector drops duplicate script****. Summaries (**AGENTS**, **interfaces**, **workflows**) **updated` **.

Bag hydrate after GET hero (restart empty UI fix)

Pickups PATCH the server items snapshot, but CharacterInventory only lived in memory—no offline save—so quitting dropped the Unity bag while the stored character stayed correct. ReplaceBagContentsFromServerHydrate on CharacterInventory rebuilds bag slots (initialCapacity baseline) from CharacterItem lines via HelerionGameData.GetItemByServerId. HelerionLocalInventoryHydrator on HelerionServer (DontDestroyOnLoad) waits for session + catalog ready, finds CharacterInventory with FindFirstObjectByType, and skips duplicate work per (characterId, CharacterInventory) so a new prefab/player instance forces a refill. Equipment remains local-only: the Character DTO still does not round-trip equipped slots.

PDD scaffold — item crafting / deconstruction

Opened a second Prompt-Driven Development folder for crafting: random CRAFTING tables (catalog ids 26–30) in buildings, melee-open UI (inventory + table), component multiset matching for craft success vs destructive failure, deconstruct via existing drop-roll semantics, Carl-style messaging and first-time achievements, inventory PATCH for mutations, and no persistent table state between sessions. Artifacts: .agents/planning/2026-05-16-item-crafting/ (rough-idea.md, idea-honing.md, empty research/, design/, implementation/ until next steps).

Update: Owner chose PDD Step 2 → preliminary research first. Draft research/research-plan.md lists seven topics (drop roll reuse, melee interact pattern, catalog/recipe index, inventory UI, PATCH flow, interior table spawn, achievements) with concrete Unity file anchors and a suggested execution order.

Update: Switched to requirements honing (Step 3); Q1 recorded in idea-honing.md (craft match strictness: exact table multiset vs extras/partials).

Update: idea-honing.md Q1 answered: consume whole table, maximize ingredient use, prefer most complicated recipe, ties → even split, batch multiples when possible; Q2 asks how “complicated” is scored (AE).

Update: Q2 = (A) distinct ingredient types; Q3 open (even split vs deterministic vs random tie-break).

Update: Q3 = (1) split batch counts on complexity ties; Q4 (unusable multiset on Craft) pending.

Update: Q4 = (a) total loss (consume table, no outputs, fail messaging) on unresolvable multiset; Q5 (Deconstruct with multiple stacks on table) pending.

Update: Q5 = (iii) deconstruct all table items (per-item drop rolls, all destroyed); Q6 (achievement persistence L/S/H) pending.

Update: Q6 — achievements session RAM only, re-earn every launch; server/API punted; Q7 (empty-table Craft / Deconstruct) pending.

Update: Q7 = (γ) empty-table Craft / Deconstruct get full fail UX (no consumption).

Update: Crafting PDD requirements honing closed (idea-honing.md closure); iteration-checkpoint.md (Step 5) records rollup + research = plan only (deep notes TBD).

Update: Executed crafting PDD research pack: eight files under .agents/planning/2026-05-16-item-crafting/research/ (synthesis.md + seven topic notes); research-plan.md marked executed; checkpoint awaits detailed design (Step 6) on owner “proceed to design.”

Update: Authored .agents/planning/2026-05-16-item-crafting/design/detailed-design.md (requirements rollup, architecture, components, resolver, PATCH rollback, tests, appendices); design accepted; implementation/plan.md (10 steps + checklist) + summary.md — crafting PDD planning run closed (Unity code not started).

Crafting implementation Step 1 (catalog indices)

HelerionCatalogIndices centralizes multiset keys and fill rules: _dropsByItemId (any item with usable drops, not only HARVESTABLE), _recipeOutputsByComponentKey ( components → output ids, excluding CRAFTING / HARVESTABLE outputs). HelerionGameData wires both at /v1/items load, TryApplyTreeLoot now reads _dropsByItemId, new APIs TryGetDropLinesForItem / TryGetRecipeOutputIds / CraftRecipeKeyCount. Editor tests: HelerionCatalogIndicesTests. PDD checklist Step 1 [x].

Crafting implementation Step 2 (bag → CharacterItem snapshot)

CharacterBagServerLines.FromSlots merges bag-only InventorySlot rows by server id (stable sort); CharacterInventory.BuildBagCharacterItemsForServer(HelerionGameData) delegates to it ( null catalog → empty list + warning) for PatchReplaceItemsCoroutine. Editor tests: CharacterBagServerLinesTests, CharacterInventoryBuildBagTests. PDD Step 2 [x].

Crafting implementation Step 3 (CraftPlanSelector)

Assets/Scripts/Helerion/Crafting/CraftRecipeDefinition, CraftSelectorLimits, CraftPlan, CraftPlanSelector.TrySelectPlan (DFS full partition, lex compare on per-batch complexities, even split for shared-component outputs). Editor tests: CraftPlanSelectorTests. PDD Step 3 [x].

Crafting implementation Step 4 (DeconstructService)

DeconstructService.ProcessTableStacks — per unit HelerionHarvestableDropRoll against catalog drops; IDeconstructCatalog + HelerionDeconstructCatalogAdapter for HelerionGameData. DeconstructBatchResult (merged output ids, DeconstructedSource list incl. WasAtomicNoDrops). Editor tests: DeconstructServiceTests. PDD Step 4 [x].

Crafting implementation Step 5 (interior crafting table)

CraftingInteriorSpawn — deterministic table id 26–30 from BuildingInstanceId (FNV-1a); TrySpawnCraftingStation instantiates catalog displayPrefab when HelerionGameData.Ready, else tinted cylinder fallback; interior layer + non-trigger colliders + CraftingStationHitHandler. InteriorSessionManager.BuildInteriorCell now takes BuildingInteriorPortal and invokes spawn before return. Editor tests: CraftingInteriorSpawnTests. PDD Step 5 [x].

Crafting implementation Step 6 (melee → workbench hook)

CraftingStationHitHandler.TryOpenFromPlayer gates on interior + HelerionGameData.Ready + CharacterSession.SessionReady, then opens CraftingPanelUI. PlayerCombat.TryDealMeleeDamage resolves GetComponentInParent<CraftingStationHitHandler> after the quest board branch and before InteriorExitWall (order matches detailed-design.md). Editor tests: CraftingStationHitHandlerTests. PDD Step 6 [x].

Crafting implementation Step 7 (CraftingPanelUI)

CraftingPanelUI — runtime overlay canvas (sort order 28), opened from CraftingStationHitHandler after gates: bag grid tracks live CharacterInventory (rebuilds when SlotCount changes), table grid uses CraftingInventoryMerge + in-memory InventorySlot list cleared on every Show(). Tap source then destination: merge, swap bag↔table, or shuffle table slots. Close (or dim click) runs MergeTableIntoBag then a single PatchReplaceItemsCoroutine over BuildBagCharacterItemsForServerPATCH on close only (design: minimize chatter). CharacterInventory.TryPlaceOrMergeWholeStack supports precise bag deposits after swaps. Editor tests: CharacterInventoryTryPlaceOrMergeTests, CraftingInventoryMergeTests. PDD Step 7 [x].

Crafting implementation Step 8 (craft commit + PATCH rollback)

Craft runs CraftPlanSelector on the table multiset (CraftingTableMultiset); CR-8 / limits → no mutation + Carl toast; CR-7 clears the table only; OK clears the table and AddItem outputs. One PatchReplaceItemsCoroutine after mutation; CharacterSession.LastError triggers CloneBagSlotsForCraftingRollback / RestoreBagSlotsFromSnapshot plus table snapshot restore. HelerionGameData rebuilds CraftRecipeDefinition list (CraftRecipes) at catalog load via TryParseComponentMultisetKey. Result strip + Carl toasts. Editor tests: multiset + snapshot + parse. PDD Step 8 [x].

Crafting implementation Step 9 (deconstruct commit)

Deconstruct runs DeconstructService.ProcessTableStacks on table slots in index order (stable), Random.value per roll, clears the table, AddItem merged outputs, one PatchReplaceItemsCoroutine, same bag/table rollback as craft on LastError. DC-5 empty table → no mutation. Shared result strip (craft + deconstruct). Editor tests: DeconstructServiceTests.StableSlotOrder, CraftingDeconstructApplyBagTests. PDD Step 9 [x].

Crafting implementation Step 10 (session “achievements” + UX polish)

CraftingSessionAchievements — per-session HashSet first craft output id / first deconstruct source id (RAM only); extra toast after successful PATCH from CraftingPanelUI hooks. Carl strings tightened on craft/deconstruct paths; disableCommitButtonsWhenTableEmpty (default on) grays Craft / Deconstruct while the table is empty or a commit is running. Editor tests: CraftingSessionAchievementsTests. PDD Step 10 [x] — crafting checklist complete until future product tweaks.

Agent summary: interior crafting workflow

Added .agents/summary/workflows.md subsection Crafting / deconstruction (interior workbench) (spawn → melee open → panel → close PATCH → craft/deconstruct + rollback). AGENTS.md Unity integration now points agents at that doc so the runtime path is discoverable without reopening the full PDD — optional post–Step 10 documentation from the crafting implementation plan.

Editor test compile: ItemComponent namespace

CraftingDeconstructApplyBagTests was missing using Helerion.OpenApi.Model;, so ItemComponent did not resolve and FakeCatalog failed to satisfy IDeconstructCatalog. Matches DeconstructServiceTests usings.