# Servidores autoritativos

## Visão geral

Decentraland executa scenes localmente na máquina de um jogador. Por padrão, os jogadores conseguem ver uns aos outros e interagir diretamente, mas cada um interage com o ambiente de forma independente. As mudanças no ambiente não são compartilhadas entre os jogadores por padrão.

Permitir que todos os jogadores vejam uma scene como tendo o mesmo conteúdo no mesmo estado é extremamente importante para que os jogadores interajam de maneiras mais significativas. Sem isso, se um jogador abrir uma porta e entrar em uma casa, os outros jogadores verão essa porta como ainda fechada, e o primeiro jogador parecerá atravessar diretamente a porta fechada para os outros jogadores.

Um **servidor autoritativo** é um processo de servidor sem interface que executa o código da sua scene, valida alterações de estado e transmite o resultado a todos os jogadores conectados. Em vez de confiar que cada cliente reporte suas próprias ações, o servidor atua como a única fonte da verdade. Isso o torna a abordagem recomendada para sincronizar scenes multiplayer.

Um servidor autoritativo é ideal sempre que a justiça for importante para a mecânica do jogo, pois você pode implementar validações anticheat elaboradas que rodam no lado do servidor. Você também pode armazenar chaves privadas e outras informações sensíveis no servidor, evitando precisar expô-las diretamente ao usuário.

Ter um servidor autoritativo também resolve um problema real: em uma configuração ponto a ponto, dois jogadores controlando algo como uma plataforma flutuante podem produzir resultados conflitantes. Cada cliente define a plataforma para uma altura diferente, e ninguém tem autoridade para decidir qual está correto. Um servidor autoritativo resolve cada alteração em um único lugar, de modo que todos os clientes convergem para o mesmo estado.

Também lhe dá um lugar para **persistir dados entre sessões**: rankings, progressão de jogador, conquistas desbloqueadas ou mudanças no ambiente, como portas abertas ou itens colocados. Quando os jogadores voltam, o mundo reflete o que aconteceu antes.

Decentraland hospeda e faz o deploy do servidor para você. Publicar sua scene pelo processo normal também publica o servidor sem interrupções, sem etapas extras nem necessidade de pagar por hospedagem.

## Configuração

### 1. Instale a versão do SDK auth-server

As APIs nativas do servidor autoritativo (`isServer`, `registerMessages`, `Storage`, `EnvVar`, etc.) estão disponíveis em uma branch separada do SDK. Execute os comandos a seguir para instalá-la no seu projeto em vez da branch padrão do SDK:

```bash
npm install @dcl/sdk@auth-server
npm install @dcl/js-runtime@auth-server
```

### 2. Configure scene.json

Opcionalmente, adicione o seguinte ao seu `scene.json` no nível raiz:

```json
{
  "logsPermissions": ["0xYourWalletAddress"]
}
```

Adicione `logsPermissions` para listar endereços de wallet que podem ver `console.log()` do servidor. Os usuários listados podem então ver os logs do servidor em produção executando o seguinte comando:

`npx sdk-commands sdk-server-logs`

### 3. Execute a preview

Use o comando padrão de preview, sem etapas extras necessárias. Ao usar a branch auth-server do SDK, a preview inicia automaticamente uma versão local do servidor autoritativo em segundo plano.

A sessão local do servidor não está conectada à de produção, então você pode testar livremente sem afetar os jogadores que estão na sua scene publicada.

## Branching de Servidor / Cliente

O código da scene na pasta `src` do seu projeto executa tanto no servidor quanto no cliente. Use a função `isServer()` para separar os caminhos de execução:

```typescript
import { isServer } from "@dcl/sdk/network"
import { initServer } from "./server/server"
import { initClient } from "./client/setup"
import { setupUi } from "./client/ui"

function main() {
  if (isServer()) {
    // Apenas servidor: lógica de jogo, validação, gerenciamento de estado
    initServer()
    return
  } else {
    // Apenas cliente: UI, tratamento de entrada, envio de mensagens
    initClient()
    setupUi()
  }
}
```

O servidor executa sua scene sem interface, sem renderização. Ele tem acesso verificado a todas as posições dos jogadores, wearables e outros dados por meio de `PlayerIdentityData` e é a única autoridade sobre o estado do jogo.

## Components sincronizados e validação

### Sincronizando Entities com todos os clientes

Use `syncEntity` para transmitir quaisquer alterações nos components indicados dessa entity:

```typescript
import { isServer, syncEntity } from "@dcl/sdk/network"

if (isServer()) {
  syncEntity(
    entity,
    [Transform.componentId, GameState.componentId],
    /* enumId */ 1
  )
}
```

A sintaxe é idêntica à usada pelo recurso [Serverless multiplayer](/creator/content-creator-pt/scenes-sdk7/networking/serverless-multiplayer.md) , o que torna trivial atualizar uma scene de usar essa arquitetura para o servidor autoritativo. Quando uma scene usa o servidor autoritativo, as atualizações de estado deixam de ser enviadas entre todos os jogadores; em vez disso, todas as atualizações de estado passam agora pelo servidor e são validadas por ele.

{% hint style="warning" %}
**📔 Nota**: Com o servidor autoritativo, o padrão ideal é fazer com que apenas o servidor chame `syncEntity`. Dessa forma, você não precisa se preocupar com a consistência do entity-id. Em vez disso, a entity é instanciada e compartilhada pelo servidor, e todos os clientes recebem a sincronização sobre essa instância. Sempre proteja isso com `isServer()`. Isso é diferente de [Serverless multiplayer](/creator/content-creator-pt/scenes-sdk7/networking/serverless-multiplayer.md), onde cada cliente chama `syncEntity` por conta própria.
{% endhint %}

### Validando alterações

