20 KiB
| id | ticket | title | status | created | resolved | decision | tags | |||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| AGD-0006 | pbs-game-facing-asset-refs-and-call-result-discard | PBS Game-Facing Asset References and Ignored Call Result Lowering | accepted | 2026-03-27 | 2026-03-27 | DEC-0005, DEC-0006 |
|
Pain
PBS ainda tem dois atritos abertos na fronteira entre ergonomia de código de jogo e lowering executável:
- referências a assets continuam centradas em
asset_nameno código-fonte, enquanto packer/runtime já convergiram para identidade estável porasset_id; expression statementscom 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 Lowering18.5. Ignored Call Results in Executable Lowering
Os pontos estáveis já conhecidos são:
- packer/runtime tratam
asset_idcomo identidade estável; asset_nameainda é a superfície mais natural para autoria e tooling;- o packer já mantém um
PackerRuntimeSnapshotcoerente 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
19reforç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:
PackerRuntimeSnapshotcomo fonte operacional coerente do conjunto de assets visíveis;- backend/compiler como owner de uma
FESurfaceContextderivada 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_namenã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 é:
assetscomo á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.uipode ser apenas namespace;assets.ui.borderspode ser apenas namespace;assets.ui.borders.panelpode ser uma folhaAddressable.
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_namelivre; - packer não precisa mais ler nem escrever
asset_namecomo variável operacional emasset.json; - packer apenas reconhece, normaliza e publica o address derivado do root do asset sem persistir esse address como estado redundante em disco;
asset_idpermanece como primary key operacional estável do asset dentro do packer/runtime;- o
addresspassa 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
addressautoral emasset_idoperacional.
Exemplo de shape ainda ilustrativo:
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
Addressablejá é 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 servicepú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
Addressableparaasset_iddurante o lowering.
Exemplo ilustrativo:
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
servicenã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_idporque a própria assinaturadeclare hostcarrega 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:
assetnão é a superfície correta para endereçamento de recursos internos;- o
assetentra como unidade de instalação/publicação/carregamento; - o acesso a internos só faz sentido depois que o conteúdo foi instalado em um
bankde runtime; - portanto, members internos pertencem à superfície do
banke não à superfície doasset.
Isso impede uma confusão comum:
assets.foo.barparece sugerir quebaré membro estável do asset em si;- mas, neste modelo,
barsó deve existir como símbolo depois que umbankespecí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
FESurfaceContextmínima, derivada da autoridade operacional já existente no packer; - essa
FESurfaceContextnão é oPackerRuntimeSnapshotnem 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>addressasset_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
FESurfaceContextBE -> FE?
Options
Option A - Keep Surface Simple, Keep Runtime Rules Visible
- Approach: manter
asset_namecomo 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 emexpression 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
assetenquanto 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:
- o jogo referencia um asset por uma superfície simbólica derivada da autoridade operacional do packer;
- esse asset é instalado/publicado em um
bankde runtime; - somente o
bankpassa a expor o espaço de endereçamento de recursos internos; - 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:
Studio- como o asset é exibido e selecionado na UI;
- se o address derivado do root substitui o
asset_namecomo identidade visível; - como impedir criação/move que faça um path atuar ao mesmo tempo como asset terminal e namespace;
packer- como o snapshot operacional publica o address normatizado;
- se
asset.jsondeixa de carregarasset_namecomo dado relevante; - como moves/renames impactam o address e a invalidade de referências;
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.barpara identidade estável de runtime; - em que ponto da superfície SDK/PBS a ponte
address -> asset_idrealmente acontece.
No momento, a direção técnica mais forte para essa ponte é:
Addressablepermanece como a surface simbólica de autoria do PBS;- a
FESurfaceContextmínima de assets carrega umaList<Addressable>, cujas entradas são compostas poraddresseasset_id; - o frontend PBS usa um tipo fake homônimo
Addressablepara consumir essa surface de forma editorial e tipada; declare hostasset-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 servicepúblico permanece apenas como wrapper ergonômico sobre esse contrato.
Additional convergence already accepted in this discussion:
- the v1
FESurfaceContextasset shape is limited toList<Addressable(address, asset_id)>; - ignored call results in
expression statementfollow a general lowering rule in v1 and SHOULD also emit a warning in the first version; - asset-facing references should be resolved at compile time whenever possible; no runtime-dynamic exception is currently required for this policy;
- future editorial refinement may tune warning policy severity or suppression rules, but the baseline v1 direction already includes an
ignored valueswarning.
Resolution
Accepted on 2026-03-27.
This agenda now resolves into two sibling decisions:
DEC-0005for PBS asset address surface,FESurfaceContext, and backend-ownedAddressablelowering.DEC-0006for ignored values general lowering and warning policy.
What is now locked:
- PBS asset-facing authoring is symbolic and based on
Addressable. - The backend exposes a minimal
FESurfaceContextwithList<Addressable(address, asset_id)>. - The backend remains owner of final validation and lowering to runtime-facing
asset_id. - Ignored values in
expression statementfollow a general lowering rule and emit a warning in v1.
Direção recomendada por enquanto:
- tratar os dois temas como uma única discussão de política de surface-versus-lowering em PBS;
- preferir a direção Option B como baseline;
- tratar a política de asset references como discussão entre três camadas explícitas:
PackerRuntimeSnapshotcomo autoridade operacional do conjunto de assets;- superfície simbólica sintetizada para consumo em PBS;
- lowering compiler-owned para identidade estável de runtime;
- explorar explicitamente a remoção de
asset_namecomo variável operacional em favor do address derivado do root relativo emassets/; - 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;
- tratar como questão central da agenda o ponto exato em que a superfície PBS/SDK converte
addressemasset_id; - 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;
- registrar explicitamente que addressing de recursos internos instalados em
banksnão pertence ao contrato básico deasset referencese deve seguir discussão/spec própria, provavelmente em superfície transversal de runtime/banks; - 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 é:
declare hostasset-backed carrega[AssetLowering(param = ...)];- o parâmetro marcado usa
Addressablecomo surface autoral; declare servicepúblico chama normalmente esse host wrapper;- o compiler resolve
address -> asset_idao baixar a chamada host-backed; - o runtime final continua vendo apenas o contrato operacional por
asset_id.
Related shape for the synthetic asset surface:
assetsis a hierarchical compile-time tree, not a flat enum;- intermediate nodes are namespaces only;
- terminal asset leaves are typed as
Addressable; - compile-time projection currently needs only
addressandasset_id; - Studio/runtime-tooling must reject terminal/namespace path collisions;
- 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:
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 parameter0must be resolved from symbolicAddressableto operationalasset_id;- the service body remains ordinary PBS code;
- the special behavior lives in reserved lowering metadata on the
declare hostsignature.