New

dot-spectate

Who is watching whom, what the server lets them see, and where the camera is — observer modes, a force-camera policy, the death-cam chain and a broadcast delay.

dot-spectate decides who a spectator may watch and computes where their camera should be. It is a competitive-integrity system before it is a camera: the camera is four lines of vector arithmetic, and the rest exists because spectator views get used to cheat.

Requires dot-core.

Usage

var spectate := DotSpectatorManager.new()
spectate.participants_fn = roster.keys
spectate.team_fn = match_node.team_of
spectate.alive_fn = world.is_alive
spectate.pose_fn = world.eye_transform_of     # eyes, not the body's origin
spectate.setup()
add_child(spectate)

spectate.next_target(me)
spectate.set_mode(me, DotSpectatorView.Mode.CHASE)

Then, separately, once a tick and once a frame:

spectate.advance(tick)
camera.global_transform = spectate.camera_of(me)

It computes a transform and touches no camera, which is what lets one manager serve a 3D rig, a 2D one (camera_2d_of gives a position and an angle) and a headless suite.

A client draws its cycle list from targets_for(me) and greys entries out with may_watch(me, them), so it never shows a name the server would refuse.

The two server-side rules

Force camera all, own team only, or nobody — with the numbering operators already type from memory. “Own team only” also forces first person, because a chase camera behind a team-mate sees round corners that team-mate cannot.
Broadcast delay The camera samples a tick in the past, so a spectator is not a live intelligence feed. Ninety seconds is the usual broadcast default; a competitive server’s is a few.

The death-camera chain

spectate.on_death("ada", where_they_fell, "bob", tick)

Death cam on the body, freeze cam on the killer, then a real target — three timers handing over to each other. The last hand-over is the one a game that writes this itself usually misses, leaving the player looking at their killer until they respawn.

A chase or death camera computed blind can end up inside a wall. Set camera_world (and camera_mask to the level’s collision layers) on any client that draws a 3D spectator camera, and both are pulled out of the level through dot-core’s DotCameraProbe: the chase camera toward the target’s eye, the death camera toward a point above where they fell, then re-aimed at the killer. If the level is between the death camera and the killer, it stands back from the body on the side away from the killer and looks at the body instead, decided once per death so a killer stepping through a doorway does not cut the camera between two places. There is one probe per viewer, kept until forget. Its easing exists only to stay out of a wall; it is not smoothing. Unset, the transform is exactly what it was, which is what a server or a 2D game wants.

Call it where the timers run — the authority. A mirroring client runs no timers, so on_death there is refused; the client is told the chain through the view’s wire form instead, which carries the death position so a death camera is not drawn from the world origin.