Use `validateBeforeChange()` para restringir quaisquer atualizações de estado em um component específico de uma entity. Isso permite que você execute uma função de validação personalizada, e as alterações só serão bem-sucedidas quando o teste de validação for satisfeito.

Se a validação retornar o valor *true*, então a alteração é aceita e propagada para todos os jogadores. Se a validação retornar o valor *false*, então a alteração é rejeitada. Uma alteração rejeitada não será passada para outros jogadores e será revertida para o jogador que tentou realizá-la.

#### Validar valores

O caso mais simples é validar que o novo *valor* sendo gravado está dentro de determinados parâmetros. Por exemplo, aceite apenas alterações em um `Transform` quando a nova posição Y for maior que 0:

```typescript
import { engine, Transform } from "@dcl/sdk/ecs"
import { Vector3 } from "@dcl/sdk/math"
import { isServer } from "@dcl/sdk/network"

const entity = engine.addEntity()
Transform.create(entity, { position: Vector3.create(10, 2, 10) })

if (isServer()) {
  Transform.validateBeforeChange(entity, (value) => {
    // Rejeita qualquer atualização que colocaria a entity em Y = 0 ou abaixo
    return value.newValue.position.y > 0
  })
}
```

Como `validateBeforeChange()` só faz sentido no servidor, sempre proteja isso com `isServer()`. No cliente, a chamada não faz nada útil.

Você pode usar isso para impedir alterações que contrariem a lógica do seu jogo, como mecanismos anticheat.

#### Validar proximidade ao jogador

Você pode combinar `validateBeforeChange()` com posições de jogadores verificadas pelo servidor para verificar se um jogador está perto o suficiente de um objeto antes de permitir que ele interaja com isso. Por exemplo, quando um jogador tenta pegar um objeto alterando sua `Transform`, você pode rejeitar a alteração se o objeto estiver a mais de 5 metros do jogador:

```typescript
import { engine, Transform, PlayerIdentityData } from "@dcl/sdk/ecs"
import { Vector3 } from "@dcl/sdk/math"
import { isServer } from "@dcl/sdk/network"

const pickableEntity = engine.addEntity()
Transform.create(pickableEntity, { position: Vector3.create(8, 1, 8) })

if (isServer()) {
  Transform.validateBeforeChange(pickableEntity, (value) => {
    // Encontre o jogador que enviou esta alteração
    for (const [playerEntity, identity] of engine.getEntitiesWith(
      PlayerIdentityData
    )) {
      if (identity.address.toLowerCase() !== value.senderAddress.toLowerCase())
        continue

      const playerTransform = Transform.getOrNull(playerEntity)
      if (!playerTransform) return false

      // Obtenha a posição atual do objeto, antes da alteração
      const objectTransform = Transform.getOrNull(pickableEntity)
      if (!objectTransform) return false

      const distance = Vector3.distance(
        playerTransform.position,
        objectTransform.position
      )

      // Permita a alteração apenas se o jogador estiver a até 5 metros
      return distance <= 5
    }

    // Remetente não encontrado entre os jogadores conectados — rejeitar
    return false
  })
}
```

Esse padrão é útil como mecanismo anticheat: ele impede que jogadores alcancem através da scene para pegar objetos com os quais não deveriam conseguir interagir.

#### Permitir alterações apenas por admins

Você também pode validar com base em *quem* está enviando a alteração. Todo valor recebido inclui um campo `senderAddress` com o endereço da wallet do remetente. Use isso para permitir alterações apenas de certos jogadores. Por exemplo, para permitir que apenas admins da scene modifiquem um component `VideoPlayer` :

```typescript
import { engine, VideoPlayer } from "@dcl/sdk/ecs"
import { isServer } from "@dcl/sdk/network"
import { isPreview } from "@dcl/asset-packs/dist/admin-toolkit-ui/fetch-utils"
import { getSceneAdmins } from "@dcl/asset-packs/dist/admin-toolkit-ui/ModerationControl/api"

const videoEntity = engine.addEntity()
VideoPlayer.create(videoEntity, { src: "videos/intro.mp4" })

if (isServer()) {
  // Cache de endereços de wallet dos admins, atualizado a partir da lista de admins da scene
  let adminAddresses = new Set<string>()

  async function updateAdminAddresses() {
    if (isPreview()) return
    try {
      const [error, response] = await getSceneAdmins()
      if (error) {
        console.error("[SERVER] Erro ao buscar lista de admins:", error)
        adminAddresses = new Set()
        return
      }
      adminAddresses = new Set(
        (response ?? []).map((admin) => admin.admin.toLowerCase())
      )
      console.log(
        "[SERVER] Cache de endereços de admins atualizado:",
        Array.from(adminAddresses)
      )
    } catch (error) {
      console.error("[SERVER] Erro ao atualizar endereços de admins:", error)
      adminAddresses = new Set()
    }
  }

  // Preencha o cache antes de conectar a validação
  await updateAdminAddresses()

  VideoPlayer.validateBeforeChange(videoEntity, (value) => {
    // Sempre permita alterações ao executar em preview, para facilitar os testes locais
    if (isPreview()) return true

    const senderAddress = value.senderAddress.toLowerCase()
    if (!adminAddresses.has(senderAddress)) {
      console.log(
        "[SERVER] Alteração não autorizada de VideoPlayer bloqueada de:",
        senderAddress
      )
      return false
    }
    return true
  })
}
```

Veja [Scene Admin](/creator/content-creator-pt/scene-editor/operar-em-direto/scene-admin.md) para mais contexto sobre como os jogadores se tornam admins em uma scene.

#### Permitir alterações apenas pelo servidor

