prometeu-studio/discussion/workflow/agendas/AGD-0006-pbs-game-facing-asset-refs-and-call-result-discard.md

374 lines
20 KiB
Markdown

---
id: AGD-0006
ticket: pbs-game-facing-asset-refs-and-call-result-discard
title: PBS Game-Facing Asset References and Ignored Call Result Lowering
status: accepted
created: 2026-03-27
resolved: 2026-03-27
decision: DEC-0005, DEC-0006
tags: [compiler, pbs, ergonomics, lowering, runtime, asset-identity, expression-statements]
---
## Pain
PBS ainda tem dois atritos abertos na fronteira entre ergonomia de código de jogo e lowering executável:
1. referências a assets continuam centradas em `asset_name` no código-fonte, enquanto packer/runtime já convergiram para identidade estável por `asset_id`;
2. `expression statements` com retorno materializado ainda precisam de política explícita de descarte, para não vazar regra de stack do backend para o código de usuário.
Os dois temas pertencem à mesma discussão porque tratam da mesma pergunta arquitetural:
Como PBS deve preservar uma superfície de autoria natural sem sacrificar determinismo no lowering e no runtime?
## Context
Esta agenda consolida dois tópicos legados do domínio PBS:
- `18.4. Asset References in Game Code - Names vs Compile-Time Lowering`
- `18.5. Ignored Call Results in Executable Lowering`
Os pontos estáveis já conhecidos são:
- packer/runtime tratam `asset_id` como identidade estável;
- `asset_name` ainda é a superfície mais natural para autoria e tooling;
- o packer já mantém um `PackerRuntimeSnapshot` coerente em memória como projeção operacional do workspace ativo;
- Studio já consome leituras normais a partir desse snapshot operacional em vez de reconstituir a verdade diretamente do filesystem a cada interação;
- o pipeline executável hoje já exige disciplina explícita de stack;
- a família `19` reforçou a direção de compiler-owned lowering e publication protocol, reduzindo espaço para ambiguidades “aceitas pelo frontend, rejeitadas mais tarde”.
Isso sugere um princípio comum:
- a linguagem pode continuar amigável na superfície,
- mas o compiler precisa assumir explicitamente o trabalho de normalizar essa superfície para um contrato executável estável.
O novo ponto importante é que, para referências a asset, essa “superfície amigável” não precisa nascer de nomes soltos escritos manualmente nem de arquivos tipados gerados sem owner claro.
Ela pode ser tratada como uma projeção simbólica derivada da autoridade operacional já existente no packer:
- `PackerRuntimeSnapshot` como fonte operacional coerente do conjunto de assets visíveis;
- backend/compiler como owner de uma `FESurfaceContext` derivada desse snapshot, sem expor o snapshot bruto ao frontend;
- Studio como IDE que consome e exibe essa superfície, mas não a possui normativamente;
- PBS como consumidor frontend dessa superfície tipada;
- backend/compiler como responsável final por validar e lowerar essa superfície para identidade estável de runtime.
O entendimento atual desta discussão mudou um ponto importante do modelo anterior:
- `asset_name` não parece justificar existência como campo operacional separado;
- o dado estrutural realmente útil já está no root do asset dentro de `assets/`;
- portanto a superfície simbólica candidata pode ser o próprio address normatizado derivado do diretório, e não um nome livre fornecido pelo autor.
Exemplo de direção:
- asset root em disco: `assets/some/dir/maluco/asset/asset.json`
- superfície simbólica projetada para código/tooling: `assets.some.dir.maluco.asset`
Na direção atual, essa superfície não deve ser tratada como enum flat simples.
O modelo preferido é:
- `assets` como árvore hierárquica sintetizada de compile;
- nós intermediários como namespaces de compile;
- apenas folhas correspondentes a asset roots reais como valores terminais do tipo `Addressable`.
Exemplo:
- `assets.ui` pode ser apenas namespace;
- `assets.ui.borders` pode ser apenas namespace;
- `assets.ui.borders.panel` pode ser uma folha `Addressable`.
Isso implica uma regra estrutural importante:
- um asset terminal não pode ocupar um prefixo que também precise funcionar como namespace para outros assets.
Exemplo inválido:
- existir asset terminal em `assets/ui`
- e também existir asset terminal em `assets/ui/borders/panel`
porque `assets.ui` não pode ser simultaneamente `Addressable` terminal e namespace.
Operational note:
- Studio should prevent creation of a new asset root when that path would collide with an existing asset terminal/namespace boundary.
- In practice, if an asset already exists at a path, Studio should not allow creating descendant assets under that same terminal path.
- Likewise, if descendants already exist, Studio should not allow creating a new asset at their prefix path.
- This must become an explicit Studio-side creation and move constraint, not just a compiler-side validation concern.
Nesse modelo:
- Studio mostra o address normatizado do asset, não um `asset_name` livre;
- packer não precisa mais ler nem escrever `asset_name` como variável operacional em `asset.json`;
- packer apenas reconhece, normaliza e publica o address derivado do root do asset sem persistir esse address como estado redundante em disco;
- `asset_id` permanece como primary key operacional estável do asset dentro do packer/runtime;
- o `address` passa a ser a identidade simbólica usada pelos compiladores para referenciar o asset em superfícies de autoria/compile;
- renomear ou mover diretório muda o address e quebra referências em compile time;
- tooling de rename/move pode existir para amortecer esse custo, mas a quebra deixa de ser ambígua e passa a ser explícita.
Com esse contexto, o coração real da discussão de asset references não é mais “nome versus `asset_id`”.
O ponto central passa a ser:
- como PBS/SDK transforma um `address` autoral em `asset_id` operacional.
Exemplo de shape ainda ilustrativo:
```pbs
fn load(addressable: Addressable, bankRef: BankRef) -> LoadError
```
A dúvida principal não é o nome exato da API, mas o lugar da resolução:
- a função do SDK recebe um símbolo/valor addressable e resolve internamente para `asset_id`;
- ou o próprio `Addressable` já é uma declaração/superfície sintetizada a partir do modelo vindo do backend, carregando a resolução necessária antes mesmo de entrar na função;
- ou ainda existe uma terceira forma híbrida em que a superfície autoral é simbólica, mas o lowering do compiler reescreve a chamada para a variante já normalizada em `asset_id`.
Essa decisão é o núcleo arquitetural da agenda porque define:
- o quanto a API pública do SDK continua “asset-address-first”;
- o quanto o compiler participa do lowering antes da chamada;
- e qual parte do sistema é owner da ponte entre identidade simbólica de compile e identidade operacional de runtime.
O modelo que hoje parece mais coerente com PBS não é colocar essa resolução no corpo de um `declare service`.
O desenho mais alinhado com a linguagem atual é:
- `declare service` público continua sendo wrapper ergonômico normal;
- a metadata reservada de lowering fica no `declare host`, junto do próprio contrato host-backed;
- o compiler consome essa metadata ao baixar a chamada host-backed;
- o parâmetro marcado como asset-facing é resolvido de `Addressable` para `asset_id` durante o lowering.
Exemplo ilustrativo:
```pbs
declare host low_assets {
[Host(module = "assets", name = "preload", version = 1)]
[AssetLowering(param = 0)]
fn preload(addressable: Addressable, bank_id: int) -> LoadError;
}
declare service Assets {
fn load(addressable: Addressable, bank: BankRef) -> LoadError {
return low_assets.preload(addressable, bank.id());
}
}
```
Neste desenho:
- o `service` não contém lógica especial de resolução;
- ele só encaminha a chamada de forma autoral/ergonômica;
- a assinatura host-backed explicita qual parâmetro sofre asset-lowering;
- o compiler sabe exatamente onde injetar o `asset_id` porque a própria assinatura `declare host` carrega o contrato;
- o lowering ocorre antes da forma final host/syscall, sem exigir placeholder artificial no corpo do `service`.
Outro limite arquitetural precisa ficar explícito:
- `asset` não é a superfície correta para endereçamento de recursos internos;
- o `asset` entra como unidade de instalação/publicação/carregamento;
- o acesso a internos só faz sentido depois que o conteúdo foi instalado em um `bank` de runtime;
- portanto, members internos pertencem à superfície do `bank` e não à superfície do `asset`.
Isso impede uma confusão comum:
- `assets.foo.bar` parece sugerir que `bar` é membro estável do asset em si;
- mas, neste modelo, `bar` só deve existir como símbolo depois que um `bank` específico expõe esse espaço de endereçamento.
Como diferentes bancos têm semânticas distintas (`tile bank`, `sound bank`, e futuros bancos), o endereçamento de internos é family-specific e provavelmente pertence a uma spec transversal de runtime/banks, não à política básica de asset references.
Tambem surge uma separação importante entre pipeline de compile e runtime packer:
- o frontend PBS não deveria consultar o packer diretamente para descobrir a superfície de assets;
- o backend deve expor ao frontend uma `FESurfaceContext` mínima, derivada da autoridade operacional já existente no packer;
- essa `FESurfaceContext` não é o `PackerRuntimeSnapshot` nem transfere ownership da resolução para o frontend;
- o frontend usa a superfície para semântica e tooling locais, enquanto o backend continua responsável por validar o valor concreto e realizar o lowering final;
- essa mesma porta abre precedente para outras estruturas futuras entrarem no frontend por um contrato estável de surface, sem acoplá-lo diretamente a serviços externos.
For the current agenda scope, the initial `FESurfaceContext` asset payload is intentionally small:
- `List<Addressable>`
- `address`
- `asset_id`
No richer asset payload is required yet for this policy discussion.
For PBS specifically, the frontend-facing fake/public type may also be named `Addressable`, as a homonymous language surface over the backend-owned `FESurfaceContext` payload.
Outro recorte importante:
- preload já existe e não é o objeto principal desta agenda;
- ele pode ser citado como contexto de integração;
- mas a discussão aqui não é “como preload funciona”;
- a discussão é “como asset references em PBS/SDK se resolvem até o ponto em que o caminho low-level já opera com `asset_id`”.
## Open Questions
- [ ] Quais outras estruturas além de assets podem, no futuro, seguir esse mesmo padrão de `FESurfaceContext` BE -> FE?
## Options
### Option A - Keep Surface Simple, Keep Runtime Rules Visible
- **Approach:** manter `asset_name` como referência de longo prazo e exigir consumo explícito de retornos quando houver valor materializado.
- **Pro:** modelo simples e pouco invasivo.
- **Con:** vaza custo operacional e detalhe de lowering para código-fonte; mantém fragilidade de rename e lookup tardio.
- **Maintainability:** baixa a média, porque o autor continua negociando diretamente com limites do backend/runtime.
### Option B - Preserve Surface Ergonomics, Normalize in the Compiler
- **Approach:** manter superfície amigável (`asset_name`, `foo();`) e explicitar no compiler as normalizações necessárias, como lowering para identidade estável quando possível e descarte automático de resultados ignorados em `expression statement`.
- **Pro:** separa melhor autoria de protocolo executável; fica alinhado com a direção já adotada na família `19`.
- **Con:** aumenta responsabilidade do compiler e exige regras claras de quando a normalização pode ou não ocorrer.
- **Maintainability:** alta, porque o contrato passa a ficar onde ele deveria estar: no compiler e nas specs de lowering.
### Option C - Introduce Rich Resource and Effect Surfaces
- **Approach:** criar abstrações mais fortes para recursos e resultados descartáveis, com novos tipos/superfícies na linguagem.
- **Pro:** potencialmente o modelo mais explícito e expressivo no longo prazo.
- **Con:** escopo bem maior; mistura redesign de linguagem com problemas que talvez possam ser resolvidos por lowering.
- **Maintainability:** incerta no curto prazo; só compensa se PBS quiser investir em abstrações novas, não apenas fechar lacunas atuais.
## Discussion
As duas agendas legadas apontam para a mesma direção recomendada:
- preservar ergonomia de autoria;
- e mover o peso de normalização para o compiler.
Isso não significa “magia implícita sem limite”.
Significa assumir, de forma normativa, que a superfície PBS pode ser mais estável e legível do que o protocolo executável final.
O ponto central desta discussão é definir o limite dessa política:
- quando PBS só normaliza lowering,
- e quando precisa criar uma abstração nova de linguagem.
Com o contexto atual do packer, a pergunta fica mais precisa:
- a autoria PBS deve continuar apontando para identificadores “humanos” sem owner operacional claro;
- ou deve consumir uma superfície simbólica derivada do snapshot coerente que o packer já mantém para o projeto?
Se a segunda direção for adotada, o enum sintético de assets não é só ergonomia de editor.
Ele passa a ser a projeção tipada, em domínio PBS, de uma autoridade operacional já existente no packer.
Isso desloca a discussão de “nome versus `asset_id`” para uma formulação melhor:
- qual é a superfície simbólica correta para o jogo autorar;
- quem é owner dessa superfície;
- e como essa superfície é lowered para a identidade estável final.
Também desloca uma segunda discussão para fora do escopo imediato:
- a agenda atual pode fechar como o jogo referencia o `asset` enquanto unidade instalável/publicável;
- mas não deve colapsar isso com o addressing dos internos expostos por `banks`.
O modelo mais correto parece ser:
1. o jogo referencia um asset por uma superfície simbólica derivada da autoridade operacional do packer;
2. esse asset é instalado/publicado em um `bank` de runtime;
3. somente o `bank` passa a expor o espaço de endereçamento de recursos internos;
4. esse espaço de endereçamento é específico da família do banco e merece contrato próprio.
Ao mesmo tempo, a discussão de root references passa a ter três frentes explícitas:
1. `Studio`
- como o asset é exibido e selecionado na UI;
- se o address derivado do root substitui o `asset_name` como identidade visível;
- como impedir criação/move que faça um path atuar ao mesmo tempo como asset terminal e namespace;
2. `packer`
- como o snapshot operacional publica o address normatizado;
- se `asset.json` deixa de carregar `asset_name` como dado relevante;
- como moves/renames impactam o address e a invalidade de referências;
3. `compiler/pbs`
- como a projeção de assets entra no compile;
- como o frontend PBS consome essa projeção sem consultar o packer diretamente;
- como o lowering converte `assets.foo.bar` para identidade estável de runtime;
- em que ponto da superfície SDK/PBS a ponte `address -> asset_id` realmente acontece.
No momento, a direção técnica mais forte para essa ponte é:
- `Addressable` permanece como a surface simbólica de autoria do PBS;
- a `FESurfaceContext` mínima de assets carrega uma `List<Addressable>`, cujas entradas são compostas por `address` e `asset_id`;
- o frontend PBS usa um tipo fake homônimo `Addressable` para consumir essa surface de forma editorial e tipada;
- `declare host` asset-backed recebe metadata reservada como `[AssetLowering(param = N)]`;
- o backend continua validando o valor concreto e resolve esse parâmetro durante o lowering da chamada host-backed;
- `declare service` público permanece apenas como wrapper ergonômico sobre esse contrato.
Additional convergence already accepted in this discussion:
1. the v1 `FESurfaceContext` asset shape is limited to `List<Addressable(address, asset_id)>`;
2. ignored call results in `expression statement` follow a general lowering rule in v1 and SHOULD also emit a warning in the first version;
3. asset-facing references should be resolved at compile time whenever possible; no runtime-dynamic exception is currently required for this policy;
4. future editorial refinement may tune warning policy severity or suppression rules, but the baseline v1 direction already includes an `ignored values` warning.
## Resolution
Accepted on 2026-03-27.
This agenda now resolves into two sibling decisions:
1. `DEC-0005` for PBS asset address surface, `FESurfaceContext`, and backend-owned `Addressable` lowering.
2. `DEC-0006` for ignored values general lowering and warning policy.
What is now locked:
1. PBS asset-facing authoring is symbolic and based on `Addressable`.
2. The backend exposes a minimal `FESurfaceContext` with `List<Addressable(address, asset_id)>`.
3. The backend remains owner of final validation and lowering to runtime-facing `asset_id`.
4. Ignored values in `expression statement` follow a general lowering rule and emit a warning in v1.
Direção recomendada por enquanto:
1. tratar os dois temas como uma única discussão de política de surface-versus-lowering em PBS;
2. preferir a direção **Option B** como baseline;
3. tratar a política de asset references como discussão entre três camadas explícitas:
- `PackerRuntimeSnapshot` como autoridade operacional do conjunto de assets;
- superfície simbólica sintetizada para consumo em PBS;
- lowering compiler-owned para identidade estável de runtime;
4. explorar explicitamente a remoção de `asset_name` como variável operacional em favor do address derivado do root relativo em `assets/`;
5. tratar como requisito arquitetural que a projeção de assets chegue ao frontend PBS pelo backend/orquestração do compile, não por consulta direta do frontend ao packer;
6. tratar como questão central da agenda o ponto exato em que a superfície PBS/SDK converte `address` em `asset_id`;
7. fechar depois duas decisões derivadas:
- política de asset references para código de jogo, agora incluindo owner, forma do address canônico e contrato de entrada BE -> FE da projeção de assets;
- política de descarte de retorno ignorado em `expression statement`;
8. registrar explicitamente que addressing de recursos internos instalados em `banks` não pertence ao contrato básico de `asset references` e deve seguir discussão/spec própria, provavelmente em superfície transversal de runtime/banks;
9. evitar introduzir nova abstração de linguagem antes de esgotar a via de normalização no compiler e a via de superfície simbólica derivada da autoridade operacional já existente no packer.
### Current preferred technical shape
Sem fechar ainda a decision final, a forma técnica hoje preferida nesta agenda é:
1. `declare host` asset-backed carrega `[AssetLowering(param = ...)]`;
2. o parâmetro marcado usa `Addressable` como surface autoral;
3. `declare service` público chama normalmente esse host wrapper;
4. o compiler resolve `address -> asset_id` ao baixar a chamada host-backed;
5. o runtime final continua vendo apenas o contrato operacional por `asset_id`.
Related shape for the synthetic asset surface:
1. `assets` is a hierarchical compile-time tree, not a flat enum;
2. intermediate nodes are namespaces only;
3. terminal asset leaves are typed as `Addressable`;
4. compile-time projection currently needs only `address` and `asset_id`;
5. Studio/runtime-tooling must reject terminal/namespace path collisions;
6. Studio should not allow creating or moving assets in ways that would make the same path act as both terminal asset and namespace prefix.
Example:
```pbs
declare host low_assets {
[Host(module = "assets", name = "preload", version = 1)]
[AssetLowering(param = 0)]
fn preload(addressable: Addressable, bank_id: int) -> LoadError;
}
declare service Assets {
fn load(addressable: Addressable, bank: BankRef) -> LoadError {
return low_assets.preload(addressable, bank.id());
}
}
```
In this example:
- the author-facing API remains `Assets.load(addressable, bank)`;
- `low_assets.preload(...)` is the host-backed contract surface;
- `[AssetLowering(param = 0)]` tells the compiler that parameter `0` must be resolved from symbolic `Addressable` to operational `asset_id`;
- the service body remains ordinary PBS code;
- the special behavior lives in reserved lowering metadata on the `declare host` signature.