Skip to content

Architecture: Networking schema catalog

This page is the living catalog of every planned network message (remote), including payload shape, validation rules, budgets, and error codes.

Golden path example (Phase 1)

This example shows how a remote is defined, validated, and handled. Use this as a template for all new remotes.

1. Type Definition (Shared)

// packages/shared-types/src/index.ts
export interface DoActionPayload {
  actionId: string;
  timestamp: number;
}

2. Validation Schema (Net)

// packages/net/src/validation.ts
import { t } from "@rbxts/t";
import { validate, bounded } from "@broblox/net";

const doActionSchema = t.strictInterface({
  actionId: bounded.string(50, 1),
  timestamp: t.number,
});

export function validateDoActionPayload(value: unknown): Result<DoActionPayload> {
  return validate(doActionSchema, value);
}

3. Remote Registry (Net)

// packages/net/src/remotes.ts
export const REMOTES = {
  DoAction: {
    name: "Intent_DoAction",
    rateLimit: { windowMs: 1000, maxRequests: 5 },
  },
} as const;

Server handler

// games/test-park/src/server/services/ActionService.ts
import { REMOTES, validateDoActionPayload, ErrorCode, Result } from "@broblox/net";
import { isFlagEnabled } from "@broblox/config-featureflags";

const playerState = new Map<number, number>();

export function handleDoAction(player: Player, rawPayload: unknown): Result<{ newCount: number }> {
  // 1. Check kill-switch
  if (!isFlagEnabled("doAction.enabled")) {
    return { ok: false, code: ErrorCode.FeatureDisabled };
  }

  // 2. Validate payload
  const validation = validateDoActionPayload(rawPayload);
  if (!validation.ok) {
    // Log violation for security scoring
    logSecurityEvent("invalid_payload", { player, remote: "Intent_DoAction" });
    return { ok: false, code: ErrorCode.InvalidPayload };
  }

  const payload = validation.value;

  // 3. Bounds check (defense in depth)
  if (payload.timestamp < 0 || payload.timestamp > os.clock() * 1000 + 5000) {
    return { ok: false, code: ErrorCode.InvalidPayload };
  }

  // 4. Apply server-authoritative state change
  const currentCount = playerState.get(player.UserId) ?? 0;
  const newCount = currentCount + 1;
  playerState.set(player.UserId, newCount);

  // 5. Return success
  return { ok: true, value: { newCount } };
}

Client caller

// games/test-park/src/client/controllers/ActionController.ts
import { RemoteController } from "./RemoteController";
import { type Result, type DoActionPayload } from "@broblox/net";

const payload: DoActionPayload = {
  actionId: "intent_ping",
  timestamp: os.clock() * 1000,
};

const result = RemoteController.Remotes.DoAction.InvokeServer(payload) as Result<{
  actionId: string;
  processedAt: number;
}>;

if (!result.ok) {
  warn(`Action failed: ${result.code}`);
}

Key patterns demonstrated

  1. Single source of truth: Remote is defined once in registry
  2. Schema validation: Server validates before processing
  3. Rate limiting: Handled by middleware (not shown, but applied)
  4. Kill-switch: Feature flag checked before any logic
  5. Stable errors: Returns ErrorCode, never throws
  6. Server authority: State change is server-side only

Global rules (apply to all remotes)

  • All inbound payloads are schema-validated on the server.
  • All inbound payloads are bounded (max sizes, max arrays, clamped numbers).
  • All inbound remotes are rate-limited (per player + per endpoint).
  • All remotes return stable error codes; no throws across the boundary.
  • The server accepts intent, never outcomes.

Common envelope conventions

Identifiers

  • requestId: string, required for any request that can be retried
  • matchId: string, required for match-scoped actions
  • seq: integer, monotonic per stream (input, fire requests)
  • clientTimeMs: integer, for lag-comp proposals (bounded window)

Error response

For request/response calls (or server acknowledgements):

  • ok: boolean
  • code: number (stable)
  • retryAfterMs?: number
  • message?: string (dev only; never leak internal server details)

Rate limit policy (baseline)

  • Hard real-time (input stream): fixed frequency (no burst); drop excess.
  • Action (fire/ability): token bucket with cooldown semantics.
  • Admin: very low rate + RBAC enforced.