O caso mais restrito é aceitar apenas gravações que se originam do próprio servidor, rejeitando qualquer alteração vinda de um cliente. Esse é o padrão ideal para estado que deve ser totalmente autoritativo: pontuações, fase do jogo, entities spawnadas, etc.

Todo valor recebido inclui um campo `senderAddress` . Quando o remetente é o servidor, esse campo corresponde à constante `AUTH_SERVER_PEER_ID`, exportada de `@dcl/sdk/network/message-bus-sync`.

O exemplo abaixo define um pequeno helper `protectServerEntity()` que aplica essa verificação a um ou mais components em uma entity específica. É uma maneira conveniente de proteger múltiplos components (como `Transform` e `GltfContainer`) em uma única chamada:

```typescript
import { engine, Entity, Transform, GltfContainer } from "@dcl/sdk/ecs"
import { Vector3 } from "@dcl/sdk/math"
import { isServer } from "@dcl/sdk/network"
import { AUTH_SERVER_PEER_ID } from "@dcl/sdk/network/message-bus-sync"

type ComponentWithValidation = {
  validateBeforeChange: (
    entity: Entity,
    cb: (value: { senderAddress: string }) => boolean
  ) => void
}

function protectServerEntity(
  entity: Entity,
  components: ComponentWithValidation[]
) {
  for (const component of components) {
    component.validateBeforeChange(entity, (value) => {
      return value.senderAddress === AUTH_SERVER_PEER_ID
    })
  }
}

if (isServer()) {
  // Depois de criar uma entity gerenciada pelo servidor:
  const entity = engine.addEntity()
  Transform.create(entity, { position: Vector3.create(10, 5, 10) })
  GltfContainer.create(entity, { src: "assets/model.glb" })
  protectServerEntity(entity, [Transform, GltfContainer])
}
```

{% hint style="warning" %}
**📔 Nota**: Sempre chame `protectServerEntity()` dentro de um bloco `isServer()` . Ele envolve `validateBeforeChange()`, que só faz sentido no servidor — chamá-lo em um cliente produz erros.
{% endhint %}

#### Components personalizados

Você também pode aplicar `validateBeforeChange()` em components personalizados definidos pela scene.

```typescript
import { engine, Schemas } from "@dcl/sdk/ecs"
import { isServer } from "@dcl/sdk/network"
import { AUTH_SERVER_PEER_ID } from "@dcl/sdk/network/message-bus-sync"

export const GameState = engine.defineComponent("game:State", {
  phase: Schemas.String,
  score: Schemas.Int,
  timeRemaining: Schemas.Int,
})

// Apenas o servidor pode modificar este component
if (isServer()) {
  GameState.validateBeforeChange((value) => {
    return value.senderAddress === AUTH_SERVER_PEER_ID
  })
}
```

## Mensagens

Components sincronizados são ótimos para estado que todos os jogadores devem ver continuamente: coisas como posições, pontuações ou fase do jogo. Mas nem tudo se encaixa nesse modelo. Às vezes, um jogador só precisa dizer ao servidor "eu cliquei neste botão" ou "quero entrar no jogo", e o servidor precisa responder com uma resposta única como "a rodada começou" ou "aqui estão suas estatísticas". Isso são eventos, não estado contínuo.

É para isso que servem as mensagens. Use `registerMessages()` para comunicação tipada e validada por schema entre clientes e o servidor. Mensagens são fire-and-forget: um cliente envia uma para o servidor, o servidor a processa e, opcionalmente, envia uma de volta. Elas não criam nenhum estado persistente por si mesmas.

### Definir Mensagens

Defina todas as mensagens em um arquivo compartilhado que tanto o servidor quanto o cliente importam. Assim, ambos os lados sempre concordam sobre quais mensagens existem e quais dados elas carregam. Cada mensagem é um `Schemas.Map` que descreve seu payload:

```typescript
import { Schemas } from "@dcl/sdk/ecs"
import { registerMessages } from "@dcl/sdk/network"

export const Messages = {
  // Cliente → Servidor
  playerReady: Schemas.Map({ displayName: Schemas.String }),
  playerAction: Schemas.Map({ action: Schemas.String, targetId: Schemas.Int }),

  // Servidor → Cliente
  gameStarted: Schemas.Map({ roundNumber: Schemas.Int }),
  gameEnded: Schemas.Map({ winnerId: Schemas.String }),
}

export const room = registerMessages(Messages)
```

### Enviar Mensagens

Os clientes só podem enviar mensagens para o servidor. Não existe mensagens diretas de cliente para cliente. O servidor pode transmitir para todos os clientes ou direcionar jogadores específicos por endereço.

```typescript
import { room } from "./shared/messages"

// Cliente → Servidor (broadcast, o servidor recebe)
room.send("playerReady", { displayName: "Alice" })

// Servidor → todos os clientes
room.send("gameStarted", { roundNumber: 1 })

// Servidor → um cliente específico (por endereço da wallet)
room.send("gameEnded", { winnerId: "Alice" }, { to: [playerAddress] })
```

### Receber Mensagens

```typescript
import { room } from "./shared/messages"

// Cliente recebe do servidor
room.onMessage("gameStarted", (data) => {
  console.log(`Round ${data.roundNumber} started!`)
})

// Servidor recebe do cliente
room.onMessage("playerReady", (data, context) => {
  if (!context) return
  const senderAddress = context.from // endereço de wallet verificado
  console.log(`[Server] ${data.displayName} is ready (${senderAddress})`)
})
```

No servidor, toda mensagem recebida inclui um objeto `context` com o endereço de wallet verificado do remetente. Use isso para saber qual jogador enviou a mensagem (nunca confie na identidade auto-relatada no payload).

### Aguarde a sincronização do estado antes de enviar

Os clientes devem esperar até que o estado da scene esteja sincronizado antes de enviar sua primeira mensagem, para evitar condições de corrida na entrada:

