Portar um mod para o carregador b1.7.3_rewrite
Mods Java não podem executar. Uma porta é uma reescrita em Rust (convidado wasm, SDK b173_api) que usa o mod descompilado como sua especificação.
Objetivo: mesma aparência, sensação e jogabilidade dentro do b1.7.3, a partir de um único arquivo em mods/ tanto no cliente quanto no servidor. Corrija bugs de origem e
otimize livremente quando a aparência e a jogabilidade permanecerem as mesmas. Nunca substitua uma tela vanilla: UI extra é uma sobreposição alternável.
Este arquivo é o mapa completo. Não leia documentos de design do carregador ou progresso, guias ou arquivos SDK inteiros para se orientar. Use as ferramentas, depois abra apenas o único arquivo de exemplo que a etapa precisa.
Espaço de trabalho
Os caminhos são relativos à raiz do espaço de trabalho, a pasta que contém b1.7.3_rewrite/, ports/ e o repositório do kit
b1.7.3_modport/. O install.sh do kit vincula suas ferramentas a tools/modport/ (escrito T/ abaixo) e esta skill a
.claude/skills/modport; de dentro de uma pasta de porta, T/ é ../../tools/modport/. Corrija ou estenda ferramentas em b1.7.3_modport/, nunca dentro de uma porta. Em uma máquina nova, ou quando uma
ferramenta falha por dependência ausente, execute T/doctor.sh: ele imprime o comando que corrige cada lacuna. Todas as ferramentas são
bash: no Windows, são executadas no Git Bash (a ferramenta Bash do Claude Code no Windows) e lidam com .exe, py e caminhos
por conta própria. Chame ferramentas Python através de T/py.sh, nunca python3 diretamente.
Modos
Verifique uma vez por sessão: test -f .modport-maintainer na raiz do espaço de trabalho.
- Modo mantenedor (o marcador existe): os repositórios do carregador, SDK e kit são do próprio usuário. Faça commit e push para eles apenas quando o usuário pedir, conforme os passos abaixo.
- Modo usuário (sem marcador, o padrão): os repositórios do carregador, SDK e kit pertencem ao seu autor upstream.
- Extensões do carregador e SDK permanecem na árvore de trabalho local. Nunca as commite nesses repositórios, nunca faça push para seus
remotos, nunca execute
gh pr create,gh issue createou qualquer outro comandoghde escrita contra eles, mesmo quando for solicitado a "compartilhar" ou "enviar upstream" uma alteração. Diga ao usuário para fazer isso ele mesmo. - Registre cada alteração no carregador ou SDK em
ports/<modid>/LOADER_CHANGES.md: o que adiciona, por que, quais arquivos. - Informe ao usuário que uma porta que precisa de uma alteração local no carregador executa apenas no carregador modificado deles, não em um carregador padrão.
- A pasta da porta é o próprio repositório do usuário: faça commit quando solicitado, envie push apenas para um remoto que o usuário configurou.
- Nunca crie o arquivo de marcador.
- Extensões do carregador e SDK permanecem na árvore de trabalho local. Nunca as commite nesses repositórios, nunca faça push para seus
remotos, nunca execute
Ferramentas
| Ferramenta | Use quando | Saída |
|---|---|---|
T/new_port.sh <modid> <jar> [nome] | Iniciando uma porta. Escreve manifestos, git init, descompila em reference/src (Vineflower, baixado e verificado no primeiro uso), executa o inventário | ports/<modid>/ |
T/inventory.sh <src> | Listando o que um mod registra: blocos, itens e entidades com classes, tiles, modelos, renderizadores, GUIs, geração de mundo, eventos | ~15 KB para um mod grande, mantido em reference/inventory.txt |
T/api.sh | Antes de escrever código contra o SDK. sdk <file> (world, register, screen, behavior, events, terrain, config, services), spec <Struct>, enum <Name> (HostCall, EntityField, ModelOp), templates, events, console, hooks | uma linha por assinatura com números de linha do SDK |
T/py.sh extract_assets.py <jar> <modid> | Uma vez, a partir da pasta da porta. Texturas, sons e idioma em assets/<modid>, chaves de idioma mapeadas para tile.<modid>.x.name, item.<modid>.x.name, <modid>.<key> | nunca commitado |
T/py.sh convert_models.py --out src/models/generated.rs <Model.java>... [--arg Model.scale=0.5] | Modelos. Construtores de ModelBase (nomes SRG ou MCP) para partes e caixas. Envolva a chamada no tools/convert_models.sh da porta | Rust gerado, nunca edite manualmente |
T/test.sh deploy ports/<modid> [--game] | A cada ciclo de teste. Compila, para o servidor de teste, instala o mod sozinho, inicia o servidor. --game primeiro recompila o jogo e cargo-b173 (após alterações no carregador ou SDK). Recusa enquanto um servidor de teste estiver em execução: outra sessão pode possuí-lo, pergunte antes de --force | console do servidor (final) |
T/test.sh errors [n] | Primeira coisa após qualquer falha | linhas de panic, trap e erro de ambos os logs |
T/modtest.sh cmd <linha do console> / log server 80 | Dirigindo e verificando o servidor | apenas linhas de resposta |
T/modtest.sh client / shot <png> | Apenas verificações visuais. Pergunte ao usuário antes de iniciar um cliente: ele abre uma janela na área de trabalho deles | PNG da janela do jogo |
Console (T/modtest.sh cmd loader ..., sintaxe de T/api.sh console): gen, map (PNG de cima para baixo), slice (PNG de corte
vertical), column (verifique antes de tp: um jogador teletransportado para dentro do terreno sufoca), fill, tp, use, interact,
close, entities, summon, slot, tile. Leia PNGs com a ferramenta Read. Coloque cadeias de verificação repetidas em pequenos scripts
em ports/<modid>/tools/ em vez de redigitá-los.
Fluxo de trabalho
- Configuração. Escolha a versão fonte mais próxima do beta (1.7.10 sobre 1.12 sobre 1.21): seus modelos, texturas e formas de dados
mapeiam quase 1:1. Jars 1.7.10 usam nomes SRG (
func_,field_): leia o significado pelo uso, não caça mapeamentos. Executenew_port.sh, depoisextract_assets.py. Copie classes de modelo vanilla que um modelo de mod estende parareference/vanilla/para o conversor. Extras de assets específicos da porta (sprites de renderizadores 3D) vão no próprio scripttools/da porta. - Planejamento. Transforme
reference/inventory.txtem linhas de TODO do PORT_STATUS.md em ordem de dependência: blocos e itens, receitas, geração de mundo e dimensão, entidades, máquinas e GUIs, estruturas, chefes, sistemas do jogador, slots extras, comportamentos de item, sons e partículas, config. Marcos terminam com um deploy e uma verificação no console. - Porte módulo por módulo. Leia a classe do mod, verifique o SDK com
api.sh, copie o padrão de um exemplo. Cite a classe fonte em PORT_STATUS.md, não em comentários de código. - Estenda o carregador apenas quando
api.shnão mostrar nenhuma maneira (lista de verificação abaixo). - Teste após cada módulo com
deploy,cmd,errors. Mantenha uma lista de bugs em PORT_STATUS.md em vez de repetir um problema; corrija a lista no final. - Finalização. Atualize PORT_STATUS.md. Execute
cargo +nightly fmtna porta ecargo clippyemb1.7.3_rewrite. Faça commit apenas quando solicitado: uma porta contém código Rust, ferramentas e manifestos, nunca assets,reference/ou arquivos de IA.ports/é ou um repositório git por porta ou um monorepo com uma pasta por mod;new_port.shdetecta qual. No modo usuário, liste as alterações locais do carregador que a porta precisa deLOADER_CHANGES.mdno relatório final.
Exemplos
Exemplos do SDK, sempre presentes: b1.7.3_rewrite/vendor/b1.7.3-modding-sdk/examples/ com testmod (cada módulo da API em
um arquivo), ruby (apenas conteúdo de dados), energy, energy_api, machines (serviços entre mods).
Uma porta finalizada, quando o espaço de trabalho tem uma, é o mapa mais rápido. A porta do Aether (ports/aether/src):
| Necessidade | Arquivo |
|---|---|
| Tabela de blocos, templates, comportamentos | blocks.rs (um array de linhas const por tipo) |
| Itens, ferramentas, armaduras, comida, receitas | items.rs, tools.rs, gear.rs, recipes.rs |
| Opções de configuração | config.rs (porte cada rolagem de taxa de spawn, as faltantes inundam o mundo) |
| Registro de entidades e auxiliares | entities/mod.rs |
| Mob passivo e montaria, hostil, voador, projétil | entities/phyg.rs + mount.rs, cockatrice.rs, zephyr.rs, projectile.rs |
| Estado de entidade visível no cliente | entities/swet.rs, entities/moa.rs (setSynced/synced) |
| Modelos e animação | models/mod.rs (part, cuboid, Program; copie para uma nova porta) |
| Chefe, HUD de barra de chefe, tela de diálogo | entities/bosses/mod.rs, valkyrie_queen.rs (tela transparent() mais canal serverbound) |
| Máquina com GUI e dados de tile | machines.rs (ScreenBuilder, #[tile_behavior]) |
| Slots de equipamento extras, camadas de renderização | accessories.rs (screens().overlay("GuiInventory", ..), playerLayer) |
| Movimento local do jogador | player.rs (lado do cliente em TickPost, estado do servidor em mapas thread_local por id de entidade) |
| Dimensão, terreno, decoração | world/mod.rs, world/terrain.rs, world/decorate.rs |
| Estruturas | world/dungeons/mod.rs, writer.rs (terrain::structure::candidates, recorte para StructureBox::populated, comprima alturas de 256 em 128) |
| Portal e viagem | portal.rs |
Formas que sempre se aplicam: chamadas GL do renderizador tornam-se operações Program (push, translate, rotate, scale, texture, blend,
lighting) em pose da entidade; onLivingUpdate/updateEntityActionState vão para onAi (substitui AI de template) ou
onTick; terrain::Random e o ruído das portas mantêm sementes idênticas; verifique isAuthority() antes de mudar o mundo
(jogador único loopa pacotes de volta, mesmo código em ambos os lados); valide cada payload serverbound; chat através de
translate("<modid>.gui.…"); sons como <modid>.<caminho.com.pontos>; texturas como <modid>:<caminho>.
Formas de dados (API do carregador 1.1, ABI 3). Identidade é sempre por nome, nunca por id numérico:
-
Variantes de bloco e face. Metadados fonte ou block states tornam-se estados nomeados:
ctx.blocks().states(nome, BlockStatesSpec)oudata/<modid>/states/<bloco>.toml.- Liste os valores de cada propriedade na ordem dos metadados fonte, para que o código
ctx.meta()continue funcionando. - Um bloco com mais de 16 estados recebe ids ocultos extras do carregador. Leia e escreva tal bloco apenas através de
world.blockState/setBlockStateouctx.state()/setState(). - Texturas por estado vão em
variants.
- Liste os valores de cada propriedade na ordem dos metadados fonte, para que o código
-
Dados por pilha. NBT de item e capacidades tornam-se componentes de dados:
stackComponent/setStackComponentcom umStackRef.- Um mod escreve apenas componentes
<modid>:*. - Pilhas com componentes diferentes nunca se mesclam.
- Dano permanece durabilidade. Use um item por variante em vez de subtipos de dano.
- Um mod escreve apenas componentes
-
Dados em coisas que o mod não possui. NBT ou capacidades de entidade, jogador, tile, chunk e mundo tornam-se slots por mod:
- entidade:
entity.setDataousetSynced - jogador:
player.setData(key, ..) - entidade de tile:
world.setTileAttachment - chunk:
world.setChunkAttachment - mundo:
setWorldData
Cada slot é chaveado pelo id do mod, assim dois mods nunca colidem.
- entidade:
-
Edições em conteúdo vanilla ou de outros mods. Edições de propriedade (dureza, luz, inflamabilidade, ferramenta) tornam-se
ctx.blocks().modify(BlockModifySpec)oudata/<modid>/modify/blocks/*.toml. Edições de lista de spawn tornam-semodifySpawnsoumodify/spawns/*.toml. Velocidade de mineração para uma classe de ferramenta vem das tagsminecraft:mineable/<pickaxe|axe|shovel>. Edições conflitantes são relatadas na tela do Mod Loader. Nunca substitua um bloco vanilla inteiro para uma mudança de propriedade. -
Geração de mundo e comandos.
- Nomeie cada recurso de geração de mundo unicamente. O nome sementeia seu aleatório, então outros mods nunca o deslocam.
- Comandos respondem a
/<modid>:<nome>. O curto/<nome>vai para o primeiro mod que o registra.
Estendendo o carregador
Apenas recursos gerais, nunca um hack de um mod. Caminhos abaixo estão dentro de b1.7.3_rewrite/. Pontos de toque, em ordem:
- Protocolo: acrescente variantes no final de
HostCall,GuestCall,GuestReplyouEntityFieldemvendor/b1.7.3-modding-sdk/mod_protocol/src/abi.rs; novos campos de especificação emspec.rscomo tiposOptionouDefault(structs de especificação são#[serde(default)], então mods antigos ainda carregam). Contratos lançados nunca mudam: um contrato alterado ganha uma nova versão. - Host: braço de match em
crates/modloader/src/host/world.rs(chamadas de tempo de execução) ouhost/register.rs(fase de registro); sistemas maiores em seus próprioscrates/modloader/src/<sistema>.rsouextensions.rs. Novos templates são linhas emcrates/modloader/src/entities.rs(ENTITY_TEMPLATES) oucontent/templates.rs(BLOCK_TEMPLATES). - Wrapper do SDK ao lado de seus irmãos em
b173_api/src/{world,register,screen}.rs, no prelude se mods o usam em todos os lugares. - Lado do cliente em
crates/client/src/modded/. Código vanilla chama novo comportamento através de um hook de uma linha com um comentário// rewrite:, listado com uma versão emcrates/modloader/hooks.toml. - Ferramentas do console de desenvolvedor em
crates/modloader/src/devtools.rs(adicione aUSAGE). cargo buildecargo clippy, depoisT/test.sh deploy ports/<modid> --game. Anote a adição onde o espaço de trabalho mantém notas do carregador. Modo mantenedor: mudanças no SDK são commitadas no submódulo do SDK, depois incrementadas na reescrita, apenas quando o usuário pedir. Modo usuário: nada é commitado lá, a mudança vai emLOADER_CHANGES.md.
Exemplo de cadeia, World::lightning: HostCall::Lightning (abi.rs) para seu braço em host/world.rs para o wrapper em
b173_api/src/world.rs.
Armadilhas conhecidas
-
HostCall,HostReplye structs de especificação são codificados em postcard. Nunca adicione um campo a uma struct existente: convidados ABI 2 parariam de decodificar. Novos dados precisam de uma nova variante ou uma nova struct. -
T/modtest.shdirige um servidor e cliente de teste compartilhados (b1.7.3_rewrite/target/modtest, servidor em seuserver/). Nunca pare, reinicie ou assuma outra sessão que outra tenha iniciado sem perguntar ao usuário. -
cargo b173 buildjá executa a compilação wasm; não há uma etapacargo buildseparada. -
Mudança no carregador ou SDK sem
deploy --game: o servidor ainda executa o binário antigo sem as novas chamadas de host. -
A janela do cliente de teste perde o foco e abre o menu de pausa; é o harness, não a porta.