Endpoint catalog (v1 target)

Notation:

  • Direction: C→S client to server, S→C server to client
  • Budget: per player unless stated

Session & compatibility

  1. Net.Handshake (C→S, request/response)
  2. Purpose: protocol compatibility and server capabilities
  3. Payload:
    • protocolVersion: number
    • buildId: string
    • deviceClass: "kbm"|"gamepad"|"touch"
  4. Server validates:
    • protocol range check
    • device enum
  5. Budget: 1 per join
  6. Errors:

    • 3001 PROTOCOL_INCOMPATIBLE
  7. Config.GetSnapshot (C→S, request/response)

  8. Purpose: deliver a validated config snapshot (non-secret)
  9. Payload:
    • knownConfigVersion?: string
  10. Budget: 1 per join + manual refresh (cooldown 30s)

Real-time match traffic

  1. Match.Input (C→S, event)
  2. Purpose: batched input command stream
  3. Payload (batched):
    • matchId: string
    • seqStart: number
    • commands: InputCommand[]
  4. InputCommand:
    • seq: number
    • dtMs: number (clamped)
    • moveX: number, moveY: number
    • lookYaw: number, lookPitch: number (clamped)
    • jump: boolean, sprint: boolean, crouch: boolean
  5. Budget:
    • 20 Hz recommended (device dependent)
    • max commands per packet (e.g. 4)
  6. Abuse handling:

    • drop packets beyond budget
    • increment security score if persistent
  7. Match.Snapshot (S→C, event)

  8. Purpose: authoritative state snapshots
  9. Payload:
    • matchId: string
    • serverTick: number
    • entities: EntityState[] (bounded)
  10. Notes:
    • clients interpolate; corrections are smoothed

Combat & abilities

  1. Combat.Fire (C→S, request/ack)
  2. Purpose: propose firing intent (server validates hit)
  3. Payload:
    • matchId: string
    • seq: number
    • weaponId: string
    • origin: Vector3 (clamped to player muzzle bounds)
    • direction: Vector3 (normalized, clamped)
    • clientTimeMs: number
  4. Budget:
    • aligned to weapon fire rate; enforce on server
  5. Server validates:
    • weapon equipped
    • ammo + cooldown
    • origin plausibility
    • optional lag-comp window
  6. Errors:

    • 2101 COOLDOWN
    • 2102 NO_AMMO
    • 2103 INVALID_STATE
  7. Combat.ActivateAbility (C→S, request/ack)

  8. Purpose: ability activation intent
  9. Payload:
    • abilityId: string
    • target?: TargetRef (bounded)
  10. Budget:
    • per-ability

Matchmaking

  1. Queue.Join (C→S, request/response)
  2. Payload:
    • mode: string (e.g. ranked_2v2)
    • partyId?: string
    • regionPreference?: string
    • deviceClass: "kbm"|"gamepad"|"touch"
  3. Budget: low (cooldown 2s)

  4. Queue.Leave (C→S, request/response)

  5. Budget: low

  6. Queue.MatchFound (S→C, event)

  7. Payload:
    • ticketId: string
    • teleportData: object (non-secret)

Moderation

  1. Report.Player (C→S, request/response)

  2. Payload:

  3. reportedUserId: number
  4. reasonCode: string
  5. freeform?: string (length limited)
  6. Budget: strict (cooldown 30s)

  7. Admin.Command (C→S, request/response)

  8. Purpose: in-experience admin actions for authorized moderators

  9. Must be RBAC gated server-side
  10. Budget: very strict

Telemetry

  1. Telemetry.EventBatch (C→S, event)

  2. Purpose: client telemetry in batches

  3. Budget: strict; sample heavily
  4. Notes:
  5. must never include secrets or personal data

Error code ranges (recommendation)

  • 1xxx: validation (schema, bounds)
  • 2xxx: gameplay (cooldowns, state)
  • 3xxx: compatibility (protocol)
  • 4xxx: authz/admin
  • 5xxx: server busy/transient

Change process

  • Any change to this catalog requires an ADR if it breaks compatibility.
  • Every new endpoint must specify:
  • payload schema
  • budget
  • abuse handling
  • observability event(s)

Game-level remotes (implemented)

The following remotes are defined per-game and follow the same golden-path pattern above. They are organized by feature area.