```typescript
import { engine } from "@dcl/sdk/ecs"
import { isStateSyncronized } from "@dcl/sdk/network"
import { room } from "./shared/messages"

engine.addSystem(() => {
  if (!isStateSyncronized()) return

  // Agora é seguro enviar mensagens
  room.send("playerReady", { displayName: "Alice" })
})
```

### Tipos de schema disponíveis

Todos os payloads de mensagens e components personalizados usam `Schemas` para serialização binária. Aqui está uma referência rápida dos tipos disponíveis:

```typescript
import { Schemas } from "@dcl/sdk/ecs"

// Tipos básicos
Schemas.String // "hello"
Schemas.Int // 42
Schemas.Float // 3.14
Schemas.Bool // true / false
Schemas.Int64 // Date.now()

// Tipos de vetor
Schemas.Vector3 // { x: 1, y: 2, z: 3 }
Schemas.Quaternion // { x, y, z, w }

// Tipos complexos
Schemas.Array(Schemas.String) // ["a", "b", "c"]
Schemas.Entity // Referência de entity
Schemas.Optional(Schemas.String) // "hello" ou undefined
Schemas.Optional(Schemas.Int) // 42 ou undefined

// Objetos aninhados
Schemas.Map({
  name: Schemas.String,
  health: Schemas.Int,
  position: Schemas.Vector3,
  playerId: Schemas.Optional(Schemas.String),
})
```

{% hint style="warning" %}
**📔 Nota**: Mensagens *devem* ser definidas usando `Schemas.Map(...)`. Você não pode enviar objetos JavaScript puros; eles falharão na serialização binária.
{% endhint %}

## Servidor Lendo Posições dos Jogadores

O servidor pode ler posições verificadas **dos jogadores** ; os clientes não podem forjá-las. Essa é a base do anticheat baseado em posição:

```typescript
import { engine, PlayerIdentityData, Transform } from "@dcl/sdk/ecs"

engine.addSystem(() => {
  for (const [entity, identity] of engine.getEntitiesWith(PlayerIdentityData)) {
    const transform = Transform.getOrNull(entity)
    if (!transform) continue

    const address = identity.address
    const position = transform.position
    // Esta posição é verificada pelo servidor — nunca confie na posição relatada pelo cliente
  }
})
```

{% hint style="warning" %}
**📔 Nota**: Sempre use `PlayerIdentityData` + `Transform` no servidor para obter posições dos jogadores. Nunca confie em valores relatados pelo próprio cliente.
{% endhint %}

## Armazenamento de Dados

Persista dados entre reinicializações do servidor. Storage é **apenas servidor**, sempre proteja chamadas com `isServer()`. O servidor pode tanto gravar quanto ler esses dados.

```typescript
import { Storage } from "@dcl/sdk/server"
```

Os dados podem ser armazenados em dois níveis:

* **World**: Use isso para dados relevantes a todos os jogadores, como rankings ou mudanças persistentes no ambiente.
* **Player**: Use isso para dados específicos do jogador, como salvar progresso ou preferências desse jogador.

{% hint style="info" %}
**💡 Dica**: Storage aceita apenas strings. Use `JSON.stringify()` / `JSON.parse()` para objetos e `String()` / `parseInt()` para números.

Durante o desenvolvimento local, o storage é gravado em `node_modules/@dcl/sdk-commands/.runtime-data/server-storage.json`.
{% endhint %}

### Storage de World — Compartilhado Entre Todos os Jogadores

```typescript
import { Storage } from "@dcl/sdk/server"

// Gravar
await Storage.set(
  "leaderboard",
  JSON.stringify([
    { name: "Alice", score: 100 },
    { name: "Bob", score: 85 },
  ])
)

// Ler
const raw = await Storage.get<string>("leaderboard")
const leaderboard = raw ? JSON.parse(raw) : []

// Deletar
await Storage.delete("leaderboard")
```

Você também pode gerenciar o storage da scene via linha de comando, usando `npx sdk-commands storage scene`:

```bash
# Definir um valor
npx sdk-commands storage scene set high_score --value 100

# Obter um valor
npx sdk-commands storage scene get high_score

# Deletar um valor
npx sdk-commands storage scene delete high_score

# Deletar todos os dados de storage da scene
npx sdk-commands storage scene clear --confirm
```

### Storage de Player — Por Endereço de Wallet

```typescript
import { Storage } from "@dcl/sdk/server"

// Gravar
await Storage.player.set(
  playerAddress,
  "progress",
  JSON.stringify({
    level: 5,
    coins: 250,
  })
)

// Ler
const saved = await Storage.player.get<string>(playerAddress, "progress")
const progress = saved ? JSON.parse(saved) : { level: 1, coins: 0 }

// Deletar
await Storage.player.delete(playerAddress, "progress")
```

Você também pode gerenciar o storage de player via linha de comando, usando `npx sdk-commands storage player`:

```bash
# Definir um valor para um jogador específico
npx sdk-commands storage player set level --value 10 --address 0x1234...

# Obter um valor para um jogador específico
npx sdk-commands storage player get level --address 0x1234...

# Deletar um valor para um jogador específico
npx sdk-commands storage player delete level --address 0x1234...

# Deletar todos os dados de um jogador específico
npx sdk-commands storage player clear --address 0x1234... --confirm

# Deletar todos os dados dos jogadores (todos os jogadores)
npx sdk-commands storage player clear --confirm
```

### Acessar dados armazenados

Você pode ver e editar os dados armazenados ao vivo no seu servidor por meio da UI de storage, inserindo este link:

