Transmission #070: Remote mob proxy parity
Server-owned mobs were spawning as bare interpolated meshes: no CharacterStats, no inspect identity, and combat only sent a WebSocket intent with no client feedback. That broke tap-to-select and floating damage numbers—the same UX local GoblinRoadSpawner mobs get for free.
Remote mob proxy setup. New RemoteMobProxySetup runs when a MOB proxy is first spawned: enables display-only CharacterStats, applies WorldNpcIdentityGenerator (seeded from entity id so names match across clients), copies capsule/animator settings from GoblinRoadSpawner, and drives StylizedMonsterAnimatorDriver from interpolated movement. Local GoblinAI and GoblinDeathHandler stay disabled—the sim owns AI and death.
Combat feedback. CharacterStats gained ApplyAuthoritativeCombatHit and SyncAuthoritativeHealth for server HP. HelerionWorldCoordinator now forwards successful combat_result messages to the registry, which shows damage numbers, plays death anim, credits kills, and despawns when target_died is set. HP also syncs from snapshot/delta hp / max_hp fields on RemoteEntityView updates.
Rebuild the Unity client to pick this up; no server deploy required for these client-only changes.
PATCH character items HTTP 500
Pickup sync used incremental add /items/- coroutines. Each server PATCH replaces the entire inventory from the patched snapshot, so overlapping pickups could race; invalid or stale item ids also surfaced as uncaught PostgreSQL FK errors (Grizzly HTML 500).
Client: RequestServerMirrorAfterLocalPickup now debounces (~350ms) into the same replace /items full-bag path as crafting and app pause — catalog-validated lines only, one authoritative snapshot.
Server (deploy when ready): InventoryRepository.update rejects unknown item ids before insert (InvalidInventoryItemException → HTTP 400 with message). CharactersApiImpl.patchCharacter catches persist-time IllegalArgumentException instead of leaking 500.
Bag snapshot warnings for local scrolls
Client-only items (e.g. NewScroll / “hello world”) have no helerionServerItemId because they are not in the Jersey catalog — only runtime catalog defs get one from HelerionGameData. Full-bag replace /items correctly omits them; the warning was noise. CharacterBagServerLines now skips empty server ids silently and only logs when a non-empty id is malformed or missing from the catalog.
Quest board interaction regression
Selection moved quest contracts off the melee loop (attack-to-open) onto tap-inspect detail, but most players still punch boards like buildings. Thin mesh colliders also made tap rays easy to miss.
Fix: Melee hits on QuestBoardSessionController call TryOpenForPlayerInteraction() again (opens selection detail on mobile, legacy modal on desktop). Use/Interact uses the same entry point with horizontal range checks. Each board grows a one-time QuestBoardInteractionVolume box collider so rays and overlap spheres register reliably.
Follow-up: SelectionDetailModalUI.BuildQuestRow put Text on the same GameObject as Image (Unity allows only one Graphic per object), which threw when opening quest offers. Accept buttons now use a child label, matching QuestBoardModalUI.
CharacterEventsApiCodegenTest
World sim added reportPresence and combatIntent receive ops on the characterWorld channel (handlers live on hand-written CharacterWorldApi, not generated CharacterEventsApi). Updated the codegen alignment test to assert each channel against its own interface.
Remote player locomotion
Interpolation alone only slides the root transform — it does not feed the HumanMale Animator. Remote player proxies now get RemotePlayerProxySetup, which drives the same Speed / MoveX / MoveZ parameters as PlayerController from measured motion and speed_mps on world updates. Local input, combat, and CharacterController stay disabled on proxies.
Inventory persistence regression
Bag restore could fail silently when the hydrator ran before the item catalog or local CharacterInventory existed, then never retried. Pickups also relied on best-effort add /items/- coroutines that might not finish before the app backgrounded.
Fixes: HelerionLocalInventoryHydrator now polls up to 120s for catalog + Player-tagged inventory and re-runs when HelerionGameData.ReadyChanged fires. CharacterSession flushes the full bag with replace /items on app pause/quit via RequestServerMirrorFullBag.
OpenAPI JSON null coercion (CustomJsonCodec)
Production Jersey JSON includes null for optional numeric fields (alignment, item value, etc.) while Unity codegen maps them to non-nullable int. That made CustomJsonCodec.Deserialize throw on successful HTTP 200 — character hydrate, catalog load, and PATCH responses all failed silently from the player’s perspective (no session, empty bag, onboarding loop). Added durable HelerionOpenApiJsonCoercion (survives OpenAPI regen) and wired it into ApiClient before Newtonsoft runs. Coercion now walks the full JSON tree so nested nulls (e.g. Achievement.drops[].chance) are fixed, not just top-level Character / Item fields. Also bumped MapChunk buildItemsPerFrame 1→12 so buildings/trees/quest boards appear sooner after terrain is ready.