Replace the fixed additive brush-size step with a fraction of the current
size, so small brushes resize a texel at a time (fine control) while large
ones jump in bigger increments. Always moves at least one texel per notch.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace the symmetric sine with a raised cosine raised to
_XRayPulseShape: a long near-zero plateau, a slow rise and a short
full peak. Both shader halves use the identical formula to stay in
phase.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
While a match is in the resolution sub-phase, hiders that have not
been found hold their current pose and position: the pose graph is
frozen (mixer speed 0, current frame re-asserted) and owner input is
disabled while gravity/grounding keep them planted.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The pipette now reads the height (occlusion) value from the own
paint mesh and enables the height toggle; picking anything else
disables the toggle while leaving value and alpha untouched.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Brush radius now derives from the mesh-wide average UV density instead of
the hit triangle's own density. Per-triangle density varied up to ~7.6x
across the mesh (spiking at UV seams and stretched islands), so the same
brushSize painted a visibly larger patch on seam triangles than on
evenly-mapped ones. A single global scale keeps the brush the same physical
size everywhere; the stamp remains a 3D box, so seams are still bridged.
Also decouple the hover preview opacity from the brush albedo alpha: the
preview now stays fully opaque and is scaled only by the Preview Opacity
property, since the ring is a size/shape indicator, not a paint-strength
preview.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A slab that spans a concave silhouette (e.g. a table side = two legs
plus a top rail, all coplanar and thin) produced one convex hull that
filled the open gap between the legs, so a player standing between them
counted as embedded. Add a refinement pass: each slab whose in-plane
coverage grid shows its convex-hull footprint is less than a
configurable fraction (hullFillThreshold, default 0.6) covered by real
surface is recursively halved along its larger PCA axis until every
piece hugs actual geometry. Solid boards (the shelf) are ~fully covered
and pass through untouched. Verified over a 9600-point grid: zero
bridging (no collider interior lies >6cm from the source surface),
shelf output unchanged.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Generate multiple convex collider proxies from a mesh by growing
triangles into thin slabs (PCA minor-axis thickness gate) and hulling
each slab, so shelves and open compartments stay hollow while boards
count as embedding surfaces. Baked meshes get readable, deterministic
asset names (scene/prefab path + hierarchy + collision index) instead
of GUIDs. ConcealmentMeter now also excludes a furniture's own concave
mesh collider when a ConcealmentColliderBaker sits in its parent,
symmetric to the existing ConcealmentVolume rule.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
SkinnedMeshRenderer.BakeMesh(mesh) bakes the renderer's world scale
into the vertices. SkinnedColliderBaker already accounts for this and
divides it back out, but ConcealmentMeter did not: it lifted the
already-scaled verts to world via localToWorldMatrix (scale again),
yielding scale-squared voxel positions. This threw off the embedded
broadphase box, the EmbeddedPercent computation (which now drives the
X-ray reveal) and the debug gizmos whenever PlayerScaler != 1.
Rescale the baked mesh to unit scale right after baking (no-op at
scale 1), matching the SkinnedColliderBaker convention documented in
PlayerScaler, so world = localToWorldMatrix holds on unit geometry.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add a second, additive HDR emission color (_XRayColor) that reveals a
player through walls as a pulsing energy glow, driven during matches:
- Shader: XRayOverlay pass (ZTest Greater, additive) for occluded parts
plus a matching additive term in the forward fragment for visible
parts, so the glow shows everywhere and never double-blends. Exposes
_XRayColor, _XRayPulseSpeed and _XRayPulseStrength for a breathing pulse.
- Render it after opaques via a RenderObjects feature (custom XRayReveal
light-mode tag) on both PC and Mobile renderers, so the through-wall
test is depth-sort independent and no longer drops out at some camera
angles/distances.
- MeshPainter drives _XRayColor alpha through a framerate-independent
fade (xrayFadeDuration) rather than a hard toggle.
- Reveal is driven per avatar on every client: all remaining un-found
hiders glow during the Resolution phase, and a hider whose
ConcealmentMeter EmbeddedPercent exceeds MatchManager.embeddedRevealPercent
glows via an owner-authoritative NetworkVariable.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Set occlusion to 0.5 (the new neutral for painted height) so Reset
HUD/Values lands on neutral, and adjust the default brush colour,
metallic/smoothness, active brush and alignment.
The pipette now samples only what is painted (albedo, metallic,
smoothness, emission), not how strongly - the albedo alpha,
metallic/smoothness opacity and emission alpha are preserved instead
of being forced to 1 in every pick path and in FinalizePick.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add the occlusion-shift, normal-intensity and parallax-intensity slider
instances to the HUD layout prefab under their stable widget names so the
binder wires them to the painter's material knobs.
Wire three material-backed look sliders (occlusion AO shift, relief normal
intensity, parallax intensity) through the paint HUD, its layout/state
persistence, the .paint slot format (bumped to v4), owner-authoritative
network sync and the runtime reset, mirroring the existing camo-knob path so
they save, restore, replicate and reset alongside the brush values.
Change the occlusion AO mapping from a division threshold to a linear shift:
AO fades from 1 down to (1 - shift) at the deepest painted hole, so a 0 shift
no longer leaves black spots. Enable _NORMALMAP alongside _PARALLAXMAP so URP
transforms the painted relief normal into world space (otherwise it discards
normalTS and the relief is invisible), and pin the added keyword in the build
keep-alive material.
Stop the pipette from picking the occlusion/height value and turn its toggle
off after a pick. Default the knobs to 0.2 shift / 1.4 normal / 1.0 parallax
across the shader, painter, network and HUD fallbacks.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Reuse the existing occlusion paint channel (no new map, MAP_COUNT stays 3):
0.5 is neutral, above 0.5 raises a hill, below 0.5 sinks a hole and also
fades ambient occlusion (saturate(2*occ)). Bind the MSO map to _ParallaxMap,
enable _PARALLAXMAP, derive a relief normal from the height gradient, and
reseed unpainted occlusion to 0.5 so untouched surfaces stay visually neutral.
Pin the _PARALLAXMAP runtime variant in the build keep-alive material.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Load now applies the selected slot immediately instead of going through a
confirmation dialog. Save keeps its OK button enabled even when the name
already exists (the warning text stays, purely informational) and writes
through with overwrite, so re-saving under the same name replaces the slot.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Remove the second HeroCutout from the Main Camera (the GameManager host is the
intended, single driver), set the GameManager cutout radius/feather, and set
hunterCount to 0.
The painter clones Player.mat (Custom/PlayerCamoLit) at runtime and enables
_METALLICSPECGLOSSMAP, _EMISSION and _OCCLUSIONMAP, all shader_feature_local.
That keyword combination existed on no PlayerCamoLit material at build time,
so Unity stripped the variant and the build fell back to the base variant -
metallic/smoothness/emission/occlusion ignored, giving the flat look. The
keep-alive material that should pin this variant pointed at URP/Lit instead
of PlayerCamoLit, so it kept the wrong shader alive. Swap its shader GUID to
PlayerCamoLit; the three keep-alive keywords already match what the painter
sets, so the needed variant is now pinned into every build.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The cutout cone's radius and feather are now multiplied by the hero target's
uniform world scale (lossyScale.x, the same body-scale source the orbit input
uses). The authored values are the scale-1 sizes, so the see-through hole and
its soft edge keep the same on-screen proportion for a small hider or a large
hunter, and a mid-match conversion tracks live since lossyScale is read every
frame.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Destroy every GameObject tagged "PreMatch" locally when the match enters
the Seek sub-phase (hooked in ApplyPhaseLocal, runs on every machine; the
objects are plain scene geometry with no NetworkObject, so each client
cleans up its own copies). Block hunters from starting a wall- or
ceiling-climb during the Hide sub-phase via a WallClimbBlockedNow() gate
in UpdateWallClimbState; an in-progress climb is exited cleanly.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Two paint-mode pan refinements:
- The max-distance clamp is back to body-relative only (CurrentScale, no zoom
factor), so the world-space pan limit is identical at every zoom level;
only the sensitivity stays zoom-scaled (finer when zoomed in).
- Don't zero the pan offset in OnDisable. The only thing that disables this
component is FreeCamMode taking over, and the freecam snaps to the live
(still panned) view. Zeroing here jumped the orbit camera the brain blends
FROM to center while the freecam sat at the panned pose - the visible
"snap to 0, then translate to the offset" on the paint->free-cam switch.
Leaving the offset frozen keeps blend-from == blend-to (both panned) for a
seamless handoff; the pan glides home on its own once the orbit input next
runs in a non-paint mode.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The save-dialog name field is a focused TMP_InputField, but global discrete
hotkeys kept reading the keyboard every frame, so typing a name like
"Checkers" cycled the pose (C) and shut the server down (K). Add a shared
TypingGuard.IsTyping check (focused TMP/legacy InputField) and short-circuit
the global handlers on it: pose cycle, network dev keys, mode switch, and
paint undo/redo. Movement was already gated out via ControlEnabled.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The middle-mouse pan now persists when the button is released and only glides back to center on leaving paint mode. Pan sensitivity and max distance scale with the orbit zoom distance, so panning stays consistent on screen at any zoom and unchanged at the default zoom.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add the BrushesSquare texture, replace the duplicated brush slots on the
player with it, show a brush preview in the paint HUD, and surface the
spawn-area tags and conversion reveal delay in the inspector.
Undo is implemented (checkpoint-based, replicated); a separate full reset
is intentionally unnecessary since undo covers it. Drop the stale open item
and polish note.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Move the implemented systems (netcode, hunter role, phase/timer match
loop, win conditions, both match modes, spawn areas, shooting,
role-based visibility, spectator freecam, networked paint) out of the
"missing" section, document the multiplayer and match-loop subsystems,
and trim the open items to what's actually left.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Save and restore the emission intensity and the three camo knobs
(half-Lambert, half-Lambert brightness, specular dampen) with a named
painting. The .paint format moves to v3 with a small scalar block
between the header and the pixel maps; v2 paintings still load with
their painter values left untouched. Loading re-syncs the emission and
camo sliders, and peers pick the values up for free through the
existing owner-authoritative paint NetworkVariables.
Add a HUD reset (Lib_ResetHud button plus context-menu fallback) that
behaves as if the saved hud-layout file were deleted: it removes the
file, restores panels to their authored prefab positions, and resets
the brush values to the painter/material defaults captured before any
carry/restore runs.
Remove the alpha bar from HsvColorPicker so alpha is driven by
standalone Slider_AlbedoAlpha / Slider_EmissionAlpha bindings; the
picker now writes RGB only and the sliders write alpha only, with no
feedback between them. Add a generic SliderCurve component that reshapes
a slider's handle position into a non-linear value (with an inverse for
setting values back), and make the slider wiring curve-aware.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Fix the in-place Conversion respawn snapping to the origin (0,0,0),
which surfaced when a hider was tagged mid wall-climb. The owner holds
authority over the NetworkTransform, so the server's Instantiate
position is ignored on the owner. Deliver the tagged world position
through a replicated _spawnOverride the server writes in OnNetworkSpawn,
and have the owner re-assert it before PlayerCharacterController's first
Update snapshots the kill-plane rescue target. This replaces the fragile
SpawnedMidSeek phase check.
Also hide a freshly converted hunter on other clients for a short
settle window (conversionRevealDelay) so nobody sees it pop in or
interpolate from the spawn frame; the shot hider is already despawned,
then the hunter cleanly materializes in its place.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Introduce a second match mode alongside Classic. In Conversion mode
hiders are invisible to each other (and to hunters during Hide), and a
hunter hit converts the hider into a full hunter in place instead of
sending them to the spectator freecam. The mode is replicated to all
clients through the existing phase named-message byte; visibility is
computed locally per client per mode.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Drive the match through three server-authoritative phases (hide, seek,
resolution) with per-phase durations, broadcasting the active phase and
countdown to clients over the existing named-message infrastructure so
MatchManager stays a plain MonoBehaviour and Main keeps zero in-scene
network objects.
During hide, hider meshes are culled on hunter clients only (each client
computes visibility locally from role, phase and found state). During
seek, hunters shoot a single raycast from the first-person camera centre
(left click, cooldown-gated); a validated hit marks the hider found,
which hides its mesh for everyone and binds the owner to spectator
freecam. Seek ends early once every hider is found, then resolution runs
before returning to the lobby.
Force AlwaysAnimate on remote replicas so the animator keeps writing bone
transforms while the body is hidden or off-screen. The Paintman animator
is authored CullUpdateTransforms, so a culled renderer froze the pose on
the rest pose and the skinned collider baked that frozen pose, landing
replicated paint stamps on the wrong body location and leaving the
painting desynced after hiders were shown again.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Choose the spawn area by player role: Hunter avatars look for a
HunterSpawnArea-tagged collider, Hiders for HiderSpawnArea. When the
role-specific area is missing, fall back to the shared SpawnArea tag, and
when that is absent too, keep the existing client-id ring placement.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Attach the HeroCutout component to the Main Camera with dialed-in cone values (radius 0.42, feather/depthFeather 0.15) and lower the deoccluder MinimumDistanceFromTarget to 0.05.
PickFromForeignMaterial read rend.sharedMaterial, which is always slot 0, so any renderer with several materials (one per submesh) always sampled the first material's values. Resolve the submesh from the hit triangle (collider triangleIndex, or the recovered render-mesh triangle) and read sharedMaterials[submesh] (clamped) instead. Single-material renderers stay on slot 0, byte-identical to before.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>