@pose_flow/babylon 0.1.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/README.md +129 -105
  2. package/dist/AI_USAGE.md +386 -0
  3. package/dist/PUBLISHING.md +110 -0
  4. package/dist/babylon-inertialization.d.ts +29 -0
  5. package/dist/babylon-inertialization.js +88 -0
  6. package/dist/babylon.d.ts +27 -5
  7. package/dist/babylon.js +181 -26
  8. package/dist/core/controller.d.ts +26 -0
  9. package/dist/core/controller.js +78 -8
  10. package/dist/core/database.d.ts +8 -2
  11. package/dist/core/database.js +62 -24
  12. package/dist/core/features.d.ts +1 -1
  13. package/dist/core/features.js +9 -11
  14. package/dist/core/inertialization.d.ts +3 -0
  15. package/dist/core/inertialization.js +15 -0
  16. package/dist/core/profile.d.ts +34 -8
  17. package/dist/core/profile.js +49 -10
  18. package/dist/core/root-motion.d.ts +5 -3
  19. package/dist/core/root-motion.js +43 -7
  20. package/dist/core/scenario.d.ts +29 -0
  21. package/dist/core/scenario.js +57 -0
  22. package/dist/core/schema.d.ts +56 -0
  23. package/dist/core/schema.js +123 -0
  24. package/dist/core/search.d.ts +40 -2
  25. package/dist/core/search.js +147 -16
  26. package/dist/core/selection.d.ts +24 -0
  27. package/dist/core/selection.js +41 -0
  28. package/dist/core/types.d.ts +24 -4
  29. package/dist/core/types.js +1 -0
  30. package/dist/index.d.ts +4 -0
  31. package/dist/index.js +4 -0
  32. package/dist/offline/src/babylon-inertialization.js +88 -0
  33. package/dist/offline/src/babylon.js +281 -0
  34. package/dist/offline/src/core/controller.js +162 -0
  35. package/dist/offline/src/core/database.js +97 -0
  36. package/dist/offline/src/core/features.js +41 -0
  37. package/dist/offline/src/core/inertialization.js +15 -0
  38. package/dist/offline/src/core/profile.js +103 -0
  39. package/dist/offline/src/core/root-motion.js +78 -0
  40. package/dist/offline/src/core/scenario.js +57 -0
  41. package/dist/offline/src/core/schema.js +123 -0
  42. package/dist/offline/src/core/search.js +181 -0
  43. package/dist/offline/src/core/selection.js +41 -0
  44. package/dist/offline/src/core/types.js +3 -0
  45. package/dist/offline/src/index.js +11 -0
  46. package/dist/offline/tools/bake.js +88 -0
  47. package/dist/offline/tools/cli.js +30 -0
  48. package/dist/offline/tools/glb.js +54 -0
  49. package/dist/offline/tools/prepare.js +165 -0
  50. package/dist/offline/tools/profile-assets.js +90 -0
  51. package/dist/offline/tools/rig.js +47 -0
  52. package/dist/offline/tools/validate-assets.js +43 -0
  53. package/package.json +48 -39