Shared metagame remotes (both games)

These remotes exist in both test-park and obby with identical or near-identical payloads.

Client→Server Functions:

Remote Payload → Response Rate Limit Purpose
GetFullPlayerData voidFullPlayerDataPayload 2/2000ms Fetch full player state after handshake
ClaimDailyReward voidDailyRewardDay \| undefined 1/2000ms Claim daily login reward
RedeemCode RedeemCodeRequestCodeRedeemResultPayload 1/3000ms Redeem a promo code
HatchEgg HatchEggRequestHatchResult[] 3/1000ms Gacha pull
CheckGamePass CheckGamePassRequestGamePassOwnershipPayload 5/2000ms Verify game pass ownership

Client→Server Events:

Remote Payload Rate Limit Purpose
EquipPet EquipPetRequest 3/500ms Equip a pet
UnequipPet UnequipPetRequest 3/500ms Unequip a pet
EquipCosmetic EquipCosmeticRequest 3/500ms Equip a cosmetic
UnequipCosmetic UnequipCosmeticRequest 3/500ms Unequip a cosmetic
ClaimBattlePassReward ClaimBattlePassRewardRequest 3/1000ms Claim a battle pass tier reward
BuyProduct BuyProductRequest 2/2000ms Initiate developer product purchase

Server→Client Events:

Remote Payload Purpose
PlayerDataSync PlayerDataSyncPayload Broadcast full player data after mutations
LevelUp LevelUpPayload Notify client of level-up
PrestigeUnlocked PrestigeUnlockedPayload Notify client of prestige unlock
QuestCompleted QuestCompletedPayload Notify client of quest completion
AchievementCompleted AchievementCompletedPayload Notify client of achievement
DailyRewardClaimed DailyRewardClaimedPayload Confirm daily reward claim
EventStarted EventActivePayload Notify client that a timed event started
EventEnded EventActivePayload Notify client that a timed event ended

Test-park-specific remotes

Client→Server Events:

Remote Payload Rate Limit Purpose
UseAbility UseAbilityRequest 10/200ms Fire combat ability
TestPark_Teleport TestParkTeleportRequest 3/1000ms Teleport to test zone

Client→Server Functions:

Remote Payload → Response Rate Limit Purpose
ReportHit ReportHitRequestHitResultPayload 10/200ms Report hit for server validation

Server→Client Events:

Remote Payload Purpose
Notification ServerNotification General server notification
TestPark_ActionResult TestParkActionResultPayload Action test result feedback toast

Obby-specific remotes

Client→Server Events:

Remote Payload Rate Limit Purpose
RequestRespawn RespawnRequestPayload 2/1000ms Request respawn at checkpoint
RequestLeaderboard void 1/2000ms Request leaderboard refresh
RequestTraining TrainingRequestPayload 2/2000ms Start attribute training
RequestEnterWorld EnterWorldRequestPayload 2/2000ms Enter a world zone
RequestExitWorld void 2/2000ms Exit current world
EquipGear EquipGearRequest 3/500ms Equip a gear item
UnequipGear UnequipGearRequest 3/500ms Unequip a gear item

Client→Server Functions:

Remote Payload → Response Rate Limit Purpose
BuyGear BuyGearRequestBuyGearResultPayload 2/1000ms Purchase a gear item

Server→Client Events:

Remote Payload Purpose
CheckpointReached CheckpointReachedEvent Notify checkpoint progress
StageCompleted StageCompletedEvent Notify stage completion
LeaderboardUpdate LeaderboardUpdatePayload Push leaderboard data
LeaderboardRefreshStatus LeaderboardRefreshStatusPayload Leaderboard refresh cooldown status
AttributeSync AttributeSyncPayload Sync player RPG attributes
TrainingComplete TrainingCompletePayload Training session result
StaminaSync StaminaSyncPayload Sync stamina state
WorldChanged WorldChangedPayload Notify world zone change
EquipmentSync EquipmentSyncPayload Sync equipped gear
HazardToggle HazardTogglePayload Hazard enabled/disabled state
HazardDamage HazardDamagePayload Hazard damage feedback
ObstacleUpdate ObstacleUpdatePayload Obstacle position/state update
ObstacleToggle ObstacleTogglePayload Obstacle enabled/disabled state