Add a geometry-based gate in PaintAtScreen that estimates how much of the
brush footprint lands on faces whose normals deviate sharply from the hit
surface. When that fraction exceeds the threshold the whole dab is
suppressed (no draw, emit, or undo mark) instead of smearing around the
edge. Backend-independent (no GPU readback) and winding-robust. Tunable via
cornerGuard / cornerGuardAngle / cornerGuardMaxFraction.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Move DefaultNetworkPrefabs.asset out of Assets/Settings back to the Assets root (where NGO expects it), and fix the PlaySceneWindow domain-reload toggle to read and write EnterPlayModeOptions correctly.
Add the HalfLambert, HalfLambert brightness and specular-damp sliders to the paint HUD panel, dial the camo values on Player.mat, and disable shadow casting on the Paintman renderer so the player's cast shadow no longer gives away its position.
Surface the PlayerCamoLit material knobs (HalfLambert, HalfLambert brightness, specular dampen) as material-backed properties on MeshPainter, guarded by an IsReady gate since the HUD binds before the material instance exists. Wire the three HUD sliders to them and seed the sliders from the live material once the painter is ready. Persist the values into the same HUD JSON as the brush settings (with a hasCamoValues flag so pre-camo save files never clobber the authored material) and carry them across a scene-switch respawn. Replicate them owner-authoritatively via a NetworkVariable in PlayerPaintSync, exactly like emission intensity, so remotes and late joiners show the same camo.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Rebuild the camo controls around hiding a painted player in a dark scene. _HalfLambert is now a two-segment curve (0 = no effect / URP Lit, 0.5 = classic half-lambert soft terminator, 1 = fully flat) and a companion _HalfLambertBrightness (-1..1) additively shifts the flattened diffuse so the body tone matches the scene. _SpecularDampen still scales down the direct highlight. The view-coupled _FormSoftness normal-bend is removed entirely (it flipped light/dark with the camera); all knobs are now term-based and view-independent, and stay byte-identical to URP/Lit at their no-op values.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The hybrid foreign-object pipette previously always overwrote albedo with the lit on-screen pixel. Gate that behind a new serialized pipetteAlbedoFromScreen flag (default off): off reads albedo texel-exact from the material like the other channels, on takes it from the rendered, fully-lit pixel. The data channels still always come from the material.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Heavy authoring pass on The Room scene plus lighting/render tuning across
scenes: Lobby gets lower ambient/reflection intensity, softer shadow
strength and a baked lighting data asset; Main and Test Map drop their
lighting data/settings links. Replace the living-room Glass materials with
reworked versions under Assets/Materials, update BrightDaySettings, and set
hunterCount to 0 in Main.
Move editor scripts to Assets/Scripts/Editor (PlaySceneWindow, SceneCollection),
move DefaultNetworkPrefabs and the Main Camera custom blends asset into
Assets/Settings, and shelve the unused HeroCutout shader/include under
Assets/Unused. Rename the Chameleon background texture, delete the obsolete
DefaultParentForEditor script and New Lighting Settings asset, add a The Room
SceneCollection asset, and drop the redundant Assets menu entry from the
StraightenKnees tool.
Custom/PlayerCamoLit is a URP 17 Lit clone whose ForwardLit pass routes
through PlayerCamo.hlsl - a faithful copy of UniversalFragmentPBR that
delegates all GI/APV/shadow/cluster-light helpers unchanged and only swaps
the three light calls for a camo variant. Three sliders flatten the form
cues that give a painted player away: _FormSoftness bends the per-pixel
normal toward the camera (softens diffuse + specular + reflections),
_HalfLambert lifts the diffuse shadow side, _SpecularDampen scales down the
direct highlight. All sliders at 0 are byte-identical to URP/Lit.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
On a foreign object the pipette now reads the data channels
(metallic/smoothness/occlusion/emission) texel-exact from the material
while taking albedo from the rendered, fully-lit on-screen pixel - so the
picked colour honours baked lightmaps/ambient/SSAO while keeping the
material's PBR fidelity. Falls back to a pure screen pick when no readable
UV is available. Pull the render+readback out of PickFromScreen into a
shared TrySampleScreenColor.
The pipette now also arms on Ctrl (in addition to Shift), and Ctrl+wheel
resizes the brush; the orbit camera ignores the wheel while either modifier
is held so it does not zoom and resize at once.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
When leaving first person for an orbit mode (Paint / Player Control), snap
the orbit axes to Camera.main's current view direction instead of restoring
the orbit's last-used angle. With WorldSpace binding + Sphere orbit + a
centered composer the camera look rotation is exactly
Euler(VerticalAxis, HorizontalAxis, 0), so the inverse mapping is exact.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
New The Room scene built from the Barking_Dog living room asset: prefab
variants under Assets/Prefabs/3D Living Room, an Environment Light prefab and
baked lighting settings. Point MatchManager at The Room (was Test Map) and
swap it into the build settings scene list. Floor_Wood material and the
living room meta files are reserialized by the URP/Unity importer. Main.unity
also carries the inspector-pinned defaults of the orbit-zoom and hunter-count
fields (matching the C# defaults, no behavior change).
The orbit radius (min/max/default and per-notch step) was in absolute world
units and ignored the player's localScale, so a small Hider started too far
out and a large Hunter too close and could not zoom out far enough. Multiply
range, step and default radius by the follow target's lossyScale (PlayerScaler
keeps the body uniformly scaled), making the inspector fields size-1 design
values that frame every role body-relative. Re-clamp every frame so a runtime
grow/shrink stays in range. GameModeManager.BindLocalPlayer calls ResetZoom
after rebinding so each new avatar starts at its scaled default zoom.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
StartMatch rolled exactly one random Hunter. Add a serialized hunterCount
field (default 1, [Min(0)], inspector-tunable) and roll N distinct random
Hunters via a partial Fisher-Yates on the client-id list, clamped to the
player count: 0 = all Hiders, >= player count = all Hunters. The match
sequence now carries a HashSet of hunter ids instead of a single id.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
hit.textureCoord is only filled for non-convex MeshColliders; on a convex
MeshCollider (or Box/Sphere/Capsule) it returns Vector2.zero, so the pick
sampled the foreign material's base map at texel (0,0) - a constant, wrong
color ("base map ignored"). Now the UV is recovered from the renderer mesh
itself (camera ray vs triangles, Moeller-Trumbore, barycentric UV) for
convex/primitive colliders, with the screen-pixel pick as fallback when the
mesh is not readable. The fast non-convex path is unchanged.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Reveal the player when geometry sits between the orbit camera and the
character: a screen-space circular mask in a URP/Lit clone (HeroCutoutLit)
clips occluders in front of the player, depth-gated so only geometry nearer
than the hero is cut. The cutout lives outside UnityPerMaterial (SRP batcher
intact) and is a no-op when disarmed, so occluders look identical to URP/Lit.
A runtime driver (HeroCutout.cs on Main/GameManager) feeds the player's
screen position, radius, feather and depth into global shader properties and
only arms while the third-person orbit vcam is live; it disarms in
first-person, free-cam and the pause menu. Applied to the lobby walls via a
dedicated occluder material (floor left solid). Shipped but shelved for now.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Hold Shift to momentarily enable the pipette (restores prior state on
release) and Shift+Scroll to resize the brush. The orbit zoom ignores the
wheel while Shift is held so it does not zoom and resize at once. The HUD
brush-size slider follows live via a BrushSizeChanged event, and the brush
preview now stays visible while Shift is held (drawn before the pipette
early-return) so the new size is visible while scrubbing.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
NetworkBootstrap now destroys itself outside the editor. Camera FOV/background
tweaks in Main, lobby light intensity, and paint HUD panel font/color polish.
SetPaused no longer Exits the active mode, so the First-Person/Free-Cam rig
keeps its CinemachineCamera priority and the view stays frozen in place instead
of blending back to the orbit camera. Pausing now only disables each rig's
per-frame look input (dropping the controller's enabled flag) and parks the
orbit rig in Disabled to free the cursor; resuming re-enters the same mode whose
snap-to-current-view is a no-op, so there is no jump on close either.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Main scene gets a full-rect Background image (Chameleon Background 1) in
the menu UI and re-enables a UI component. Lobby scene receives extensive
authoring (sky, lighting, props). New Lobby Sky and White Glow materials;
Yellow Glow emission toned down from ~89 to 64.
Two player-controller additions:
NoClimb: colliders tagged "NoClimb" are now invisible to wall-climb and
ceiling-grab. The grab detector (OnControllerColliderHit) and all three
climb probes (ProbeWall, ProbeCeiling, WallInFront) skip them, so such a
surface still blocks the capsule physically but can never be grabbed,
re-probed onto, or treated as a ceiling. WalkableGroundBelow is left
untouched (it only accepts walkable ground). The tag is read from a
serialized field (default "NoClimb"); an empty tag disables the check.
Rescue: a kill-plane fail-safe. The owner captures its spawn point on the
first Update frame (after OnNetworkSpawn's ring offset has landed) and,
if it falls below rescueMinY (-100), teleports back there with all
momentum cleared, toggling the CharacterController around the write like
PlaceOnSpawnRing. Owner-only; the authoritative NetworkTransform
replicates the reset. Runs ahead of the control gate so it rescues in any
mode.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The debounced auto-save compared painter values against the on-disk
baseline, so after any slider/toggle change the values stayed different
every frame and the 0.5s timer was pushed forward endlessly, never
firing. Saving only happened via the direct drag-end path. Compare
against the previous-frame snapshot instead so the timer fires once the
values settle. Also pin hudSaveId to a stable key so the layout is
shared across scenes instead of split per-scene.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
In Main (no active session) the context-sensitive button now reads "Exit"
and quits the application (stops play mode in the editor), so the player
can leave the build without Alt+F4. Connected behaviour is unchanged:
"Disconnect" leaves the session, "Back to Lobby" ends the match for the
map authority. The button is now interactable whenever idle.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
HUD panel positions and brush values were written only from
OnApplicationQuit/OnDestroy, which is not guaranteed in a build (Alt-F4,
process kill, crash), so layout work was lost. DraggablePanel now exposes
an IEndDragHandler callback that saves the layout the moment a panel is
dropped. Brush-value changes are detected against a disk baseline in
LateUpdate and flushed after a 0.5s debounce so per-frame slider drags
do not write every frame. The teardown save path is removed.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
MouseSensitivity is a static, PlayerPrefs-backed multiplier (default 1,
clamped 0.1-4.0) that scales the mouse-look delta in the FreeCam,
FirstPerson and ThirdPerson controllers. Writes flush immediately so the
value survives a hard build exit. MouseSensitivitySlider binds the
Settings-panel slider to it (range read from the inspector) and mirrors
the value into its label, syncing on panel open via OnEnable.
Main.unity now contains the session-manager canvas directly (the
Online Session Manager prefab was unpacked) and wires the slider binder.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add a Resources material that sets all three PBR shader_feature keywords so
core build stripping retains the URP/Lit variants the runtime mesh painter
enables on the fly. Fixes metallic/smoothness/occlusion rendering as constant
in player builds.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Panel shows in Main while disconnected, auto-hides once a connection is
established, and toggles open/closed with Escape in the Lobby or on a Map.
- Escape now pauses gameplay (active mode exits, cursor freed, paint and
movement disabled) instead of ending the match directly; closing restores
the prior mode via SwitchTo.
- Disconnect button is context-aware: leaves the session in the Lobby, acts
as "Back to Lobby" (EndMatch) for the match authority on a Map, and stays a
plain disconnect for non-authority clients on a Map.
- Removed the Escape->EndMatch handler from MatchManager; match end now routes
through RequestEndMatch with the same authority routing.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
ReleaseToNoPlayer tore down the mode cycle but never raised ModeChanged,
so the paint HUD - which only hides via that event - stayed open after a
shutdown that happened while PaintMode was active. Invoke ModeChanged(null)
after the teardown so any mode-following UI hides.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Players can now host and join over the internet without port-forwarding
via Unity Gaming Services Relay + Lobby, exposed through the unified
Multiplayer Services SDK (Sessions API).
- OnlineSessionManager: anonymous auth, CreateSessionAsync (public,
relay-backed), QuerySessionsAsync server browser, JoinSessionByIdAsync,
and session leave wired into the existing net-stop teardown.
- Per-install auth profile derived from the project path
(mirror_<sha1(dataPath)>) so junction-mirror clones get distinct UGS
player ids, avoiding the same-machine 409 LobbyConflict on join.
- Install com.unity.services.multiplayer 2.2.4 (pulls Relay/Lobby/
Authentication/Core) and attach OnlineSessionManager to the
NetworkManager in Main.
Loopback dev flow (NetworkBootstrap H/C/J/K) is left untouched; the
online path is additive. MatchManager and gameplay are unchanged since
Sessions starts the host/client through NGO as before.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Host keeps authority as before; a dedicated server (no host player) hands
start/end authority to the first connecting client and reassigns it if that
client leaves. The Start Match trigger now polls a server-side physics overlap
instead of relying on OnTriggerEnter, so it fires for the authority client's
remote avatar (moved by NetworkTransform, no CharacterController.Move on the
server) as well as the host's owned avatar. Esc-to-end routes a named message
from the authority client to the server when that client is not the host.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Persist each owner's paint snapshot on the MatchManager across the
despawn/respawn that happens on a lobby<->map scene switch, then reload
and rebroadcast it onto the freshly spawned avatar so painted meshes
survive the transition. Cleared on disconnect/shutdown to avoid stale
carry across sessions.
Isolate concurrent snapshot transfers by id so a respawning replica's
late-join request and the carry broadcast no longer share one reassembly
buffer (which decoded a mixed deflate blob -> "Corrupted data"), and drop
transfers with an older cutoff so a blank late-join cannot overwrite the
carried image.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The HUD save key was derived from the active scene name, which now changes
across the additive lobby/map loads (Main -> Lobby -> Test Map). Save and
restore landed in different files, so settings appeared lost. Derive the key
from the HUD's own (stable, persistent) scene instead.
Brush settings also live on the avatar's painter, which is despawned and
respawned on every scene switch, resetting them to prefab defaults. Mirror
the brush values into an in-memory snapshot on the persistent HUD and seed the
freshly bound painter from it, so in-session edits carry across the respawn.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Mid-match joiners now spawn a dedicated empty Spectator prefab (NetworkObject
+ SpectatorSetup only) instead of a fully stripped Player prefab. The FreeCam
rig is a standalone scene object in Main, so the spectator needs no avatar body,
renderer, collider, or CharacterController -- eliminating the leftover-body
artifact and the fragile per-peer strip path entirely.
- SpectatorSetup.cs (new): owner binds GameModeManager.BindSpectator (FreeCam-only)
- Spectator.prefab (new): empty avatar (Transform + NetworkObject + SpectatorSetup),
registered in DefaultNetworkPrefabs, wired on MatchManager in Main
- GameModeManager: add BindSpectator/BuildSpectatorModes; drop the spectator
branch from BindLocalPlayer; ReleaseToNoPlayer frees the cursor on teardown
- MatchManager: independent entropy-seeded RNG for fair hunter pick (was sharing
the global engine RNG and always landing on the same client); spawn the
spectator prefab for mid-match joiners; cursor-safe return-to-Main teardown
- FreeCamController: guard IgnorePlayerCollision against a disabled capsule
Also bundled (unrelated user asset/UI edits): PaintHudPanel layout tuning,
font atlas (arialroundedmtprocyr_bold SDF), forestpack_foliage material.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Host presses Esc to end a running match: swap Test Map back to Lobby and
respawn everyone as the neutral Player (server-gated, host-only).
- A client joining mid-match is spectator-held (no avatar) and spawns into
the lobby when the match ends.
- On shutdown/disconnect, unload every gameplay scene so only Main is left
loaded & active (NGO's Dispose does not unload additively-synced scenes).
- Generalize RespawnAll to a prefab selector + position so match-start
(role variants) and match-end (neutral Player) share one path.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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>