Dois mundos, uma cena.
As três entradas anteriores desta frente terminaram no mesmo lugar: o Godot não expõe a VehicleConstraint do Jolt, e não há arranjo de nós Joint3D que a substitua. Medir esse vão já estava feito. Esta entrada é sobre atravessá-lo — e a travessia tem um nome: uma GDExtension em C++ que roda um PhysicsSystem do Jolt próprio, compilado por mim, em vez de tentar alcançar o que está embutido no motor.
A bifurcação: dois desenhos possíveis
Antes de escrever uma linha, havia duas maneiras de chegar na VehicleConstraint. Uma delas já estava descartada pela evidência da faixa A — mas vale registrar por quê, porque é a pergunta que qualquer pessoa faz primeiro.
Desenho B · alcançar o Jolt embutido
O Godot 4 já usa o Jolt como backend de física. A ideia óbvia é pegar carona: pedir ao motor o PhysicsSystem que ele já mantém e adicionar a constraint lá dentro.
Não funciona. O módulo embutido não expõe os símbolos do Jolt na API de extensão — nem a VehicleConstraint, nem os VehicleCollisionTester, nem os controllers. Não é questão de dificuldade: a superfície simplesmente não existe.
Desenho A · mundo Jolt próprio
A extensão compila o Jolt como dependência sua e cria o próprio PhysicsSystem. Tem acesso total à biblioteca, porque a biblioteca é dela.
É o caminho escolhido. Custa manter dois mundos em sincronia — o da física e o da cena — e transforma o clone do Jolt em dependência de build. Em troca, nada fica fora de alcance.
~/Projects/JoltPhysics deixa de ser material de leitura e vira dependência de compilação. A versão do Jolt passa a ser uma decisão do projeto, não um detalhe do motor — o que é exatamente o controle que faltava.O que foi construído, em ordem de dependência
Quatro peças, cada uma só fazendo sentido se a anterior fechou. A ordem não é burocracia: é o que permite saber onde está o erro quando algo quebra.
| Peça | O que ela prova |
|---|---|
| godot-cpp | Que os bindings compilam contra a versão do editor que eu tenho de fato. Sem isso, nada mais é verificável. |
| JoltProbe | Que a ponte existe — a extensão carrega, a classe aparece no ClassDB, a propriedade chega ao Inspector e o Godot chama o meu _physics_process em C++ a cada passo. Sem uma linha de física. |
| vehicle_probe | Que o Jolt sozinho está certo. Mesmo veículo, mesma VehicleConstraint, num binário de terminal — sem Godot em lugar nenhum. |
| JoltVehicle | Que as duas metades se juntam: o mundo do Jolt avança dentro do loop do Godot e o resultado chega nas transforms da cena. |
O vehicle_probe existir separado é o que torna o resto diagnosticável. Quando algo der errado adiante, a pergunta "é o Jolt ou é a ponte?" tem resposta em um comando.
E é assim que a extensão aparece do lado do editor: JoltVehicle é um nó como qualquer outro na árvore, com os quatro filhos que recebem a transform de cada roda. Nada aqui denuncia que a física por trás não é a do motor.

