Skip to content

Multiplayer#784

Closed
foxtacles wants to merge 101 commits intoisledecomp:masterfrom
foxtacles:multiplayer
Closed

Multiplayer#784
foxtacles wants to merge 101 commits intoisledecomp:masterfrom
foxtacles:multiplayer

Conversation

@foxtacles
Copy link
Copy Markdown
Member

@foxtacles foxtacles commented Mar 28, 2026

disregard

foxtacles and others added 30 commits February 28, 2026 12:00
…ing, vehicle support

- WebSocket relay server (Cloudflare Worker + Durable Object)
- Remote player character cloning with walk/idle/ride animations
- Vehicle support for remote players
- INI config for relay URL
- Extension hook for world transition ROI management
- Replace manual vector math in UpdateTransform with CalcLocalTransform and vec.h macros (LERP3, SET3, MV3, DISTSQRD3, ZEROVEC3)
- Remove all SDL_Log debug logging from multiplayer code
- Strip extraneous comments that restate the code
- Extract CreateAndSpawnPlayer helper to consolidate repeated spawn pattern
- Simplify UpdateVehicleState control flow
- Remove unused includes (SDL_log.h, mxgeometry/mxmatrix.h)
Add serialization framework using C++ templates and table-driven lookup
to sync plant and building state between players. Includes world snapshot
routing to requesting peer, relay server Docker support, and fixes for
building color sync, ride vehicle visibility, and ARM compilation.
…extension

Revert plant/building manager globals back to private class statics
(matching master) and use friend declarations for extension access.
Move CreateCharacterClone out of LegoCharacterManager into a new
CharacterCloner class in the multiplayer extension.
Move world state synchronization logic (snapshots, events, entity
mutation routing) into a dedicated WorldStateSync class, reducing
NetworkManager from ~790 to ~420 lines.
Split relay.ts into protocol.ts (constants, binary helpers), gameroom.ts
(Durable Object), and a thin relay.ts entry point. Replace magic numbers
with named constants matching protocol.h. Run wrangler directly as PID 1
in Docker so Ctrl+C shuts down gracefully instead of being swallowed by npx.
…sion

Read "multiplayer:room" from INI options (default "default") and use it
in Connect() instead of a hardcoded string. Return early if room is blank,
matching the relay URL check. Rename p_mgr to p_networkManager for consistency.
Use 'using namespace Extensions' and value_or() instead of verbose
fully-qualified names and has_value()/value(), matching the pattern
used in legotextureinfo.cpp.
- Tier 1 (plants + buildings): marked as implemented
- Tier 2 (NPC customization): downgraded to low value because NPCs are
  randomly spawned and pathed per-session, so players never see the same
  NPCs in the same locations
- Tier 4 (vehicles): corrected to account for autonomous vehicle coasting
  after exit (m_worldSpeed is never reset), split into Tier 4a (visibility
  only) and 4b (coasting position sync), increased effort estimate
