* Decouple 3rd person camera from player movement
Arrow keys now move the player relative to the camera direction instead
of using tank controls. The camera stays static unless explicitly
controlled via right-click drag, mouse wheel, or touch gestures.
Adds a single hook in CalculateNewPosDir that lets the extension take
over movement computation. The extension manages its own velocity
smoothing, computes camera-relative directions from an absolute yaw,
and writes nav controller velocities via friend class access.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Clean up ThirdPersonCamera: remove dead m_orbitYaw, extract helpers
Remove m_orbitYaw (always DEFAULT_ORBIT_YAW) and pass yaw directly to
ComputeOrbitVectors, eliminating save/restore blocks at all call sites.
Extract GetLocalYaw() and InitAbsoluteYaw() helpers to deduplicate
repeated atan2 patterns. Simplify SetupCamera by passing DEFAULT_ORBIT_YAW
directly. Move TURN_RATE constant to file scope.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Hide cursor while orbiting 3rd person camera with right mouse button
Move SDL event handling from LegoInputManager::UpdateLastInputMethod to
an exported function called at the end of SDL_AppEvent, so cursor state
changes aren't overridden by isleapp's SDL_ShowCursor(). Use
SDL_SetWindowRelativeMouseMode to hide the cursor and freeze its position
while the right mouse button is held in 3rd person camera mode.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Rapid customize clicks would orphan running click animations because
SetClickAnimObjectId overwrote the tracked ID without stopping the old
animation first. When movement later called StopClickAnimation, only
the last animation was stopped, leaving earlier ones driving the ROI
transform and causing the player model to stay behind while the camera
moved forward.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Add optional sound effects to emotes
Replace the raw 2D string emote table with a struct-based EmoteEntry/EmotePhase
table so each emote phase can declare an optional 3D spatialized sound effect.
The sound plays via LegoCacheSoundManager when the emote phase starts, positioned
at the character's ROI. No extra network sync needed since sounds trigger locally
when TriggerEmote is called on each client.
Adds "crash5" sound to the BNsDis01 (disassemble) emote phase.
https://claude.ai/code/session_013XkwJJZ4RbNcBMFESuPTfJ
* Extract animation/asset tables from protocol.h into animdata.h
Move EmotePhase, EmoteEntry structs, animation table externs
(walk/idle/emote/vehicle), and helpers (IsMultiPartEmote,
IsLargeVehicle, DetectVehicleType) into a dedicated animdata.h/cpp
pair. This keeps protocol.h focused on wire-format concerns (message
structs, serialization, enums) while animdata.h owns game asset data.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
* Add stateful multi-part emote system with disassemble/reassemble
Introduces a generalized multi-part emote framework where emotes can have
two phases. The first trigger plays phase 1 and freezes the character at its
last frame; the second trigger plays phase 2 to restore normal state.
Movement is blocked for the entire duration of a multi-part emote (from
phase 1 start through frozen state to phase 2 completion). The frozen
state is synced to all peers via customizeFlags bits in PlayerStateMsg,
so new joiners see disassembled players correctly.
The emote table is now a 2D array (g_emoteAnims[][2]) where [1] is the
phase-2 animation name (nullptr for one-shot emotes). Adding future
multi-part emotes only requires a new row in the table.
https://claude.ai/code/session_01L5FiuVFUqASR93iJcaXfEi
* Fix emote movement blocking and frozen state sync
Move movement blocking from CalculateTransform hook (which broke the
camera by skipping p_transform output) to ThirdPersonCamera::Tick where
it zeroes speed/velocity directly. Remove ShouldBlockMovement and
ShouldInvertMovement hooks entirely.
Rebuild frozen emote animation cache in InitAnimCaches when the frozen
state was set before the ROI was available (state message arrived before
world was ready).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Clean up emote branch: remove unused include, extract ClearFrozenState helper
- Remove unused multiplayer.h include and using-directive from legopathactor.cpp
- Extract ClearFrozenState() to DRY up 4 identical frozen state reset blocks
- Clarify bit-encoding comment with mask value and emote ID limit
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
* Suppress right-click game interactions during free camera orbit
Rename IsTouchInputSuppressed to IsThirdPersonCameraActive and simplify
to return TRUE whenever the 3rd person camera is active. Use this in
ProcessOneEvent to skip right-click-only events, preventing accidental
game object interactions while orbiting the free camera.
https://claude.ai/code/session_01KuQtP2prfbVho2myKZ2pxE
* Move touch suppression logic into HandleTouchInput extension hook
Instead of checking IsThirdPersonCameraActive (which suppressed all
touch input whenever the camera was active), add a dedicated
HandleTouchInput hook that only suppresses touch input during active
multi-touch camera gestures. Move g_finger static into LegoInputManager
as m_touchFinger and grant friend access to MultiplayerExt so the
extension can manipulate touch state directly.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
* Fix 180-degree camera flip when exiting sub-worlds without cam anim
When returning from sub-worlds (jukebox, hospital exterior, etc.) with
3rd person camera active, the camera/player flipped 180 degrees. This
happened because SpawnPlayer calls Enter() → TurnAround() before
PlaceActor(), and PlaceActor overwrites the ROI direction with the
path's standard convention (z = forward). For spawn points with cam
anims, OnCamAnimEnd corrected this, but spawn points with m_location=0
(like jukebox exterior) had no correction.
Replace m_roiUnflipped with m_needsDirectionFlip flag that tracks world
transitions. OnWorldEnabled sets the flag, and the first Tick after
PlaceActor completes flips the ROI to backward-z and re-setups the
camera. ReinitForCharacter now always flips the ROI direction, handling
both Disable→Enable toggles and enabling 3rd person after a 1st-person
spawn.
https://claude.ai/code/session_01NQ9vy9Qr3aH6LNsRNLEEtY
* Fix camera flip regressions for vehicle exit and world transitions
The unconditional ROI flip in ReinitForCharacter caused a 180-degree
flip when exiting vehicles (Enter's TurnAround follows and double-flips).
Restore conditional flip using m_roiUnflipped, but now also set it in
OnWorldEnabled (even when disabled) so cold-enabling 3rd person after a
world transition correctly flips from PlaceActor's forward-z.
Key changes:
- Remove m_roiUnflipped clearing from OnActorEnter: Enter() is always
followed by PlaceActor which overwrites the ROI, so clearing the flag
prematurely caused the cold-enable case to miss the needed flip.
- Add orbit camera override in OnActorEnter during world transitions
(m_needsDirectionFlip && m_active) to suppress the 1st-person camera
flash from Enter's TransformPointOfView.
- Clear m_roiUnflipped in OnCamAnimEnd alongside m_needsDirectionFlip,
since the cam anim's PlaceActor + flip handles the correction.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Defer orbit camera setup during world transition loading freeze
Remove the premature SetupCamera call from OnActorEnter's world
transition path. The stale orbit camera view (computed before
PlaceActor runs) would freeze on screen during the ~500ms world
load, appearing as a wrong-direction flash. The Tick correction
after PlaceActor now handles the initial orbit camera setup at
the correct position.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Skip orbit camera while cam anim is running to prevent actor glitch
When a cam anim plays with 3rd-person camera active, ApplyOrbitCamera
was fighting the cam anim each frame. If the cam anim was interrupted
(space bar), its end handler read the ViewROI position — which was set
by our orbit camera (elevated, behind player) — and placed the actor
at that position, causing it to glitch into the air.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Add documentation for ROI direction conventions and camera corrections
Documents the forward-z vs backward-z conventions, all code paths that
require direction correction, and the flags that coordinate them.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Refactor 3rd-person camera from backward-z to forward-z convention
Switch the orbit camera to use forward-z (matching PlaceActor's native
output), eliminating all FlipROIDirection/TurnAround corrections. The
display clone flips to backward-z when syncing from the native ROI so
character meshes face correctly. Use actor state (c_disabled) instead of
m_animRunning to guard against cam anim conflicts, allowing the orbit
camera to resume as soon as the player is released.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Reset orbit camera position on world re-entry
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Remove ShouldInvertMovement and update documentation
* Remove header
Extract ~420 lines of duplicated character animation logic from RemotePlayer
and ThirdPersonCamera into a shared CharacterAnimator component. Both classes
now compose a CharacterAnimator member that handles walk/idle/emote animation
playback, vehicle ride animations, click animation tracking, name bubbles,
and animation cache management.
Behavioral differences between consumers (emote transform save/restore) are
handled via CharacterAnimatorConfig.
Also extract duplicated username encoding (letter indices to ASCII) from
NetworkManager::BroadcastLocalState and ThirdPersonCamera::CreateNameBubble
into EncodeUsername() in protocol.cpp.
Replace C standard library usage across the multiplayer extension with SDL
equivalents: sprintf->SDL_snprintf, sscanf->SDL_sscanf, atoi->SDL_atoi,
strcmp->SDL_strcmp, fabsf->SDL_fabsf, floorf->SDL_floorf, and remove
unnecessary <cmath>, <cstdio>, <cstdlib> headers.
Add m_displayActorFrozen flag to distinguish INI-configured display
actors from auto-derived ones. Derive displayActorIndex (actorId - 1)
at the top of every Tickle(), ensuring it is valid before the 3rd
person camera toggle or any broadcast. This eliminates the native ROI
fallback path in ThirdPersonCamera which was buggy (remote player ROIs
not appearing, customization not propagating, 3rd person camera not
working without INI config).
Remove all dead branches that checked IsValidDisplayActorIndex before
deciding between clone and native ROI paths, since the display actor
index is now always valid. Simplify ResolveActorInfoIndex to a single
parameter and remove the actorId fallback.
When exiting a race, LegoRace::Create stashes the UserActor and sets
it to NULL. The destructor restores it, but runs later than
OnWorldEnabled, so NotifyPlayerCountChanged sees a NULL UserActor and
doesn't count the local player. Fall back to GameState::GetActorId()
which is restored earlier (in LegoRace::Enable(FALSE)).
* Add free camera orbit controls to multiplayer third-person camera
Replace the fixed camera offset with dynamic orbit parameters (yaw,
pitch, distance) computed from spherical coordinates. Mouse wheel
controls zoom, right-click drag controls orbit, and two-finger
touch gestures support pinch-zoom and orbit on touchscreens.
The controls are always active when third-person mode is enabled.
A single SDL event forwarding hook is added to the main event loop;
all other changes are contained within the multiplayer extension.
https://claude.ai/code/session_013FyPCrJSaHxiJwdfGBVnYP
* Fix linker error and refactor orbit camera controls
Move HandleSDLEvent extension call from ISLE (no EXTENSIONS define) into
LegoInputManager::UpdateLastInputMethod in LEGO1 to fix unresolved
symbol errors. DRY up orbit camera code with ClampPitch/ClampDistance/
ResetOrbitState/ApplyOrbitCamera helpers. Simplify direction vector
computation. Fix mouse orbit yaw direction.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Fixes
---------
Co-authored-by: Claude <noreply@anthropic.com>
* Sync sky color and light position in multiplayer
Add ENTITY_SKY and ENTITY_LIGHT to the WorldEvent system so the host
controls sky color (hue/saturation via observatory sun/moon/palette
buttons) and light position (globe arrows) with the same
host-authoritative pattern used for plants and buildings. Non-host
players send requests to the host who applies and broadcasts. Sky/light
state is appended to the world snapshot so joining players get the
current values.
https://claude.ai/code/session_01X2cPVQEo7c92wpWA7QPPMG
* Clean up sky/light sync: remove debug logging, DRY apply logic, fix host routing
- Extract ApplySkyLightState helper to deduplicate sky/light apply code
between RestoreSkyLightState and HandleWorldSnapshot
- Remove all SDL_Log debug calls and SDL_log.h includes
- Remove dead OnWorldEnabled method from WorldStateSync (replaced by
OnHostChanged in OnSaveLoaded)
- Fix HandleSkyLightMutation host path: return FALSE to let local
switch case proceed, instead of duplicating via ApplyWorldEvent
- Simplify isle.cpp HandleControl: split observatory cases into
individual switch arms with single early-return multiplayer hook
- Add save load hooks to sync world state with multiplayer peers
- Fix player count to exclude local player without valid actor
- Support broadcast snapshots (targetPeerId=0) in relay server
---------
* 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
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)
* 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
* 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.
- 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
* 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
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.
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.
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
* 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>
- 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
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.
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.
Move world state synchronization logic (snapshots, events, entity
mutation routing) into a dedicated WorldStateSync class, reducing
NetworkManager from ~790 to ~420 lines.
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.
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.
- 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)
- 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
CI / ${{ matrix.name }} (false, -DCMAKE_SYSTEM_NAME=WindowsStore -DCMAKE_SYSTEM_VERSION=10.0.26100.0, false, false, Visual Studio 17 2022, true, Xbox One, windows-latest, amd64, false, true) (push) Waiting to run
CI / ${{ matrix.name }} (false, -DCMAKE_TOOLCHAIN_FILE=/opt/devkitpro/cmake/3DS.cmake, false, devkitpro/devkitarm:latest, false, Ninja, true, Nintendo 3DS, ubuntu-latest, true) (push) Waiting to run
CI / ${{ matrix.name }} (false, -DCMAKE_TOOLCHAIN_FILE=/opt/devkitpro/cmake/Switch.cmake, false, devkitpro/devkita64:latest, false, Ninja, Nintendo Switch, true, ubuntu-latest, true) (push) Waiting to run