[decentraland.org/storage](https://decentraland.org/storage)

Você também pode chegar a esta página pelo Creator Hub. Abra a aba **Manage** , clique nos três pontos ao lado de um lugar onde você publicou conteúdo e selecione **View server data**.

Lá você pode ver uma lista de todos os worlds e land onde você pode publicar scenes.

Abra sua scene e depois a aba **Scene** ou **Player** tab.

Na aba **Scene** você verá uma lista de todas as variáveis armazenadas. A partir daí, você pode editar ou remover qualquer uma dessas variáveis clicando no ícone de lápis ou lixeira.

![Activate stream](/files/27f5aa23d1a394558e6055d03bed001f53ee36b4)

Na aba **Player** Na aba

### Alterando a estrutura dos dados

Os dados armazenados em produção **não são apagados quando você publica uma nova versão da sua scene**. Isso é ótimo para rankings, progresso do jogador e mudanças persistentes no ambiente que os jogadores esperam que continuem existindo além de pequenas atualizações na sua scene.

O outro lado é que os dados no storage foram gravados por uma versão anterior do seu código. Se o seu novo código espera uma forma diferente, fazer o parse ou ler esses dados antigos pode falhar de maneiras sutis. Um campo que você renomeou estará ausente. Um campo que antes era uma string e agora é um objeto lançará erro quando você tentar acessar uma propriedade nele. Um jogador que não faz login há meses pode carregar dados anteriores a uma estrutura que seu código não sabe mais como tratar.

{% hint style="warning" %}
**📔 Nota**: Mudanças de schema não afetam apenas a primeira leitura após um deploy. Os dados armazenados permanecem até serem sobrescritos ou deletados, então um valor em formato antigo pode aparecer a qualquer momento, muitas vezes de um jogador que voltou e do qual você já tinha esquecido.
{% endhint %}

#### Melhores práticas

* *Sempre faça o parse de forma defensiva*. Trate qualquer coisa que saia do storage como entrada não confiável, mesmo que você a tenha gravado. Envolva `JSON.parse()` em um `try/catch`, verifique se os campos existem antes de lê-los e tenha um valor padrão sensato pronto quando não existirem:

  ```typescript
  import { Storage } from "@dcl/sdk/server"

  const raw = await Storage.player.get<string>(playerAddress, "progress")
  let progress = { level: 1, coins: 0 }
  if (raw) {
    try {
      const parsed = JSON.parse(raw)
      progress = {
        level: typeof parsed.level === "number" ? parsed.level : 1,
        coins: typeof parsed.coins === "number" ? parsed.coins : 0,
      }
    } catch {
      // Dados antigos ou corrompidos — voltar aos valores predefinidos
    }
  }
  ```
* *Adiciona campos, não os renomeies nem remova*. A alteração de esquema mais segura é uma aditiva: introduz um novo campo com um valor predefinido e deixa os campos existentes como estão. Os dados antigos simplesmente não terão o novo campo, que o teu parsing defensivo já trata. Renomear um campo obriga a que todos os registos antigos deixem de funcionar.
* *Versiona os teus objetos armazenados*. Inclui um `version` campo desde o primeiro dia. Quando leres os dados, faz branching com base na versão e migra as estruturas antigas para a atual antes de as usares. Isto mantém o resto do teu código a funcionar com uma única estrutura atual:

  ```typescript
  type ProgressV2 = { version: 2; level: number; coins: number; xp: number }

  function migrate(raw: any): ProgressV2 {
    const version = raw?.version ?? 1
    if (version === 1) {
      // v1 não tinha campo xp — atribui-lhe o valor predefinido
      return {
        version: 2,
        level: raw.level ?? 1,
        coins: raw.coins ?? 0,
        xp: 0,
      }
    }
    return raw as ProgressV2
  }
  ```
* *Escreve de volta o valor migrado*. Assim que atualizares um registo na memória, guarda-o novamente para que a próxima leitura já esteja no novo formato. Com o tempo, isto esvazia o conjunto de registos com estrutura antiga sem precisares de um script de migração único.
* *Para alterações incompatíveis, usa uma nova chave*. Se a nova estrutura for realmente incompatível e não valer a pena migrar, escreve para uma nova chave de armazenamento (por exemplo `progress_v2`) e ignora a antiga. A chave antiga fica inofensivamente no armazenamento e evitas qualquer caminho de leitura que tenha de a interpretar. Podes limpar as chaves antigas mais tarde através da [UI de armazenamento](https://decentraland.org/storage) ou dos `npx sdk-commands storage` comandos.
* *Testa com dados reais de produção*. Antes de publicar uma alteração estrutural, extrai alguns registos reais da UI de armazenamento e executa o teu novo código de parsing sobre eles. Os casos extremos que causam problemas são normalmente registos que não sabias que existiam.
* *Deixa uma saída de emergência*. Tem em mente que podes editar ou eliminar registos individuais a partir da UI de armazenamento ou via `npx sdk-commands storage`. Se um único jogador ficar preso num estado errado após uma alteração de esquema, podes corrigir o respetivo registo diretamente sem voltar a publicar.

## Variáveis de Ambiente

Configura a tua scene sem codificar valores diretamente no código. As variáveis de ambiente são úteis para dados sensíveis e também para flags de funcionalidade ou parâmetros que podem ser alterados facilmente sem voltar a publicar a tua scene.

As variáveis de ambiente são **apenas servidor**. Protege-as com `isServer()`. O server pode ler variáveis de ambiente, mas não alterar os seus valores.

`EnvVar.get()` retorna um `Promise<string>` e resolve para uma string vazia quando a variável não está definida, por isso fornece sempre um valor de recurso para valores em falta:

```typescript
import { EnvVar } from "@dcl/sdk/server"

const maxPlayers = parseInt((await EnvVar.get("MAX_PLAYERS")) || "4")
const gameDuration = parseInt((await EnvVar.get("GAME_DURATION")) || "300")
const debugMode = ((await EnvVar.get("DEBUG")) || "false") === "true"
```

### Dados sensíveis

As variáveis de ambiente são especialmente úteis para armazenar chaves privadas, códigos de resgate de recompensas e outros dados sensíveis que seria arriscado expor no código compilado da scene pública.

Podes armazenar chaves privadas no storage do server e deixar apenas o server lê-las com `isServer()`. Dessa forma, os dados sensíveis nunca passam pela máquina do jogador.

### Desenvolvimento Local

Para usar variáveis de ambiente enquanto corres o projeto localmente, cria um ficheiro `.env` na raiz do teu projeto:

```
MAX_PLAYERS=8
GAME_DURATION=300
DEBUG=true
```

Importante: Adiciona `.env` ao teu `.gitignore`, para que estes valores potencialmente sensíveis nunca sejam carregados para os content servers públicos.

### Alterar variáveis de ambiente

A forma mais simples de alterar os valores das tuas variáveis de ambiente é através da UI de armazenamento.

Podes aceder aos dados armazenados pelo storage da scene introduzindo esta ligação:

[decentraland.org/storage](https://decentraland.org/storage)

Você também pode chegar a esta página pelo Creator Hub. Abra a aba **Manage** , clique nos três pontos ao lado de um lugar onde você publicou conteúdo e selecione **View server data**.

Lá você pode ver uma lista de todos os worlds e land onde você pode publicar scenes.

Abra sua scene e depois a aba **Environment** tab. Deves ver todas as variáveis de ambiente no projeto.

![Activate stream](/files/edfd5114a87984feb83b09a561d63f1b1ce0282d)

Nota que não podes ler os valores de nenhuma destas variáveis de ambiente (isso serve para proteger dados sensíveis), mas podes eliminar ou substituir qualquer uma delas. Basta clicar no ícone do lápis ou do caixote do lixo.

Também podes gerir variáveis de ambiente via linha de comandos, usando `npx sdk-commands storage env`:

```bash
# Definir uma variável
npx sdk-commands storage env set MAX_PLAYERS --value 8

# Eliminar uma variável
npx sdk-commands storage env delete OLD_VAR

# Eliminar todas as variáveis de ambiente
npx sdk-commands storage env clear --confirm
```

Também podes apontar para um ambiente específico com a flag `--target` :

```bash
# Publicar em staging
npx sdk-commands storage env set MY_KEY --value my_value --target https://storage.decentraland.zone

# Publicar num server de desenvolvimento local
npx sdk-commands storage env set MY_KEY --value my_value --target http://localhost:8000
```

As variáveis de ambiente publicadas têm precedência sobre os valores `.env` .

## Estrutura de Projeto Recomendada

Separar o código do server, do client e o código partilhado mantém a base de código legível à medida que cresce:

```
src/
├── index.ts              # Ponto de entrada — branching isServer()
├── client/
│   ├── setup.ts          # Handlers de input, remetentes de mensagens
│   └── ui.tsx            # React ECS UI (lê estado sincronizado)
├── server/
│   ├── server.ts         # Game loop, handlers de mensagens, mutações de estado
│   └── gameState.ts      # Funções auxiliares para o estado do server
└── shared/
    ├── schemas.ts        # Definições de Component + validateBeforeChange
    └── messages.ts       # registerMessages() — importado por ambos os lados
```

{% hint style="info" %}
**💡 Dica**: Mantém todas as `registerMessages()` chamadas e definições de componentes personalizados em `shared/`. Tanto o server como o client importam de lá, garantindo que estão sempre de acordo sobre os schemas das mensagens.
{% endhint %}

## Melhores Práticas de Performance

Cada alteração de um component envia o *component data* inteiro pela rede. Isto é diferente do que o Colyseus faz, que envia apenas diffs. Ao desenhares components personalizados, tem isto em mente. A solução ideal pode ser guardar dados em components separados, com base na frequência de alteração.

### ❌ Evita components monolíticos

```typescript
import { engine, Schemas } from "@dcl/sdk/ecs"

// RUIM — alterar a pontuação também envia o array de posições
const GameState = engine.defineComponent("GameState", {
  playerAScore: Schemas.Int,
  playerBScore: Schemas.Int,
  timer: Schemas.Int,
  playerPositions: Schemas.Array(Schemas.Vector3), // payload grande
})
```

### ✅ Prefere components atómicos

```typescript
import { engine, Schemas } from "@dcl/sdk/ecs"

// BOM — cada atualização é pequena e independente
const PlayerScore = engine.defineComponent("PlayerScore", {
  playerA: Schemas.Int,
  playerB: Schemas.Int,
})

const GameTimer = engine.defineComponent("GameTimer", {
  secondsLeft: Schemas.Int,
})
```

*Regra geral*: agrupa campos que mudam em conjunto e com uma frequência semelhante. Separa dados que mudam rapidamente (timers, posições) de dados que mudam lentamente (pontuações, configuração).

### Reduz a frequência de mensagens

Evita enviar mensagens em cada frame. Agrupa ou limita a frequência sempre que possível:

```typescript
import { engine } from "@dcl/sdk/ecs"
import { room } from "./shared/messages"

let lastSend = 0
engine.addSystem((dt) => {
  lastSend += dt
  if (lastSend > 0.1) {
    // a cada 100 ms
    room.send("position", transform.position)
    lastSend = 0
  }
})
```

Por exemplo, se o server controlar um temporizador de contagem decrescente, não é necessário enviar atualizações a todos os jogadores a cada segundo. O melhor é que cada client calcule a passagem do tempo por sua conta e que o server transmita o seu estado atual a cada 30 segundos, mais ou menos, para garantir consistência.

## Erros Comuns

### Esquecer a validação no estado apenas do server

Sem `validateBeforeChange`, os clients podem escrever em qualquer component:

```typescript
import { engine, Schemas } from "@dcl/sdk/ecs"
import { isServer } from "@dcl/sdk/network"
import { AUTH_SERVER_PEER_ID } from "@dcl/sdk/network/message-bus-sync"

// ❌ RUIM — os clients podem fazer batota
const Score = engine.defineComponent("Score", { value: Schemas.Int })

// ✅ BOM — apenas server
if (isServer()) {
  Score.validateBeforeChange((v) => v.senderAddress === AUTH_SERVER_PEER_ID)
}
```

### Confiar em valores fornecidos pelo client

Nunca deixes que um client dite os seus próprios valores para dados importantes como saúde, pontuação ou posição:

```typescript
import { room } from "./shared/messages"

// ❌ RUIM
room.onMessage("setHealth", (data) => {
  player.health = data.health // o client controla o valor!
})

// ✅ BOM — o server calcula o resultado
room.onMessage("takeDamage", (data) => {
  const damage = calculateDamage(data.source)
  player.health = Math.max(0, player.health - damage)
})
```

### Enviar mensagens antes da sincronização do estado

Os clients têm de esperar até o estado estar sincronizado antes de interagir:

```typescript
import { engine } from "@dcl/sdk/ecs"
import { isStateSyncronized } from "@dcl/sdk/network"

engine.addSystem(() => {
  if (!isStateSyncronized()) return
  // seguro enviar mensagens
})
```

### Aguardar que o server arranque

O server só fica ativo se houver pelo menos um jogador presente na scene. Se não estiver lá ninguém, o server encerra após alguns minutos.

Quando o primeiro jogador entra na scene depois de algum tempo de inatividade, o server demora alguns segundos a arrancar. O código da tua scene deve estar preparado para ter de esperar que o server fique online. Os pedidos iniciais ao server devem ter mecanismos de captura e repetição para proporcionar resiliência.

## Exemplo Completo

Um contador multijogador mínimo: clica num botão, o server incrementa uma pontuação sincronizada. O server persiste o contador em `Storage` para que o valor sobreviva a reinícios do server. Lembra-te de que o server encerra quando não há jogadores na scene, por isso, sem storage, a contagem voltaria a zero sempre que a scene ficasse vazia de jogadores.

```typescript
import { engine, Schemas } from "@dcl/sdk/ecs"
import { registerMessages, isServer, syncEntity } from "@dcl/sdk/network"
import { AUTH_SERVER_PEER_ID } from "@dcl/sdk/network/message-bus-sync"
import { pointerEventsSystem } from "@dcl/sdk/ecs"
import { Storage } from "@dcl/sdk/server"

// 1. Define mensagens (partilhado)
const Messages = {
  increment: Schemas.Map({}),
  stateUpdate: Schemas.Map({
    count: Schemas.Int,
    lastPlayer: Schemas.String,
  }),
}

// 2. Define um component apenas do server (partilhado)
const Counter = engine.defineComponent("Counter", {
  value: Schemas.Int,
  lastPlayer: Schemas.String,
})

// 3. Cria a room
const room = registerMessages(Messages)

export async function main() {
  if (isServer()) {
    // === SERVER ===

    // Apenas o servidor pode modificar este component
    Counter.validateBeforeChange(
      (v) => v.senderAddress === AUTH_SERVER_PEER_ID
    )

    // Restabelece o contador a partir do storage caso o server tenha reiniciado
    const savedCount = await Storage.get<string>("counter")
    const savedPlayer = await Storage.get<string>("lastPlayer")
    const initialCount = savedCount ? parseInt(savedCount) : 0
    const initialPlayer = savedPlayer ?? "none"

    const counterEntity = engine.addEntity()
    syncEntity(counterEntity, [Counter.componentId], 1)
    Counter.create(counterEntity, {
      value: initialCount,
      lastPlayer: initialPlayer,
    })

    room.onMessage("increment", async (_data, context) => {
      if (!context) return
      const counter = Counter.getMutable(counterEntity)
      counter.value += 1
      counter.lastPlayer = context.from

      // Persiste no storage para que o valor sobreviva a reinícios do server
      await Storage.set("counter", String(counter.value))
      await Storage.set("lastPlayer", counter.lastPlayer)

      room.send("stateUpdate", {
        count: counter.value,
        lastPlayer: context.from,
      })
    })
  } else {
    // === CLIENT ===
    const button = engine.addEntity()
    // ... adiciona Transform, MeshRenderer, etc.

    pointerEventsSystem.onPointerDown(button, () => {
      room.send("increment", {})
    })

    room.onMessage("stateUpdate", (data) => {
      console.log(`Contagem: ${data.count} (último clique por ${data.lastPlayer})`)
    })
  }
}
```

## Testar Localmente

A preview padrão trata de tudo. Ao usar a branch auth-server do SDK, o server local arranca automaticamente em segundo plano juntamente com a preview do client.

Para testar interações multijogador localmente, abre a preview em duas janelas separadas; cada janela é tratada como um jogador diferente. Liga cada janela com um endereço diferente. Ambos os clients ligam-se à mesma instância local do server.

Usando o Creator Hub, clica no botão Preview uma segunda vez e isso abre uma segunda janela do explorer do Decentraland. Tens de ligar as duas janelas com endereços diferentes. As mesmas sessões permanecem abertas enquanto a scene recarrega.

Como alternativa, podes abrir uma segunda janela do explorer do Decentraland escrevendo o seguinte num URL do browser:

> `decentraland://realm=http://127.0.0.1:8000&local-scene=true&debug=true&multi-instance=true`

### Dicas de debugging

* *Prefixa os teus logs* com `[SERVER]` ou `[CLIENT]` para os conseguires distinguir no terminal:

  ```typescript
  import { isServer } from "@dcl/sdk/network"

  if (isServer()) {
    console.log("[SERVER] Starting...")
  } else {
    console.log("[CLIENT] Starting...")
  }
  ```
* *Verifica a sincronização do component* no client, registando a contagem de entities:

  ```typescript
  import { engine } from "@dcl/sdk/ecs"

  engine.addSystem(() => {
    const entities = Array.from(engine.getEntitiesWith(MyComponent))
    console.log("[CLIENT] Synced entities:", entities.length)
  })
  ```

## Debug em Produção

Para veres `console.log()` a saída do teu server publicado, o endereço da tua wallet tem de estar listado no `logsPermissions` array em `scene.json`:

```json
{
  "logsPermissions": ["0xYourWalletAddress"]
}
```

Sem isto, os logs do server ficam ocultos em produção, até mesmo para o dono da scene.

Faz stream dos logs ao vivo do server a partir da linha de comandos, executando isto na pasta do teu projeto

```bash
npx sdk-commands sdk-server-logs
```

Também podes especificar manualmente o nome do world nos logs com:

```bash
npx sdk-commands sdk-server-logs --world WORLD_NAME.dcl.eth
```

Ao veres logs de uma scene num world com várias scenes ou de parcels em Genesis City, passa também uma `posição` para as coordenadas:

```bash
npx sdk-commands sdk-server-logs --world WORLD_NAME.dcl.eth --position=x,y
```

Ser-te-á pedido que assines uma mensagem com uma das wallets listadas em `logsPermissions` para autenticar. Assim que estiveres ligado, verás a saída do server `console.log()` em tempo real, o que é útil para diagnosticar problemas sem precisares de voltar a publicar.

### Ver dados de storage

Podes aceder aos dados armazenados pelo storage da scene introduzindo esta ligação:

[decentraland.org/storage](https://decentraland.org/storage)

Você também pode chegar a esta página pelo Creator Hub. Abra a aba **Manage** , clique nos três pontos ao lado de um lugar onde você publicou conteúdo e selecione **View server data**.

Lá você pode ver uma lista de todos os worlds e land onde você pode publicar scenes.

Abre o world ou os dados do jogador para veres a informação armazenada para cada um.

Por exemplo, se um determinado jogador tiver um problema ao jogar a tua scene, podes procurá-lo pela address e ver que dados estão armazenados para ele, para entender a situação. Talvez tenha encontrado um caso extremo em que acabou com dados contraditórios. Podes até limpar ou editar os dados desse jogador nesta página, para o restaurar para um estado estável.

## Controlo de Versões

Cada versão publicada da tua scene recebe o seu próprio hash ID exclusivo, e cada hash é emparelhado com a sua própria instância do server. Isto significa que o código do client e o código do server avançam sempre em conjunto; não existe uma janela em que um client a correr lógica antiga fale com um server a correr lógica nova (ou vice-versa).

Quando publicas uma atualização:

* *Os jogadores já presentes na scene* continuam a ver a versão antiga da scene até saírem e voltarem a entrar. Os seus clients mantêm-se ligados à instância do server que corresponde ao hash antigo.
* *Os novos jogadores que chegam* depois da atualização carregam a nova versão da scene e ligam-se à nova instância do server.

Isto garante que o estado do client e do server nunca fica desincronizado por causa de uma alteração de schema ou de um component renomeado. Uma atualização nunca pode quebrar a sessão de um jogador que já está na tua scene.

A contrapartida é que, durante uma pequena janela logo após um deploy, os jogadores podem ficar divididos por duas instâncias de server diferentes. Um jogador que já lá estava e um jogador que acabou de chegar podem não se ver nem conseguir interagir através da scene, mesmo estando na mesma scene, até os jogadores antigos saírem e voltarem a entrar.

{% hint style="info" %}
**💡 Dica**: Os dados armazenados através do [Storage](#data-storage) service (como tabelas de classificação, progresso do jogador ou alterações persistentes no ambiente) *não* são apagados entre versões. O storage é persistido ao nível da localização e partilhado entre todas as instâncias de server que apontam para a mesma scene, por isso as novas versões retomam exatamente de onde a anterior ficou.
{% endhint %}

## Migrando do Colyseus

Se tens uma scene existente construída em Colyseus, a tabela abaixo mapeia padrões comuns do Colyseus para os equivalentes no SDK7:

| Colyseus                        | SDK7 Authoritative Server                           |
| ------------------------------- | --------------------------------------------------- |
| `room.send(type, data)`         | `room.send(type, data)` — mesma API                 |
| `room.onMessage(type, cb)`      | `room.onMessage(type, cb)` — mesma API              |
| `room.state.players` (schema)   | `syncEntity` + components personalizados            |
| Serialização JSON               | Serialização binária (automática via `Schemas`)     |
| Aplicação de server separada    | Mesma base de código — `isServer()` branching       |
| Hosting de server personalizado | Integrado: a preview corre o server automaticamente |

Principais diferenças a ter em mente:

* *Serialização*: o Colyseus envia diffs JSON; o SDK envia o component completo em cada alteração. Mantém os components pequenos (ver [Melhores Práticas de Performance](#performance-best-practices)).
* *Modelo de estado*: o Colyseus usa uma árvore de estado mutável com diffing automático. O SDK usa components ECS sincronizados via `syncEntity` e protegidos com `validateBeforeChange`.
* *Hosting*: Não há deployment de server separado. O authoritative server é implantado automaticamente em conjunto com a scene.


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.decentraland.org/creator/content-creator-pt/scenes-sdk7/networking/authoritative-servers.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