Vehicle é a classe registrada pela GDExtension; Wheel0..3 são só malhas que ela posiciona.A armadilha: paridade de ABI
O erro mais caro da sessão não foi de lógica nem de física. Foi de flag de compilação — e é o tipo de coisa que só se aprende batendo.
Sintoma
A libJolt.a compilou. O programa de teste compilou. O link falhou com dezenas de undefined reference to JPH::AssertFailed — um símbolo que eu nunca escrevi e não estava tentando usar.
Causa
A lib foi compilada em modo Distribution, que define NDEBUG e portanto desliga os asserts do Jolt. O meu programa compilou sem NDEBUG, então os headers do Jolt ligaram JPH_ENABLE_ASSERTS e passaram a esperar um símbolo que a lib não tinha.
O NDEBUG foi só o sintoma visível. O problema real é maior: o Jolt muda o layout das structs conforme um punhado de defines e conforme as instruções SIMD habilitadas. Quando esses conjuntos divergem entre a lib e quem a inclui, o caso gentil é um erro de link como este. O caso ruim é compilar, linkar e quebrar em runtime, sem mensagem nenhuma.
| O que precisa bater | Por quê |
|---|---|
| NDEBUG | Decide se JPH_ENABLE_ASSERTS existe — e portanto se o símbolo JPH::AssertFailed é esperado no link. |
| JPH_OBJECT_STREAM | Adiciona RTTI e atributos de serialização às classes. |
| JPH_DOUBLE_PRECISION | Troca o tipo Real — muda o tamanho de toda posição no sistema. |
| JPH_PROFILE_ENABLED | Insere campos de profiling dentro das structs. |
| -mavx2, -mfma, ... | Mudam alinhamento e a implementação SIMD inline nos headers. |
SConstruct da extensão quanto o script do programa de teste leem o compile_commands.json que o CMake gerou ao compilar a própria lib, e espelham dali. Se o build do Jolt for reconfigurado, os dois acompanham sozinhos — a paridade deixa de depender de disciplina.entry = next(e for e in entries if "/Jolt/" in e["file"])
args = shlex.split(entry["command"])
defines = [a[2:] for a in args if DEFINE_RE.match(a)] # JPH_* e NDEBUG
simd = [a for a in args if SIMD_RE.match(a)] # -mavx2, -mfma, ...O detalhe de versão que o plano não previa
O roadmap dizia "clonar o godot-cpp na branch que casa com o editor". Não dá: o repositório não tem branch 4.7 — as branches vão até a 4.5, mais a master, e a tag mais recente é godot-4.5-stable.
O que resolve é que a master já embute o arquivo extension_api-4-7.json, e o parâmetro api_version=4.7 seleciona esse arquivo. Antes de confiar nisso, comparei o arquivo embutido com o --dump-extension-api tirado do binário do editor instalado aqui: mesmo version_full_name, mesmo conjunto de classes.
--dump-extension-api e --dump-gdextension-interface geram exatamente o que o godot-cpp consome. Se um dia a versão do editor não tiver correspondente no repositório dos bindings, o caminho é compilar contra o dump do próprio binário via custom_api_file=.A prova: os mesmos números dos dois lados
Uma ponte que parece funcionar não vale nada. O teste que decide é comparar o veículo rodando dentro do Godot com o mesmo veículo rodando num binário de terminal sem Godot algum — e ver se os números batem.
| Grandeza (após 3 s, acelerando) | Jolt puro | Dentro do Godot |
|---|---|---|
| Altura do corpo | 0.844 | 0.844 |
| Comprimento da suspensão · dianteira | 0.3773 | 0.3773 |
| Comprimento da suspensão · traseira | 0.3513 | 0.3513 |
| Impulso de suspensão · dianteira | 55.4 | 55.4 |
| Impulso de suspensão · traseira | 67.1 | 67.1 |
Iguais até a última casa. Isso diz uma coisa específica e valiosa: a ponte não distorce a física. O que o Godot mostra é o que o Jolt calculou, não uma aproximação atravessada por conversão de unidades ou por um passo de tempo diferente.
teoria: m·g·Δt = 1500 × 9,81 / 60 = 245,2 N·s
erro: 0,1 %
E os números fecham com a teoria, não só entre si. Em regime, a soma dos quatro impulsos de suspensão tem que sustentar o peso do veículo em um passo — e sustenta, com 0,1 % de erro. A repartição também é a esperada: a traseira comprime mais que a dianteira (0,3513 contra 0,3773) e carrega mais impulso (67,1 contra 55,4). É transferência de carga sob aceleração, aparecendo sozinha, sem ninguém ter programado "transferência de carga".
E, finalmente, o que os números descrevem — dirigindo o carrinho na cena de demo, com a física resolvida pelo Jolt compilado aqui:
JoltVehicle dirigido no Godot. A suspensão que trabalha aqui é a VehicleConstraint — a mesma que o VehicleBody3D do motor não expõe.Quem é dono de quê
Rodar dois mundos exige decidir, sem ambiguidade, quem manda em cada coisa. Ambiguidade aqui vira jitter, corpo tremendo ou simulação que diverge da imagem.
| Responsabilidade | Dono |
|---|---|
| Estado físico (posição, velocidade, contatos) | Jolt — a cena é só reflexo |
| Quando o passo acontece | Godot — o _physics_process dá o tique |
| Tamanho do passo | Fixo, com clamp — um travamento do processo não pode virar um salto grande demais pro solver |
| Transforms dos nós | Escritas pela extensão a cada passo; mexer nelas por fora é sobrescrito |
| Input do motorista | Godot, repassado via SetDriverInput |
| Colisão do veículo com o mundo | Não é do Godot — corpos StaticBody3D da cena não existem para o Jolt. O mundo precisa ser construído dos dois lados |
A conferência: o molde do professor
A decisão de arquitetura acima foi tomada antes de eu ver o GDChrono — a GDExtension que o professor escreveu ligando o Godot ao Project Chrono. Ler o código dele depois é o melhor tipo de conferência: ou eu tinha acertado por conta, ou aprenderia onde errei.
Ch::World dele guarda um chrono::ChSystemSMC próprio, e o ChManager é um Node3D com _ready() e _physics_process(double delta) — exatamente o desenho A, exatamente a mesma forma de nó. Duas pessoas chegaram no mesmo lugar sem combinar, o que é a melhor evidência de que o lugar está certo.As diferenças estão todas em como a decisão é executada, e nas três ele está à frente — a cena construída a partir da física, um sistema de build só, e o passo desacoplado do quadro. Mais o achado que não estava no plano: o código dele já roda um rover Viper articulado sobre terreno SCM deformável.
A leitura completa, com o código lado a lado e o que ela muda no plano, está em Duas pontes, um desenho.
O que isso muda na frente
O teto do Godot deixou de ser o teto do projeto
A tabela comparativa listava features do Jolt marcadas como "o Godot não expõe". Essa coluna não é mais uma parede: é uma lista de coisas que agora dá para alcançar, uma a uma, pela extensão.
o gap virou backlog em vez de impedimentoA leitura de código virou execução
Tudo que a entrada Um corpo só, quatro bengalas descreveu lendo os headers — o raycast que acha o chão, a mola que vira constraint, o clamp μ·N — agora está rodando e imprimindo número. As duas entradas se checam.
dá pra medir o que antes só dava pra descreverO protótipo raycast ganhou um par de comparação
Existe agora, na mesma máquina, um veículo por penalidade (o protótipo em GDScript) e um por constraint (este). O contraste que a primeira entrada tratou no conceito pode ser medido lado a lado.
o mesmo cenário, os dois paradigmasO que continua em aberto
Adotar o padrão do GDChrono para a cena
Trocar os nós fixos Wheel0..3 por construção a partir da física, com cada nó se sincronizando sozinho — e considerar migrar o build inteiro para CMake, que dissolve a paridade de ABI em vez de administrá-la.
Geometria da cena → mundo do Jolt
Enquanto o chão for construído dentro da extensão, não dá pra modelar terreno no editor. É o obstáculo mais concreto entre este estado e um cenário de verdade.
O rover articulado
Vale repetir o que a entrada anterior estabeleceu: a VehicleConstraint é um modelo lumped, de um corpo só. Ela não é o rover articulado — é o degrau anterior. O rover pede corpos e juntas de verdade, e agora existe por onde construí-los.