- Updated priority order, effort summary, and opinion sections
- Configurable room size with relay-enforced ceiling (MAX_PLAYERS_CEILING)
- Room config written to isle.ini via OPFS (same pattern as existing settings)
- Multiplayer extension disabled in INI for solo play (zero overhead)
- Hash-based routing (#r/name) consistent with existing isle.pizza navigation
- Three-word Lego Island-themed room names (brave-red-brick style)
- Collapsible in-game overlay
- Animation research: identified CNs###xx generic animations, click body flip,
  disassemble/reassemble, and procedural flip; documented playback mechanisms
  and which are portable to remote players
- Animations broadcast-only (not played on local player)
- String-based animation names in protocol (matches internal lookup)
- Room full: game exits, frontend shows error modal on reload
- Removed future layers reference
- Three animation categories: walking (6 options), idle (3 options), one-off emotes (2 options)
- Walk/idle selections carried as uint8_t indices in PlayerStateMsg (auto-sync on join)
- Emotes remain one-shot MSG_EMOTE messages with emoji popup for local player
- WASM exports: mp_set_walk_animation, mp_set_idle_animation, mp_trigger_emote (index-based)
- Identified all CNs###xx animations with UI labels and descriptions
- Remote player state machine: walk → stationary → idle timeout → emote support
- CNs004xx excluded (duplicate of CNs001xx)
…k label

- Animation ROI maps: lazily built and cached, invalidated on world change
- Room full UX: toast style with sessionStorage to survive page reload
- Rename Sneaky walk animation to Leaning
- Remove hardcoded multiplayer config from emscripten config.cpp
- Add relay HTTP endpoints for room preview (GET) and creation (POST)
  with capacity check, CORS headers, and configurable max players
- Add WebSocket rejection detection (room full/503) via onclose flag
- Add CheckRejected extension call in IsleApp::Tick for clean shutdown
  through SDL_APP_SUCCESS path instead of calling exit()
- Set Module._exitCode in JS for sessionStorage-based toast after reload
* Export MultiplayerExt symbols accessed from isle executable

When lego1 is built as a shared library (DLL), symbols used by the isle
executable through the Extension<T>::Call() template need to be exported.
Add LEGO1_EXPORT to MultiplayerExt::enabled and MultiplayerExt::CheckRejected()
which are referenced from isleapp.cpp via the template instantiation.

https://claude.ai/code/session_01HHMmowothg25fephi6iidq

* Use inline const instead of constexpr for extension function pointers

constexpr cannot be used with dllimport function addresses since they
are resolved at load time through the IAT, not at compile time. Change
to inline const which preserves the single-definition semantics (via
inline) while allowing runtime initialization of the function pointers.

https://claude.ai/code/session_01HHMmowothg25fephi6iidq

* Fix multiplayer extension link errors across DLL boundary

When lego1 is a shared library, Extension<T>::Call() instantiated from
isle.exe references MultiplayerExt::enabled and CheckRejected() which
are not exported. Instead of exporting internal symbols (which also
breaks constexpr with dllimport), add an exported IsMultiplayerRejected()
wrapper that keeps the Extension<T>::Call() instantiation inside lego1.

https://claude.ai/code/session_01HHMmowothg25fephi6iidq

* Guard IsMultiplayerRejected with EXTENSIONS ifdef

extensions.cpp is only compiled when ISLE_EXTENSIONS is ON, so the
wrapper function and its call site need #ifdef EXTENSIONS guards for
targets like x86 MSVC where extensions are disabled.

https://claude.ai/code/session_01HHMmowothg25fephi6iidq

* Move IsMultiplayerRejected definition to multiplayer.cpp

The function is declared in multiplayer.h and belongs with the rest of
the multiplayer extension code, not in the general extensions.cpp file.

https://claude.ai/code/session_01HHMmowothg25fephi6iidq

---------

Co-authored-by: Claude <noreply@anthropic.com>
…orts

Implement the animation system from the Phase 1 plan:

Protocol: Add walkAnimId/idleAnimId fields to PlayerStateMsg (2 extra bytes
per 15Hz tick), add MSG_EMOTE (type 9) with EmoteMsg struct, and define
shared animation lookup tables (walk: 6 anims, idle: 3, emote: 2).

NetworkManager: Store local walk/idle animation indices, include them in
every state broadcast, handle incoming MSG_EMOTE by dispatching to the
target remote player's TriggerEmote(). Add SetWalkAnimation(),
SetIdleAnimation(), SendEmote(), GetPlayerCount() public API.

RemotePlayer: Replace per-animation raw pointers with AnimCache struct
and lazy m_animCacheMap (name -> ROI map, built on first use, cleared on
Despawn). UpdateFromNetwork() detects walk/idle ID changes and swaps the
active animation cache. UpdateAnimation() now has three states: moving
(configurable walk anim), emote (one-shot with duration tracking,
interrupted by movement), and idle (configurable idle anim after 2.5s
timeout). Add TriggerEmote() for one-shot emote playback.

WASM exports: mp_set_walk_animation(), mp_set_idle_animation(),
mp_trigger_emote(), mp_get_player_count() with EMSCRIPTEN_KEEPALIVE.
CMakeLists.txt adds EXPORTED_FUNCTIONS and EXPORTED_RUNTIME_METHODS
for Svelte ccall/cwrap access.

https://claude.ai/code/session_01BEYdu8gXr1QmYwzRRgaEA6
Move WASM exports (mp_set_walk_animation, mp_set_idle_animation,
mp_trigger_emote) from multiplayer.cpp into a dedicated
wasm_exports.cpp, added to the isle target (guarded by both
EMSCRIPTEN and ISLE_EXTENSIONS) so the linker keeps the symbols.

Replace the polling mp_get_player_count export with push-based
playerCountChanged CustomEvents dispatched from NetworkManager.
The count only reflects players visible in the Isle world: null
when the local player is outside Isle, filtered by remote player
worldId when inside. Remove the now-unused GetPlayerCount() method.
Move all Emscripten-specific multiplayer code under platforms/emscripten/
and introduce an abstract PlatformCallbacks interface for outbound
notifications, mirroring the existing NetworkTransport pattern. This
removes all #ifdef __EMSCRIPTEN__ blocks from networkmanager.cpp.
* Add feasibility plan for reusing multiplayer animation system for third-person camera

Evaluates reusing the multiplayer extension's RemotePlayer animation system
(BuildROIMap, AssignROIIndices, ApplyAnimationTransformation) for the local
player to enable a third-person camera mode. Conclusion: feasible with only
3 single-line extension hooks added to core game code.

https://claude.ai/code/session_01NC3zdQZ4nqEcYjyvStqcdD

* WIP: Third-person camera with animation reuse and movement fix

* Fix third-person camera bugs: vehicles, remote facing, emote distortion (#2)

- Fix spawn pose and building re-entry by applying idle frame 0 and
  reinitializing on world enable
- Handle vehicle transitions: ride animations for small vehicles,
  first-person fallback for large vehicles and helicopter
- Keep vehicle dashboards visible for exit controls
- Disable third-person camera for large vehicles, fix ROI cleanup
- Move HandleActorExit hook to end of Exit() for immediate reinit
- Fix remote player facing 180 degrees wrong by negating direction
  in BroadcastLocalState when third-person camera is active
- Fix Hat Tip emote distortion from compounding transform scale by
  saving clean parent transform at emote start and restoring after
  each frame's animation application

* DRY cleanup for third-person camera branch

- Extract shared DetectVehicleType() to protocol.h/cpp (was duplicated
  in ThirdPersonCamera and NetworkManager)
- Remove no-op HandlePostApplyTransform hook chain (called every frame
  for every LegoPathActor but did nothing)
- Add ThirdPersonCamera::ClearAnimCaches() helper (pattern repeated 5x)
- Add AnimUtils::EnsureROIMapVisibility() inline helper (loop repeated
  5x across ThirdPersonCamera and RemotePlayer)
- Remove redundant static_cast in multiplayer.cpp (UserActor() already
  returns LegoPathActor*)
- Delete THIRD_PERSON_CAMERA_ANIMATION_REUSE_PLAN.md development artifact
- Fix broadcast direction: use IsActive() instead of IsROITurnedAround()
  so the negate in BroadcastLocalState only fires when movement inversion
  is active, not based on a default-true flag

- Fix vehicle ROI direction for 3rd-person camera: undo Enter()'s
  TurnAround on small vehicles so the backward-z convention is preserved.
  Vehicles are placed with ROI z opposite to visual forward, and Enter()'s
  TurnAround breaks this for 3rd-person rendering. Applied in both
  OnActorEnter (entering while 3rd-person enabled) and ReinitForCharacter
  (enabling 3rd-person while already on a vehicle)

- Fix vehicle direction on exit: apply extra TurnAround in OnActorExit
  when 3rd-person is active, since Exit()'s TurnAround assumes Enter()'s
  TurnAround is still in effect

- Add WrappedUpdateWorldData() after manual direction flips in Disable()
  and ReinitForCharacter() to keep bounding volumes consistent and prevent
  stale world data from causing momentary camera/direction glitches

- Remove unused IsROITurnedAround() method

- Fix data race between WASM exports and game thread
* Fix 3rd person camera 180-degree flip after cam anim ends

When a cam anim (NPC interaction cutscene) ends, FUN_1004b6d0 places the
player actor at the camera's final position using the camera's direction,
which uses standard convention (z = visual forward). The 3rd person camera
relies on backward-z convention (z = visual backward, from TurnAround).
This mismatch caused the camera to face 180 degrees wrong permanently
after any cam anim.

Add a HandleCamAnimEnd extension hook in FUN_1004b6d0 that, when the 3rd
person camera is active, flips the ROI direction back to backward-z and
re-establishes the correct camera position.
* Implement display actor override for multiplayer extension

Add displayActorIndex to the multiplayer protocol, allowing players to
choose any of the 66 character models from g_actorInfoInit via the
multiplayer:actor INI setting. The visual display is decoupled from the
gameplay actor ID while maintaining backward compatibility.

- Protocol: Add displayActorIndex field to PlayerStateMsg and validation helpers
- RemotePlayer: Use display actor name for cloning instead of actorId
- NetworkManager: Broadcast/handle displayActorIndex, respawn on display change
- ThirdPersonCamera: Create/manage display clone ROI for local player override
- INI: Read multiplayer:actor setting and resolve to g_actorInfoInit index

* Use array syntax for INI option access in display actor setup

Consistent with how relayUrl and room are read from options.

* Fix display actor ROI handling in 3rd person camera

- Fix direction flip targeting display clone instead of native ROI in
  Disable(), ReinitForCharacter(), and OnCamAnimEnd(). The native ROI is
  the source of truth for TransformPointOfView and Tick() sync.
- Fix use-after-free: DestroyDisplayClone() now nulls m_playerROI when
  it points to the destroyed clone, preventing dangling pointer access
  in BuildRideAnimation after a 3rd→1st→3rd person toggle on a vehicle.
- Recreate display clone in ReinitForCharacter() vehicle branch.
- Extract EnsureDisplayROI() helper to deduplicate clone setup pattern.
- Move IsValidDisplayActorIndex() to charactercloner.h, replacing magic
  number 66 with sizeOfArray(g_actorInfoInit).

* Remove display actors plan document
Renders a billboard text bubble showing each remote player's display
name. Includes a WASM export to toggle visibility from the frontend.

- Bitmap font renderer generates paletted textures for name labels
- Billboard quad faces the camera each frame via orientation matrix
- Bubble visibility managed globally by NetworkManager toggle
- Fix miniwin D3DRMIMAGE constructor code style (static_cast, const)
* WIP: Add character customization to multiplayer

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Refine character customization: fix message buffering, DRY up code, request-based model

- Register NetworkManager with TickleManager via HandleCreate hook in
  LegoOmni::Create(), so packets are processed continuously instead of
  buffering between Connect() and OnWorldEnabled()
- Spawn unspawned remote players in OnWorldEnabled() (created before
  world was ready)
- Switch to request-based customization: HandleROIClick sends
  MSG_CUSTOMIZE to server, server echoes to all peers, HandleCustomize
  applies state and plays effects
- DRY up SwitchVariant to delegate LOD cloning to ApplyHatVariant
- Add ApplyChange helper consolidating the switch-on-changeType pattern
- Fix InitFromActorInfo to derive dependent color parts from independent
  parts (matching Unpack rules)
- Remove dead code: m_hasBeenTicked, ApplyCustomizeChange on
  RemotePlayer, m_localCustomizeState on NetworkManager
- Add null ROI checks in HandleCustomize for unspawned players
- Move MSG_CUSTOMIZE constant to shared protocol.ts

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Fix

* Fix character customization bugs from code review

- BUG-1: Add bounds check in SwitchColor to prevent OOB access from
  unvalidated network input (p_partIndex could exceed array bounds)
- BUG-2: Enforce allowRemoteCustomize on receiver side in HandleCustomize
  (was only checked on sender side, byppassable by malicious client)
- BUG-3: Document stale-state sound asymmetry for remote targets
- OBS-1: Remove unused bodyVariantIndex from CustomizeState (never
  modified, wasted 3 bits per state message)
- NAME-1: Fix p_ prefix convention on ApplyCustomizeChange parameters
foxtacles and others added 22 commits March 23, 2026 17:41
Add RoomRegistry durable object for tracking public rooms with
stale-entry cleanup. GameRoom notifies the registry on connect,
disconnect, and creation. Extract duplicated CORS_HEADERS to a
shared cors.ts module and remove dead OPTIONS handlers from DOs.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Required for Cloudflare free plan to back Durable Objects with SQLite storage.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When AttemptReconnect() disconnects a still-CONNECTING socket and creates
a new one, the old socket's onclose fires asynchronously after Connect()
clears m_disconnectedFlag, setting it back to 1. With connectedFlag=1
and disconnectedFlag=1 both stuck, the state machine bounces between
STATE_CONNECTED and STATE_RECONNECTING every frame (30-60x/sec) without
ever reaching the backoff timer or attempt limit.

Guard all WebSocket event handlers to only modify shared flags when
their socket is still the active one in Module._mpSockets.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Camera animations compute high listener velocities via
CalculateWorldVelocity, and miniaudio's default Doppler factor of 1.0
shifts the pitch/speed of all spatialized sounds accordingly. Disable
Doppler on sounds created by the multiplayer AudioPlayer using a friend
class to access LegoCacheSound's private ma_sound handle.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Players near multiple location points now see cam animations from all
overlapping locations instead of only the nearest one. Location proximity
radius reduced from 15 to 5 units. NPC animations unchanged (still
proximity-based). JSON output updated from "location" to "locations" array.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
PhonemePlayer resolved ROIs by name, but multiplayer participants have
unique ROI names (e.g. pepper_mp_8) that don't match the animation actor
name in the SI phoneme track (pepper). Pass actor-name-to-ROI aliases
from SetupROIs through to PhonemePlayer::Init so it can resolve
participant ROIs correctly.

Also lower/raise background audio volume for the duration of scene
animations.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace the single ScenePlayer/m_playingAnimIndex with a map of
ScenePlayers keyed by animation index, allowing non-overlapping groups
of players to play different animations simultaneously. Each player can
still only participate in one animation at a time.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The engine derives characterIndex from the last 2 chars of the animation
filename, which misses animations with suffixes like "in" (infoman), "p1",
"sl". These were incorrectly categorized as e_otherAnim and hidden from
multiplayer despite having valid named character performers.

Categorize based on whether the animation has at least one named character
in its performer mask (g_actorInfoInit indices 0-47 + 54-57) instead of
requiring characterIndex >= 0. This adds 23 animations to the catalog.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Manually override 6 spectator-only animations (557, 596, 709-711, 754)
as cam anims in the catalog, with 754's location set to Hospital.

For animations referencing world-placed models (BIRD, SHARK, CAT) whose
LOD data isn't independently registered in the ViewLODListManager, add a
scene ROI cloning fallback in SetupROIs. DeepCloneROI recursively clones
the ROI hierarchy sharing LOD geometry via refcount, producing independent
copies safe for concurrent playback. Moved to AnimUtils as a reusable
utility.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add vehicle-based filtering to the multiplayer ScenePlayer so that
animations requiring a specific vehicle (skateboard, bike, motorcycle)
are only offered when the performer is actually riding that vehicle.

- Add vehicleMask to CatalogEntry from AnimInfo::m_unk0x2a
- Three-state vehicle detection: on foot, on own vehicle, on foreign vehicle
- Filter performer animations by vehicle state in eligibility computation
- Spectator-only roles remain visible regardless of vehicle state
- Host validates vehicle state on interest and re-validates during countdown
- Cancel active sessions when local player's vehicle state changes

Includes temporary debug logging tagged TODO(vehicle-filter).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…rVehicleId

GetCharacterVehicleId used hardcoded g_characters[] indices but was
called with g_actorInfoInit[] indices. Since g_actorInfoInit has an
extra "infoman" entry at index 5, all characters after Laura were
off-by-one — bikers got the wrong vehicle and sy (Shiney Doris) fell
off the switch entirely, disabling filtering completely.

Replace with data-driven lookup (actorInfoInit name → g_characters
vehicleId), consolidate duplicate GetVehicleCategory into Catalog,
remove dead characterIndex field, fix stale g_characters comments,
remove temporary debug logging, and DRY local vehicle state computation.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Stop active ScenePlayer animation in OnActorEnter/OnActorExit before
modifying ride animation state — the ScenePlayer may still hold a
reference to the ride vehicle ROI that ClearRideAnimation frees.
Deactivate() and OnWorldDisabled() already had this guard.

Add alignment padding to MessageHeader (13→14 bytes) so uint16_t fields
in packed protocol structs no longer sit at odd offsets (UBSan violation).
Breaking wire format change — all clients and relay must update together.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Extract generic SI file reading and audio extraction from
Multiplayer::Animation::Loader into a new Multiplayer::SIReader class.
This eliminates the coupling where NetworkManager reached into the
Animation namespace to extract horn WAV data, and removes the wasteful
intermediate horn cache (LegoCacheSound::Create copies PCM data, so the
cache served no purpose after template creation).

- New Multiplayer::SIReader owns SI file handle, header-only reading,
  lazy object loading, and audio track extraction
- New Multiplayer::AudioTrack struct (moved from SceneAnimData::AudioTrack)
- Animation::Loader delegates SI access via SetSIReader() pointer
- NetworkManager owns SIReader, passes it to Loader, uses it directly
  for horn sound extraction via ExtractFirstAudio()
- Consolidate duplicate horn vehicle arrays into single g_hornVehicles[]
- Move HornMsg next to EmoteMsg in protocol (both one-shot broadcasts)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ComputeChildOffsets and ApplyHierarchyTransform had the multiplication
operands swapped. In row-major convention with translation in row 3:
  offset = child_world * inverse(parent_world)
  child_new = offset * new_parent_world

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
In large vehicles the third-person camera display ROI is frozen at the
entry position, so the name bubble stayed there. Fall back to the actual
UserActor ROI when the camera controller is inactive due to a large
vehicle, matching how remote players already handle this.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The build exit animations (e.g. c_ips002ro_RunAnim for helicopter) are
triggered via FUN_10060dc0 in SpawnPlayer, which bypasses the
m_enableCamAnims check. Mark m_playedExitScript = TRUE for all vehicle
build states in EnforceDisableNPCs to prevent them from firing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@foxtacles foxtacles closed this Mar 28, 2026
Replace magic numbers with named constants across third-person camera,
multiplayer, and common utilities. Extract duplicated code into shared
helpers: CancelExternalAnim, StartEmotePhase, DeriveDependentIndices,
ReaddROI, SendFixedMessage. Deduplicate finger-down handling via
TryClaimFinger and tighten Extensions::Enable() dispatch with a
table-driven approach. Fix missing <functional> include in sceneplayer.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@foxtacles foxtacles reopened this Mar 28, 2026
foxtacles and others added 4 commits March 28, 2026 15:22
The inline `class LegoROI*` in Catalog::GetVehicleState was being
resolved within `Multiplayer::Animation` namespace on some compilers
(e.g. devkitA64 GCC), creating an incomplete type. Move the forward
declaration before the namespace block.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Extend the multiplayer animation system to support animations from all
three game worlds (ACT1, ACT2, ACT3) while playing in the Isle world.

Catalog: Parse DTA files directly for all worlds instead of borrowing
from LegoAnimationManager. World-encoded animIndex (top 2 bits = world
slot) provides globally unique IDs without wire protocol changes.

Loader: Support multiple SI files (isle.si, act2main.si, act3.si) with
lazy opening and composite (worldId, objectId) cache keys.

WDB: Load missing model LODs from WORLD.WDB for all worlds during
catalog refresh, using LegoPartPresenter for parts and
LegoModelPresenter for compound models (ray, chptr).

Protocol: AnimCompleteMsg now carries animIndex instead of objectId.

Also fix pre-existing bugs:
- PhonemePlayer UAF when multiple tracks target the same ROI
- ModelDbModel buffer overflow on word-aligned strlcpy reads
- SIReader UBSan violation on uninitialized filetype enum

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Include 3dmanager/lego3dmanager.h to provide the full class definition,
which was only forward-declared via legovideomanager.h.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The SI extra data format uses colon as key-value separator
(e.g. PTATCAM:HEAD;INFOHAT), matching KeyValueStringParse's
delimiter set. The parser incorrectly required an equals sign,
so PTATCAM directives were never found and the feature was
completely inactive.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@foxtacles foxtacles closed this Mar 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants