Replace the connection-approval role selection with a server-authoritative
match flow built on NGO additive scene management:
- Scenes: Main is the persistent foundation (NetworkManager, GameModeManager,
cameras, HUD); Lobby and Test Map load/unload additively. Add Lobby + Test Map
to the build settings; remove the obsolete SampleScene.
- MatchManager (plain MonoBehaviour singleton): loads Lobby on server start,
StartMatchTrigger lets the host start the match, rolls one random Hunter (rest
Hider), swaps Lobby -> Test Map (strictly serialized per NGO), respawns each
player as its role variant. Forces additive client sync mode so clients keep
Main instead of single-loading it (fixes "Destination scene is not valid").
- Server-driven spawning: null out NetworkConfig.PlayerPrefab at runtime to
disable auto-spawn, then spawn players after the lobby floor exists (fixes
falling through the floor) with double-spawn and transition guards.
- StartMatchTrigger: server-gated OnTriggerEnter, only the host's own player
starts the match.
- NetworkBootstrap: drop role selection / connection approval; everyone spawns
as the neutral Player prefab. Host/Client/Server/Shutdown buttons stay.
- NetworkPlayerSetup.ScopedModes flag (baked on Hunter/Hider prefabs): lobby
avatar gets the full mode set, match variants get the role-scoped set.
- GameModeManager.BindLocalPlayer reads ScopedModes and rebinds the painter
unconditionally (fixes stale painter binding on respawn -> brush painting in
every mode).
- MeshPainter.PaintingEnabled defaults to false (unbound/pre-spawn/remote never
paints); PaintHud visibility now also gates on a bound painter (fixes the HUD
showing at startup before any avatar exists).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Pointer-based per-painter history of the canonical maps with copy-on-write
sharing (only maps touched by a stroke are cloned). A stroke (press..release)
is one step; a Load resets the history to the loaded maps as the sole baseline.
Network sync replicates undo/redo as tiny checkpoint/restore commands instead
of broadcasting full map snapshots: every peer runs the same COW history keyed
by owner-assigned checkpoint ids and reproduces restores locally. A peer that
cannot reproduce a target (undo across its join boundary) requests a targeted
snapshot from the owner as fallback.
Hardening: ordered send FIFO keeps stamps + checkpoints + restores in
production order (a marker never overtakes the stamps it finalizes); stamp
overflow carries over to the next frame instead of being decimated, so remote
content matches the owner; remote always inserts the checkpoint id to stay
1:1 aligned. Owner-authoritative snapshot fallback replaces the silent
host no-op. Adds a logSync toggle (default on) tracing when each
checkpoint/restore/snapshot is sent, applied, or falls back.
Also includes paintings-panel layout tuning in PaintHudPanel.prefab
(inspector edits).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Persistence rework:
- Drop auto-load on setup and auto-save on teardown; the painter now always
seeds fresh from the material.
- Named slots under MeshPaint/Saved/<name>.paint via the existing CHMP
machinery: SavePaintAs / LoadPaintByName / DeletePaint / ListSavedPaintings /
PaintExists. Slot files carry the display name in the header.
- Remove the obsolete persistence partials (PlayerCharacterController,
PlayerPoseController, ThirdPersonOrbitInput) and their references.
Paintings HUD panel:
- Authored Panel_Paintings + Save/Load/Delete dialogs into PaintHudPanel.prefab
(TMP throughout); PaintHud.Library.cs is now a pure binder that clones the
entry template per slot.
- Save dialog blocks empty/existing names; Load and Delete confirm first.
- Fix dialog z-order: open dialogs SetAsLastSibling so a dragged panel can no
longer cover them.
- Lib_ListContent horizontal offsets corrected to 0 (was clipping entry labels
out of the masked viewport).
Multiplayer:
- Loading a painting re-syncs to all peers via the snapshot broadcast path
(BroadcastLoadedPaint), covering later late-joiners for free.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Roles are chosen in the bootstrap dev panel before Host/Client. NGO
ConnectionApproval is enabled at runtime and maps the role payload to
the matching prefab variant per connection (Hunter/Hider), so each peer
spawns the correct avatar including the host.
- PlayerRole.cs (new): single source of truth enum { Hider, Hunter }.
- NetworkBootstrap.cs: role toggle, ConnectionApproval + role payload,
server ApprovalCheck mapping role -> PrefabIdHash.
- NetworkPlayerSetup.cs: serialized PlayerRole field + Role property.
- GameModeManager.cs: BuildModes(role) - Hunter -> [First Person, Paint],
Hider -> [Third Person, Paint, Free Cam]; binds role from spawned avatar.
- Hunter.prefab: role = Hunter override.
- SampleScene.unity: NetworkBootstrap prefab references wired.
Also includes user inspector scale tuning (Hunter 2.5->2.4, Hider 0.5->0.7).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Wall/ceiling/ground climb probes now skip any player collider (own and
remote) instead of only the local hierarchy, so running into another
chameleon no longer triggers the climb mechanic. New players are placed
on a per-clientId ring at spawn (host stays at origin) to avoid stacking
into one collision pile.
Also persists the serialized defaults of the recent network fields into
the Player prefab (incl. the spawnRingRadius inspector tuning) and a TMP
dynamic-atlas reset artifact.
Point the first-person camera at the dedicated FirstPersonTarget anchor
(eye height) instead of the shared CameraTarget, in both the runtime
SetPlayer rebind and the editor ResolveReferences fallback.
Rebind the paint HUD to the locally spawned avatar's MeshPainter via a
new GameModeManager.PainterChanged event. Since the spawn-on-connect
refactor, no player exists at PaintHud.Start(), so the HUD bound to a
null painter (no brush masks shown, color/settings were no-ops).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replicate brush strokes as self-contained commands instead of texture
pixels: the owner emits each committed stamp (local placement plus brush
state, byte-quantized) and remotes replay it through the same draw path,
reproducing identical maps without streaming megabytes.
- MeshPainter.Net.cs: PaintStampNet serializable stamp, OnStampCommitted
owner-capture event (no-op without listener), ApplyNetworkStamp remote
replay, seedable spray for deterministic speckle.
- MeshPainter.cs: emit stamp after drawing, seed spray only when synced.
- PlayerPaintSync.cs: NetworkBehaviour batching owner stamps reliably
(ServerRpc -> ClientRpc) with echo guard; remote disables local painting.
- Player/Hider/Hunter prefabs: PlayerPaintSync plus NGO GlobalObjectIdHash.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add PlayerAnimationSync NetworkBehaviour with owner-authoritative
NetworkVariables for pose index and locomotion category (idle/walk/run/fall).
Remote avatars switch their PlayerPoseController into network-controlled mode,
skipping local input, sprint reset, capsule resize and camera framing, and
take pose index plus locomotion externally. Disable pose persistence on
network-driven remotes so foreign avatars can't overwrite the local save.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Install Netcode for GameObjects 2.13.0 + Unity Transport 2.6.0 and wire up
the first networking milestone: syncing player position/rotation across clients.
- NetworkBootstrap: Host/Client/Server controls (OnGUI + H/C hotkeys) for
local clone testing over loopback 127.0.0.1:7777.
- NetworkPlayerSetup: per-avatar owner gating. The local owner binds the
GameModeManager and cameras to itself; remote avatars disable their
controller/CharacterController and receive transform via NetworkTransform.
- GameModeManager.BindLocalPlayer: central runtime rebind to the locally
spawned player.
- FirstPersonController.SetPlayer: explicit runtime rebind hook.
- Player prefab: NetworkObject + NetworkTransform (owner-authoritative,
position + rotation) + NetworkPlayerSetup; registered as PlayerPrefab.
- Scene: NetworkManager + UnityTransport added; the fixed scene player
instance removed in favor of spawn-on-connect.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Play a dedicated falling animation when the player is airborne and
descending past a scale-aware velocity threshold.
- PlayerCharacterController: new IsFalling state + Fall Animation Speed
threshold (true only when !grounded && !wallClimbing && descending,
scaled via CapsuleScale); explicitly false during wall/ceiling climb.
- PlayerPose: per-pose Fall Clip + Fall Clip Speed (mirrors walk/run).
- PlayerPoseController: select the fall clip while falling, fall back to
the idle clip when a pose has none (no regression).
- Author content: Paintman-Fall.anim assigned to Pose_Idle; UMotion
project, Player prefab and scene authoring.
- Docs: check off the fall animation in GAME_DESIGN.md.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
When running backwards toward the camera into a wall, the grab decided on
the camera-relative world direction (S pushes into the wall -> grab) while
the base release decided on the raw S axis (v < 0 -> immediately let go),
causing a per-frame grab/release flicker.
Mirror the existing _wallStrafeSign A/D latch with a new _wallClimbSign:
the camera picks, per climb-key press, which of W/S climbs up (the one
pushing into the wall) via Dot(camForward, wallNormal) and latches it.
All up/down tests now run through the effective climb axis vEff -
wall->ceiling handoff, top-out detach/hop, the alongSurface mapping and
the base release - so grab and release can no longer contradict each
other. Reset on grab and in ExitWallClimb. Facing the wall and grabbing
with W is unchanged (vEff == v).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Introduce a FirstPersonController / FirstPersonMode pair (mirroring the
free-cam mode) wired into the GameModeManager Tab cycle. In first person the
mesh is hidden, the eye sits on CameraTarget plus a per-pose
firstPersonCameraTargetYOffset, and the character body yaw is driven to match
the camera via a new single-writer FaceYaw hook on PlayerCharacterController.
Also includes user content edits (poses, prefabs, scene) carried along.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
FreeCamController: ease movement via dt-correct Vector3.SmoothDamp on a world-space velocity (accel/decel ramp) and lag the look with Mathf.SmoothDampAngle. Both are framerate-independent and collapse to instant at smoothTime 0; smoothed state is snapped on activation (SyncToCurrentView) so entering freecam never jumps to catch up.
Bundles accumulated deferred work: PlayerScaler uniform-scale helper, scale-aware wall/ceiling climb probes and the double-scale skinned-collider bake fix, plus user content authoring (Sit pose + animation, font SDF, scene/prefab edits).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace the CPU island-mask dilation guard on the GPU backend with a
coverage pass rendered through the same brush rasterizer, so sub-pixel
rim texels that the GPU brush can never reach fall into the gutter and
stay paintable (no more frozen loaded pixels at island edges).
Also includes user content: additional brush masks (dirt) with an
expanded mask selector in the HUD prefab, and CharacterController /
wall-climb inspector tuning.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Add mask selector with previews to the brush card (data-driven from
painter.brushMasks); persist activeBrushIndex with HUD layout.
- Fix alpha/coverage artifacts when overpainting after load or backend
switch: derive the dilation source marker from a deterministic UV island
mask instead of resetting it to "unpainted" on every seed.
- Import the two final brush mask textures (Brushes/).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Pipette samples all painted channel values (albedo, metallic, smoothness,
occlusion, emission) at the clicked texel and adopts them into the brush
with every opacity forced to 1, instead of painting. The screen raycast is
extracted into a shared TryRaycastPaintMesh helper used by painting, preview
and the pipette. A pipette toggle is added to the HUD panel prefab and wired
in PaintHud.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Spray brush mode: bakes a procedural scattered-dot mask per stamp (random
position, size, alpha within the brush disc) and paints it through the
existing single-stamp pipeline; dot centers clamped to (0.5 - dotR) so the
spray footprint matches the solid brush radius.
Paint interval: float on MeshPainter; 0 keeps the continuous interpolated
stroke, >0 emits a single timed dab every x.xx seconds.
Color picker alpha: horizontal alpha bar on both HSV pickers, runtime-built
in Rebind (no prefab re-bake), passed through to brushColor.a / emissionColor.a.
Metallic/smoothness + occlusion paint opacity: new opacity fields applied in
both CPU and GPU backends (GPU MSO draw split into MS and occlusion sub-stamps
for independent opacity), with cloned alpha sliders wired into the HUD.
Also includes the prefab layout tweaks and scene adjustments.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Split the single HUD panel into four independent per-channel sub-panels
plus a global brush-size panel, each a standalone RectTransform with its
own header (drag-handle ready). PaintHud now binds to a baked layout
prefab via recursive name lookup instead of building everything in code,
with a code-gen fallback. HsvColorPicker gains Rebind() to re-apply the
procedural gradient sprites and drag relays that a prefab cannot serialize.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>