Back to blog
Your FirstPart 7 of 9

Wednesday, April 22, 2026

Your First Visibility

cover

Starting Point

The project now has:

  • a multiplayer portal
  • authentication
  • synchronized player entities
  • AI-driven NPCs

That is enough to create a real multiplayer world, but without visibility every player still risks receiving far too much data.

This post adds interest management.

In Altruist, visibility answers one question every tick:

which observers can currently see which entities?

1. Make Players Real Observers

Register the player as an observer after spawning the world object.

var player = new PlayerPrefab(_entityIds.Next(), "Pilot", transform)
{
    ClientId = clientId,
};

world.SpawnLightweight(player);
_visibilityTracker.Observe(player);

That last line is important:

  • Observe(player) explicitly registers the player as an observer
  • it does not send packets by itself
  • on the next tick, nearby entities are treated as newly visible

2. Send Spawn Packets on Visibility

Altruist does not invent your spawn packet shape. It only tells you that visibility changed.

Create a handler:

using Altruist;
using Altruist.Gaming;
using Altruist.Physx;

namespace SpaceGame.Visibility;

public struct DroneSpawnPacket : IPacketBase
{
    public uint MessageCode { get; set; }
    public uint EntityId { get; set; }
    public string Name { get; set; }
    public float[] Position { get; set; }

    public DroneSpawnPacket()
    {
        MessageCode = 1200;
        EntityId = 0;
        Name = "";
        Position = new float[3];
    }
}

[CollisionHandler]
public sealed class VisibilityPackets
{
    private readonly IAltruistRouter _router;

    public VisibilityPackets(IAltruistRouter router)
    {
        _router = router;
    }

    [CollisionEvent(typeof(EntityVisible))]
    public void OnPlayerSeesDrone(EntityVisible _, PlayerPrefab observer, DroneEntity drone)
    {
        _router.Client.SendAsync(observer.ClientId, new DroneSpawnPacket
        {
            EntityId = drone.VirtualId,
            Name = drone.Name,
            Position = new[]
            {
                drone.Transform.Position.X,
                drone.Transform.Position.Y,
                drone.Transform.Position.Z
            }
        });
    }

    [CollisionEvent(typeof(EntityInvisible))]
    public void OnPlayerLosesDrone(EntityInvisible _, PlayerPrefab observer, DroneEntity drone)
    {
        _router.Client.SendAsync(observer.ClientId, new EntityRemovePacket
        {
            EntityId = drone.VirtualId
        });
    }
}

That gives you the most important split in the whole system:

  • visibility decides who should know about the entity
  • your packet layer decides what the client needs to render it

3. Keep Sync Separate

Visibility is for first appearance and disappearance.

Synchronization is for changes after that.

The intended client flow is:

  1. visibility fires EntityVisible
  2. your server sends a full snapshot or spawn packet
  3. sync sends later position / state patches
  4. visibility fires EntityInvisible
  5. your server sends a remove packet

Do not try to create client entities from sync alone.

4. Clean Disconnects

When a player leaves the world, remove them as an observer before destroying the entity:

public override async Task OnDisconnectedAsync(string clientId, Exception? exception)
{
    var player = GetPlayer(clientId);
    if (player != null)
        _visibilityTracker.RemoveObserver(player);

    await base.OnDisconnectedAsync(clientId, exception);
}

That gives the visibility system one last chance to emit invisible transitions for everything the player was still tracking.

5. Why This Completes the First Arc

With visibility in place, the first series arc becomes coherent:

  • the first portal post gave you real player entities and sync
  • authentication gave those connections identity and protection
  • vault and autosave made player data durable
  • the regen step made player state feel alive
  • AI made the world active
  • visibility makes the world efficient and observer-aware

That is a strong baseline for a real game server.

Where to Go Next

From here, the natural next steps are:

Continue the Series

Your First