@@ -0,0 +1,386 @@
1
+ # Tutorial de integração para IA — @pose_flow/babylon
2
+
3
+ Contrato local em desenvolvimento, revisado em 28/10/2026: Fases 1/2 e implementações parciais das Fases 3/4, com perfis v2, bancos v2 e schemas v1; package.json local 0.2.0. Não presumir que alterações deste workspace já foram publicadas: conferir a versão instalada, seus .d.ts e a migração antes de integrar. **Ler este documento antes de integrar ou modificar o uso da biblioteca.** Não presumir recursos futuros a partir do nome “motion matching”.
4
+
5
+ Fonte mantida no repositório: docs/AI_USAGE.md. Build: dist/AI_USAGE.md. Após instalar pelo npm: node_modules/@pose_flow/babylon/dist/AI_USAGE.md. Também pode ser localizado com `import.meta.resolve('@pose_flow/babylon/AI_USAGE.md')` em Node ESM, sem importar o Markdown como JavaScript.
6
+
7
+ ## 1. Escopo e instalação
8
+
9
+ Biblioteca TypeScript/ESM de busca de poses e reprodução de locomoção, com canais configuráveis de ossos e trajetória; os exemplos validados são bípedes. Não depende de MMORPG, servidor específico ou personagem específico. O núcleo não importa Babylon; o adaptador é uma entrada separada.
10
+
11
+ ```powershell
12
+ npm install @pose_flow/babylon @babylonjs/core@9.32.1 @babylonjs/loaders@9.32.1
13
+ ```
14
+
15
+ A base local migrou para Babylon 9.32.1: core de desenvolvimento e loaders estão fixos nessa versão; a faixa peer de core é ^9.32.1. Manter core/loaders alinhados no consumidor e conferir `npm ls @babylonjs/core @babylonjs/loaders` para evitar versões incompatíveis ou cópias duplicadas. Esta alteração deixa de declarar suporte a Babylon 7/8; não contornar a faixa peer com force/legacy-peer-deps. Outras versões 9.x admitidas pela faixa ainda exigem validação no projeto consumidor. Conferir a versão publicada da biblioteca: o exemplo acima requer uma distribuição que contenha esta migração.
16
+
17
+ O pacote local distribui runtime JS, tipos, licença, README, estes guias e CLI offline `pose-flow` para inspeção/preparação/validação/bake. Não inclui Blender, assets, perfis ou bancos prontos. A versão publicada ainda deve ser conferida. Os comandos `npm run bake` e `npm run validate:assets` abaixo são do repositório de desenvolvimento, não scripts automaticamente instalados no consumidor.
18
+
19
+ ## 2. Contrato dos dados
20
+
21
+ Preparar uma vez, offline, um perfil JSON v2, um schema JSON v1, um conjunto de animações JSON v1, os GLB e um banco v2 com manifest.json + poses.bin. Servir esses arquivos pela pasta pública do projeto consumidor. Nunca reconstruir todo o banco em cada carregamento de avatar.
22
+
23
+ - Metros, segundos, Y acima, frente +Z. Bake/laboratório usam cena right-handed. Yaw positivo gira +Z para +X. +X coincide com a esquerda anatômica dos exemplos olhando +Z; não confundir com direita da câmera.
24
+ - Não mudar a convenção de toda uma cena existente para adaptar esta lib; adaptar os dados/referenciais do personagem ou gerar banco correspondente.
25
+ - Controller raiz deve ter escala 1; aplicar profile.scale nos rootNodes do modelo, não no controller. A escala das fontes no bake deve coincidir com a do modelo.
26
+ - `rig` mapeia papéis semânticos do schema aos nomes de nodes do modelo; `rig.root` continua obrigatório para validar o rig. boneMap associa nomes das fontes aos do modelo; não faz retargeting de bind pose, proporções ou orientação local.
27
+ - Animações são in-place. stripRoot é opcional e remove as trilhas de posição (inclusive componentes) e a variação de rotação do node indicado. Uma rotação constante de autoria integra a pose e é preservada; canais de rotação com tangentes não são considerados constantes. Usar apenas quando essa política reproduzir o que foi indexado no bake. Para clipes já preparados, **omitir stripRoot no perfil JSON**; o parser recusa string vazia no perfil. Somente no argumento rootName do BabylonMotionPlayer passar `profile.stripRoot ?? ''` para desativar essa remoção.
28
+ - Cada clipe usa nominalVelocity OU motionFile. Velocidade nominal é autoria em m/s finais, não uma medida. motionFile é entrada offline com trajetória medida de hips (proxy de pelvis) ou root dedicado; aceita looping com composição de ciclos.
29
+ - Features são definidas pelo schema. O preset `LOCOMOTION_SCHEMA` preserva 27 dimensões: pés XYZ (0–5), velocidades dos pés no mundo expressas localmente (6–11), velocidade do controller local (12–14), posições futuras XZ (15–20) e direções futuras XZ (21–26), em +0,2/+0,4/+0,6 s. Outros schemas usam dimensões/roles/horizontes próprios; usar `SchemaQueryBuilder`, sem offsets fixos.
30
+ - poses.bin guarda float32 normalizados little endian; manifest guarda mean/std usados também pela query. Usar decodeDatabase; não normalizar a query duas vezes.
31
+
32
+ Perfil v2 para um rig já compatível (trocar nomes e arquivos pelos reais):
33
+
34
+ ```json
35
+ {
36
+ "version": 2, "id": "hero", "label": "Hero",
37
+ "assetBase": "/assets/hero/", "model": "Hero.glb", "database": "/mm/hero/",
38
+ "scale": 1, "sampleRate": 30,
39
+ "rig": { "root": "root", "leftFoot": "foot.l", "rightFoot": "foot.r" },
40
+ "stripRoot": "root",
41
+ "schema": "/schemas/biped-locomotion.json",
42
+ "animationSet": "/animation-sets/hero.json"
43
+ }
44
+ ```
45
+
46
+ O arquivo do conjunto contém `{ "version": 1, "id": "hero-locomotion", "clips": [...] }`. Cada clipe mantém id/file/animation/gait/looping e nominalVelocity OU motionFile. Pode declarar rootOrientation: [x,y,z,w], quaternion unitário local absoluto para preparar a orientação estática do root indicado por stripRoot. Por exemplo: `{ "id":"walk", "file":"Locomotion.glb", "animation":"Walk", "gait":"walk", "looping":true, "nominalVelocity":[0,0,1.8] }`. Copiar o preset de schema do repositório; para fornecê-lo pelo runtime, serializar `LOCOMOTION_SCHEMA`. Os JSON externos não vêm no npm.
47
+
48
+ ### Canais de schema v1
49
+
50
+ `{ version: 1, id: 'meu-schema', channels: [...] }`. Cada canal declara `id` único, `kind`, `weight` não negativo por dimensão e `normalization: 'standard' | 'none'`. Pelo menos um peso deve ser positivo.
51
+
52
+ | kind | Opções | Dimensões |
53
+ | --- | --- | --- |
54
+ | bone-position | bone: papel semântico | XYZ atual, 3 |
55
+ | bone-velocity | bone: papel semântico | Velocidade no mundo em eixos atuais locais, 3 |
56
+ | root-velocity | nenhuma | Velocidade local do controller, 3 |
57
+ | trajectory-position | horizons: segundos futuros | XZ por horizonte, 2 × amostras |
58
+ | trajectory-facing | horizons: segundos futuros | Direção XZ por horizonte, 2 × amostras |
59
+
60
+ Exemplo de canal: `{ "id":"hand", "kind":"bone-position", "bone":"leftHand", "weight":0.5, "normalization":"standard" }`; mapear `leftHand` em `profile.rig`. Exemplo de trajetória: `{ "id":"path", "kind":"trajectory-position", "horizons":[0.15,0.35,0.75,1], "weight":3, "normalization":"standard" }`.
61
+
62
+ `standard` usa média/desvio por dimensão; desvio abaixo de 0,0001 recebe escala 1. `none` usa média 0/escala 1. Custo: soma de peso × diferença normalizada². Limites iniciais: até 128 canais, 64 horizontes por canal e na união global, em ordem crescente dentro de (0,10] s. Posição/velocidade de ossos são da pose atual; histórico/offsets temporais de ossos não estão implementados. Opções desconhecidas no schema são rejeitadas.
63
+
64
+ `compileSchema` deriva dimensões, offsets, roles e a união ordenada dos horizontes de todos os canais. Usar `predictTrajectory(input, builder.layout.horizons)`, inclusive se posição e direção tiverem horizontes distintos. Um schema só de ossos aceita trajetória vazia. `rootMotionFeatures(track,time,step,horizons)` aceita a mesma união no bake medido.
65
+
66
+ `SchemaQueryBuilder.build(pose, trajectory)` retorna features brutas + schemaSignature. `SchemaPose` contém `rootVelocity` e `bones: { [papel]: {position:[x,y,z], velocity:[x,y,z]} }`. O leitor Babylon genérico recebe `Map<papel, TransformNode>`. O manifest v2 embute schema completo, dimensões, horizontes e identidade canônica, além de mean/std. A identidade considera id, versão e conteúdo ordenado dos canais, incluindo pesos/normalização; não é um hash com perda. Não editá-la manualmente. Carregar com `decodeDatabase(manifest, bytes, expectedSchema)` e manter a assinatura produzida pelo builder na query. A busca rejeita incompatibilidades mesmo se as dimensões coincidirem.
67
+
68
+ ### Migração da base 0.1.0
69
+
70
+ O decoder rejeita bancos v1: fazer novo bake com perfil v2 ou chamar explicitamente `migrateLegacyDatabase(oldManifest, oldBytes)`. Esse helper valida apenas o antigo preset de 27 dimensões/0,2/0,4/0,6 s e retorna Database v2 sem I/O, preservando estatísticas e features normalizadas. Para salvar, usar db.manifest e encodeFeatures(db). Não migra perfis/fingerprint nem transforma um banco para outro schema. Nunca trocar só a versão ou reinterpretar os bytes. O laboratório com perfil v2 exige novo bake.
71
+
72
+ Para migrar o perfil: mover clips para o JSON de conjunto v1, remover clips do perfil, declarar version:2, schema e animationSet. No carregamento, chamar parseProfile, carregar as duas referências e chamar resolveProfile; passar o perfil **resolvido** a profileSignature. Regenerar banco se mudar qualquer uma das configurações. O conteúdo dos GLB é registrado separadamente em assetHashes no manifesto.
73
+
74
+ Os helpers antigos featureVector/BabylonPoseReader e constantes DIMENSIONS/HORIZONS permanecem exclusivos do preset original. buildDatabase sem terceiro argumento e defaultWeights() selecionam esse preset; customizar com buildDatabase(metadata, raw, schema) e defaultWeights(schema). Queries sem schemaSignature só são aceitas no preset original exato. Novas integrações devem usar o builder. Esta mudança de formatos/perfis exige migração e deve ter versão 0.x apropriada antes de publicar; nenhum pacote novo foi publicado por esta implementação.
75
+
76
+ No repositório da lib, após copiar assets e perfil:
77
+
78
+ ```powershell
79
+ npm run validate:assets -- --inspect public/assets/hero/Hero.glb
80
+ npm run validate:assets -- --profile public/profiles/hero.json
81
+ npm run bake -- --profile public/profiles/hero.json
82
+ ```
83
+
84
+ Inspeção sugere nomes, preserva ambiguidades e não transforma um rig incompatível. Registrar perfis em public/profiles/index.json apenas para o laboratório. Regenerar banco se alterar perfil, GLB, escala ou clipes. A fingerprint identifica configuração, não substitui hash dos arquivos GLB.
85
+
86
+ ## 3. Exemplo completo do adaptador
87
+
88
+ O exemplo abaixo é um módulo TypeScript para o consumidor. O jogo fornece uma Scene já compatível e um controller TransformNode sem escala que ele próprio movimenta. Usar uma instância de player/matcher/reader por personagem; fontes e banco podem ser compartilhados numa integração mais avançada. Aqui os recursos são próprios para tornar o ciclo de vida explícito.
89
+
90
+ ```ts
91
+ import { SceneLoader, TransformNode, type Scene, type AnimationGroup, type AssetContainer } from '@babylonjs/core';
92
+ import '@babylonjs/loaders/glTF/index.js';
93
+ import {
94
+ parseProfile, resolveProfile, profileSignature, decodeDatabase, MotionMatcher,
95
+ SchemaQueryBuilder, predictTrajectory, filterLocomotionGaits, toLocal,
96
+ type DatabaseManifest, type TrajectoryInput, type Gait,
97
+ } from '@pose_flow/babylon';
98
+ import { BabylonMotionPlayer, BabylonSchemaPoseReader } from '@pose_flow/babylon/babylon';
99
+
100
+ export async function createMotionAvatar(scene: Scene, controller: TransformNode, profileUrl: string) {
101
+ if (!scene.useRightHandedSystem) throw new Error('Preparar banco/referencial compatível com a cena antes da integração.');
102
+ const json = async (url: string) => {
103
+ const response = await fetch(url);
104
+ if (!response.ok) throw new Error(`Falha ao carregar ${url}: ${response.status}`);
105
+ return response.json();
106
+ };
107
+ const config = parseProfile(await json(profileUrl));
108
+ const [schemaJSON, setJSON] = await Promise.all([json(config.schema), json(config.animationSet)]);
109
+ const profile = resolveProfile(config, schemaJSON, setJSON);
110
+ const builder = new SchemaQueryBuilder(profile.schemaDefinition);
111
+ const manifest = await json(profile.database + 'manifest.json') as DatabaseManifest;
112
+ if (manifest.profileId !== profile.id || manifest.profileSignature !== profileSignature(profile)) {
113
+ throw new Error('Banco e perfil divergentes. Regenerar offline.');
114
+ }
115
+ const response = await fetch(profile.database + 'poses.bin');
116
+ if (!response.ok) throw new Error('Banco binário ausente');
117
+ const db = decodeDatabase(manifest, await response.arrayBuffer(), builder.layout.schema);
118
+ const owned: AssetContainer[] = [];
119
+ let player: BabylonMotionPlayer | undefined;
120
+ try {
121
+ const body = await SceneLoader.LoadAssetContainerAsync(profile.assetBase, profile.model, scene);
122
+ owned.push(body); body.animationGroups.forEach(g => g.stop()); body.addAllToScene();
123
+ for (const node of body.rootNodes) {
124
+ node.parent = controller;
125
+ if (node instanceof TransformNode) node.scaling.scaleInPlace(profile.scale);
126
+ }
127
+ const nodes = new Map(body.transformNodes.map(n => [n.name, n]));
128
+ for (const name of Object.values(profile.rig)) if (!nodes.has(name)) throw new Error(`Osso ausente: ${name}`);
129
+ const targets = new Map(nodes);
130
+ for (const [source, target] of Object.entries(profile.boneMap ?? {})) {
131
+ const node = nodes.get(target); if (!node) throw new Error(`Destino ausente: ${target}`);
132
+ targets.set(source, node);
133
+ }
134
+ const sources = new Map<string, AnimationGroup>();
135
+ for (const file of new Set(manifest.clips.map(c => c.file))) {
136
+ const asset = await SceneLoader.LoadAssetContainerAsync(profile.assetBase, file, scene);
137
+ owned.push(asset); asset.animationGroups.forEach(g => g.stop());
138
+ for (const clip of manifest.clips.filter(c => c.file === file)) {
139
+ const group = asset.animationGroups.find(g => g.name === clip.animation);
140
+ if (!group) throw new Error(`Clipe ausente: ${clip.animation}`);
141
+ sources.set(clip.id, group);
142
+ }
143
+ }
144
+ const playback = new BabylonMotionPlayer(scene, manifest.clips, sources, targets, 0.15, profile.stripRoot ?? '');
145
+ player = playback;
146
+ const reader = new BabylonSchemaPoseReader(controller,
147
+ new Map(builder.layout.bones.map(role => [role, nodes.get(profile.rig[role])!])), builder.layout.schema);
148
+ const matcher = new MotionMatcher(db);
149
+ const available = new Set(manifest.clips.map(c => c.gait));
150
+ const initial = Math.max(0, manifest.clips.findIndex(c => c.gait === 'idle'));
151
+ playback.update({ clip: initial, time: 0, switched: true, match: null }, 0);
152
+ // Avaliar uma vez para obter a pose inicial antes de iniciar a query.
153
+ // Fazer durante inicialização, sem intercalar com um render loop concorrente.
154
+ scene.render();
155
+ let pose = reader.read(0);
156
+ return {
157
+ beforeRender(dt: number, input: TrajectoryInput, allowStop = false, allowTurn = false) {
158
+ const gaits = filterLocomotionGaits(available, Math.hypot(...input.velocity));
159
+ if (!allowStop) gaits.delete('stop'); if (!allowTurn) gaits.delete('turn');
160
+ const local = toLocal(input.velocity[0], input.velocity[1], input.yaw);
161
+ const state = matcher.update(dt, {
162
+ ...builder.build({ ...pose, rootVelocity: [local[0], 0, local[1]] }, predictTrajectory(input, builder.layout.horizons)),
163
+ gaits: gaits as ReadonlySet<Gait>,
164
+ });
165
+ playback.update(state, dt); return state;
166
+ },
167
+ afterRender(dt: number) { pose = reader.read(dt); },
168
+ reset() { matcher.reset(); reader.reset(); pose = reader.read(0); },
169
+ dispose() { playback.dispose(); owned.forEach(asset => asset.dispose()); },
170
+ };
171
+ } catch (error) { player?.dispose(); owned.forEach(asset => asset.dispose()); throw error; }
172
+ }
173
+ ```
174
+
175
+ Ordem obrigatória por frame: **movimentar/rotacionar controller pelo gameplay → beforeRender → scene.render → afterRender**. Registrar afterRender só uma vez por frame lógico, mesmo com múltiplas câmeras. dt é em segundos. input.velocity e desiredVelocity são [X,Z] do mundo; yaw/desiredYaw são radianos; aceleração/frenagem são m/s² e turnSpeed rad/s. Derivar esses valores do controller do jogo, não da animação.
176
+
177
+ O exemplo não adiciona um render loop: integrar ao loop existente. Não usar setInterval para animação. Definir allowStop durante frenagem e allowTurn para mudança de heading em baixa velocidade conforme as capacidades reais do banco; eles disponibilizam gaits, não forçam um clipe. Não fazer `matcher.reset()` a cada frame. Resetar histórico em teleporte/reconciliação brusca; velocidades espúrias prejudicam a seleção.
178
+
179
+ ## 4. API e comportamento que a IA deve preservar
180
+
181
+ - `PoseSearch.search({features,schemaSignature,gaits?,excludedClips?,context?})` retorna melhor Match ou null. Features são brutas. Pesos e normalização vêm dos canais do schema; no preset original, pose/velocidade 1 e trajetória/direção 3 por dimensão. Para custo customizado no matcher, seus pesos estão em matcher.search.weights. Filtros de contexto/entrada/exclusão precedem também o fallback físico de velocidade.
182
+ - `MotionMatcher.update(dt, query)` retorna clip/time/switched/match. clip é índice no manifest; clip < 0 indica ausência de playback. match é o último candidato da busca, que pode diferir do playback por continuidade/cooldown; não apresentá-lo sempre como o clipe renderizado.
183
+ - Defaults: searchInterval=0.1 s, minimumHold=0.12 s, improvement=0.1, jumpPenalty=0.5, timeSeparation=0.2 s, reselectDelay=0.6 s. Critérios de tuning locais; não parâmetros oficiais do Unreal.
184
+ - Sem loop: clamp no fim, exclusão temporária do clipe concluído. Loop: tempo circula na duração. Não somar dt novamente ao tempo retornado antes de chamar o player.
185
+ - `filterLocomotionGaits`: acima de 0.2 m/s retira idle se há marcha; abaixo de 0.05 retira marchas se há idle. Preserva fallback em conjuntos incompletos e não decide prioridades de combate.
186
+ - `BabylonMotionPlayer` cria grupos próprios e mistura tempos diferentes do mesmo clipe. Usar update antes de renderizar. Não iniciar as mesmas fontes sobre o personagem em paralelo. Grupos pausados + goToFrame com pesos são intencionais e cobertos por testes em Babylon 9.32.1.
187
+ - `BabylonSchemaPoseReader` lê ossos semânticos visíveis após avaliação/blending; o wrapper BabylonPoseReader continua para dois pés; não substituir por pose pura do banco quando houver crossfade.
188
+ - `parseProfile` valida perfil v2; `parseSchema`/`parseAnimationSet` validam os JSON independentes e `resolveProfile` confere roles requeridos; `suggestRig` só propõe nomes; `profileSignature` identifica configuração. Validar assets offline é uma etapa separada.
189
+ - `rootMotionFeatures` e `sampleRootMotion` operam em trajetórias medidas com yaw contínuo, clamp por padrão e composição de ciclos quando looping=true. Não aplicar seu deslocamento sobre a posição já simulada do controller.
190
+
191
+ ## 5. Combate, multiplayer e recursos
192
+
193
+ Gameplay/servidor continuam responsáveis por colisões, posição, dano, hitbox, cooldowns e janelas de ação. O runtime implementado hoje é de apresentação de locomoção. Não transmitir clipId como autoridade de ataque/acerto.
194
+
195
+ **Planejado, ainda sem API implementada:** a etapa 4.1 de docs/ROADMAP.md inclui animação de combate no escopo obrigatório da 1.0.0: ataques/combos, ataques direcionais/em movimento, esquivas/rolamentos, bloqueios/contra-ataques, reações a impactos e transições com locomoção. Pose Search escolherá variante e entrada entre candidatos autorizados pelo consumidor, com intervalos de entrada/saída, progressão temporal protegida, eventos de apresentação e máscaras/camadas. Não cadastrar novos gaits de combate nem presumir que as opções atuais de continuidade garantem execução segura de golpes. Regras de combo, interrupção e relógio de ataque continuarão externos; eventos visuais não aplicarão dano nem controlarão janelas autoritativas. Motion warping segue como expansão posterior, fora do aceite in-place da 1.0.0.
196
+
197
+ O player usa corpo inteiro por padrão e aceita máscara opcional de nomes exatos dos nodes do modelo no sétimo argumento de opções (ver seção da Fase 4). Players da biblioteca com máscaras sobrepostas são recusados; grupos externos do consumidor não são detectados automaticamente. Não sobrepor grupos nos mesmos nodes. Para transferir controle de corpo inteiro, dispor o player anterior antes de criar o próximo; para camadas, definir propriedade disjunta. Reiniciar a seleção a partir da pose visível ao retornar. Isso não implementa contratos de execução de golpes/combos da etapa 4.1.
198
+
199
+ Banco e fontes podem ser carregados uma vez por cena; player, matcher e histórico do leitor precisam ser individuais. Liberar grupos do player na remoção do avatar. Não dispor fontes compartilhadas antes de todos os consumidores. O exemplo possui seus próprios assets e não dispõe o controller fornecido pelo jogo.
200
+
201
+ ## 6. Diagnóstico e verificação
202
+
203
+ | Sintoma | Conferir |
204
+ | --- | --- |
205
+ | Arquivos não encontrados | Perfil e assets devem ser servidos pelo consumidor; não vêm no npm |
206
+ | Banco desatualizado | Perfil/fingerprint e novo bake; substituição de GLB exige bake mesmo sem mudar perfil |
207
+ | Ossos ausentes/deformação | Nomes, boneMap, bind pose, retargeting prévio; não apenas renomear ossos |
208
+ | Sem animação/T-pose | Fontes corretas, bootstrap, update antes do render, leitura depois |
209
+ | Idle andando ou marcha parado | Gaits permitidos por velocidade, unidades, pesos e cobertura do banco |
210
+ | Pés deslizam | Velocidade nominal, escala, velocidade real, direção; não há foot locking/IK |
211
+ | Movimento duplicado | Root motion não removido ou aplicado junto ao controller |
212
+ | Direção invertida | Referencial da cena/câmera, yaw e transformação mundo→local |
213
+ | Muitos saltos | dt/unidades, reset indevido, pesos, minimumHold/improvement e qualidade dos clips |
214
+
215
+ No consumidor, testar idle, aceleração, walk/run, lados/costas quando disponíveis, frenagem, giro, transições, teleporte, múltiplos avatares e remoção. Medir no navegador alvo; resultados de benchmark Node não são orçamento de renderização. Um sample correto não prova qualidade de todo o ciclo.
216
+
217
+ Não implementados: retargeting automático, IK/foot locking, contatos de pés, motion warping, movimento autoritativo por root motion, worker, PCA/KD-tree, quadrúpedes ou schema visual configurável. Inertialization opcional está descrita na seção da Fase 4, com limites próprios. Não prometer eliminação de sliding ou paridade com Unreal.
218
+
219
+ Fase 1 implementa schemas configuráveis e arquivos de conjunto separados. O laboratório oferece biped-locomotion (27 dimensões) e biped-hands (33 dimensões, mãos/horizontes diferentes). A implementação parcial da Fase 3 acrescenta contexto extensível, intervalos, roteamento de bancos e política de idle, conforme a seção abaixo. Não há síntese de olhar, camadas ou comparação conjunta de custos entre bancos independentes. Preparação genérica GLB está implementada na Fase 2; retargeting automático continua futuro; os schemas não adaptam animações de rigs incompatíveis.
220
+
221
+ ## 7. Manutenção obrigatória
222
+
223
+ Planejamento neural (28/10/2026): `docs/features/animacao-neural/README.md`, no repositório, analisa treinamento offline com animações do projeto, um híbrido de candidatos e a alternativa de síntese neural, com estimativas de esforço e custos separados entre preparação, navegador e servidor. Nada disso está implementado ou distribuído no npm. O plano recomenda execução no cliente, preservando a autoridade de gameplay/servidor; não há modelo inicial, treinamento automático, runtime neural ou novo comando/API disponível. As contas são cenários com premissas, não medições nem garantias de custo/desempenho.
224
+
225
+ Investigação documental (28/10/2026): no repositório, `docs/features/locomocao-web/README.md` reúne oportunidades de trajetória, transições, diagnóstico, múltiplos personagens e cenários repetíveis, com fontes e critérios de aceite. É planejamento, não uma API disponível; não acrescenta dependências, código externo, modelos neurais ou recursos ao runtime. Inertialization, worker, índices acelerados e novos diagnósticos continuam futuros. A pasta de pesquisa não acompanha o pacote npm atual.
226
+
227
+ Para toda implementação/correção na biblioteca, revisar e atualizar **este arquivo fonte na mesma alteração**, inclusive registrando correções internas relevantes aqui. Atualizar exemplos, contratos, limitações e diagnóstico afetados. Só descrever como validado o que foi executado. Não editar dist/AI_USAGE.md manualmente.
228
+
229
+ Rodar typecheck/testes/build conforme a mudança; `npm run check:docs` confirma que dist contém as cópias atuais. `prepack` recompila e copia os documentos antes de empacotar/publicar; isso não substitui a revisão semântica do tutorial. Usar docs/PUBLISHING.md para release; publicação exige pedido explícito do mantenedor.
230
+
231
+ Registro de manutenção (28/10/2026): tutorial inicial da série 0.1.x, entradas npm atualizadas para @pose_flow/babylon, perfis genéricos, bootstrap por pose animada, filtros de velocidade e políticas de root/blending documentados. Banco e perfis de exemplo continuam fora do npm. Guias passam a acompanhar build/pacote a partir da próxima versão publicada; não assumir que estavam na primeira 0.1.0.
232
+
233
+ Revisão do roadmap (28/10/2026): documentadas as diferenças entre perfis reutilizáveis atuais e schemas/conjuntos configuráveis planejados. Nenhuma API ou capacidade de animação foi alterada nesta revisão documental.
234
+
235
+ Complemento do roadmap (28/10/2026, histórico anterior à task-13): explicitados múltiplos idles e a distinção entre deslocamento, corpo e olhar. A task-13 implementa filtros/política descritos abaixo; cobertura visual de olhar lateral permanece pendente.
236
+
237
+ Marco de estabilidade (28/10/2026): o roadmap passa a exigir uma release estável 1.0.0, com API/dados versionados, migração, pipeline distribuível, múltiplos rigs, cenários visuais, desempenho e instalação em consumidor independente validados. O processo prevê versões candidatas antes da release. A base atual permanece 0.1.0; este marco não declara recursos futuros implementados nem autoriza publicação imediata. Consultar ROADMAP.md para o escopo e PUBLISHING.md para os critérios de release.
238
+
239
+ Fluxo de testes práticos (28/10/2026): o MMORPG será consumidor durante o desenvolvimento. No repositório da lib, task/summary.md lista relatos por prioridade e status PENDENTE/PARCIAL/CONCLUIDO; task/_modelo/ fornece o relato e pastas de evidências para copiar em task/task-NN/. Registrar versão instalada, perfil/banco, reprodução, esperado/observado e validações executadas. Problemas exclusivos do consumidor aguardam confirmação no cenário original antes de concluir. O acompanhamento é manual, não acompanha o npm e não implica integração já aplicada ou nova API.
240
+
241
+ Restrição de escopo (28/10/2026): neste projeto/chat é proibido alterar o jogo em D:\MMORP_WEB\mmorpg-polygon-multiplayer\mmorpg-polygon-multiplayer, inclusive assets, dependências, configuração e arquivos gerados, ou executar comandos que escrevam nele. Consultas para análise são somente de leitura. Corrigir a biblioteca aqui; se a causa for do consumidor, registrar diagnóstico/orientação na task. O usuário ou responsável pelo jogo realiza a integração e os testes práticos naquele projeto. Exemplos de integração deste guia são instruções para o consumidor, não autorização para este chat modificar o jogo.
242
+
243
+ Manutenção — Fase 1 (28/10/2026): formatos de banco/perfil v2; schema v1 com canais/normalização/horizontes configuráveis; identidade canônica e rejeição de incompatibilidades; migração explícita do banco v1; conjunto de animações separado; leitor Babylon genérico; bake usa o mesmo player/modelo alvo e política de root do runtime. Exemplos e ciclo de integração acima atualizados. Ferramentas, schemas/perfis/conjuntos e bancos continuam fora do npm; nenhuma publicação realizada. Evidências e limites da implementação estão em docs/VALIDATION.md no repositório.
244
+
245
+ Correção de strafe — task-05 (28/10/2026): preservada a orientação constante de autoria do root em bake/player, mantendo remoção de deslocamento e heading variável. Adicionado Clip.rootOrientation (e campo homônimo no conjunto): quaternion unitário local absoluto aplicado como parte da pose, com crossfade, sem alterar o controller. Exige stripRoot mapeado; sem override, preserva a rotação constante da fonte ou usa a orientação inicial do rig quando a rotação da fonte varia. Criar o player antes de animar o rig, para capturar a referência inicial correta. rootOrientation é preparação explícita da pose, não retargeting, IK, motion warping nem comando de yaw do gameplay. Alterá-la exige novo bake. Bancos anteriores desta base que removeram uma orientação constante também devem ser regenerados; a assinatura do schema não identifica mudanças no algoritmo de preparação.
246
+
247
+ O pack KayKit possui strafes de corrida com orientação de autoria ±60°, inadequada à trajetória lateral pura do exemplo. O conjunto kaykit configura rootOrientation com yaw ±90° para alinhar as pernas à direção lateral. Isso gira a pose do rig; não implementa controle independente de torso/olhar. O laboratório passa a testar strafes à velocidade nominal declarada (3,2 m/s nesses exemplos), inclusive A/D puro com Manter orientação quando houver clipe nominal alinhado. A velocidade é de autoria, não medição automática. A inspeção de pés próximos ao chão é somente diagnóstico offline, não contato/foot locking em runtime; sliding residual ainda pode existir, especialmente em transições e escalas diferentes.
248
+
249
+ ## Manutenção — primeiro incremento da Fase 2 (28/10/2026, histórico)
250
+
251
+ Clipes aceitam `range: [inicio,fim]` em segundos. O bake guarda `sourceStart` e duração recortada; BabylonMotionPlayer aplica o mesmo intervalo, incluindo o primeiro frame da fonte e loops. Fazer novo bake.
252
+
253
+ `motionFile` aceita source `root` ou `hips` e looping. `sampleRootMotion(track,time,looping=false)` e `rootMotionFeatures(track,time,derivativeStep,horizons,looping=false)` compõem deslocamento e yaw dos ciclos, inclusive negativos. `cropRootMotion(track,start,end)` interpola limites. Velocidade nominal é autoria. A composição não corrige emenda de pose nem elimina sliding.
254
+
255
+ O manifesto v2 ganha `assetHashes` SHA-256 por URL de GLB/trajetória. A fingerprint continua somente configuração. O laboratório exige hashes e recusa assets alterados; bancos antigos exigem novo bake. `decodeDatabase` valida o formato, sem ler arquivos/rede. O consumidor externo deve conferir hashes ou controlar identidade dos assets na distribuição. O laboratório faz uma leitura extra dos bytes, além do carregamento Babylon.
256
+
257
+ CLI Node incluída no pacote local: `pose-flow inspect modelo.glb`, `pose-flow validate --profile public/profiles/rig.json`, `pose-flow bake --profile public/profiles/rig.json`. `--public-dir caminho` muda a raiz dos URLs públicos em validação/bake; `--profile` é caminho de arquivo. Caminhos relativos ao diretório atual do consumidor. A base atual usa Babylon 9.32.1; o loader GLTF 9.32.1 é dependência do pacote. A preparação offline original foi testada com Node 24.21.0 e Babylon 7.54.3; os resultados da migração estão na task-15. Não fazer bake no início do jogo. Assets, perfis e bancos são fornecidos pelo consumidor. Não houve publicação.
258
+
259
+ Consulte PROFILES.md para remoção de root e roteiro offline. Esse primeiro incremento foi parcial; as pendências de preparação foram atendidas na continuação abaixo. Retargeting automático, IK e inertialization não foram implementados.
260
+
261
+ ## Manutenção — preparação e validação da Fase 2 (28/10/2026)
262
+
263
+ A CLI distribuída inclui `pose-flow prepare --config preparation.json`. JSON v1: input/output GLB distintos, animation, rootNode, source (`hips`/`root`), sampleRate (1–120), position (`horizontal`/`all`), heading boolean; opcionais unitScale positivo e yawOffset em radianos. Paths relativos ao diretório atual. Ver PROFILES.md para contrato, exemplo e limites. A ferramenta exporta um GLB com trilhas preparadas, `.motion.json` com a trajetória medida e `.preparation.json` com configuração/hash/proveniência técnica. Executar offline. A licença da fonte continua sendo responsabilidade de quem fornece o asset.
264
+
265
+ A política horizontal retira XZ e preserva bob Y; all fixa XYZ no ponto inicial. heading=true retira variação de yaw, preservando orientação inicial e inclinação. Dados são reamostrados linearmente na taxa configurada. unitScale/yawOffset são aplicados ao GLB e à medida em conjunto; não substituem a escala do perfil. GLB precisa ser autossuficiente, com um buffer, início zero, escalas positivas/uniformes e sem shear. Para scene roots animados, converter eixos/unidades antes. Seleção de osso, eixos e política é explícita, sem inferência por personagem. Não aplicar stripRoot novamente à trilha já retirada.
266
+
267
+ `pose-flow validate --profile ... --check-bind` compara geometria de bind relativa ao root e cobertura de joints; exige um skin e inverse bind matrices MAT4 float32. Falta de dados falha. Essa checagem detectou Medium → Large incompatível apesar dos mesmos nomes; Large → Large passou. Não equivale a retargeting nem valida contatos.
268
+
269
+ O descritor pode conter `averageVelocity`, média das velocidades locais medidas das amostras do bake. O laboratório usa essa referência para velocidade de teste de marcha; nominalVelocity continua autoria. O jogo continua responsável pelo deslocamento, colisões e combate. A câmera do laboratório se enquadra pelo tamanho do modelo, sem condições por nome.
270
+
271
+ Novas demonstrações por JSON: mannequin-large (126 poses, idle/walk/run próprios), paladin-measured (351 poses, caminhada medida em loop e parada sem loop preparada a 2×), mannequin-retarget (32 poses, experimento offline de caminhada Medium → Large). O último usa constraints/bake nativos do Blender configurados explicitamente, com bob sem recalibração de proporção; não é retargeting automático da biblioteca, não tem idle/contatos e não valida rigs arbitrários. A reprodução foi conferida no navegador. Os GLB/JSON/licenças não entram no pacote npm.
272
+
273
+ A preparação exige novo bake quando mudar bytes ou políticas. Foram comparadas as poses dos sete perfis e a velocidade das poses medidas num controller deslocando/girando, inclusive emenda: erro amostral máximo aproximadamente 0,003 m/s. Isso não garante suavidade de qualquer dataset nem ausência de sliding. Retargeting automático próprio, IK, foot locking e inertialization continuam futuros. Nenhuma alteração ou teste no MMORPG e nenhuma publicação.
274
+
275
+ ## Manutenção — frenagem do laboratório (task-07, 28/10/2026)
276
+
277
+ `Query.velocityTolerance?: number` ativa uma faixa física de compatibilidade no canal root-velocity, em m/s, antes do custo ponderado de poses. Requer esse canal no schema; valores negativos/não finitos falham. Padrão ausente mantém a busca anterior. Filtros de gait/excludedClips são aplicados antes da faixa. Se nenhum candidato estiver dentro dela, comparar somente os de menor erro de velocidade, preservando fallback sem liberar indiscriminadamente poses paradas. O núcleo continua sem engine/I/O.
278
+
279
+ Quando o tempo corrente fica incompatível e há um candidato distante melhor em velocidade, MotionMatcher pode trocar após minimumHold, sem exigir a margem de melhora do custo de pose. Mantém searchInterval e timeSeparation; não reinicia por pequenas diferenças entre amostras próximas. A faixa não é garantia frame a frame: cobertura incompleta, avanço entre buscas e retenção podem produzir erro maior. Não altera o controller nem janelas de combate. A nova opção tem uma passagem adicional linear sobre os candidatos; `npm run benchmark -- --velocity-band` mede esse caminho em Node.
280
+
281
+ O laboratório usa faixa de 0,2 m/s ao frear e crossfade de 0,30 s. Exibe Ritmo do clipe (velocidade indexada, nominal ou medida conforme o descritor) para comparar com Velocidade do controller. Isso não mede contato dos pés.
282
+
283
+ A preparação aceita `playbackRate` opcional entre 0,25 e 4 (padrão 1). Valores acima de 1 encurtam o GLB e a trajetória, aumentando a velocidade medida, sem mover o controller em runtime. Todos os tempos dos canais LINEAR/STEP são reescalados; CUBICSPLINE em canais retidos exige reamostragem offline anterior quando rate difere de 1. O relatório e o bake registram a duração resultante. A taxa sampleRate é aplicada antes dessa alteração temporal. Alterar rate exige preparar e fazer bake novamente.
284
+
285
+ No perfil paladin-measured, a parada a 2× substitui a parada lenta no conjunto; o arquivo original e o perfil Paladin legado foram preservados. Não é nominalVelocity disfarçada: os samples e o GLB foram retimados em conjunto e o banco usa velocidades derivadas. O resultado reduz a seleção de poses quase estáticas durante o avanço, mas não implementa contatos, IK ou foot locking e não promete eliminar sliding residual de transições/datasets incompatíveis. Evidências e limites em task/task-07/task.md.
286
+
287
+ ## Manutenção — tremida na saída (task-08, 28/10/2026)
288
+
289
+ `Query.allowTemporalReselection?: boolean` controla somente saltos de tempo dentro do mesmo clipe looping. O padrão true preserva o comportamento anterior. False deixa o ciclo corrente avançar normalmente e completar a emenda; a busca continua ativa e pode selecionar outro clipe. Não bloqueia trocas obrigatórias por gait/exclusão/término nem replanejamento de one-shots. Também não é uma trava de contatos ou uma escolha manual de clipId.
290
+
291
+ O laboratório define false quando a intenção está estável (erro de velocidade desejada/atual menor que 0,05 m/s e erro angular menor que 0,08 rad). Em aceleração, frenagem ou giro volta a permitir replanejamento. Esses limiares são política do consumidor de demonstração, não limites universais da biblioteca. Outros consumidores devem considerar também troca de contexto/ação, teleporte e reconciliação; não manter a fase protegida quando o estado da personagem exige uma nova decisão. Resetar matcher/reader nos casos já descritos no tutorial.
292
+
293
+ A investigação encontrou retrocessos repetidos de aproximadamente 0,7 para 0,5 s na caminhada, com feedback entre crossfade/velocidade da pose visível e nova busca. No cenário reproduzido, proteger a fase removeu 12 retrocessos amostrados em dois ciclos de caminhada. Os testes mantêm busca para outro clipe, retorno ao replanejamento, wrap natural e o cenário de frenagem corrigido. Dados/bancos não mudam por essa opção. Não há inertialization ou foot locking novos.
294
+
295
+ ## Manutenção — planejamento de animação de combate (task-12, 28/10/2026)
296
+
297
+ Roadmap ampliado por pedido do mantenedor para incluir combate visual como entrega obrigatória da 1.0.0, com contratos e critérios próprios na etapa 4.1. Esta alteração é documental: não adiciona APIs, eventos, máscaras, bancos de combate ou garantias de sincronização ao runtime atual. O procedimento de transferência de controle de corpo inteiro acima continua sendo o contrato disponível. Implementação e validação de combate permanecem pendentes; a task-10 continua acompanhando máscaras. Sem mudança no processo de build/publicação.
298
+
299
+ ## Manutenção — seleção contextual, Fase 3 parcial (task-13, 28/10/2026)
300
+
301
+ Clipes do conjunto/manifesto aceitam `context?: Record<string,string>` e `entryWindows?`, `exitWindows?`, `excludedWindows?`. As chaves são semântica explícita do consumidor: por exemplo set/equipment/posture/look/action/combo. Não inferir contexto de nomes de arquivos/clipes. Campos opcionais preservam os formatos perfil v2/banco v2/conjunto v1; bibliotecas anteriores não aplicam esses filtros e não devem reproduzir bancos que dependam deles. Alterar metadados exige novo bake/fingerprint.
302
+
303
+ ```ts
304
+ const matcher = new MotionMatcher(db, {
305
+ idleVariation: { interval: 4, historySize: 2 },
306
+ });
307
+ // A cada frame, após construir features/trajectory no referencial atual:
308
+ const state = matcher.update(dt, {
309
+ ...builder.build(pose, points),
310
+ gaits, // Set(['idle']) ativa a política enquanto parado
311
+ context: { set: 'relaxed', posture: 'standing', look: 'forward' },
312
+ allowTemporalReselection: false, // usar apenas quando a intenção estiver estável
313
+ });
314
+ player.update(state, dt);
315
+ ```
316
+
317
+ Todas as chaves solicitadas precisam existir com valor igual no candidato; metadado ausente não é curinga. Contexto/gait/excludedClips e intervalos de entrada/exclusão são aplicados antes do custo e do fallback de velocidade. Pool vazio retorna null; matcher sem clipe válido devolve clip=-1 e o player limpa a reprodução. Se a reprodução corrente ainda for compatível mas não houver novas entradas, ela continua naturalmente. O consumidor decide a apresentação alternativa; nunca interpretar ausência como permissão de ação.
318
+
319
+ Intervalos fechados `[início,fim]` usam segundos relativos ao clipe recortado/retimado, dentro de `[0,duration]`. Ausente libera; entryWindows=[] impede entradas, exitWindows=[] impede saídas discricionárias, excludedWindows=[] não exclui nada. As entradas fora das janelas e dentro das exclusões não concorrem, mas a reprodução atravessa esses trechos. Não há remoção física dos samples. exitWindows limita trocas discricionárias no tempo corrente; término, contexto/gait inválido e excludedClips prevalecem. dt que pula a janela aguarda próxima oportunidade ou término. Não é contrato seguro de golpes/combos nem trava autoritativa de saída.
320
+
321
+ idleVariation é opt-in e só atua quando gaits contém exclusivamente idle. Antes de interval mantém o clipe, respeitando a política de replanejamento temporal. Depois busca outro idle elegível por custo, sem sortear/forçar clipId ou tempo; historySize controla variantes recentes a evitar. Se necessário relaxa histórico antigo, nunca os filtros do consumidor. Se só existir uma variante, mantém o ciclo. A troca respeita minimumHold e exitWindows; no máximo uma variação por update, sem recuperar múltiplas mudanças após dt longo. Sair de idle reinicia o timer; reset limpa timer/histórico.
322
+
323
+ O perfil kaykit-context demonstra dois subconjuntos no mesmo banco/normalização com Idle_A/B e Walking_A/B/C. Corpo (yaw), deslocamento (velocidade/trajetória) e olhar (context.look) têm entradas separadas. O olhar apenas filtra clipes preparados; não gera pose de cabeça. Esse dataset cobre somente forward. Caminhada olhando lateralmente, máscaras e cobertura visual integral permanecem pendentes. Não declarar a Fase 3 concluída por esses filtros.
324
+
325
+ Para bancos independentes, `selectContextBank(banks,context)` recebe `{id,context,database}[]`, retorna o único banco compatível ou null e rejeita ambiguidade/IDs repetidos. Roteia antes da busca, sem comparar custos entre normalizações. O consumidor usa o schema daquele banco, valida rig/preparação e mantém seu matcher/player. Não há transferência automática de pose/tempo entre bancos ou validação de retargeting pelo roteador. Para competição conjunta de variantes, usar um bake compatível com estatísticas comuns.
326
+
327
+ Task-11: corrigido cabeçalho para identificar a base local sem afirmar a versão publicada atual e distinguido stripRoot do perfil (omitir) e argumento do player (string vazia). Sem alteração do parser, build ou publicação; confirmação no cenário original continua com o responsável pelo consumidor.
328
+
329
+ ## Manutenção — diagnóstico, máscaras e transições, Fase 4 (task-14, 28/10/2026)
330
+
331
+ ### Inspecionar a busca e a continuidade
332
+
333
+ `PoseSearch.inspect(query,limit=5)` faz uma passagem diagnóstica opt-in e retorna best, candidates (índice/clipe/tempo/custo, custos por canal e erro físico de velocidade quando há esse canal), contagens de rejected, exemplos limitados, eligible/total e velocityLimit. Limit é inteiro de 1 a 50. Usa a mesma busca exata, estatísticas e pesos de search; os custos por canal somam o custo total. Rejeições têm precedência única: excluded-clip, gait, context, entry-window, excluded-window, velocity. Contagens não representam todas as causas simultâneas. O limite físico reflete o fallback do pool elegível; null significa não aplicado ou pool vazio.
334
+
335
+ `new MotionMatcher(db,{diagnostics:true})` guarda snapshot da query da última busca efetiva, incluindo exclusões internas de término/histórico de idle. `matcher.inspect(limit)` inspeciona esse snapshot; retorna null sem diagnóstico ou antes da primeira busca. Não representa necessariamente a query do frame atual. `matcher.decision` informa se houve busca, motivo da decisão, clipes anterior/atual, custos comparados e parâmetros de continuidade. Entre buscas reason=search-interval. Histórico do laboratório armazena até 32 trocas; o núcleo não acumula um log ilimitado. Inspeção tem custo adicional e deve ficar desligada/menos frequente fora de diagnóstico.
336
+
337
+ `readCandidateTrajectory(db,index)` reconstrói metros/direções por horizonte com mean/std do banco. Position/facing são opcionais conforme os canais presentes. Não confundir com posição absoluta do controller. O laboratório desenha trajetória desejada em verde e a do candidato em laranja no referencial atual; candidato pode diferir do playback por continuidade.
338
+
339
+ ### Máscaras e propriedade
340
+
341
+ ```ts
342
+ const lowerMask = profile.masks?.['lower-body'];
343
+ const upperMask = profile.masks?.['upper-body'];
344
+ if (!lowerMask || !upperMask) throw new Error('Perfil sem máscaras de pernas/tronco');
345
+ const legs = new BabylonMotionPlayer(scene, clips, sources, targets,
346
+ 0.30, profile.stripRoot ?? '', { mask: lowerMask });
347
+ const torso = new BabylonMotionPlayer(scene, clips, sources, targets,
348
+ 0.30, profile.stripRoot ?? '', { mask: upperMask });
349
+ // Atualizar ambos antes de scene.render(). Não somar dt novamente a state.time.
350
+ legs.update(locomotionState, dt);
351
+ torso.update(presentationState, dt);
352
+ ```
353
+
354
+ Os nomes da máscara são **do modelo**, após boneMap. Não há expansão automática de descendentes. Lista vazia, duplicada, node não mapeado/ambíguo e ausência de trilhas elegíveis falham. A propriedade é por node, incluindo todas as suas propriedades, reservada no construtor e liberada em dispose (não em clip=-1/reset). Dois players da lib sobre o mesmo node são recusados. Não há detecção de AnimationGroups criados fora da lib: o consumidor coordena esses grupos. Partilhar fontes é permitido; grupos de reprodução são próprios e fontes permanecem intactas.
355
+
356
+ Máscaras respeitam range/sourceStart, loops, rootOrientation/stripRoot e crossfade de tempos do mesmo clipe. O root só recebe a orientação constante se pertencer à máscara; clipes das outras camadas herdam a transformação do pai controlado pela base. Não duplicar root em duas máscaras. Comparar trilhas locais e poses dos ossos selecionados sob a mesma cadeia de pais; combinação de camadas não equivale à pose de corpo inteiro de um único clipe. Uma ação de tronco ainda herda deslocamento/orientação de hips/root da base. Preparar rigs/clipes de acordo.
357
+
358
+ Perfis v2 aceitam `masks: {nome:[nodes...]}` e `layerPreview:{baseMask,overlayMask,context}` opcional apenas para o laboratório. layerPreview exige duas máscaras disjuntas e contexto explícito; não autoriza ação de gameplay. O novo perfil kaykit-transitions permite pernas em locomoção e Interact no tronco, com entrada escolhida por Pose Search e avanço visual sequencial simples. É uma prévia de apresentação; não entrega ataques, combos, eventos/janelas autoritativas ou todas as garantias da etapa 4.1. Task-10 permanece parcial até confirmação no cenário original pelo responsável do jogo.
359
+
360
+ ### Inertialization opcional
361
+
362
+ No mesmo argumento de opções: `{transition:'inertialization',inertialHalfLife:0.1}`. Padrão transition=crossfade preserva a reprodução anterior. Inertialization mantém um único grupo ativo e captura pose local visível/velocidade de apresentação ao trocar clipe ou tempo. Calcula residual para a pose/velocidade de entrada, amortecendo-o criticamente sobre posição/escala e log de quaternion. Não continua avaliando o clipe de saída; interrupções partem da pose já corrigida. A meia-vida é em segundos e corresponde a metade do offset com velocidade residual inicial zero. `decayInertialOffset` é a função escalar do núcleo, sem engine.
363
+
364
+ Suporte atual: trilhas locais position/scaling/rotationQuaternion, inclusive componentes compatíveis. Euler não removido e outras propriedades são recusados no modo inercial. A correção de root constante continua compartilhada com o crossfade/bake. A derivada de entrada é amostrada da animação em ±1/120 s (ou passo menor para clipe curto), com loops/recortes corretos; a de saída vem de poses visíveis consecutivas. Rotação usa aproximação em espaço tangente, sem garantia de continuidade angular exata para grandes giros; velocidades/seams ruins e diferenças grandes de escala podem causar overshoot. Validar conteúdo e meia-vida por rig. Não é IK, foot locking, correção de contato nem garantia de melhor aparência que crossfade.
365
+
366
+ Manutenção numérica: o residual evita a operação Infinity × 0 após pausas muito longas; elapsed=0 preserva exatamente o offset mesmo com meia-vida positiva muito pequena.
367
+
368
+ Chamar update antes de scene.render e ler a pose depois. A correção é aplicada em onAfterAnimationsObservable, antes das matrizes mundiais/rendering; respeita a máscara. dt=0 não estima nova velocidade; há no máximo um avanço do residual por update, mesmo se renderizar várias vezes. O primeiro frame após uma troca mantém o offset inicial (sem consumir dt da troca). `player.reset()` limpa grupos/histórico de transição e retém propriedade; chamar também matcher.reset e reader.reset em teleporte/reconciliação brusca. dispose remove observer/grupos e libera propriedade, sem dispor fontes, modelo ou controller. Nenhuma posição autoritativa é alterada.
369
+
370
+ ### Gravar/reproduzir comparações
371
+
372
+ No laboratório, escolher teste e transição, abrir “Gravar e comparar cenário”, gravar, parar e reproduzir com cada modo. Exportar/importar JSON é local. Query/resultado do matcher, dt, posição/yaw do controller e tempo apresentado (incluindo saltos de fase do teste) são registrados; não são comandos do jogo. A reprodução usa **queries gravadas**, não feedback de uma nova pose visual. Isso isola o efeito do player para comparar modos com as mesmas decisões; não comprova estabilidade de uma simulação fechada diferente. Gestos manuais de tronco não são gravados; usar corpo inteiro para comparar apresentação do cenário base.
373
+
374
+ Reiniciar a apresentação ou trocar o modo de transição/camada encerra a gravação antes do reset e preserva o trecho válido; não introduz silenciosamente um reset do matcher no meio de um arquivo reproduzível. Ao recriar players, o laboratório restaura a referência local original do modelo antes do bootstrap, para não capturar uma pose já animada como bind/baseline de tracks ausentes.
375
+
376
+ `captureScenarioFrame`, `scenarioDatabaseIdentity`, `parseMotionScenario` e `restoreScenarioQuery` estão no núcleo. Formato v1 exige identidade do manifesto/features, opções do matcher, filtros e valores finitos. Rejeita dados incompatíveis, gait inválido, tempo/clipe fora dos limites, mais de 6.000 frames ou dois milhões de valores de features; importação do laboratório limita arquivo a 16 MB. Identidade é fingerprint de conteúdo não criptográfica, sem garantia de segurança. JSON é dado; não executa código. Não editar bancos/saídas geradas para satisfazer identidade.
377
+
378
+ Fase 4 continua acompanhada como PARCIAL até registrar o aceite aplicável e confirmar as limitações de camadas no cenário original; Fase 3 continua parcial por cobertura de olhar/dataset. Schemas atuais usam pose atual e não exigiram histórico temporal de ossos novo nesta entrega. Sem mudança no processo de build/publicação; nenhuma publicação automática.
379
+
380
+ ## Manutenção — migração Babylon 9.32.1 (28/10/2026)
381
+
382
+ Core/loaders e lockfile atualizados juntos. A API pública e os formatos de perfil v2/banco v2/schema v1 permanecem iguais. Os 95 testes passaram em 9.32.1, incluindo reprodução real em NullEngine, máscaras, crossfade e inertialization. Os nove perfis existentes foram rebaked offline; os 18 arquivos manifest.json/poses.bin ficaram idênticos por SHA-256 à referência 7.54.3, sem necessidade de substituir esses bancos por causa desta migração. Isso não garante equivalência para assets/perfis externos: validar preparo/bake/player com o conteúdo do consumidor e manter exatamente a mesma remoção de root.
383
+
384
+ Babylon 9 introduz diagnóstico de limite de uniforms de esqueletos; o NullEngine usado offline reporta limites artificiais baixos e emite aviso para os rigs com 65 ossos. Esse aviso não é medição de GPU do jogo e não invalida o bake. Em dispositivos reais, conferir capacidades/renderização; não aplicar configurações de GPU do laboratório indiscriminadamente ao consumidor. A atualização não resolve a conversão LH/RH da task-09 nem confirma as camadas no MMORPG (task-10). Não há mudança em deslocamento autoritativo, combate ou contratos multiplayer.
385
+
386
+ Laboratório conferido em Edge/WebGL2 com Knight (incluindo camadas), Paladin e Mannequin Large, nos dois modos de transição; sem erros/avisos no roteiro final. Instalação isolada confirmou imports públicos, core único e CLI validate. Evidências e limites da migração: task/task-15/.
@@ -0,0 +1,110 @@
1
+ # Publicar @pose_flow/babylon no npm
2
+
3
+ Fonte deste documento: `docs/PUBLISHING.md`. O build gera `dist/PUBLISHING.md`, incluído no pacote npm. Atualizar o documento de origem, nunca a cópia.
4
+
5
+ ## Preparar uma atualização
6
+
7
+ Migração Babylon (task-15, 28/10/2026): core peer ^9.32.1, core de desenvolvimento e loaders de produção fixos em 9.32.1. A base anterior era 7.54.3; esta mudança remove o suporte declarado a 7/8 e exige instrução de migração antes de distribuir. A versão local do pacote permanece 0.2.0; escolher nova versão 0.x adequada à quebra antes de publicar, sem sobrescrever uma versão existente. Conferir `npm ls @babylonjs/core @babylonjs/loaders` na biblioteca e em instalação isolada, com core/loaders alinhados. Não copiar o lockfile da biblioteca para o jogo. O processo build/prepack continua igual.
8
+
9
+ Executar na raiz do repositório da biblioteca. Os comandos abaixo não pertencem ao projeto consumidor.
10
+
11
+ ```powershell
12
+ Set-Location D:\motion_match
13
+ npm ci
14
+ npm run typecheck
15
+ npm test
16
+ ```
17
+
18
+ Antes da versão, atualizar `docs/AI_USAGE.md` para toda implementação/correção e este documento se o processo mudar. Não prosseguir se algum comando falhar.
19
+
20
+ ## Aumentar a versão
21
+
22
+ ### Marco planejado de estabilidade 1.0.0
23
+
24
+ O roadmap do repositório exige uma release estável 1.0.0 como entrega final. A série atual 0.1.x não recebe essa classificação por esta atualização documental. Não aumentar a versão agora por causa do planejamento.
25
+
26
+ Antes da 1.0.0, concluir os critérios de docs/ROADMAP.md, preparar candidatos 1.0.0-rc.N e verificar instalação, ferramentas offline e reprodução em consumidor independente. Registrar evidências, matriz de suporte, metas/resultados de desempenho, notas da versão e migração dos contratos 0.x. Defeitos críticos ou de alta severidade no escopo suportado bloqueiam a release. Executar as verificações abaixo para o pacote candidato e novamente para o pacote final; conferir a versão exata instalada, sem presumir que latest aponta para um candidato.
27
+
28
+ Após aprovação dos critérios, a entrega final deve ser 1.0.0, em vez de aplicar automaticamente o incremento patch descrito para manutenção de 0.x. Publicar somente mediante pedido explícito do mantenedor e conferir instalação da versão publicada. Após 1.0.0, preservar contratos em patch/minor; quebras exigem major e guia de migração. A topologia dos pacotes/CLI será definida na etapa de distribuição; os arquivos e comandos atuais abaixo descrevem somente o pacote existente.
29
+
30
+ ### Mudança de formatos na Fase 1 (ainda sem publicação)
31
+
32
+ O código local usa banco/perfil v2 e schema v1. Antes da próxima release, escolher uma versão 0.x apropriada à quebra de contrato e publicar as instruções de migração incluídas em AI_USAGE.md; não tratar esta entrega como simples correção patch. A versão em package.json não foi aumentada nesta implementação. Esse era o conteúdo na Fase 1. A Fase 2 abaixo inclui CLI offline; JSON externos e bancos continuam fora do pacote.
33
+
34
+ ### Manutenção da série atual
35
+
36
+ A publicação de 0.1.0 foi informada pelo mantenedor. Versões já publicadas não podem ser sobrescritas. A inclusão destes documentos exige uma nova publicação para chegar aos consumidores.
37
+
38
+ Para uma correção ou atualização de documentação, executar **uma única vez**:
39
+
40
+ ```powershell
41
+ npm version patch --no-git-tag-version
42
+ ```
43
+
44
+ Se a versão local ainda for 0.1.0, isso gera 0.1.1 e atualiza package.json/package-lock.json, sem criar commit ou tag. Não repetir o comando se já aumentou a versão para essa entrega. Para funcionalidade compatível, avaliar `npm version minor --no-git-tag-version`; para quebra de contrato, planejar a versão e a migração explicitamente, especialmente enquanto a lib estiver em 0.x.
45
+
46
+ ## Build e inspeção
47
+
48
+ ```powershell
49
+ npm run build
50
+ npm run check:docs
51
+ npm pack --dry-run --ignore-scripts
52
+ ```
53
+
54
+ Conferir a versão e os arquivos listados: biblioteca JS/tipos em dist/, dist/AI_USAGE.md, dist/PUBLISHING.md, README.md, LICENSE e package.json. Personagens, animações, bancos, perfis de exemplo, testes e fontes TypeScript não entram no pacote. Ferramentas compiladas e seu runtime interno em dist/offline/ acompanham a CLI.
55
+
56
+ O `--ignore-scripts` acima é usado **somente após o build explícito**, para inspecionar sem compilá-lo novamente. No fluxo normal de `npm pack` e `npm publish`, o `prepack` executa o build e copia a documentação atual. Não usar `--ignore-scripts` na publicação normal.
57
+
58
+ Para gerar um .tgz local e testar a instalação antes de publicar:
59
+
60
+ ```powershell
61
+ npm pack
62
+ ```
63
+
64
+ No projeto de teste, instalar o caminho do .tgz exibido pelo comando anterior. Não assumir o nome/versão do arquivo. Testar imports do núcleo/adaptador e localizar o tutorial em node_modules/@pose_flow/babylon/dist/AI_USAGE.md.
65
+
66
+ ## Autenticar e publicar
67
+
68
+ ```powershell
69
+ npm login
70
+ npm whoami
71
+ npm publish --access public
72
+ ```
73
+
74
+ Verificar a conta e a permissão no escopo `pose_flow`. Concluir a autenticação de dois fatores solicitada pelo npm. Não armazenar senha, token ou códigos de recuperação no repositório/documentos. Uma IA só deve executar a publicação se o mantenedor pedir essa ação explicitamente; a autenticação interativa é feita pelo mantenedor.
75
+
76
+ Depois, conferir o registro:
77
+
78
+ ```powershell
79
+ npm view @pose_flow/babylon version
80
+ ```
81
+
82
+ No projeto consumidor:
83
+
84
+ ```powershell
85
+ npm install @pose_flow/babylon@latest
86
+ ```
87
+
88
+ Conferir a versão efetivamente instalada no lockfile e ler o tutorial daquela versão. Não copiar dist/ manualmente para node_modules.
89
+
90
+ ## Erros comuns
91
+
92
+ - E403 com mensagem de 2FA: conferir 2FA da conta e autenticar novamente. Não é erro do conteúdo do tarball.
93
+ - Sem permissão no escopo: conferir acesso à organização pose_flow e nome do pacote.
94
+ - Versão já existente: consultar a versão publicada e preparar uma versão nova; não tentar sobrescrever.
95
+ - Tutorial ausente/desatualizado: editar docs/AI_USAGE.md, executar build e check:docs, inspecionar o pacote.
96
+
97
+ ## Referências oficiais
98
+
99
+ - [npm publish](https://docs.npmjs.com/cli/v11/commands/npm-publish/): publicação e versões imutáveis.
100
+ - [npm version](https://docs.npmjs.com/cli/v11/commands/npm-version/): incremento de versão e opção sem tag Git.
101
+ - [Lifecycle scripts](https://docs.npmjs.com/cli/v11/using-npm/scripts/): prepack.
102
+ - [Configurar 2FA](https://docs.npmjs.com/configuring-two-factor-authentication/).
103
+
104
+ ## CLI offline — Fase 2, sem publicação
105
+
106
+ O build agora executa também `tsc -p tsconfig.offline.json`. `bin.pose-flow` aponta para `dist/offline/tools/cli.js`; `dist/offline/src/` contém o runtime interno das ferramentas. Os exports públicos permanecem dist/index.js e dist/babylon.js. Não importar caminhos internos da CLI como API.
107
+
108
+ `@babylonjs/loaders@7.54.3` passou de devDependency para dependency para permitir inspeção/bake após instalar. Babylon core continua peerDependency. Lockfile atualizado; versão não alterada. Conferir CLI/runtime interno e ambos os guias no dry-run. Testar instalação em diretório independente e executar `npx --no-install pose-flow validate --profile public/profiles/rig.json` e `npx --no-install pose-flow bake --profile public/profiles/rig.json`. O teste deve ficar fora do MMORPG protegido.
109
+
110
+ A CLI agora inclui `prepare --config preparation.json`, com os módulos compilados prepare/glb/rig. Conferir esses módulos no pacote. Os scripts Python de conversão e o experimento Blender não são distribuídos; Blender não é dependência do runtime nem da CLI GLB. Testar também prepare → validate → bake no pacote instalado com GLB de entrada do consumidor. `--check-bind` é opcional e exige dados de skin/bind suportados. O pacote continua sem assets, perfis, bancos ou publicação automática.
@@ -0,0 +1,29 @@
1
+ import { Quaternion, Vector3, type Scene, type TransformNode } from '@babylonjs/core';
2
+ export interface LocalPose {
3
+ position: Vector3;
4
+ rotation: Quaternion;
5
+ scaling: Vector3;
6
+ }
7
+ export type LocalPoses = Map<TransformNode, LocalPose>;
8
+ export declare function captureLocal(node: TransformNode): LocalPose;
9
+ export declare function rotationVector(q: Quaternion): Vector3;
10
+ export declare function poseVelocity(before: LocalPose, after: LocalPose, dt: number): LocalPose;
11
+ /** Owns local presentation residuals only. Evaluation happens before world matrices/rendering. */
12
+ export declare class BabylonInertializer {
13
+ private scene;
14
+ readonly halfLife: number;
15
+ private observer;
16
+ private last;
17
+ private velocity;
18
+ private residuals;
19
+ private target;
20
+ private dt;
21
+ private elapsed;
22
+ private revision;
23
+ private renderedRevision;
24
+ constructor(scene: Scene, halfLife: number);
25
+ update(target: LocalPoses, targetVelocity: LocalPoses, dt: number, switched: boolean): void;
26
+ private render;
27
+ reset(): void;
28
+ dispose(): void;
29
+ }