# Achados de 29/08 — tabelas do client, EXP, HP, skills

Registo do que se descobriu, com a prova de cada coisa e o que ficou por fazer.

## 1. Onde estão as tabelas do client

    C:\Games\Axion\ResUnpak\Interface\IE_Lod\

**909 ficheiros**, já desempacotados, um por idioma (`.lod_pt`, `.lod_en`, `.lod_rus`, ...).
Procurou-se em todo o disco D e não há lá um único `.lod`; estão só aqui.

As maiores, em português:

| tabela | bytes | tabela | bytes |
|---|---|---|---|
| quest | 8 874 574 | itemAttr | 4 311 660 |
| string | 6 509 483 | npcDialogue | 3 287 493 |
| itemBase | 6 392 460 | skillLevel | 1 948 845 |
| skillBase | 1 343 915 | npcInfo | 534 306 |
| skillData | 247 959 | petBase | 39 037 |

### skillBase decifra; skillLevel e skillData não

`tools/lod_crypto.py` abre o `skillBase.lod_pt` limpo:

    magic  20101118        (o mesmo dos outros .lod)
    u32[1] 2710            numero de registos

E 2710 bate com os **2712** ids do nosso `Skill.ini`.

O `skillLevel` e o `skillData` decifram para ruído com a mesma cifra. Não é o mesmo
formato; precisam de trabalho à parte.

### A âncora para decifrar o registo

O jogador mandou a dica do client de "Golpe de ombro" nível 1:

    Tipo: Dano      mana 6      area 1,5 m      Hits 2
    Ataque fisico Bonus  4 ~ 11        Recuperacao 4,0 s

Procurou-se o par de u32 `(4, 11)` no ficheiro decifrado: há **exactamente uma**
ocorrência, no offset **84170**, com um `2` ao lado (os 2 hits) e o float `2.0` logo a
seguir. É o ponto de entrada para mapear o resto do registo.

**Os valores não são floats.** Procurou-se o float 1,5 (a área) e não aparece uma única
vez no ficheiro — estão guardados como inteiros.

**O registo é de tamanho variável.** 760 392 bytes decifrados para 2710 registos não dá
número inteiro, e nenhum passo fixo de 40 a 400 bytes produz ids crescentes em `[0:4]`.
Não é a estrutura simples do `npcInfo`.

## 2. EXP — medida no fio

### O defeito que estava em vigor

`db.calcular_exp()` sempre soube ler a EXP e o nível da espécie e aplicar a penalização.
Mas o chamador em `world_server.py` passava `mob_id=1`, que não existe: caía sempre no
valor de recurso (24) e no nível de mob 1. **Toda a fauna dava 24 e nunca havia
penalização.** Corrigido — passa `_npc_do_alvo`.

### Como se mediu

Emparelhou-se cada subida de EXP no `08 01` (`[69:77]` LE u64, nível em `[90:94]` BE) com
a morte que a causou (`08 06` com hp=0). **Só contam os saltos com exactamente uma morte**
entre dois pacotes — o `08 01` é periódico, e foi ignorar isso que produziu o erro de
23/08 ("72 por aranha" eram três aranhas de 24).

Ferramenta: `tools/medir_exp.py` -> `Data/Monsters/MobExp.ini`.

| espécie | nível | EXP | n | origem |
|---|---|---|---|---|
| Cão branco | 26 | 610 | 215 | fio |
| Espadachim águia | 26 | 501 | 735 | fio |
| Guardião Estripador | 26 | 450 | 47 | fio |
| Peekskin | 22 | 291 | 42 | fio |
| Tirano sanguinário | 17 | 233 | 120 | fio |
| Cão pequeno | 14 | 224 | 16 | fio |
| Velocista do pântano | 14 | 174 | 49 | fio |
| Lagarto do pântano | 13 | 154 | 30 | fio |
| Javali-de-sabre | 7 | 131 | — | jogador |
| Filhote de urso feroz | 5 | 92 | — | jogador |
| Aranha pequena | 1 | 24 | 4 | fio |

A Aranha pequena tem **três fontes independentes** no mesmo número: a medição automática,
a observação do jogador, e a coluna `exp_base` do `Mob.ini` que já lá estava.

### Não há fórmula só-por-nível

Testaram-se várias formas contra os três pontos do jogador (24 / 92 / 131 aos níveis
1 / 5 / 7). Todas rebentam mais acima:

| forma | prevê nv17 | nv22 | nv26 |
|---|---|---|---|
| quadrática pelos 3 pontos | 376 | 530 | 668 |
| a·n^1,0 (a=20,4) | 346 | 448 | 530 |
| k·(n+3)² (k=1,35) | 540 | 844 | 1135 |
| **medido** | **233** | **291** | **450 / 501 / 610** |

E o argumento que fecha a questão: **três espécies de nível 26 dão 450, 501 e 610**, com
amostras de 47, 215 e 735. Mesmo nível, EXP diferente. O HP também não explica — a razão
exp/hp dá 0,48 · 0,94 · 0,99 · 0,76 · 0,72 · 0,69.

A EXP é dado por espécie, como o HP e o dano.

### Penalização por diferença de nível

Confirmou-se que **mob acima do jogador não tem penalização nem bónus**: a razão dá 1,00
em todas as diferenças de +12 a +25 com amostra.

Pontos novos no meio da curva, que faltavam (`Data/Character/ExpPenalty.ini`):

    dif 52 -> 37,3%   Tirano,  base 233 -> 87   n=4
    dif 56 -> 39,0%   Lagarto, base 154 -> 60   n=3
    dif 66 -> 12,4%   Tirano,  base 233 -> 29   n=5

Meseta longa e depois queda rápida. Bate com o relato de 2 de EXP numa Aranha ao nível 99
(8,3% com diferença 98). **Amostra de 3 a 5 abates por ponto** — mostram a forma, não o
valor exacto.

### Truncagem dupla

O jogador observou: nível 4 a matar Aranha de nível 1 dá **21**. São 24 x 0,875 = 21,0
exactos, e nós dávamos 20:

- `penalidade_nivel()` devolvia `int(87.5)` = 87
- `calcular_exp()` fazia `int(24 x 0,87)` = 20

Passa a devolver fraccionário e a arredondar no fim. É a terceira vez que este erro
aparece na EXP.

## 3. HP dos monstros

### O fio não dá o HP absoluto

O `08 06` do oficial manda a vida do monstro em **percentagem**: o campo do máximo
(`[19:23]`) é `100` em todos os pacotes de todas as espécies, e `[15:19]` é quanto por
cento resta.

Tentou-se calcular por dentro — `HP = dano / percentagem que tirou x 100`, já que o
`05 02` traz o dano em unidades. **Não presta**: dá ~100 para todas as espécies, incluindo
uma Aranha de nível 1 e um Cão branco de nível 26. Causas prováveis: a percentagem é
inteira (1% de resolução), o byte do dano estoura acima de 255, e há mais de um golpe
entre duas leituras da barra. Não se isolou qual domina, portanto não se usam esses
números.

### O defeito que estava em vigor

Nenhum mob recebia o HP da tabela. O `NpcSpawn.ini` não tem coluna de HP, portanto
`hp_max` vinha vazio e caía no valor de recurso `HP_MAX = 100` — **para toda a fauna**. A
coluna `hp_max` do `Mob.ini`, com 20 espécies medidas, não era lida em lado nenhum.

Corrigido em `db.spawns()` via `_hp_da_especie()`:

| espécie | nível | antes | agora |
|---|---|---|---|
| Aranha pequena | 1 | 100 | 50 |
| Javali-de-sabre | 7 | 100 | 133 |
| Lagarto do pântano | 13 | 100 | 204 |
| Tirano sanguinário | 17 | 100 | 336 |
| Fada | 1 | 100 | 1663 |

Das 25 espécies no mundo, 15 têm HP medido e **10 continuam com 100**. No catálogo
inteiro são 906 de 926 sem valor.

## 4. População dos monstros

O `NpcSpawn.ini` (extraído dos ficheiros `.npc` do client) tem uma coluna `quantidade` que
o `db.spawns()` lia e deitava fora. São **518 linhas que declaram 1223 monstros**.

O Urso furioso tem 12 linhas que somam **85** — tínhamos um sétimo. E as quantidades
batem com o que se contou nas capturas:

| espécie | declarado | visto no oficial | tínhamos |
|---|---|---|---|
| Aranha pequena | 181 | 140 | 57 |
| Filhote de urso feroz | 105 | 55 | 26 |
| Javali-de-sabre | 55 | 48 | 34 |

**O id dos extras tem de ser nosso.** Mediu-se antes de mexer: comparando os ids que o
oficial usa com o que cada esquema prevê, numerar por LINHA acerta 63,9% dos ids em comum
e numerar por MONSTRO acerta 0%. O client conhece um por ponto; os outros são do servidor.

Custo: o trabalho por ciclo do mundo passou de 5,1 ms de mediana para 14,3 (p90 20,8,
máximo 43,9). Ainda cabe nos 50 ms do tick, mas a folga passou de 10x para 3,5x.

## 5. Skills

O `Skill.ini` que temos veio do `String.lod` e **só tem texto**. Das 461 skills com vários
níveis, **431 têm a descrição idêntica em todos**, e nenhuma traz número de dano.

Mediram-se no fio (`tools/medir_skills.py` -> `Data/Skills/SkillMedida.ini`): **26 skills**
com dano, número de golpes por uso e recuperação.

| skill | usos | dano | hits | recup. |
|---|---|---|---|---|
| 7001 | 4292 | 62 | 1 | 1,5 s |
| 612 | 573 | 34 | 1 | 3,1 s |
| 7004 | 245 | 216 | 1 | — |
| 611 | 67 | 28 | 2 | 1,4 s |
| 7002 | 47 | 46 | 2 | 28,4 s |

**O campo do id é `[12:16]` BE do `05 01`.** Chegou-se lá por eliminação, contando quantos
valores distintos cada offset toma em 5.665 pacotes:

    [36:38]  246 valores, 3 batem com o Skill.ini   <- e um contador
    [12:16]   26 valores, 8 batem com o Skill.ini   <- este

Limites: o dano dá a volta acima de 255 (é o byte baixo); a recuperação é um **limite
superior** (menor intervalo observado entre dois usos); faltam mana e área, que a dica
mostra e o fio não.

## 6. Wiki russa — tentada, fraca

`gamexp.com` era o publicador russo e tinha a base oficial com 720 monstros, cada um com
HP, EXP, MP, respawn e agressão. O site devolve 503; leu-se do arquivo da web
(`tools/extrair_wiki_mobs.py` -> `Data/Monsters/MobWiki.ini`).

Só **94 dos 720** estavam arquivados, e **nenhum de nível baixo** — portanto não dá para
conferir contra as nossas 20 espécies medidas, que são quase todas abaixo do nível 26. Sem
essa conferência não entra na base; fica para consulta.

Nota de método: o arquivo não guardou `?from=0&limit=50` (dá 404). A primeira página está
guardada sem query nenhuma e só a partir de `from=50` é que os parâmetros existem.

Ficheiros de servidor do Karos/Rosh não existem publicamente — há vários pedidos no
RaGEZONE e nenhuma resposta com ficheiros.

## 7. Por fazer

- Decifrar o registo do `skillBase` a partir do offset 84170.
- Descobrir a cifra do `skillLevel` e do `skillData`.
- HP e EXP das 906 espécies sem valor. Com o `npcInfo.lod` original na mão e 900 tabelas
  ao lado para cruzar, vale mais tentar por aí do que pela wiki.
- Drops: **57 das 69 espécies do mundo não largam nada** (só 18 de 926 têm tabela).
- Divisão de EXP em grupo proporcional ao nível — não há captura de grupo para medir.

## 8. skillLevel e skillData decifram — era o modo errado

O `decrypt_lod_stream` tem um parametro `pular_zero`, e o proprio comentario dele ja avisava
que `skillLevel`, `skillData`, `itemBase` e `itemAttr` precisam de `pular_zero=False`. Foi
so passar:

| tabela | modo | registos | valores de byte distintos |
|---|---|---|---|
| skillBase | `pular_zero=True` | 2710 | 162 |
| skillLevel | `pular_zero=False` | **4623** | 148 |
| skillData | `pular_zero=False` | **4146** | 229 |

No modo errado saem 256 valores distintos e quase nenhum zero -- entropia de ruido. Esse e
o teste rapido para saber se uma tabela esta a decifrar bem.

### Onde estao os valores da dica

`skillBase` da o estatico: nome, id, icone, animacao. "Golpe de ombro" esta no offset 39332
e o id dele e **1010**. Os campos sao cadeias com o comprimento a frente, e ate os numeros
estao como texto -- por isso o registo nao tem tamanho fixo.

`skillLevel` da os valores por nivel, em **doubles**. Procurando a descricao "Causar dano ao
inimigo com golpes cortantes" (offset 272351) e lendo os doubles antes dela:

    -338    6.00   mana 6
    -298    4.00   recuperacao 4,0 s
    -198    4.00   \  Ataque fisico Bonus 4 ~ 11
    -190   11.00   /
    -206   10.00   ?
    -134  100.00   ?
    -126    0.70   ?

Quatro dos valores da dica batem no sitio. Faltam a area (1,5 m) e os hits (2), que nao
aparecem como double nesta janela -- ou estao mais atras, ou sao inteiros.

**Cuidado com o alinhamento:** os doubles nao estao em fronteiras de 4 bytes. Ler u32 de 4
em 4 da valores como 16400 e 16422, que sao metades de doubles (0x4010 = 4.0, 0x4026 = 11.0)
e nao campos. Foi isso que escondeu os numeros na primeira tentativa.

### Por fazer aqui

Mapear o registo inteiro a partir destes quatro pontos e gerar uma tabela por skill e nivel.

## 9. skillLevel e skillData mapeados

### skillLevel -> Data/Skills/SkillLod.ini

Registo de tamanho variavel: **284 bytes de bloco binario + 18 cadeias numericas + 1
descricao**. Com esta regra os 4623 registos leem-se de fio a pavio e sobram 32 bytes.

Bloco binario, mapeado pela dica do client e confirmado nos cinco niveis do "Golpe de
ombro", onde os valores crescem sozinhos:

| offset | tipo | campo | nv 1 -> 5 |
|---|---|---|---|
| @0 | u32 | id da skill | 5001 nos cinco |
| @4 | u32 | nivel | 1 2 3 4 5 |
| @16 | u32 | nivel exigido | 1 3 5 7 9 |
| @40 | double | mana | 6 8 9 11 12 |
| @80 | double | recuperacao (s) | 4,0 nos cinco |
| @180 | double | dano minimo | 4 7 11 16 22 |
| @188 | double | dano maximo | 11 15 21 28 36 |
| @252 | double | ? | 0,70 0,75 0,80 0,85 0,90 |

Saem **4623 linhas, 2691 skills distintas, 947 com dano**. As restantes sao passivas e
buffs. 3745 trazem nome, vindo do skillBase (o id fica 196 bytes antes do nome).

### skillData -> Data/Skills/SkillEfeito.ini

Registo de **44 bytes + 3 cadeias**. Nao sao os valores de combate -- sao os modificadores
que a skill aplica.

| offset | tipo | campo |
|---|---|---|
| @0 | u32 | id da skill (1289 batem com o skillLevel) |
| @4 | u32 | tipo do efeito, 104 valores, nenhum acima de 136 |
| @24 | double | valor 1 |
| @32 | double | valor 2 |

Saem 4146 linhas, 104 tipos, 4004 com valor. Le-se pelo cruzamento com a descricao:

    id 1105  tipo 28  valor  -5   "Maior protecao e chance de ataque"
    id 1106  tipo 28  valor -10   "Ataque 4 vezes com a espada..."
    id 1107  tipo 28  valor -15   "Defesa enfraquecida ao aumentar a forca de ataque"

### A armadilha dos doubles, duas vezes

Nas duas tabelas os doubles nao caem em fronteiras de 4 bytes. Lidos como u32 de 4 em 4
aparecem campos falsos com valores enormes -- 16400 e 16422 no skillLevel (metades de 4.0 e
11.0), 3230760960 e 3227058176 no skillData (metades altas de @24 e @32). Nas duas vezes
pareciam campos proprios e nao eram.

### O que fica por identificar

- A **area** (1,5 m) e os **hits** (2) da dica do client nao aparecem em nenhuma das duas
  tabelas. O "Golpe de ombro" nem sequer esta no skillData -- so la estao as skills que
  aplicam modificadores.
- O **@252** do skillLevel, que cresce 0,70 -> 0,90 por nivel. Parece uma taxa.
- O significado dos **104 tipos** do skillData. Mapea-los exige cruzar cada tipo com o texto
  das skills que o usam, ou observar em jogo; dos numeros nao se adivinha.

## 10. Fantasia (cash) -- comportamento medido

Cinco fichas da mesma Feiticeira de nivel 30, uma por peca equipada, mandadas pelo jogador em
29/08. Os deltas saem por subtraccao directa:

| estado | ATQ Fis | ATQ Mag | Prot. fis | Prot. mag | Saude | Critico |
|---|---|---|---|---|---|---|
| nada | 17 | 32 | 34 | 24 | 1975 | 8,8 |
| +1 peca | **25** | **65** | 34 | 24 | 1975 | 8,8 |
| +2 pecas | 25 | 65 | 34 | **50** | 1975 | 8,8 |
| +3 pecas | 25 | 65 | **67** | 50 | **2045** | **11,8** |
| +4 pecas | **26** | **69** | **100** | 50 | 2045 | 11,8 |

O que cada passo deu:

    peca 1   ATQ fisico +8    ATQ magico +33
    peca 2   proteccao magica +26
    peca 3   proteccao fisica +33
    peca 4   proteccao fisica +33

E os bonus de conjunto, que aparecem exactamente onde a dica do client os anuncia:

    3 pecas   Saude +70,00   Chance de critico +3,00
    4 pecas   Forca de ataque +6%   (17->26 no fisico, 65->69 no magico)

Os +6% batem: 65 x 1,06 = 68,9, arredondado 69.

### O que isto fecha e o que nao

FECHA a mecanica: cada peca da atributos proprios, e ha bonus a 3 e a 4 pecas, cumulativos e
aplicados sobre o total.

NAO FECHA a origem dos numeros. A `ItemPackage` (que ja temos) liga o item ao pacote e ao
efeito -- para o Traje de danca da `pacote 700257, ref 210027, efeito 60188 tipo 49 valor 92`
e `efeito 60189 tipo 50 valor 100`. Mas 92 e 100 nao sao os numeros que a ficha mostra, e nao
ha conversao obvia. O `tipo` esta identificado (o mesmo efeito_id aparece no ItemBonus como
"Forca de Ataque Fisico"), o `valor` nao.

Falta mapear `itemMixedOption` e `itemMixedSetEffect`, que decifram mas ainda nao foram lidas.

### Offsets da ficha (08 02) descobertos pelo caminho

Medidos equipando as "Exemplo das asas do Caos" (999900040) na mesma sessao, cujos valores a
nossa tabela ja tinha:

    [102]  float  proteccao fisica    +50,00  contra `defesa: 50.0` na tabela
    [126]  float  proteccao magica     +8,00  contra `defesa_mag: 8.0`

Dois campos, dois valores, ao centimo -- e sao offsets que nao estavam identificados.

## 11. itemMixedOption e itemMixedSetEffect -- mapeados

Os dois decifram e a estrutura sai limpa. Nenhum tem o cabecalho magico dos outros .lod: sao
uma contagem u32 seguida de registos de tamanho fixo.

### itemMixedOption -- 1176 registos de 16 bytes

    (pacote, grau, efeito, ordem)

    700153  grau 0  efeito 60188  ordem 1
    700153  grau 0  efeito 60189  ordem 2
    700153  grau 1  efeito 60190  ordem 1
    700153  grau 1  efeito 60191  ordem 2
    700154  grau 0  efeito 60192  ordem 1

Diz que efeitos cada pacote tem, por grau. Bate com a `ItemPackage` que ja tinhamos, que faz
a ponte item -> pacote.

### itemMixedSetEffect -- 364 registos de 12 bytes

    (conjunto, numero de pecas, efeito)

    conjunto 1   3 pecas -> efeito 60204
    conjunto 1   4 pecas -> efeito 60205
    conjunto 2   3 pecas -> efeito 60206
    conjunto 2   4 pecas -> efeito 60207

Sao 182 conjuntos, dois patamares cada. E exactamente a estrutura que a dica do client mostra
como "Pacote bonus(3)" e "Pacote bonus(4)", e bate com o que se mediu nas cinco fichas: o
bonus entra ao equipar a terceira peca e outro ao equipar a quarta.

### Onde a cadeia parte

A cadeia esta toda montada menos o ultimo elo:

    item -> pacote (ItemPackage) -> efeito (itemMixedOption / itemMixedSetEffect) -> VALOR?

O `ItemBonus` tem os mesmos efeito_id, mas com valores que nao sao os medidos:

    efeito 60188  tipo 49  "Forca de Ataque Fisico PK"  92
    efeito 60204  tipo 50  "O poder do Ataque Magico PK"  311

Medido na Feiticeira: peca 1 deu +8 fisico e +33 magico; o conjunto de 3 deu +70 de Saude e
+3 de critico. Nenhum desses numeros aparece.

Repare-se no sufixo **PK** nos nomes. Sao efeitos de outro contexto que partilham a faixa de
ids -- ou o `ItemBonus` nao e a tabela certa para os resolver.

O `tipo` esta identificado (49 = ataque fisico, 50 = ataque magico) e serve; o `valor` nao.
Falta a tabela que da valor a um `efeito` no contexto de fantasia. Nao esta no
`UniqueOptionType`, que e por codigo/grupo e nao por efeito.

Candidatas por explorar: `itemMixedExchangeOption`, `itemOptionExtract`, `itemCombAttr`.

## 12. A cadeia da fantasia, fechada

Faltava o ultimo elo e ele estava no `skillLevel` -- os "efeitos" das fantasias SAO SKILLS.
Foi por isso que nao apareciam em nenhuma tabela de item: procurava-se no sitio errado.

A cadeia inteira:

    item             ItemPackage        item_id -> pacote, ref, efeito
    pacote           itemMixedOption    (pacote, grau) -> efeitos
    conjunto         itemMixedSetEffect (conjunto, n de pecas) -> efeito
    efeito           skillLevel @264    -> codigo
    codigo           UniqueOptionType   -> (grupo, grau, descricao)

Exemplo completo, do Traje de danca (torso):

    item 100170736 -> pacote 700257, efeito 60188
    efeito 60188 no skillLevel: @264 = 40615
    codigo 40615 no UniqueOptionType: grupo 406, grau 15, "Forca de Ataque Fisico"

E o codigo e simplesmente `grupo * 100 + grau`.

### O que o `grupo` significa

O `UniqueOptionType` tem 321 codigos em 40 e tal grupos, e os nomes sao os campos da ficha:

    706  Forca de Ataque Fisico        717  Proteccao fisica
    708  Poder magico. ataques         719  Magico. proteccao
    711  Chance de Critico             721  Chance de evasao
    446  Saude                         447  Mana

Sao exactamente os atributos que se mediram a subir nas cinco fichas da Feiticeira: ataque
fisico, ataque magico, proteccao fisica, proteccao magica, Saude e critico. Nenhum de fora.

### O que fica por confirmar

RESOLVIDO NA SECCAO 13: o `grau` E o valor, em 4xx. Nao ha escala. O paragrafo abaixo fica
como registo do raciocinio, mas a conclusao dele esta ultrapassada.

Se o `grau` e o VALOR em unidades ou um patamar numa escala. Os graus vao de 1 a 8 na maioria
dos grupos e ate 20 nalguns, e os valores medidos foram +8, +33, +26, +70 e +3 -- nao ha
correspondencia directa com um grau de 1 a 8, portanto ha uma escala pelo meio.

Para a resolver bastam duas pecas de fantasia cuja diferenca de grau se conheca: mede-se o
delta na ficha das duas e a escala sai por divisao.

### Velocidade de movimento -- existe, e nao aparece na ficha

Grupo **427**. Esta no `UniqueOptionType` como qualquer outro atributo, mas a ficha do
personagem nao tem linha para ele -- confirmado nas cinco imagens da Feiticeira, onde nenhuma
das duas colunas o mostra.

Foi o jogador que o disse: "tem Velocidade de movimento tambem, mas nao mostra na ficha, mas
conta". Ou seja: atributo real, invisivel na interface. Quem implementar isto nao pode
validar pelo que aparece na tela -- tem de medir o deslocamento.

Liga-se ao que ja estava anotado no world_server: `OFF_VEL_MOV_A = 1068` e `OFF_VEL_MOV_B =
1100`, dois offsets do 02 05 marcados como NAO ligados porque escreve-los la partia o pacote.
Agora sabe-se que ha uma fonte de dados por tras deles.

### Tres familias de grupos, com os mesmos nomes

O `UniqueOptionType` repete os mesmos atributos em faixas diferentes:

    201/401/701  Forca            206/406/706  Forca de Ataque Fisico
    202/402/702  Vitalidade       208/408/708  Poder magico. ataques
    ...          ...              417/717      Proteccao fisica

Catorze atributos existem em 4xx E em 7xx com o nome identico. Nao sei o que distingue as
familias -- absoluto contra percentagem, ou item contra conjunto, ou normal contra PK (os
nomes no ItemBonus dos mesmos efeitos traziam o sufixo PK).

Isto importa porque escolher a familia errada da o atributo certo com a escala errada. Fica
por medir, e a forma de o fazer e a mesma: duas pecas de familias diferentes, e comparar.

So na faixa 4xx, sem par em 7xx: **427 Velocidade de movimento**, 435 Efeito negativo,
446 Saude, 447 Mana.

### Nove atributos NAO aparecem na ficha

Dos 25 atributos do `UniqueOptionType`, 16 tem linha na ficha do personagem e nove nao:

    Velocidade de movimento          A forca atual de um ataque fisico
    Alcance de habilidade            O poder atual do Ataque Magico
    Classificacao de ataque          Proteccao fisica atual
    Classificacao de proteccao       Proteccao magica atual
    Efeito negativo

Os quatro "atual" existem a par dos normais (`Proteccao fisica` e `Proteccao fisica atual`).
Provavelmente um e o valor base e o outro o efectivo depois de tudo somado, e so o base tem
linha na ficha.

ISTO E UMA ARMADILHA DE VALIDACAO, e vale a pena escreve-la: estes nove nao dao para conferir
pelo que muda na tela. Implementar um deles mal nao produz sintoma nenhum na ficha. Cada um
precisa do seu proprio teste:

    velocidade de movimento    medir o deslocamento no fio
    alcance de habilidade      medir a que distancia a skill ainda acerta
    classificacao de ataque    entra provavelmente na taxa de acerto (ver seccao das taxas)

### Atributos de PK -- existem, sao nove, e sao numeros a parte

O jogador levantou isto e esta certo. O switch `FUN_006076cb` do `AMo.exe` -- o mesmo que ja
dava os nomes das caracteristicas especiais -- tem um bloco contiguo de PK nos casos 49 a 57,
que espelha nove atributos de combate pela mesma ordem dos normais:

    normal                                    PK
     6  Forca de Ataque Fisico          49  Forca de Ataque Fisico PK
     8  Poder magico. ataques           50  O poder do Ataque Magico PK
    10  Classificacao de ataque         51  Classificacao de ataque PK
    11  Chance de Critico               52  Chance de Creta PK
    12  Multiplicador de critico        53  Coef. Creta PK
    16  Classificacao de proteccao      54  Classificacao de proteccao PK
    17  Proteccao fisica                55  Proteccao fisica PK
    19  Magico. proteccao               56  Proteccao magica PK
    21  Chance de evasao                57  Coef. evasao PK

Os nomes NAO foram casados por indice por mim: saem do switch lido no Ghidra e cruzado com o
`string.lod`, e a cadeia ja tinha sido confirmada ao centimo noutro caso (Asas de anjo, tipos
46/47/108 contra o que o jogador ve no ecra).

SAO VALORES INDEPENDENTES, nao o mesmo campo com outra etiqueta. No ItemBonus ha 56 itens que
carregam o normal e o PK ao mesmo tempo, e no mesmo refino os dois numeros nao tem relacao
fixa:

    item 46219  refino 0    proteccao fisica  10     PK 306,15
    item 43418  refino 0    proteccao fisica  11     PK 120,00
    item 41019  refino 0    proteccao fisica  11     PK 156,00

O PK anda 10 a 30 vezes acima e traz decimais onde o normal e sempre inteiro. Se fossem o
mesmo campo a razao seria constante; nao e.

CUIDADO COM A CONTAGEM DE LINHAS. A primeira leitura destes numeros assustou -- "Proteccao
fisica PK" com 1976 linhas contra 790 da normal, o atributo mais comum do jogo, o que nao
casa com ser so de PvP. Nao sao 1976 itens: sao 143 itens vezes os ~14 niveis de refino. A
normal da 62 itens. A chave do ItemBonus e (item_id, refino), e contar linhas conta refinos.

O SERVIDOR NAO OS SOMA, E ESTA CERTO ASSIM. O `TIPO_DE_BONUS` do db.py nao inclui 49-57, e o
comentario ja la dizia porque: dependem de um sistema de PK que nao existe. Se fossem
somados a proteccao normal, a defesa ficava uma ordem de grandeza acima do oficial.

O QUE ISTO NAO EXPLICA. Os efeitos 60188 e 60189 resolvem no ItemBonus para tipo 49 com 92,00
e 100,00. Sao valores reais e sao de PK, portanto invisiveis na ficha normal -- mas pertencem
a skill passiva 60505, que varios itens partilham (1034, 3034, 5034, 9034). O `efeito_id` e
propriedade da SKILL, nao do item. Logo aquele 92 nao e do fato: e da passiva. Os deltas
medidos no fato (+8, +33, +26, +70, +3) continuam sem origem identificada, e a hipotese "o
ItemBonus e a tabela errada" nao fica resolvida por aqui.

## 13. A escala da fantasia -- nao havia escala nenhuma  (ERRADA, ver a 43)

> **Esta seccao esta desmentida.** O valor NAO sai dos digitos do codigo: o codigo
> e uma chave do skillData, como nas skills. As quatro medicoes que a "confirmam"
> abaixo acertaram no numero com o codigo errado. Fica escrita porque o modo como
> se enganou interessa -- a correccao e a explicacao estao na **seccao 43**.

O elo que faltava desde a seccao 12 nao era uma tabela: **o valor esta dentro do proprio
codigo**. A leitura certa e

    codigo = grupo * 100 + VALOR

Os dois ultimos digitos, que se andava a tratar como um "grau" numa escala por descobrir,
sao o valor em unidades. Procurava-se uma tabela de conversao que nao existe.

### Como se confirmou

O efeito 60189 -- o torso, usado por 210 fatos -- tem duas ranhuras: `40832` e `40608`. Pela
regra dao "Poder magico. ataques +32" e "Forca de Ataque Fisico +8". A ficha da Feiticeira ao
equipar a primeira peca marcou **fisico +8**. Exacto.

Contra as cinco fichas de 29/08:

    peca 1 (torso)    fis +8         40608  g406 valor 8     EXACTO
    peca 3 (pernas)   prot fis +33   41733  g417 valor 33    EXACTO
    peca 4 (pes)      prot fis +33   41733                   EXACTO
    conjunto 3 pecas  critico +3     41103  g411 valor 3     EXACTO
    conjunto 4 pecas  forca atq +6%  43906  g439 valor 6     EXACTO

Cinco batidas exactas. A do critico ate IDENTIFICA um grupo que o UniqueOptionType nao lista:
**g411 = "Chance de Critico"**, por medicao e nao por deducao.

### O sistema inteiro sao 16 efeitos

Nao ha centenas de combinacoes: o ItemPackage liga 1764 itens a exactamente 16 efeitos,
quatro por ranhura -- dois graus vezes duas opcoes. O item sorteia UMA das duas do seu grau,
nao acumula as duas. Ver `server/Data/Items/FantasiaEfeito.ini`, gerado por
`tools/extrair_efeitos_fantasia.py`.

    torso    60188 fis+15                    60189 mag+32, fis+8
             60190 fis+25                    60191 mag+50, fis+13
    pernas   60192 prot fis+32               60193 prot mag+24
    /pes     60194 prot fis+33               60195 prot mag+15
    maos     60196 critico+3, prot fis+27    60197 class atq+6,  prot fis+27
             60198 critico+3, prot fis+29    60199 class atq+12, prot fis+29
    pe       60200 vel mov+10, prot fis+27   60201 class atq+6,  prot fis+27
             60202 vel mov+15, prot fis+29   60203 class atq+12, prot fis+29

FECHA A DUVIDA DA VELOCIDADE DE MOVIMENTO. O jogador tinha dito que existe mas nao aparece na
ficha. Aparece aqui, nos sapatos, com +10 e +15 -- g427, o grupo que ja estava anotado. Era
verdade e agora tem numero.

### O que separa a familia 4xx da 7xx

Os graus dizem-no: em 4xx vao ate 99, em 7xx nunca passam de 10 ou 11. Portanto **4xx e o
valor e 7xx e um patamar**. Todos os deltas que bateram ao centimo vieram de grupos 4xx.

O unico que nao bateu veio de 7xx: o bonus de 3 pecas traz `g700 valor 6` e o client anuncia
"Saude +70,00". Ali o 6 e um patamar e a escala continua por achar. E o unico buraco que
resta, e agora esta isolado num sitio so.

### Dois residuos, e sao os dois magicos

    medido fis +8        tabela +8     bate
    medido prot fis +33  tabela +33    bate
    medido mag +33       tabela +32    falta 1
    medido prot mag +26  tabela +24    falta 2

Os dois fisicos batem exactamente e os dois magicos ficam curtos. Nao e arredondamento: uma
diferenca de 2 em 48 nao sai de arredondar. O padrao aponta para os valores magicos da ficha
serem DERIVADOS (de Inteligencia ou Sabedoria, com multiplicador) enquanto os fisicos sao
mostrados em cru. Fica por medir, e mede-se equipando so a peca de proteccao magica em dois
personagens com Inteligencia diferente.

### Onde NAO estava

Vale registar as tabelas que se abriram e nao serviam, para nao se repetir a busca:

    itemMixedGrade      (pacote, valor, flag)  os 1 e 40 sao os LIMITES do grau
    itemMixedType       (item, tipo)           10164 entradas, tipo 1 ou 2
    itemMixedPeriod     (pacote, grau, ref, n) duracao
    itemMixedExchange   (item, efeito)         2904, outra via item -> efeito
    ItemBonus           tem os mesmos efeito_id mas com valores de PK (92, 100)

O ItemBonus foi a pista falsa mais cara: tem os ids certos, resolve-os para nomes com sufixo
PK e devolve 92 e 100. Nao sao do fato -- sao da passiva 60505, que varios itens partilham.

### Ligado no servidor

    tools/extrair_efeitos_fantasia.py   gera as tres tabelas
      Data/Items/FantasiaEfeito.ini       efeito -> (grupo, valor)      42 linhas
      Data/Items/FantasiaConjunto.ini     peca -> conjunto           10164 linhas
      Data/Items/FantasiaSet.ini          conjunto + N pecas -> efeito 364 linhas

    db.bonus_da_fantasia(entity_id)     soma pecas e bonus de conjunto
    db.bonus_do_equipamento             passou a incluir a fantasia
    world_server                        102, 126 e 170 passaram a levar o bonus

O grupo resolve-se com `grupo % 100` e cai no MESMO `TIPO_DE_BONUS` que o ItemBonus ja usava
-- a centena e a familia, o resto e o atributo. Nada a jusante precisou de mudar.

TRES CAMPOS DA FICHA NAO LEVAVAM BONUS NENHUM. Sao derivados das caracteristicas pelo
CAMPOS_DERIVADOS, e por isso mostravam um numero certo para um personagem nu e ignoravam o
equipamento por completo:

    102  proteccao fisica   levava a defesa das pecas mas nao as opcoes
    126  proteccao magica   nao levava nada
    170  chance de critico  nao levava nada

Confirmado no personagem do jogador (tres pecas do Traje de danca, conjunto 53):

    proteccao fisica   21 -> 108   = 21 base + 33 das pecas + 54 da fantasia
    chance de critico  6,6 -> 12,6 = +3 da mao e +3 do conjunto de 3

### A instancia guarda a opcao que sorteou, em bruto[76:80]

O ItemPackage lista sempre DUAS opcoes por peca e a instancia sorteia uma. Onde: o u32 LE em
**@76** dos 147 bytes. Nas pecas do jogador as tres traziam 60189 -- que e o efeito do TORSO,
correcto numa e lixo nas outras duas, porque o `dar_item` monta a peca copiando o registo de
outro item como molde.

Por isso o servidor VALIDA o efeito contra as duas opcoes do item antes de o usar, e o
`dar_item` passou a sortear e gravar um efeito valido. Sem a validacao, uma luva ganhava
ataque magico de torso.

### O que ficou de fora, e porque

    familia 7xx        o numero e patamar, nao valor. Aplicar o 6 quando a medicao diz 70
                       seria pôr um erro conhecido. Fica ignorado, com aviso no arranque.
    velocidade movim.  a fantasia da +10%/+15% e o servidor ja os soma, mas os offsets sao
                       do 02 05 (OFF_VEL_MOV_A/B) e escrever la parte o pacote. Nao ligado.
    residuo magico     mag +33 medido contra +32 na tabela, prot mag +26 contra +24.

## 14. Apagar item nao chegava ao client

O jogador: "quando deleto o itens, nao ta sumindo nao atualizando o invetario, e itens
expirado ainda estao la". Sao dois sintomas com a MESMA raiz -- o servidor mexia no banco e
nunca avisava o client, que continuava a desenhar o que tinha em memoria.

### O que o oficial manda

Do traffic.log em t=74570, quando o jogador arrastou um item para o caixote:

    +0.000  CLIENT  06 41   38 b    pedido
    +0.219    serv  06 06  158 b    o item, com quantidade 0
    +0.219    serv  06 41   38 b    o eco

Nos mandavamos SO o eco. Faltava o 06 06, que e o que tira a peca da tela.

### O byte [10] separa PACOTE SOLTO de PAR

Primeira leitura, de 77 pacotes: `[10]=1` nos 76 de mover e `[10]=0` no unico de apagar. Dai
escrevi "separa apagar de mover", que estava incompleto.

Com o apanhar do chao medido em 30/08 fecha-se melhor:

    [10] = 1   faz parte de um PAR -- mover, equipar, reorganizar: sai da posicao antiga
               com quantidade 0 e entra na nova com a real
    [10] = 0   vem SOZINHO -- apagar, e tambem apanhar do chao

Nos mandavamos 1 no apanhar, e a mensagem da tela saia sem o numero: "Voce comprou: Carat."
em vez de dizer quantos. Foi o jogador que reparou. A quantidade vai a zero nos dois casos -- no mover porque a posicao ANTIGA fica
vazia e a seguir vem outro 06 06 com a nova.

### Os expirados tinham o mesmo problema, e mais um

O `apagar_itens_expirados` devolvia so a CONTAGEM, portanto quem chamava nao tinha como
avisar. Passou a devolver os itens.

E corria tarde: so na entrada no mundo, que acontece DEPOIS de o inventario ser montado. O
inventario ja tinha sido desenhado com os expirados dentro. Agora corre tambem antes do
06 05.

FICA POR FAZER: um item que expire A MEIO da sessao continua na tela ate ao relogin. Para o
resolver e preciso um tique periodico; o oficial ha-de mandar o 06 06 na hora, e isso nao
esta medido.

## 15. A dica do oficial confirma a cadeia inteira

O jogador mandou a caixa de texto do "[Terno para banquete (torso)]" no servidor oficial.
E a validacao independente que faltava -- nao e a nossa ficha a concordar com a nossa
tabela, e o CLIENT DELES a dizer os mesmos numeros:

    Opcoes selecionadas
      Poder magico. ataques   +32,00      efeito 60189 -> g408 valor 32
      Forca de Ataque Fisico   +8,00      efeito 60189 -> g406 valor 8
    Pacote bonus(3)
      Chance de Critico        +3,00      efeito 60204 -> g411 valor 3
      Saude                   +70,00      efeito 60204 -> g700 valor 6
    Pacote bonus(4)
      Forca de ataque          +6,00%     efeito 60205 -> g439 valor 6

RESOLVE O RESIDUO MAGICO da seccao 13. A dica diz +32,00 e a tabela diz 32: a tabela estava
certa. O +33 que a ficha mostrava vem do lado da FICHA -- os campos magicos sao derivados e o
numero desenhado nao e a soma crua. Nao ha nada a corrigir na tabela.

DA O PRIMEIRO PONTO DA ESCALA 7xx: `g700 valor 6` vale Saude +70. Falta um segundo ponto
(existe `g700 valor 8` no efeito 60206) para saber se e linear.

### O registo de item, medido no oficial

Do 06 06 do oficial a equipar e desequipar a mesma peca (t=76386 a 76420):

    [0]      container      0 equipado, 1 mochila
    [1]      slot
    [2:6]    item_id        u32 LE
    [6]      slot           REPETIDO aqui quando equipado, 0 quando na mochila
    [7]      refino
    [12:16]  aquisicao      unix LE     26/08/2026 20:07
    [16:20]  quantidade     1 ao chegar, 0 na posicao que deixa
    [56:60]  validade       unix LE     02/09/2026 20:07  (7 dias exactos)
    [76:80]  efeito         torso 60189, pernas 60193, maos 60197

A NOSSA CODIFICACAO DA VALIDADE BATE: [12:16] e [56:60] em unix LE, como ja escrevemos.

### O bug: cada peca levava o efeito do TORSO

O oficial poe em [76:80] o efeito da RANHURA da peca. Nos poviamos 60189 -- o do torso -- nas
quatro, porque o `dar_item` monta a peca copiando o registo de outra como molde e o molde
trazia o efeito dele.

Corrigido no `registro_item`, ao lado do [6], e nao so no `dar_item`: assim as pecas que ja
estao no banco tambem saem certas.

    peca      guardado   enviado
    torso     60189      60189
    pernas    60189      60192   corrigido
    maos      60189      60196   corrigido
    pe        60189      60200   corrigido

## 16. O 06 46 que eu inventei escondia a fantasia

A fantasia nao aparecia no personagem, e a culpa era de um pacote que o servidor oficial nao
manda. No fio dele ha exactamente dois 06 46 do servidor e os DOIS sao ecos de cliques do
jogador -- nunca manda um por iniciativa propria, nem na sequencia de entrada, que o mesmo
log contem (02 00, 02 01, 02 05).

Nos mandavamos um a cada login, com o flag guardado da ultima vez. O sentido do flag eu tinha
deduzido da frase do jogador ("cliquei pra desativar o mostrar e ativei de novo") em vez de o
medir, e estava AO CONTRARIO: 0 nao e mostrar, e esconder. Resultado: o servidor mandava
esconder o fato a cada entrada, e o botao da aba Forma continuava a dizer que estava ligado.

Retirado. A regra que fica: **nao mandar o que o oficial nao manda.** Uma deducao a partir de
uma frase nao vale um pacote no fio.

Com isso o fato e as asas aparecem, e a ficha bate com a tabela da seccao 13:

    Proteccao fisica 140 (+119)   =  21 base + 33 das pecas + 86 da fantasia
                                     (60192 +32, 60196 +27, 60200 +27)
    Chance de Critico 11,7        =  ~5,7 base + 3 da mao + 3 do conjunto de 3

## 17. Reorganizar a mochila -- dois defeitos

### Os pares comecam em 16, nao em 14

No 06 51 do oficial o contador em [10:14] diz 18 e a partir de 14 ha 19 pares de 2 bytes --
um a mais. Comecando em 16 dao exactamente 18, e a ordem bate PAR A PAR com os 18 pacotes
06 06 que ele mandou a seguir. Os dois bytes em [14:16] valem `02 2a` nos dois pedidos que
temos, o do oficial e o nosso: nao e um par, e um campo por identificar.

Com o desvio errado, o servidor lia a lista deslocada e atribuia slots ja ocupados.

### Compactar slots colide sempre com a chave unica

O log disse-o por extenso: `Violation of UNIQUE KEY constraint`. Mover o item do slot 4 para
o 0 rebenta enquanto o slot 0 estiver ocupado por outro que so se move a seguir -- e numa
compactacao isso e a regra, nao a excepcao.

A conexao e `autocommit=True`, portanto um par de instrucoes NAO e atomico. Foi por isso que
a primeira tentativa (mover tudo para 100+slot e depois subtrair 100) deixou nove itens
presos nos slots 100 a 108: metade gravou e a outra falhou.

A saida e uma INSTRUCAO UNICA com CASE, porque o SQL Server so verifica o indice no fim da
instrucao:

    UPDATE dbo.personagem_item SET slot = CASE WHEN personagem_item_id = ? THEN ? ... END
    WHERE personagem_item_id IN (...)

Confirmado numa tabela temporaria com a mesma restricao, incluindo o caso mais duro, uma
TROCA pura de dois slots. Passa.

## 18. A duracao dos itens esta em DOIS offsets

As "Asas de anjo (24 horas)" apareciam com "Expirou." em vermelho, e no banco a validade
delas era zero -- o client le zero como expirado.

O importador do catalogo (tools/importar_atributos.py) le o indice 31 dos 39 u32 do itemBase,
que e o offset 124. Mas ha DOIS campos de duracao, e os itens de horas usam o outro. Medido
nos 19806 itens do catalogo:

    @120    40 itens    86400 / 43200 / 21600      as de 6, 12 e 24 HORAS
    @124  7636 itens    2592000 / 604800 / ...     as de 30, 7, 14, 3 dias

NENHUM item tem os dois preenchidos, e os nomes confirmam os dois lados ("O lobo Sagrado
(6 horas)" tem 21600 em @120; "Terno para banquete" tem 604800 em @124). Nao se sabe o que
distingue os campos; sabe-se que sao exclusivos, e por isso vale o que estiver preenchido.

`db.duracao_do_item` le os dois, e o `dar_item` passou a tirar dali a validade em vez de
depender do `--dias`. O `--dias` continua a valer para dar um item por um prazo diferente do
de fabrica.

## 19. O leitor de logs comia 10% dos bytes

Andei a concluir coisas a partir de capturas incompletas. O `tools/analise/ler_hook.py`
partia os pacotes na fronteira do bloco de leitura do socket: um pacote grande chega em duas
chamadas ao WSARecv, e a segunda metade era deitada fora sem aviso. **9,8% dos bytes de uma
captura**, em silencio.

O que isso escondia: o `06 05` do oficial, um inventario de 4275 bytes. Cheguei a escrever
neste documento que "o oficial nunca manda 06 05" e a partir dai a caçar o inventario no
09 02, que afinal sao 24 bytes de cabecalho e 4160 a zero.

Corrigido: cose os blocos, guardando a sobra POR SOCKET (ha mais do que uma ligacao no mesmo
log, e misturar as sobras corrompe as duas). Logo apareceram oito pacotes que nunca tinha
visto: 06 25, 06 28, 08 05, 0a 0c, 0a 0e, 0a 13, 0d 0f, 0e 0c.

E A SEGUNDA VEZ QUE O MESMO DESCUIDO CUSTA UMA CONCLUSAO ERRADA -- antes tinha sido ler so o
primeiro pacote de cada bloco. Sempre que uma leitura de log disser "o oficial nao manda X",
conferir primeiro quantos bytes o leitor deixou por consumir.

### O inventario do oficial, agora que se ve

    4275 bytes = 12 de cabecalho + 29 registos de 147     a mesma aritmetica que montamos
    [10] = 5 nos dois
    [11] = contagem
    [6:10] = o id de QUEM RECEBE

O nosso levava em [6:10] o id do personagem CAPTURADO (10654) em vez do jogador. Corrigido.

## 20. "Expirou." resolvido

O tooltip passou a mostrar "Valido ate: Sen 5,2026 (16:41) (Temporariamente)".

NAO SEI QUAL DAS CORRECCOES O CUROU, e vale dize-lo em vez de escolher uma:

    0a 0a      passamos a responder ao pedido de hora do client   -- ja estava, e falhava
    02 05[949] o relogio vinha CONGELADO em 23/08 dos moldes      -- corrigido depois
    06 05[6:10] mandava o id de outro personagem                  -- corrigido depois
    o item     999900040 tem a duracao em @124; as asas de 24 h em @120

As duas ultimas entraram juntas, e o item tambem mudou. Para separar bastava dar outra vez
uma peca de 24 horas e ver: se aparecer com data, foi um dos relogios; se disser "Expirou.",
o campo @120 quer outro tratamento.

## 21. Reorganizar, confirmado no fio

Funciona: 4 itens mudaram de slot, sem erro, e a mochila ficou compactada de 0 a 5 sem
buracos. As tres correccoes que fizeram falta:

    pares comecam em [16], nao em [14]
    os slots dos itens FORA da lista sao intocaveis -- e o que dava a violacao de chave unica
    um UPDATE unico com CASE, porque a conexao e autocommit e compactar colide sempre
    e o eco do 06 51 no fim, que o oficial manda e nos nao mandavamos

## 22. A bandeira do fato na tela de personagem: 02 01 [25]

    [25] = 0   mostra a fantasia
    [25] = 1   esconde

Medido com o controlo que faltava, e que o jogador montou: a MESMA personagem do oficial,
com o MESMO equipamento (armadura + fantasia + asas), fotografada duas vezes na tela de
seleccao, mudando so o botao entre as duas. Os dois 02 01 diferem em TRES bytes -- [4] e [5],
que sao a sequencia, e o [25]. Mais nada.

O nosso vinha a 1 do molde: mandavamos "esconder" a cada entrada, e a fantasia nunca aparecia
por mais certo que o vector estivesse.

AS DUAS CONVENCOES SAO OPOSTAS, e liguei-as ao contrario a primeira vez:

    06 46 [10]   1 = LIGAR o botao Forma       (e isto que a coluna `mostrar_fato` guarda)
    02 01 [25]   1 = ESCONDER a fantasia

Portanto [25] e a NEGACAO do valor guardado. Com a inversao trocada a tela de personagem
mostrava a fantasia enquanto o botao dentro do jogo estava desligado, e foi o jogador que deu
por isso: "tem que ficar de acordo com a Forma dentro do jogo".

Que o 1 do 06 46 e LIGAR sai do comportamento observado, nao da minha leitura de uma frase: o
client entra sempre com a Forma desligada, o valor guardado e 0 e o botao aparece desligado.
Se 0 fosse mostrar, o botao apareceria ligado.

PORQUE E QUE ISTO DEMOROU. Comparei primeiro personagens DIFERENTES -- classe, nivel, HP e
aparencia mudam todos ao mesmo tempo, e o [25] apareceu duas vezes na lista de candidatos e
foi descartado as duas por acompanhar a classe por acaso (valia 1 nos dois personagens de
classe 600 e 0 no de classe 410). So o par "mesma personagem, so o botao muda" o isolou.

A licao e a de sempre neste projecto, e ja custou o dia inteiro: **um controlo com uma
variavel vale mais do que dez comparacoes com todas soltas.**

### E o que se descobriu de caminho

Ao ir verificar os candidatos falsos apareceram dois campos mal escritos no 02 01:

    HP maximo   u32 LE em [27]    escrevia-se >H em [26]
    MP maximo   u32 LE em [35]    escrevia-se >I em [32]

Offset e ordem de bytes errados nos dois, e a escrever por cima dos vizinhos. Medido em tres
niveis do oficial (HP 185 / 332 / 2045 e MP 80 / 140 / 686), e o 2045 e o mesmo numero da
ficha da Feiticeira da seccao 10, o que fecha a identificacao. Foi tambem o que matou o outro
candidato: o byte [36] que eu andava a olhar e o byte alto do MP.

O [265] tambem foi candidato e tambem caiu: vale 4 so na personagem que usa um CONJUNTO de
armadura, e zero em todas as outras -- e a contagem de pecas de conjunto.

## 23. Itens de HORAS contam noutro campo

As "Asas do Caos (24 horas)" continuavam "Expirou." depois de tudo o resto estar certo. Sao
outra familia: os 40 itens com a duracao em @120 (6, 12 e 24 horas) nao usam a validade.

Medido no fio do oficial no "Ferro (24 horas)" (item 48143), seis registos ao longo de 84
segundos da mesma sessao:

    [56:60] validade    ZERO nos seis
    [48:56] DOUBLE      2433 -> 2363 -> 2343, a descer um por segundo

Sao SEGUNDOS que faltam, em tempo real. O client olha para esse double; a validade nem a le.

    @124 (dias)   -> [56:60] validade absoluta, [48:56] a zero
    @120 (horas)  -> [48:56] segundos restantes (double), [56:60] a zero

E A TERCEIRA VEZ QUE UM DOUBLE DESALINHADO ME ENGANA -- as outras duas foram no skillLevel e
no skillData. Li primeiro um FLOAT em [52] e vi 5,094, que e a metade alta do double em [48].
Desta vez o erro nao passou despercebido, e a conta que o denunciou vale a pena guardar:
escrevi 24,0 como float em [52], o que da o double 2^29 = 536.870.912 segundos, e o client
mostrou "6213 dias 18 h 47 min". Sao 536.870.820 segundos. Bate ao segundo, e foi isso que
identificou o campo -- o proprio erro serviu de medicao.

### O item sai da tela sozinho, sem relogin

O jogador: "no original ja some dentro do jogo mesmo, do mesmo jeito quando deleto um item".
Ou seja o mesmo 06 06 de remocao do caixote -- nao ha pacote novo a inventar, reaproveita-se
o build_item_removido.

Feito com um tique de 15 segundos, chamado de DOIS sitios: o ramo do keepalive (que so corre
com o jogador parado) e o caminho normal dos pacotes (para quem esta a andar ou a bater).
Com um so, a peca esperava que o jogador ficasse quieto.

E o double do tempo restante e RECALCULADO a cada envio, a partir da aquisicao, em vez de
guardado a descer no banco:

    @120 (horas)  restante = duracao - (agora - aquisicao)
    @124 (dias)   restante = validade - agora

Guardar um numero que envelhece daria o mesmo defeito da posicao de entrada de 27/08 -- ler
o valor ja gasto e torna-lo a gastar no login seguinte.

FICA POR MEDIR: se o tempo conta com o item GUARDADO na mochila ou so equipado. A conta acima
assume tempo real, que e o que se viu no oficial -- mas nessa captura o item estava em uso.

## 24. A bandeira da Forma: os dois pacotes usam a MESMA convencao

    0 = mostrar     1 = esconder      no 02 01 [25] e no 06 46 [10]

Virei isto ao contrario duas vezes por deduzir em vez de medir. As duas medicoes que mandam:

    a) oficial, mesma personagem e mesmo equipamento, duas fotos da tela de seleccao so com
       o botao mudado: [25]=0 com a fantasia a vista, [25]=1 sem ela
    b) o nosso log: quando o jogador clicou para ACTIVAR a Forma, o client mandou 06 46
       com [10]=0

E FALTAVA A OUTRA METADE: no login nao anunciavamos o estado, entao o client entrava sempre
no estado dele enquanto a tela de personagem seguia o nosso. As duas telas diziam coisas
diferentes. Agora o 06 46 sai sempre na entrada, com o valor guardado.

## 25. Regeneracao de MP -- nao existia

O jogador: "falta a regeneracao do mp que nao ta funcionando". Nao estava a falhar: nunca
tinha sido escrita. E o corte no topo do `regenerar()` escondia-a duas vezes -- a condicao de
saida era `h.hp >= h.hp_max`, portanto com a vida cheia o ciclo nem chegava ao resto. Mesmo
depois de acrescentar o MP, sem mexer nessa linha ele so regeneraria com HP a faltar.

### O MP vem no 08 01, o HP no 08 06

E por isso e que a medicao anterior nao o apanhou. Os tiques de HP chegam pela barra de
ENTIDADE (08 06), que so carrega vida; o MP so aparece na barra do heroi, mandada com menos
frequencia do que os tiques. O passo tem de sair da divisao:

    passo = (MP ganho entre dois 08 01) / (intervalo / 5,35)

### O intervalo e o mesmo do HP

No personagem de nivel 68 do oficial o MP subiu +144 em 64,4 s e outra vez +144 em 64,8 s.
A 5,35 s por tique dao 12,0 exactos nas duas. Nao ha relogio proprio para o MP.

### A tabela, e o que ela nao tem

    mp_max 180 -> 3      mp_max 242 -> 4      mp_max 904 -> 12

Tres pontos, e e pouco -- o HP tem oito. Havia mais oito candidatos nas capturas e foram
todos postos de lado por serem subidas de 1 ou 2 de MP em janelas longas, que e ruido e nao
tique. Estes tres exigiram subida de pelo menos 3, tres tiques ou mais e o HP parado.

Interpola-se por mp_max e fora da faixa devolve-se a ponta, sem extrapolar. Com tres pontos
uma formula seria invencao.

O ATRASO ate ao primeiro tique NAO FOI SEPARADO do HP: nas capturas os dois regeneram ao
mesmo tempo, portanto usa-se o mesmo 36 s. Se um dia se vir MP a subir com o HP ainda parado,
os atrasos sao diferentes e isto muda.

### O 08 06 so sai quando o HP muda

E a barra de VIDA, e e ela que liga o efeito visual da regeneracao. Manda-la num tique em que
so o MP subiu deixaria a aura acesa com o HP cheio -- o mesmo sintoma de 28/08, por outro
caminho.

## 26. A regeneracao arranca ao GUARDAR A ARMA, nao ao fim de um relogio

O jogador disse-o em duas frases: "o atraso e mesmo, pq saiu do modo de combat" e depois "no
momento que guarda a arma que a regeneracao comeca". Esta certo, e o fio mede-o.

    ultimo ataque -> arma GUARDADA    25 amostras: 9,9 15,5 16,2 22,0 33,0 42,1 68,2 89,6 ...
    arma GUARDADA -> primeiro tique   22 amostras: TODAS entre 5,1 e 5,4 s

O segundo nao tem dispersao nenhuma: e UM INTERVALO exacto. O primeiro varia de 10 a 90
segundos, porque "ultima pancada" nao e o fim do combate -- o modo de combate dura enquanto
houver combate, e quem o fecha e o 03 02 que embainha a arma.

O ESPERA_FORA_DE_COMBATE = 36 s que eu tinha era a MEDIA desse primeiro numero. Media de uma
coisa que varia de 10 a 90 nao e uma constante: e a media do sinal errado. Ficou como recurso
de ultimo caso, para quando o 03 02 nao chegar.

Agora o servidor ouve o 03 02 do client: ao guardar a arma limpa o carimbo de combate e
acerta o relogio, para o proximo tique cair um intervalo a frente -- que e exactamente o que
o oficial faz.

E vale a pena guardar o metodo, porque foi ele que resolveu isto e o [25] do 02 01 no mesmo
dia: quando uma medicao tem dispersao grande, quase sempre o que esta errado nao e o ruido --
e a variavel que se escolheu para ancorar a medicao.

## 27. Premios de entrada -- e eu tinha os dias CRAVADOS

O jogador mandou a janela "Premios de entrada" a mostrar os dias 27 e 29 circulados e
"(2/5)", e perguntou se era molde. Era pior: eram bytes que eu proprio cravei.

Quando liguei o 0a 0a copiei o fim do pacote de uma captura -- `00000001 00000002 1b 1d` --
com o comentario honesto de que nao os percebia. Percebem-se agora:

    [6:10]  BE  relogio unix               (so no 0a 0a)
    [10:14] BE  entradas CONSECUTIVAS
    [14:18] BE  entradas do MES
    [18:]       um byte por dia do mes em que se entrou

`1b 1d` = 27, 29. Ou seja o servidor anunciava a assiduidade da personagem capturada a toda
a gente, e era exactamente isso que aparecia na tela.

CONFERE-SE SOZINHO, e foi assim que se validou: o valor em [14:18] e sempre o comprimento da
lista de dias. Numa captura (1, 2) com os dias 27 e 29; noutra (1, 1) com o dia 30 -- e essa
segunda foi feita depois da meia-noite, o que bate com a data.

### Os pacotes

    0a 0a   CLIENT pede (18 bytes a zero)   -> relogio + contadores + dias
    0a 0b   CLIENT abre a janela            -> so os contadores
    10 04   o servidor manda a seguir, com 00000001. HIPOTESE: aviso de correio.

O jogador: "o premio se nao engano foi pro email". Se o premio vai para a caixa de correio,
entao NAO HA pacote de resgate nenhum -- o servidor deposita o item no correio e avisa, e o
10 04 com 1 le-se bem como "uma mensagem nova". Explicaria porque e que a captura do clique
em resgatar nao trouxe pacote nenhum de resgate.

Confirma-se abrindo o correio no oficial com o hook armado: se o 10 04 mudar de numero
conforme as mensagens por ler, esta identificado.

O `0a 0a` do servidor MUDA DE TAMANHO: 18 bytes quando nao ha dias, 19 com um dia, 20 com
dois. E o que revela que a cauda e uma lista, e nao campos fixos.

### O que ficou implementado

Duas colunas em dbo.personagem (`entrada_mes`, `entrada_dias`) e `db.registar_entrada`, que
marca o dia de hoje, reinicia quando o mes muda e conta as consecutivas para tras a partir de
hoje ate ao primeiro buraco.

FICA POR MEDIR: o resgate. Na captura o jogador abriu a janela mas o premio "e enviado para o
e-mail", e nao apareceu nenhum pacote de resgate distinto -- so o 10 04 com 1. Os quatro
06 0c do client (valores 1, 2, 3 e 5) chegam todos juntos na entrada e parecem ser outra
coisa, nao o botao.

E o terceiro campo do 0a 0b: a unica amostra traz 30, que naquele personagem e tanto o dia de
hoje como o unico dia do mes. Com uma amostra nao da para separar as duas leituras.

## 28. So a Maga tinha skills -- a lista vinha do stream

O jogador: "so ta soltando skill da maga". As funcoes de skill sempre foram agnosticas a
classe -- procuram o id na SkillLod. O que estava errado era a LISTA que o client recebe.

O 05 05 saia do stream de entrada gravado, ou seja toda a gente recebia as skills da
personagem capturada, uma Maga.

### O formato do 05 05

    [0:2] 05 05   [2:6] seq   [6:10] entidade BE   [10:12] contagem BE
    [12:]  N ids u32 LE, e a seguir N niveis u32 LE

Medido comparando dois:

    classe 400   1..6 11..14  7001 7002 7003 7004  2100000001..5   -> 19
    o stream     1..6 11..14  11001                2100000001..5   -> 16

As dez da frente e as cinco do fim sao iguais nos dois -- so muda o punhado do meio, que e
o da classe.

### SkillTree_<classe>.lod

Ha um por classe, 49 ficheiros. Decifra com `pular_zero=False` e o formato e:

    @0  u32  20101118   carimbo de versao, igual em todos
    @4  u32  contagem
    @8  N registos de 12 bytes: (skill_id, x, y)

    100  1001 1002 1004 1006 1005 1003      400  7001 7005 7002 7003 7004 7006
    200  3001 3002 3003 3004 3005           500  9001 9002 9003 9005 9004 9006
    300  5001 5002 5003 5004 5005           600  11001 11002 11003 11004 11005
    800  15003 15002 15005 15004 15201

O x e o y sao a posicao do icone na janela -- valores como 21, 62, 75, 103, 129, 144, que se
repetem entre classes. Nao sao nivel nem custo.

### Desvio conhecido

Mandam-se TODAS as skills da arvore da classe. O oficial manda so as APRENDIDAS -- o
personagem de classe 400 tinha 4 das 6. Nos nao guardamos que skills o jogador aprendeu, que
e a mesma tabela em falta que ja obriga o skill_para a assumir nivel 1. Enquanto ela nao
existir, dar a arvore toda e melhor do que dar a de outra classe.

## 29. Tres defeitos de inventario e criacao

### Criar personagem dizia "nome em uso" em TODOS os nomes

Nao era o nome: a constante PRIMEIRO_ENTITY_ID tinha desaparecido do db.py, a criacao
rebentava com NameError, o `except` devolvia None e o world_server traduzia QUALQUER falha
para "nome em uso". O jogador tentou nome atras de nome e levou sempre a mesma recusa.

Reposta a constante (20000, que bate com o `@primeiro_id` do procedimento) e corrigida a
mensagem, que agora manda ver o motivo real no log em vez de inventar um.

LICAO: uma mensagem de erro que ADIVINHA a causa custa mais do que uma que diz "falhou, ve o
log". Esta escondeu um NameError durante nao se sabe quanto tempo.

### Mover item de slot -- o 06 08 nao era tratado

    06 08 | seq | jogador BE | cont_orig slot_orig | item BE | cont_dest slot_dest

O oficial responde com o par de 06 06 do costume e ecoa o 06 08. Nunca o tratamos, e por isso
nao se conseguia arrastar nada dentro da mochila. Implementado, incluindo a TROCA quando o
destino ja tem item -- num UPDATE unico, pela mesma razao do reorganizar.

Porque e que so deu na vista depois de reorganizar: o 06 08 nunca funcionou, mas antes o
client desenhava o movimento sozinho e as posicoes por acaso ainda batiam com o banco.
Compactar tirou a ilusao.

### O Carat nao empilhava

O `dar_item` abria monte novo a cada vez -- o jogador acabou com 51, 15, 15 e 16. Agora junta
ao monte existente respeitando o `empilha_max` do catalogo (1 nas pecas, 100 nas pocoes, um
numero enorme no Carat).

## 30. O saco nao se apanha por proximidade -- o 06 03 e BIDIRECCIONAL

O jogador: "no nosso nao aparece essa mao pra coletar o item, ta coletando o carat so de
chegar perto". Estava certo, e o erro tem uma raiz que vale registar.

A nota de 28/08 dizia que o 06 03 e "o saco a SAIR DO CAMPO DE VISAO". Isso saiu de olhar so
os pacotes DO SERVIDOR. O client manda o mesmo opcode -- 645 vezes nas capturas -- e do lado
dele quer dizer **"quero apanhar este saco"**.

    CLI  06 03 | seq | jogador BE | id do saco BE | item BE | 00 5a

A sequencia medida no fio do oficial:

    41666,5  srv 06 04   o saco nasce
    41675,9  CLI 06 03   nove segundos e meio depois, com o jogador em cima
    41676,1  srv 06 06   o item entra na mochila
    41676,1  srv 06 03   o saco sai

Sao esses 9,3 segundos que dizem tudo: no oficial o saco fica no chao a espera do clique.
Nos apanhavamos assim que o jogador entrasse no raio.

Trocado: a recolha passou a ser a pedido (`entity_sim.apanhar_saco`), e a distancia continua
a ser verificada mas como VALIDACAO, nao como gatilho -- um pedido de um saco do outro lado
do mapa nao passa.

LICAO, e e a terceira vez hoje: **um opcode nao tem um significado, tem um por SENTIDO.**
Censar so um lado do fio da meia verdade, e a metade que falta parece nao existir.

## 31. Fletta: o recurso era o valor de UMA especie

O jogador: "ta ganhando muita exp de fletta quando matei aranha com personagem novo". A
Aranha pequena e de nivel 1 e levava FLETTA_POR_ABATE = 83, que e o valor medido num Bico de
alumina de NIVEL 9. Toda a especie nao medida levava o mesmo.

### Como se mede, e onde e facil enganar-se

Emparelha-se cada subida de fletta no 08 01 com a morte que a causou. Se houver MAIS DE UMA
morte na janela, o par nao vale. A primeira passagem deu ao Filhote de urso feroz (nivel 5) o
valor 65 -- que e o do Javali-de-sabre (nivel 7) -- so porque morreram juntos. Com o filtro
de "uma morte na janela" o Filhote volta aos 46 que ja estavam medidos numa captura
dedicada.

### O que a medicao mostra

    nivel  fletta   f/nivel   especie                amostras
      1      12      12,00    Aranha pequena           20
      5      46       9,20    Filhote de urso feroz    20
      7      65       9,29    Javali-de-sabre          15
      9      83       9,22    Bico de alumina           4
     14     129       9,21    Urso furioso              3
     17     157       9,24    Tirano sanguinario        2

O `f/nivel` fica entre 9,20 e 9,29 em cinco das seis, e o nivel 1 destoa. Ha uma curva por
tras, mas seis pontos nao chegam para a escrever -- e uma recta simples falha o nivel 1.

Por isso o servidor INTERPOLA entre os pontos medidos, e so fora da faixa e que segue o
declive (~9,2 por nivel). Ficou:

    nivel  3 -> 29     nivel 10 -> 92     nivel 13 -> 120    nivel 35 -> 323

O 323 do nivel 35 pode conferir-se: mediu-se 321 no Goblin ladrao, com quatro amostras.

## 32. As skills libertam-se por NIVEL, e a regra estava na tabela

O jogador, depois de eu mandar a arvore toda: "vc libero as skill sem clica pra liberar".
Nao ha clique nenhum -- ha nivel, e o dado ja estava no SkillLod.

Cada skill tem um `nivel_req` por nivel de skill. O do NIVEL 1 e o nivel do personagem em
que ela aparece:

    classe 600   11001@1  11002@3  11003@3  11004@3  11005@4
    classe 400   7001@1   7003@2   7002@3   7004@3   7006@4   7005@9

CONFIRMADO NO FIO, e a coincidencia e exacta: na captura da Feiticeira o personagem de
classe 400 tinha so a 7001 e, no instante em que o 05 05 passou a 17 entradas com a 7003
dentro, chegaram tambem um 08 02 e dois 08 01 com HP e MP cheios -- a assinatura do level up.
A 7003 exige nivel 2.

O personagem de classe 400 do oficial que tinha 4 skills (7001, 7002, 7003, 7004) e portanto
de nivel 3, que e onde a lista salta de duas para quatro.

### O que o 05 05 carrega

    [10:12] BE contagem, depois N ids u32 LE e a seguir N NIVEIS u32 LE

Os niveis nao sao decorativos: numa captura passaram de 1 para 2 numa skill quando o jogador
gastou um ponto. Nos mandamos 1 em todas, porque nao guardamos os pontos por skill -- e a
mesma tabela em falta que obriga o skill_para a assumir nivel 1.

## 33. Tabela de skills por personagem, e uma segunda fonte que a confirma

### dbo.personagem_skill

    entity_id, skill_id, nivel, pontos

`db.sincronizar_skills(entity_id, classe, nivel)` acerta o que o personagem tem ao nivel em
que esta e devolve {skill_id: nivel}. O 05 05 passou a sair dela -- ids E niveis -- e o
calculo de dano tambem: o `bonus_da_skill` recebe agora o nivel da SKILL, e nao o do
personagem como ate 30/08.

A coluna `pontos` fica a zero. O jogador tem "Pontos de habilidade" na janela, mas as duas
subidas de nivel de skill que apanhei nas capturas foram AMBAS por nivel de personagem --
nao ha captura que isole o pacote do gasto de ponto. A coluna existe para quando houver.

### A calculadora oficial, apontada pelo jogador

    https://karos.gamexp.com/about/talant-calculator/

Os dados nao estao no HTML: vem de um POST a `/ajax/handler.php?action=Calculator.getSkills`
com `prof_id`, e o script guarda-os em `$("body").data("skillz")`. Chamar o endpoint de fora
da pagina da 400 -- a forma que funciona e usar o proprio `gmxp.ajax.handle` no browser, que
e como a pagina o chama.

Sao 316 skills nas 7 profissoes, com as arvores de avanco incluidas.

### O que ela deu que os .lod nao davam

    ns1/nsl1, ns2/nsl2   a skill PRE-REQUISITO e o nivel dela -- a estrutura da arvore
    hits                 quantos golpes a skill da
    maxl                 nivel maximo (5 em quase todas, 2 na 15201)
    gt                   1 marca a skill RAIZ da classe

O `hits` fecha uma coisa que a seccao 8 dava por perdida: "a area (1,5 m) e os hits (2) da
dica nao aparecem em nenhum dos campos". O 5001 "Golpe de ombro" tem hits=2, que e
exactamente o que a dica do client dizia. A area continua sem sitio.

### A confirmacao, e a unica discrepancia

O `ll` da calculadora e o mesmo numero que o `nivel_req` que extraimos do skillLevel.lod.
Conferidas as 38 skills das classes base: **37 batem ao numero**.

A que difere e a 7003 "Codigo oculto: reforco":

    nosso client   2, 14, 20, 35, 68
    calculadora    2,  7, 12, 17, 22

NAO E ERRO DE EXTRACCAO: a progressao de mana do nosso (5, 8, 9, 11, 14) acompanha esses
niveis, e a 1003 e a 5003 -- que tem o mesmo padrao 2,7,12,17,22 -- saem iguais nas duas
fontes. E uma diferenca real de dados entre o client Axion e o oficial russo. Para o nosso
servidor manda o client, que e o que os jogadores tem a frente.

## 34. O nivel LIBERTA, o ponto APRENDE -- e eu tinha lido ao contrario

O jogador, a olhar para a janela de habilidades: "mas eu nao aprendi ainda, ali ta como se eu
tivesse usando 3 ponto de skill". E depois, a frase que resolve: "o nivel libera a skill, mas
eu tenho que clicar pra aprender ou evoluir".

### Porque e que eu tinha concluido mal

Vi a lista do 05 05 crescer no MESMO INSTANTE em que chegava um 08 01 com HP e MP cheios, e
concluí que subir de nivel dava a skill. Dava a DISPONIBILIDADE. O jogador carregava no botao
logo a seguir e as duas coisas caiam no mesmo segundo do log.

O que separa as duas leituras e a coluna dos PONTOS, no 08 01 [90:94]:

    62202,81  pontos 2 -> 1      gastou
    62204,17  pontos 1 -> 0      gastou outra vez
    62231,16  05 05 n=18 -> 20   duas skills novas

Duas descidas de ponto, duas skills. Nao ha ambiguidade.

LICAO: dois eventos no mesmo segundo do log nao sao um evento. Quando A e B aparecem juntos,
a pergunta certa e o que mais mudou -- aqui foi uma coluna que eu nao estava a olhar.

### O pacote do clique: 05 06

    CLI  05 06 | seq | jogador BE | skill BE | 00 24
    srv  05 06 (eco) + 08 02 + 08 01 com os pontos ja descontados

Um ponto por nivel: cada 05 06 fez os pontos descerem exactamente 1 e a soma dos niveis do
05 05 subir exactamente 1.

### O que ficou

    dbo.personagem_skill        (entity_id, skill_id, nivel, pontos)
    db.sincronizar_skills       so garante a skill INICIAL da classe -- a que tem gt=1
    db.skills_disponiveis       o que o nivel permite, ja com os pre-requisitos
    db.aprender_skill           valida e gasta o ponto
    05 06 no world_server       trata o clique e devolve o eco mais a lista

A tabela foi limpa: as skills que la estavam tinham sido dadas por mim, nao aprendidas.

## 35. A gravacao do buff -- confirma o 05 06 e revela o 05 09

Captura `usandobuff.log`, feita pelo jogador a aprender duas skills e a usar o buff do
Vagabundo (classe 300).

### O 05 06 que implementei bate byte a byte

    CLI  05 06 00 00 00 2b | 00 00 2d f2 | 00 00 13 8b | 00 24
    srv  05 06 00 00 02 ce | 00 00 2d f2 | 00 00 13 8b | 00 24
    srv  08 01  pontos_skill 1 -> 0

`0x138b` = 5003. Mesmo formato, mesmos campos, mesma resposta. E o `08 01` a seguir confirma
o custo de um ponto por nivel, que era a suposicao com que implementei.

### 05 0a -- acompanha o ganho de ponto

    05 0a | seq | 00 01 89 13 | 00 00 00 00 00 00       16 bytes

Sai junto com o 08 01 que sobe os pontos e com o 05 05. Os bytes [6:10] sao iguais nas duas
ocorrencias. Nao esta identificado.

### 05 09 -- o BUFF

Sai depois de lancar a 5002 "Foco", que e a skill de buff:

    05 09 | seq | 00 | jogador BE [7:11] | 01 | 00 01 | 00 00 |
           skill BE u16 [16:18] | 01 | 03 | 00*6 | 04 b0 | 00*4      32 bytes

    [11]    1 LIGA o buff, 0 DESLIGA-O
    [16:18] = 0x138a = 5002        a skill que criou o buff
    [26:28] = 0x04b0 = 1200        DURACAO EM SEGUNDOS

### O fim do buff e o MESMO pacote, com um byte mudado

A captura `cabandobuf.log`, de 1336 segundos, apanha as duas pontas:

    +82,98    05 09 ... 2d f2 | 01 | ... 13 8a ...     liga
    +1282,97  05 09 ... 2d f2 | 00 | ... 13 8a ...     desliga

**1199,99 segundos** entre as duas. Sao os 1200 que estavam em [26:28] -- a duracao confirma-
se ao centesimo, e o campo deixa de ser candidato.

Nao ha pacote proprio de fim: e o mesmo 05 09 com o [11] a zero.

E NAO ESTA EM TABELA NENHUMA NOSSA. Procurei no SkillLod (os campos 88, 104, 172 e 252 da
5002 valem 0, 0, 10 e 0), no SkillEfeito e no `mtime` da calculadora oficial, que e zero em
todas as skills da classe 300. O numero vem do servidor ou de uma tabela por abrir.

### O buff NAO mexe na ficha

Comparei os 25 pacotes 08 02 da captura, antes, durante e depois. Fora o numero de sequencia,
nao muda um unico byte. O "Foco" (5002) nao altera nada do que a ficha mostra.

Isso limita o que ha para implementar do lado do servidor: guardar que o buff esta activo,
mandar o 05 09 a ligar, e mandar outro a desligar ao fim da duracao. O efeito em si, se
existir, e do client ou nao existe.

## 36. O buff, decifrado de ponta a ponta

O jogador mandou a dica da skill: "Foco -- Tipo Suporte, Mana 3, Hits 1, Recuperacao 20,0s,
Duracao 20 Min, Chance de Critico +2,0". E depois: "isso e lvl1 a skill, level 2 de aumentar
chance de critico". Com isso fecha-se a cadeia toda.

### A cadeia

    skillLevel @264   -> codigo         5106001..5106005, o ULTIMO digito e o nivel da skill
    skillData codigo  -> (tipo, valor)  tipo 11 = Chance de Critico, valor 2/4/6/8/10
    skillLevel @140   -> duracao        double, 1200 segundos = os "20 Min" da dica
    skillLevel @136   -> hits           u32
    05 09 [11]        -> 1 liga, 0 desliga

O `tipo` e o MESMO switch FUN_006076cb que ja alimenta os bonus de item, portanto o
TIPO_DE_BONUS do db serve tal e qual.

### Campos que isto identifica no skillLevel

    @136  hits        vale 2 na 5001 e 1 na 5002 -- bate com a dica e com a calculadora
    @140  duracao     1200 na 5002 (buff) e 0 nas de ataque

O @136 fecha metade do que a seccao 8 dava por perdido: "a area (1,5 m) e os hits (2) da dica
nao aparecem em nenhum dos campos". A area continua sem sitio.

### E corrige uma leitura minha

Ao varrer o registo li um double de -2,0 em @138 e quase o tomei pelo valor do critico com o
sinal trocado. Nao existe: @138 sobrepoe-se ao @140, e o que la esta e a duracao. E a QUARTA
vez que um double desalinhado me engana neste ficheiro. A regra que funciona e ver o campo
nos CINCO NIVEIS da mesma skill e em skills de tipo diferente -- o que for real varia (ou nao
varia) de forma explicavel; o que for artefacto muda sem sentido.

### Porque e que a ficha nao mudou na captura

Comparei os 25 pacotes 08 02 e nenhum byte mexeu. O servidor NAO reenvia a ficha ao ligar o
buff -- quem soma os +2 na tela e o client, a partir destas mesmas tabelas. Do lado do
servidor o que e preciso e contar os +2 no COMBATE, que e coisa que o client nao faz por nos.

NAO IMPLEMENTADO. Falta guardar o buff activo, mandar o 05 09 a ligar e outro a desligar ao
fim dos 1200 s, e somar o efeito ao combate enquanto durar.

## 37. Buff implementado

    Data/Skills/SkillBuff.ini   707 skills com duracao, 2785 linhas de efeito
    db.buff_da_skill            (duracao, [(nome_interno, valor)])
    entity_sim.BUFFS            {entity: {skill: (acaba_em, nivel, efeitos)}}
    entity_sim.build_buff       o 05 09, com [11]=1 a ligar e 0 a desligar
    entity_sim.rodar_buffs      desliga ao fim do tempo, no ciclo do mundo

O `tipo` do SkillBuff e o mesmo switch dos bonus de item, portanto o TIPO_DE_BONUS serve tal
e qual e nao houve tabela nova de nomes.

O 05 09 que geramos sai com os mesmos 32 bytes do oficial: [11]=1, skill 5002, nivel 1,
duracao 1200.

### Onde o buff conta

NO COMBATE, e so ai. A tela ja soma os +2 do "Foco" sozinha a partir das tabelas do client --
comparei os 25 pacotes 08 02 da captura e nenhum byte muda ao ligar o buff. O que o client
nao faz por nos e a conta do critico, e e la que o `bonus_dos_buffs` entra.

### O que NAO se aplica, e porque

    negativos    a 1002 poe proteccao fisica -10 no ALVO. O jogador confirmou: "tem ataques
                 que contem debuffs tambem". Aplicar exige saber a que entidade o 05 09 vai,
                 e a unica captura que temos e de um buff em SI PROPRIO. Fica no log.
    controlo     tipos 85, 86 e 87 -- impossibilidade de mover, atacar e usar objectos. Nao
                 sao um numero a somar: mexem no movimento e no ataque.
    tipos fora do TIPO_DE_BONUS   os mesmos de sempre (PK, arena), agora tambem contados.

Nos tres casos o servidor escreve no log o que nao aplicou, em vez de aplicar ao calhas.

## 38. Passivas -- duracao ZERO nao e "sem efeito"

O jogador mandou a dica da "Habilidade de evasao" (5003): "Tipo de habilidade: passiva",
"Destreza +2,0" -- e a ficha dele a mostrar "Destreza 13 (+2)".

A cadeia e a MESMA dos buffs, so nao tem prazo:

    skillLevel @140 = 0    passiva
    skillLevel @264        os codigos, como sempre
    skillData codigo       (tipo, valor)

    5003 -> 5002001..5 -> tipo 3 (Destreza) com 2, 3, 4, 5, 6 conforme o nivel da skill

A PRIMEIRA VERSAO DO EXTRACTOR FILTRAVA POR `0 < duracao` e deixou as passivas todas de
fora -- 3084 linhas. Corrigido: agora entra tudo o que tem codigo de efeito, e a tabela
passou de 2785 para 5869 linhas.

### Onde entram

Pela MESMA porta do equipamento (`bonus_do_equipamento`), e de proposito: quem chama isso ja
poe o resultado na ficha e no combate, e uma passiva de Destreza +2 tem de contar nos dois
sitios exactamente como um item que desse Destreza +2. Por isso o "(+2)" aparece na ficha sem
mais nada ter sido mexido -- o campo OFF_BONUS_CARACTERISTICAS ja era escrito a partir dali.

### As skills base com efeito, por classe

    100  1002 Coacao        prot fisica -10     DEBUFF        1004 Raiva  atq +10 e prot -10
    100  1003 Golpe no corpo  controlo (85/86/87)             1005 Provocacao  tipo 25
    200  3004 Condenacao    class. proteccao -1  DEBUFF
    300  5002 Foco          critico +2  BUFF 1200s            5003 Destreza +2  PASSIVA
    400  7004 Regen de mana mana +10  BUFF 20s                7006 Colapso saude -6  DEBUFF
    600  11002 Corrosao     saude -5  DEBUFF                  11004 Regen de mana  BUFF

A 1004 "Raiva" merece nota: da ataque +10 EM QUEM LANCA e proteccao fisica -10 tambem em
quem lanca. A minha regra actual -- "tem valor negativo, logo e debuff de alvo" --
classifica-la-ia mal. Quando houver captura de um debuff, o criterio certo nao sera o sinal
do valor mas a entidade que o 05 09 nomeia.

## 39. Os campos derivados contam o BONUS das caracteristicas

O jogador, ao ver a passiva a dar Destreza 13 (+2) e o critico a ficar nos 7,1: "com a
destreza aumentada, aumentou a chance de critico?".

Nao aumentava. Os CAMPOS_DERIVADOS saiam so da caracteristica BASE.

### A medicao

1178 fichas do oficial que tinham bonus de Destreza:

    offset  campo                      so base   base+bonus
      66    chance de evasao              0         1178
      98    classificacao de ataque       0         1178
     110    (por identificar)             0         1096

Zero contra 1178. E o critico confirma-se pelo outro lado, porque tem um termo de nivel: os
sete valores seguidos da Arqueira (7,17 7,23 7,29 ... 7,53, de 0,06 em 0,06) dao k inteiro
com base+bonus -- 7,05 + 0,0604k para k de 2 a 8 -- e 9,4 so com a base.

### Porque e que isto importa mais do que parece

O bonus de caracteristica nao vem so das passivas: vem dos itens, da fantasia e agora das
skills. Enquanto os derivados ignoravam o bonus, TODOS eles estavam errados para qualquer
personagem com equipamento -- evasao, classificacao de ataque e critico incluidos, e o
critico entra na conta do combate.

Ficou a contar. Na ficha do Trapa: evasao 7,00 -> 7,10, classificacao 9,00 -> 10,00,
critico 6,60 -> 7,50.

## 40. O buff sobrevive ao logout

O jogador: "todos buffs e itens com contagem ficam ali em cima, fica salvo quando saio e
quando volto continua a contagem, no nosso quando uso o buff e relogo, o buff some". E buff e
estado do PERSONAGEM, nao da sessao.

    dbo.personagem_buff   (entity_id, skill_id, nivel, acaba_em)

### O que o oficial manda no relogin, medido

    +0,35   srv 05 09  [11]=1 skill=5002 resta=342     no 02 04, entrada no personagem
    +4,28   srv 05 09  [11]=1 skill=5002 resta=338     outra vez no 04 00, entrada no mundo
   +59,89   srv 05 09  [11]=1 skill=5002 resta=1179    segunda sessao, continua a contar

O campo [26:28] leva o tempo QUE FALTA, e nao a duracao cheia. E isso que faz o relogio da
tela continuar de onde estava em vez de recomecar -- e nota-se: 342, 338, 1179, 1175, tudo a
descer com o tempo real.

### Relancar um buff activo desliga-o primeiro

   +30,29   srv 05 09  [11]=0 skill=5002 resta=312     o que ainda faltava
   +30,29   srv 05 09  [11]=1 skill=5002 resta=1200    e a duracao cheia

Dois pacotes no mesmo instante. Mandar so o segundo deixaria o client com dois relogios para
o mesmo icone. Implementado.

MEDIDO E NAO IMPLEMENTADO: o oficial manda o 05 09 DUAS vezes na entrada, uma no 02 04 e
outra no 04 00. Nos mandamos so na segunda. Nao se viu diferenca na tela, mas fica dito.

## 41. Usar item -- o 06 02, e o que NAO se consegue implementar

Captura `iniciante.log`: o jogador usou "Um presente de principiante" da mochila.

    CLI  06 02 | seq | jogador BE | container | slot | item BE [12:16] | ... | alvo BE | 00
    srv  06 06 x5   os cinco itens que sairam do presente
    srv  06 02      o eco
    srv  06 06      o presente com quantidade 0 -- consumido

O presente 999000121 deu 999000134 x20, 999000135 x20, 1000001363, 1000008850 e 1000008860.

### O que falta, e nao e pouco

O CONTEUDO NAO ESTA EM TABELA NENHUMA DO CLIENT. Varri os 909 ficheiros a procura do id do
presente e ele so aparece no itemBase e no itemBase_Name -- em nenhum ha os ids do que sai
dele por perto. Quem decide o que vem dentro e o servidor.

O MESMO PARA AS POCOES: `atributos_do_item` devolve vazio para todas, e nao ha coluna de cura
no catalogo. O `itemInvokeSkill` parecia a resposta -- item que invoca uma skill -- mas tem 26
registos, poucos de mais para cobrir as pocoes do jogo.

POR ISSO NAO IMPLEMENTEI. Tratar o 06 02 sem saber o efeito significa consumir o item e nao
dar nada -- o jogador perde a pocao e fica na mesma. E pior do que o botao nao funcionar.

O que se pode fazer sem inventar: gravar o uso de cada tipo de item no oficial e ler o efeito
do fio (o 08 01 diz quanto HP a pocao devolveu, os 06 06 dizem o que o presente deu). E
trabalho de medicao, item a item.

### E uma correccao: o itemDismantal EXISTE

Ficou escrito no ramo do 06 56 que "a tabela que diz que materiais cada item devolve nao foi
encontrada em nenhuma das 909 do client". Foi encontrada agora, a procurar outra coisa:
`itemDismantal`, 61 registos de 80 bytes. Nao esta lida, mas ja se sabe onde esta.

LICAO: "nao encontrei" envelhece mal. Vale a pena datar essas frases e re-verificar antes de
as usar como razao para nao fazer uma coisa.

## 42. A cadeia das caixas, medida

Captura de 30/08 09:36: o jogador equipou a Phoenix e foi abrindo caixas umas dentro das
outras. Sai tudo medido, sem uma derivacao:

    Um presente de principiante   -> Presente (NV.10), 20 pocoes de saude, 20 de mana,
                                     Caixa dos ternos, Phoenix (7 dias)
    Caixa dos ternos              -> Caixa com terno
    Caixa com terno               -> quatro "Terno para o banquete" (400200098/101/104/107)
    cada um desses                -> uma peca da fantasia (100170494/500/506/512)

### A Phoenix EQUIPA-SE

    CLI  06 00 ... 01 08 ff | 3b 9a ec 9c    da mochila slot 8, destino automatico
    srv  06 06 x2                            sai da mochila, entra no equipado
    srv  06 00 ... 00 06 06 | 3b 9a ec 9c    container 0, slot 6

Confirma o que o catalogo dizia (tipo 1, slot 6). Nao e item de usar.

### O padrao que NAO foi extrapolado

As quatro caixas de peca seguem uma relacao limpa: +3 na caixa da +6 na peca.

    400200098 -> 100170494    400200104 -> 100170506
    400200101 -> 100170500    400200107 -> 100170512

Era tentador gerar as centenas de caixas 400200xxx a partir disto. Nao se fez. A relacao vale
para QUATRO pontos de um unico conjunto, e este projecto ja pagou caro por extrapolar poucos
pontos -- a curva de fletta, o passo da regeneracao, a duracao dos itens. Cada caixa nova
entra na tabela quando for aberta com o gravador ligado.

### E um ramo que falhava calado

O jogador tentou abrir a caixa noutra personagem e nao apareceu NADA no log. O ramo do 06 02
so escrevia quando reconhecia a caixa ou quando o conteudo era desconhecido -- se o item nao
batesse na posicao, saia em silencio. Agora diz sempre o que recebeu e o que encontrou.

LICAO: um ramo que falha em silencio nao se diagnostica. Se ha um `if` que decide se algo
acontece, o `else` tem de dizer porque nao aconteceu.

## 43. A fantasia nao tem mecanismo proprio -- e skillData, e o 7xx cai

A seccao 13 dizia que o valor de um efeito de fantasia estava nos DOIS ULTIMOS DIGITOS do
codigo da ranhura (`codigo = grupo * 100 + valor`) e que nao havia tabela de escala pelo
meio. Estava errado, e o modo como estava errado vale mais do que a correccao.

### Como se apanhou

O jogador equipou o "Terno para banquete" no oficial e mandou a captura, o tooltip de uma
peca e as fichas antes e depois. Alinhando cada `06 00` com o `08 02` seguinte:

    torso  100170494   ATQ fisico +15
    pes    100170500   Prot fisica +82        <- a tabela dizia 32
    mao    100170506   Prot fisica +33, Critico +6, Saude +70
    pe     100170512   Prot fisica +33        <- a tabela dizia 27

O `bruto[76:80]` de cada instancia nomeia o efeito que ela sorteou, e os quatro batiam com
o que o ItemPackage previa (60188, 60192, 60196, 60200). O emparelhamento estava certo; era
o VALOR que nao estava. E o tooltip da peca `pe` diz, em letra do proprio client,
"Proteccao fisica+33.00" -- contra os 27 que os digitos davam.

### O que e mesmo

O codigo da ranhura e uma CHAVE DO skillData, exactamente como nas skills:

    skillLevel @264   ate 5 codigos
    skillData codigo  tipo @4, valor plano @24 (double), valor percentual @32 (double)

    codigo 41727 -> tipo 17 protecao_fisica  plano 33   (digitos diziam 27)
    codigo 41732 -> tipo 17 protecao_fisica  plano 82   (digitos diziam 32)
    codigo 42710 -> tipo 27 velocidade_movim  pct  10   (digitos diziam 10, por acaso)
    codigo 43906 -> tipo 71 poder_de_ataque   pct   6
    codigo 70006 -> tipo 46 saude            plano 70   (digitos diziam 6)

Portanto NAO HA cadeia de fantasia nenhuma a parte: bonus de item, efeito de fantasia,
buff e passiva sao todos o mesmo mecanismo, com o mesmo `tipo` do switch FUN_006076cb.

### A familia 7xx nunca existiu

Ficou um dia inteiro por explicar porque e que o bonus de 3 pecas trazia `g700 valor 6` e o
client anunciava "Saude +70,00" -- procurava-se a escala que converte 6 em 70. Nao ha
escala: `70006` e uma chave, o skillData tem la o 70 a espera, e o "700" nao e grupo
nenhum. Procurava-se uma tabela inventada tendo a verdadeira ao lado.

### Prova de que os digitos nao podiam ser

Depois de refeita, a tabela traz linhas que a aritmetica de dois digitos nao consegue
produzir de todo:

    codigo 41113 -> chance_critico 7.2     nao ha dois digitos que deem 7,2
    codigo 30324 -> saude 300              digitos dariam 24
    codigo 41733 -> protecao_fisica 132    digitos dariam 33

A terceira e a que explica porque a teoria sobreviveu tanto tempo: em 29/08 mediu-se uma
peca a dar "prot fisica +33" e escolheu-se o 41733 porque os digitos batiam. O VALOR
calhava certo -- ha um codigo, o 41727, que da mesmo 33 -- e por isso a medicao "confirmou"
o mecanismo em vez de o desmentir. Quatro batidas certas por quatro escolhas de codigo
erradas.

### Plano e percentagem sao campos separados

`@24` e o valor plano e `@32` o percentual, e ambos podem valer no mesmo registo. O que
esta a zero nao conta. A percentagem entra DEPOIS do plano, medido nas fichas:

    ATQ min  (15 + 15) * 1,06 = 31,8  ->  ficha mostra 32
    ATQ max  (17 + 15) * 1,06 = 33,9  ->  ficha mostra 34

### Conferido

Sete valores, sete certos, com o servidor a somar pela tabela nova: prot fisica +148 nas
pecas (mais 70 da Phoenix, que e o que essa asa da, = +218 medidos na ficha), saude +70,
critico +6 (3 da peca e 3 do pacote de 3), e o +6% do pacote de 4 nos dois ataques.

`tools/extrair_efeitos_fantasia.py` refeito, `FantasiaEfeito.ini` com colunas
`efeito origem codigo tipo atributo plano pct`, e o `db.py` sem `_atributo_do_grupo`,
`GRUPO_FANTASIA_PERCENTUAL`, `GRUPO_FANTASIA_EXTRA` nem o aviso do 7xx -- nada disso tinha
razao de existir.

### A licao, que e a mesma de sempre

Quatro medicoes exactas nao confirmam um mecanismo, confirmam quatro numeros. O que separou
foi um caso onde o mecanismo previa 27 e o client escrevia 33 -- e esse so apareceu porque
o jogador mandou o tooltip de uma peca em vez de so a ficha somada. Numero somado esconde o
erro; numero por peca mostra-o.

## 44. Fletta: dois campos "desconhecidos" que eram a propria fletta

O jogador relogou e a barra de fletta veio a zero. O banco tinha `fletta_exp=79,
fletta_pontos=3` guardados e a carga do disco corria (o log diz "progresso do heroi 20114
recuperado do disco"), mas o `08 01` que saia dizia `exp=104` -- em TODAS as sessoes, sempre
o mesmo 104.

### O byte 57 estava a ser pisado

No `build_hero_gage`, por esta ordem:

    struct.pack_into("<I", p, 57, heroi.fletta_exp)   # escreve 57, 58, 59, 60
    ...
    struct.pack_into(">I", p, 54, heroi.contador54)   # escreve 54, 55, 56, 57

O 57 e o byte BAIXO da fletta_exp, e o `contador54` escrito em big-endian punha la o byte
baixo dele. Com fletta 79 (0x4f) e contador54 104 (0x68), saia 104.

### O `contador54` nunca existiu

Estava anotado como "[54:58] BE u32, +12 por abate, sem identificacao", com uma nota que
devia ter dado o alarme: *"e de UM byte: no oficial chegou a 214, ganhou 46 e voltou pra
4"*. Nao da a volta -- 214 + 46 = 260, e 260 & 0xFF = 4. E o byte baixo de um campo maior.

Medido no oficial (`traffic_classe_guerreiro_vagabundo`), abate a abate, na zona 48-72:

    byte 57:  0c -> 18 -> 24 -> 30 -> 3c -> 48      (12, 24, 36, 48, 60, 72)
    bytes 58, 59, 60:  00 00 00 sempre

So um byte muda, e `BE@54` da os mesmos numeros porque 54, 55 e 56 sao zero. Um campo, nao
dois. O "+12 por abate" e a fletta dessa especie -- o outro valor anotado, +83, era de outra
especie, o que ate confirma que a fletta varia com o bicho.

### O `campo61_de` tambem era a `fletta_necessaria`

Vivia como "campo [61:65] do 08 01, nao sabemos o que e", com uma curva `1.33^n` ajustada
aos tres unicos pontos conhecidos: 125, 375, 500 nos niveis 1 a 3. Sao exactamente os tres
primeiros valores de `fletta_necessaria`, medida em 29/08 em 28 pontos com formula fechada.

E o palpite ia escrito POR CIMA da formula, porque as duas linhas escreviam no offset 61 e a
ultima ganhava. Batia ate ao nivel 3 e mentia do 4 para cima.

### O que se corrigiu

Fora o `pack_into` em 54 e o segundo em 61; fora a leitura do `contador54` do molde e o seu
incremento; a funcao `campo61_de` e o dicionario `CAMPO61` deixaram de existir. A coluna
`contador54` fica no banco porque o procedimento `dbo.salvar_progresso` tem um numero fixo
de parametros -- passa a gravar zero.

### A licao

Duas vezes no mesmo pacote, o mesmo erro: um campo ja conhecido apareceu segunda vez com
outro nome, por ser lido numa janela deslocada ou por se lhe ter ajustado uma curva. Antes
de ajustar curva a um "campo desconhecido" com poucos pontos, ver se algum campo JA
CONHECIDO gera esses mesmos pontos. E um campo que parece dar a volta num byte quase nunca
da a volta: esta a ser lido pela metade.

## 45. O relogio do buff PARA quando o jogador sai

A seccao 40 pos os buffs a sobreviver ao logout, guardando `acaba_em` -- um instante Unix.
Sobreviviam, mas continuavam a contar: quem lancasse um buff de vinte minutos e voltasse no
dia seguinte nao encontrava nada. O jogador corrigiu: *"a contagem nao para quando estou
deslogado, tinha que pausar, e voltar da onde parou quando eu logasse"*.

Guarda-se agora o que FALTA, nao QUANDO ACABA -- a coluna passou a chamar-se
`restante_seg`. Uma duracao guardada fica parada sozinha; um prazo nao.

    lancar   grava a duracao cheia
    sair     grava o que sobrou      (entity_sim.pausar_buffs, chamado na desconexao)
    entrar   repoe com o que sobrou  (repor_buffs, que ja mandava o 05 09 com o resto)

Se o servidor cair sem passar pela desconexao, fica gravado o que o lancamento escreveu, ou
seja a duracao cheia: o jogador recupera o tempo que ja tinha corrido. Erra a favor dele, e
poupa escrever no banco a cada volta do ciclo.

### MEDIDO no oficial, e nao e o logout que conta

Escrito primeiro como "nao medido, vem do que o jogador observou". O jogador foi gravar
(`karos_20260830_1433.log`) e a captura decide -- e mostra mais do que se perguntou.

A LIGACAO NUNCA CAIU. Ha keepalives de dez em dez segundos do principio ao fim. O que ele
fez foi voltar ao ECRA DE PERSONAGENS (par de `02 01` aos +98,25 s) e regressar ao mundo
aos +339,63 s (`02 05`). E o buff parou na mesma:

    + 57,42 s   05 09 diz que restam 1119 s     (no mundo)
    + 98,25 s   sai para o ecra de personagens  ->  40,8 s no mundo, 1119 - 41 = 1078
    +339,63 s   05 09 diz que restam 1079 s     (241 s no ecra nao contaram)

Um segundo de folga em 241. O relogio conta o tempo NO MUNDO, nao o tempo ligado.

Isto obrigou a uma segunda chamada: alem da desconexao, o `pausar_buffs` entra tambem no
tratamento do pedido de selecao de personagem. Sem ela, quem voltasse ao ecra e regressasse
tinha perdido o tempo la parado -- que era precisamente o defeito que se estava a corrigir,
so que por outra porta.

### O jogo fechado a serio: segunda captura

`karos_20260830_1446.log`, o mesmo personagem e o mesmo buff, agora com o jogo mesmo
fechado. Em hora absoluta, juntando as duas capturas:

    14:32:39   05 09 diz que restam 1075 s
    14:33:08   ultimo pacote -- o jogo fecha   ->  29 s no mundo, logo ~1046 a saida
               . . . 12 min 25 s (745 s) com o jogo fechado . . .
    14:45:33   volta ao mundo, 05 09 diz que restam 1038 s

745 segundos fora, 8 gastos. Se o relogio corresse teriam restado 301. As duas hipoteses
estao a centenas de segundos uma da outra -- nao ha aqui nada por decidir.

Os 8 segundos sao a folga entre o ultimo pacote apanhado e o instante em que o jogo fechou
mesmo; 8 em 745 e um por cento. Fica dito em vez de arredondado a zero.

### A regra

Duas medicoes, dois caminhos diferentes, uma regra so:

    | saida                 | tempo fora | buff gastou |
    | ecra de personagens   |    241 s   |     0 s     |
    | jogo fechado          |    745 s   |    ~8 s     |

O relogio conta o TEMPO NO MUNDO, nao o tempo ligado. E por isso que o `pausar_buffs` tem
de entrar nos dois sitios: na desconexao e no pedido de selecao de personagem.

A pergunta era "pausa ou nao pausa". A resposta veio, mas a coisa que ninguem tinha pensado
em perguntar -- que estar no ecra ja conta como estar fora -- so apareceu porque a captura
trouxe o intervalo inteiro em vez de so o instante da resposta.

## 46. Montaria -- o modo 2 do 03 02, e a velocidade que estava no itemAttr

O jogador gravou a montar e a desmontar no oficial (`karos_20260830_1505`). A sequencia:

    SEND 05 03  id=5 extra=1 + posicao     comeca a invocar
       ... 2 s de canalizacao ...
    SEND 05 04  id=5 + posicao             acaba
    SEND 03 02  [10]=2                     MONTOU
    RECV 03 02  [10]=2, com a entidade em [12:16]
    RECV 03 16  corpo a zero, e depois 08 02 e 08 01

    SEND 03 02  [10]=0                     DESMONTOU -- sem 05 03/05 04 nenhum

O `[10]` do 03 02 NAO E UM BOOLEANO: vale 0 (normal), 1 (arma sacada) e 2 (montado). Nos
liamos `bool(body[10])` e uma montada teria entrado como "arma sacada". O 05 03 e o 05 04 ja
eram ecoados desde 29/08, portanto a canalizacao nao precisou de nada.

### A velocidade de movimento, finalmente

Comparando a ficha antes e depois de montar, MUDA UM CAMPO SO: o `08 02` no offset 82, de
71,5 para 99. Varrendo todas as capturas oficiais, os valores que ali aparecem sao:

    65,00  x2210   a base -- e o modo que se mediu no fio ha dias
    71,50  x4      65 x 1,10, o +10% das botas do fato cash
    84,50  x31     65 x 1,30
    90,00  x1      o outro modo medido no fio
    99,00  x1      90 x 1,10, montado

Os dois candidatos de 28/08, no `02 05`, eram falsos: o [1100] vale 65 em TODAS as trinta
capturas e nunca muda, e o [1068] e a MANA (na captura de 30/08 vale 98 e a ficha do jogador
diz "Mana 98"; varrendo a zona 900-1200 a procura dos oito numeros da ficha dele, foi a
unica batida). O 90 que fazia o [1068] parecer velocidade era so a mana de varios
personagens calhar 90.

A MONTARIA SUBSTITUI A BASE, nao soma: 90 x 1,10 = 99. Somar daria 171,5.

### As 100 montarias, e onde estava o numero

Sao 100 itens, todos slot 6, categoria 6, tipo 1, com modelo `HAM_RIDER_NN`. O mecanismo nao
olha ao item, portanto uma implementacao serve as cem.

A velocidade de cada uma esta no `itemAttr`, no nono double (indice 8) -- todas as 100 tem
linha la. O indice 6 e a proteccao fisica e o 7 a magica, e essas entram ao EQUIPAR, nao ao
montar. A Phoenix da [6]=70 e [8]=90, que sao exactamente os dois numeros medidos: +70 de
proteccao na ficha ao equipar, e o 99 = 90 x 1,10 ao montar.

O CAMINHO ERRADO, que se andou: procurou-se no blob de 156 bytes do proprio item (u32, u16,
float e byte, em todos os offsets), no ItemBonus, no petBase e no transformBase. Nada. Deu-se
entao por assente que so existia no TEXTO da descricao, e escreveu-se uma ferramenta que o lia
dali -- 21 das 100, e portanto 79 sem velocidade. Isso teria sido errado de duas maneiras: 79
montarias a velocidade base, e TRES ERRADAS, porque os dragoes (48010, 48011, 48012) anunciam
80 na descricao e a tabela diz 90. O client mostra o da tabela.

O que fez voltar atras foi o jogador dizer "nao inventa nada, todas montaria tem na descricao
dela o que elas dao certinho". Nao estava na descricao, mas estava a dizer a coisa certa: o
numero existe para as cem. Faltava era a tabela, e o `itemAttr` nunca tinha sido olhado.

## 47. A escolha da janela "Selecione Opcoes" vai no proprio 06 02

A ultima caixa da cadeia do fato abre uma janela com DUAS opcoes -- sao as duas linhas que o
ItemPackage da a peca. O jogador viu que a escolha nao valia nada: *"aparece 2 param.
diferente, vc so colocou 1 independente do que eu escolher"*.

Nao ha pacote proprio para a escolha. Ela viaja no `06 02` que abre a caixa, nos bytes
[17:21] em big-endian. Medido no oficial (captura das 13:35), nas quatro caixas finais:

    13:35:18  06 02 ... 17 da 91 a2  |  00 00 eb 1c  ->  60188  (torso)
    13:35:24  06 02 ... 17 da 91 a5  |  00 00 eb 20  ->  60192  (pernas/pes)
    13:35:31  06 02 ... 17 da 91 a8  |  00 00 eb 24  ->  60196  (mao)
    13:35:37  06 02 ... 17 da 91 ab  |  00 00 eb 28  ->  60200  (pe)

Sao codigos de efeito do ItemPackage, os mesmos da seccao 43.

NAS CAIXAS ANTERIORES DA CADEIA o mesmo campo traz a PROPRIA ENTIDADE (0x2df2, o id do
personagem), porque ali nao ha nada a escolher. Em vez de tentar distinguir os dois casos
pelo valor, o `dar_item` valida a escolha contra as opcoes que o item tem: um id de
entidade nunca esta nessa lista, portanto cai fora sozinho e volta-se ao sorteio.

Ate aqui o servidor sorteava sempre, desse o jogador a opcao que desse.

## 48. Senha errada: o silencio corrigiu-se, a mensagem nao se escolhe

O jogador: *"na pagina de login, quando coloca senha errada da dando disconect, nao aparece
nada de senha errada"*.

### O defeito, e era so este

O `login_server` ja mandava uma recusa -- os bytes exactos de uma rejeicao capturada do
oficial -- e a seguir fazia `return`, o que fecha o socket no mesmo instante. O client nao
chegava a desenhar o popup.

Na captura da rejeicao real (`captures/real_server_reject_01.log`) o oficial manda os 345
bytes, ESPERA a resposta de 19 bytes do client e so entao a ligacao cai. Posta a mesma
espera (`recusar`, ate 3 s), o popup passou a aparecer.

### A mensagem, essa, nao vem de nos

O client tem uma tabela de motivos -- `FUN_00644745` do AMo.exe -- que traduz um numero
numa das mensagens do `string.lod`:

      1  Fechamento do jogo devido a um erro do sistema
      2  A versao do cliente nao foi atualizada
      3  Nome de usuario ou senha invalidos
      4  Classe errada          5  Linha errada        6  Esse nome ja esta em uso
      8  A conta esta em uso    9  Objeto ausente     12  Nao ha autoridade
     15  Entrada de outro computador               16  Saida a pedido do administrador
     17  Personagem bloqueado                      18  Erro do Game Guard
    661  A sua autorizacao expirou

E o 3 que se queria. Nao se consegue: **o 661 nao esta nos bytes que mandamos** -- procurou-se
nos dois ficheiros da recusa, em BE e em LE, e nao aparece -- e no binario inteiro o `0x295`
nao existe uma unica vez como imediato (varrido o `.text` todo pelo IDA, 0 ocorrencias). O
client inventa-o quando conclui que o login nao completou. Nao ha campo para preencher.

O erro do `launchpoint.exe` que salta depois do OK e a acao presa a esse mesmo 661: o client
quer relancar pelo lancador da NHN, que nao existe na pasta -- nem na instalacao oficial.

### O palpite que correu mal, e a licao

Tentou-se pôr o 3 no byte [13] do `02 00`, o candidato obvio. **[13] nao e o motivo: e o
numero de MUNDOS da lista de servidores.** O client leu a recusa como um login BOM, desenhou
a tela de escolha com tres mundos e uma senha errada passou a entrar. Dois minutos de brecha.

A licao nao e "nao adivinhar" -- as vezes so ha adivinha, como aqui, porque o oficial entra
por TOKEN e nao tem senha no client: a recusa dele por credenciais mas e mesmo a 661, e o
login por utilizador e senha e invencao nossa, sem nada para copiar. A licao e ONDE se
adivinha: nunca num campo que tambem faz parte da resposta de SUCESSO. Um palpite errado num
campo desses nao falha -- autoriza.

### Fica assim, de proposito

O jogador decidiu passar o login para token mais a frente, e ai o erro de credenciais deixa
de ser do client e passa a ser do launcher. Trocar a string 47779 do `string.lod` arrumava a
redaccao hoje e ficava errada depois.

## 49. As "cinco linhas" da tela de mundo eram imagens pintadas

O jogador: *"quero remover esses lixos abaixo e deixar so a linha que o server chama"*. A
tela "Escolhendo o mundo" mostrava CINCO linhas -- uma com nome e quatro vazias, todas com
uma pastilha de regiao, e as duas primeiras com um selo NEW.

### Tres hipoteses erradas, e como cairam

**Era a contagem que mandamos?** Nao. Gravou-se o nosso proprio fluxo de entrada e nao ha um
unico `5` em pacote nenhum:

    hop1  02 06 (32)  server_count=0  ids=[]
    hop1  02 00 (290) [13]=1  servidor 42  canal 2
    hop2  02 06 (34)  server_count=1  ids=[42]

**Era tabela no binario?** Nao. Procuraram-se os cinco ids de servidor (1, 22, 28, 41, 42)
colados, e com passo regular de 2 a 128 bytes, em u16 e u32 -- nada. As classes
`CGuiServerSelectList` e `CGuiServerList` existem como texto mas SEM referencias, portanto
nao sao RTTI. Houve um falso positivo pelo caminho que vale a pena registar: apareceram
1041, 1042 e 1028 juntos, com 12 bytes de intervalo, e pareciam a tabela -- sao CODIGOS DE
PAGINA do Windows (japones, coreano, chines) dentro da `___lc_codepage_func` da biblioteca C.

**Era o `status_flag` do nosso canal?** Nao. Posto a 0, a tela ficou igual.

### O que era

Sao IMAGENS EM COORDENADAS FIXAS no `ResUnpak/Interface/IE_Script/Contents/Contents_Enter.Sct`,
no bloco `#.GUI 30034`. Tinha onze imagens de fundo:

    3.BG 6113 em y=71    selo NEW
    4.BG 6113 em y=101   selo NEW
    5.BG 6114 em y=71    pastilha de regiao
    6.BG 6115 em y=101   pastilha
    7.BG 6115 em y=131   pastilha
    8.BG 6114 em y=161   pastilha
    9.BG 6114 em y=191   pastilha

Dois selos e cinco pastilhas -- exactamente o que se via. A lista sempre teve UMA entrada; as
outras quatro "linhas" eram decoracao desenhada por cima do vazio. Tiradas essas sete linhas
(e renumerado o mapa-mundi de 10 para 3, com `NumBGImage` de 11 para 4), fica so a linha do
servidor.

### O que resolveu

Comparar com OUTRO client. O jogador apontou o `Downloads\Arena`, que mostra duas linhas: o
binario e outra versao (3,7 MB contra 5,1) mas o `Contents_Enter.Sct` e quase do mesmo
tamanho, e o bloco 30034 dele tem TRES imagens em vez de onze. A diferenca apontou as sete
linhas certas em segundos, depois de horas a procurar no sitio errado.

Fica tambem a conta que ainda faz sentido, se um dia se quiser encolher o painel: a altura
segue `260 + 22 * linhas` -- os 370 nossos sao 5 linhas, os 304 do Arena sao 2.

### Ao lado disto

O nome do mundo e a string `1000 + server_id` da tabela do client -- mandamos 42 e ele vai
buscar a 1042. Estava "Kart" e passou a "Axion" (`string.lod_pt`, com copia em
`string.lod_pt.antes_axion`). Note-se que esses ids sao slots de NOME DE MAPA
(1002 Posetunia, 1003 "Berneo (recomendado)"...); o 1042 calhou servir de nome de mundo.

E corrigiu-se, pelo caminho, o `02 00` do login de sucesso: era copiado do frame de RECUSA,
que tem um byte a menos, e ia desalinhado do offset 12 em diante.

## 50. A cifra do client do Arena, aberta por um ficheiro de tres bytes

Nao ha client oficial mais recente que o nosso, de 2019. O do servidor Arena e de 29/06/2026
-- sete anos de conteudo a mais -- mas as tabelas dele nao abriam.

### O que nao era

Os ficheiros SEM sufixo de idioma (`SkillTree_*.lod`, 49 deles) abriam com o nosso
decifrador de sempre. Os `_en` e `_sa` -- 202 -- nao. Portanto a cifra base era a mesma e
so os LOCALIZADOS estavam diferentes.

Varreu-se a constante `0x4F` nos 256 valores. Depois o passo com que a tabela de substituicao
e extraida do cabecalho, o inicio do fluxo cifrado e a chave inicial, em 400 x 256
combinacoes, com a assinatura `20101118` como gabarito. Zero. Nao era variacao de parametros.

O binario deles esta empacotado com **Themida** (seccoes `.themida` e `.boot`; a `.text`
ocupa 4,45 MB em memoria e 1,88 MB no ficheiro), portanto ler o decifrador no codigo tambem
nao era caminho barato.

### O ficheiro de tres bytes

O `IE_CharFiltering.lod` do client oficial **nao esta cifrado**: sao `30 0d 0a`, ou seja um
"0" e um fim de linha. O do Arena tem tambem tres bytes: `bd 80 87`. Claro e cifrado do mesmo
ficheiro, lado a lado -- e o XOR sai igual nos tres:

    0xbd ^ 0x30 = 0x8d      0x80 ^ 0x0d = 0x8d      0x87 ^ 0x0a = 0x8d

E so isso: um XOR de UM BYTE por cima do `.lod` normal. A chave util e `0x0D`. Com ela os 202
ficheiros abrem, e o decifrador que ja tinhamos trata do resto -- `decrypt_lod_stream(...,
xor=XOR_ARENA)`.

Horas de forca bruta contra os parametros da cifra, e quem resolveu foi o ficheiro mais
pequeno da pasta. Vale a pena guardar como metodo: **num conjunto cifrado, procurar primeiro
o ficheiro cujo claro se conhece** -- e o menor costuma ser o mais provavel de se conhecer.

### O que se ve la dentro

    tabela              nosso    arena     delta
    itemAttr            34336    51198   +16862
    skillLevel           4623     8851    +4228
    skillBase            2710     4991    +2281
    itemLinkMap           113      285     +172
    SkillTree_x11/x12      19    30-32   +10..13   (em 32 das 49 arvores)
    skillData            4146     3345     -801
    itemBase            19806    15569    -4237

O grosso da diferenca esta nas SKILLS: quase o dobro dos niveis, e 10 a 13 skills a mais em
cada arvore de classe avancada. Ha menos itens no catalogo deles mas muito mais linhas de
atributo -- catalogo mais curto, com mais graus de refino por peca.

Os nomes dos mundos confirmam de passagem a regra `1000 + server_id` da seccao 49: o
`string.lod_en` deles tem `1001 = 'Arena'` e `1002 = 'ASTERION (NEW)'`, que sao exactamente as
duas linhas que a tela deles mostra.

## 51. A barra de atalhos nao e guardada por ninguem

O jogador: *"a barra de skill em baixa deve ta puxando do mold pq eu altero, quando relogo
volta assim"*. Parecia molde nosso. Nao e -- o jogo simplesmente nao guarda a barra.

### O que se mediu

    o client manda 06 0b por cada ranhura        SIM, medido no nosso e no oficial
    o servidor oficial responde ao 06 0b         NAO, nunca -- quatro amostras
    algum pacote devolve a barra                 NENHUM
    ficheiro na pasta do jogo                    NAO
    AppData                                      NAO
    registo (HKCU\Software\Karos)                NAO -- so graficos e chat

O `06 0b` tem 26 bytes:

    [10:14] ranhura, BE      [14:18] o que la fica, LE
    [18:22] 9 ou 10          [22:26] varia (0, 1, 4, 5, 7), por perceber

O `05 0c` parecia a candidata obvia -- 12 bytes de cabecalho e 15 ranhuras de 13 (id LE,
tipo LE, cinco zeros) -- mas nao pode ser: o jogador usou a ranhura **32**, e ali so cabem
15. E comparando os DOIS LOGINS da mesma captura do oficial, um antes de mexer na barra e
outro depois, o `05 0c` sai byte a byte IGUAL e vazio nas duas vezes. O que muda entre eles
e so o inventario, a ficha e o calendario.

### O que enganava

A barra ficava, no oficial. Mas o "relogar" era **voltar ao ecra de personagens** -- o
processo do client nunca morria e a barra ficava na MEMORIA dele.

E a SEGUNDA vez no mesmo dia que esta distincao inverte uma conclusao. No relogio dos buffs
(seccao 45) foi igual: "desloguei" e a captura mostrou keepalives de dez em dez segundos com
a ligacao viva. Fica a regra: **num relatorio de persistencia, perguntar sempre se o
processo chegou a morrer**, porque "saiu" e "fechou o jogo" nao sao a mesma coisa e a
diferenca decide a resposta.

### O que ficou feito na mesma

`dbo.personagem_atalho` guarda os quatro campos do `06 0b`, e o `build_barra` repoe as
ranhuras 0 a 14 no `05 0c`. Isso e MAIS do que o oficial faz. Fica ligado porque nao
atrapalha e porque, no dia em que se achar como as ranhuras acima de 14 viajam, ja ha onde
ir buscar o que guardar.

## 52. O campo [19:23] do `08 06` e a MANA, nao o HP maximo

Estava a escrever o HP maximo ali desde o inicio. Passou despercebido porque para o heroi
os dois numeros andam na mesma ordem de grandeza e nada de visivel se partia.

O que desmente, na captura oficial:

    mob 952    HP 128 -> 123 -> 118 -> 113 -> 108 -> 25 -> 0    campo = 100 SEMPRE
    mob 957    HP  90 ->  64 ->  16 ->  11 ->   6 ->  0         campo = 100 SEMPRE
    ent 11277  HP ate 296                                       campo = 116,119,121,122,125,...

Duas provas independentes:

  - vida ACIMA do maximo nao existe, e o 952 tem 128 com o campo em 100;
  - um maximo nao oscila, e o do 11277 (o jogador) sobe de 116 para 130 -- e mana a
    regenerar.

Nos 6 monstros medidos o oficial poe 100 fixo. O HP maximo verdadeiro nao viaja neste
pacote: o client aprende-o no `04 02` da aparicao e guarda-o -- e por isso e que a barra
funciona sem o maximo vir aqui.

Corrigido: `build_hp` passa a receber `mp`, com `MP_DE_MOB = 100` por omissao e o `h.mp`
verdadeiro no ciclo de regeneracao do heroi. O tique ficou byte a byte igual ao oficial.

### O que NAO era o problema da barra

Duas hipoteses medidas e postas de lado antes de chegar aqui:

  - *"o oficial manda os `08 06` aos pares no tique, um marcado e um normal"*. Manda no
    PRIMEIRO tique do 952 e mais em lado nenhum -- os outros oito tiques dos dois mobs vao
    sozinhos com a marca a 1. Nao e o mecanismo.
  - *"nos repomos a vida do bicho por tras"*. Nao ha regeneracao de mob no nosso codigo; o
    unico sitio que repoe vida e o renascimento.

## 53. Efeito periodico em quem lanca -- a "Regeneracao de mana" (11004)

O jogador: *"da maga nao e passiva, mas quando eu uso nao aumenta os 10 que fala ai"*.

A tabela diz tudo, e bate com o balao do client:

    11004  nv1   duracao 20   periodo 2   tipo 29 (mana)   valor +10
    11004  nv2   duracao 20   periodo 2   tipo 29 (mana)   valor +20

Ou seja DEZ tiques de +10 mana, nao +10 de mana MAXIMA durante 20 s. Nos tratavamos assim:
o `aplicar_buff` metia a skill no `BUFFS`, e o `bonus_dos_buffs` somava o +10 aos bonus de
ficha. A mana maxima subia 10 e mana nenhuma entrava -- exactamente o que se via.

E o mesmo motor do dano continuo, com o sinal trocado e outro destinatario. Criado o
`PERIODICOS` (gemeo do `DEBUFFS`, mas na entidade de quem lanca) e o `rodar_periodicos`,
que repoe vida/mana e manda a ficha (`08 01`) e a barra (`08 06`). Uma skill com periodo
deixa de entrar no `BUFFS`, senao contava duas vezes.

### O 08 06 tambem serve para a mana sozinha

O ciclo de regeneracao so mandava o `08 06` com `if falta_hp`, por causa da aura que ficava
presa com a vida cheia. O corte estava no sitio errado -- o oficial manda mesmo o pacote com
o HP parado:

    ent 11277   HP 296  MP 125     <- HP ja no maximo
    ent 11277   HP 296  MP 128
    ent 11277   HP 296  MP 130

Quatro pacotes so de mana em dez. A aura presa era provavelmente o defeito da seccao 52: com
o HP maximo no campo do MP, um tique so de mana saia com os dois numeros iguais ao anterior,
sem anunciar mudanca nenhuma. Isto e explicacao, nao medicao.

### O que NAO serve como regra: `alcance10 == 0`

Tentei usar o alcance para decidir quem recebe o efeito periodico -- a 11004 tem 0 e a
Corrosao tem 165. Nao aguenta: a "Bola de fogo" (50122) e a "Flecha venenosa" (1050010)
tambem tem 0 e vao claramente no alvo. Fica o sinal do valor a decidir (positivo em quem
lanca, negativo no alvo), com uma excepcao conhecida por resolver: as auras que gastam mana
DO PROPRIO (3104, 4102) tem valor negativo e deviam ficar em quem lanca.

## 54. Como o oficial entrega a "Regeneracao de mana" (gravado em 30/08, 16:44)

Janela 60162..60182 do `C:\RoshHook\traffic.log`. O jogador (entidade 11277 = `2c 0d`)
lancou a 11004 (`00 00 2a fc`) duas vezes.

### Vai em quem lanca

    05 09  entidade 2c0d  LIGA     skill 11004  dur 0x14 = 20 s
    05 09  entidade 2c0d  DESLIGA  skill 11004  dur 0        (20,4 s depois)

Confirma o que se supunha: o periodico do proprio anuncia-se na entidade do lancador,
ao contrario do debuff, que vai na do alvo.

### A mana viaja no 08 01, e o primeiro tique e NO LANCAMENTO

Nao ha um unico `08 06` para a entidade do jogador nos 20 segundos -- conferido pacote a
pacote. A mana esta no `08 01` (a ficha), campo [23:27] LE:

    60161.974  MP 114        antes
    60162.219  MP 124        <- o mesmo instante do 05 09. O primeiro +10 e ja aqui.
    60164.371  MP 134        +2,15 s
    60166.610  MP 140        travou no maximo (seriam 144)

Layout do 08 01 conferido de novo aqui: [7:11] entidade BE, [15:19] HP, [19:23] HP max,
[23:27] MP, [27:31] MP max -- todos LITTLE-endian.

### Um so numero, no lancamento

    05 02 ... 2a fc 01 | ff ff ff ff | 0d 2c 00 00 | 01 | f6 ff ff ff
               skill     sem alvo      o proprio     bnd    -10

e mais nenhum nos 20 s. Os tiques seguintes nao mostram numero; so a barra mexe.

O SINAL VEM TROCADO: o campo e de dano, portanto repor 10 de mana sai como -10.

### O campo do dano tem QUATRO bytes, nao um

Estava escrito aqui que "[27] e o dano e satura em 255, [24:26] e sempre zero". A metade
do meio esta certa, a outra nao: `[27:31]` e um i32 LITTLE-endian. Os dois unicos pacotes
de todas as capturas em que `[28:31]` nao e zero sao exactamente estes dois lancamentos da
11004, com `f6 ff ff ff`.

O positivo continua a saturar em 255 -- nunca se viu golpe maior no fio, e alargar o campo
por deducao ja custou caro antes. Fica como decisao consciente, nao como leitura do campo.

### Layout completo do 05 02, agora que ha casos que o separam

    [7:11]  BE  quem lanca
    [12:16] BE  skill
    [17:21]     ffffffff quando nao ha alvo; um numero pequeno quando ha (por decifrar)
    [22:26] LE  o alvo   (o proprio, no caso do periodico)
    [26]        bandeira: 1 normal, 2 critico, 4 continuo, 5 bloqueio, 6 esquiva
    [27:31] LE  i32 com sinal -- o numero que flutua
    [31]        cor; 0 na reposicao, mesmo com a bandeira 1

### Porque o numero da 11004 nao aparecia (mesmo com a mana a subir certa)

A mana subia bem -- dez tiques de +10, de 21 a 124 -- e numero nenhum no ecra. O pacote
estava quase todo certo; falhavam dois campos que NUNCA se escreviam e ficavam com o valor
do molde:

    oficial   [17:21] ff ff ff ff   [21] 00
    nos       [17:21] 00 00 00 0b   [21] 01     <- lixo do molde

[17:21] CONTINUA POR DECIFRAR. Vale 13, 13 e 11 nas skills 11002/11003/11001 do mesmo
jogador e ffffffff na 11004 e nos golpes que o monstro da. Tentei o custo de mana e nao
bate: a 11001 custa 4 no nivel 5 e o campo diz 11. Escreve-se so no caso medido -- quem
lanca sobre si proprio -- para nao mexer no golpe normal, que ja funciona.

Tambem [47:51]: sobre si proprio nao ha "virar-se para quem bateu", e o rumo que serve e o
que ja vinha no pedido. Sem isto saia 0,63 rad onde o oficial mandou 5,92.

Depois disto o pacote fica igual ao oficial em tudo menos os tres bytes do rumo, que
mudam porque o jogador se virou entre o pedido e a resposta.

### O numero em todos os tiques -- escolha de jogo

O oficial mostra o numero UMA vez, no lancamento. A pedido do jogador (30/08) o nosso
mostra em cada um dos dez tiques. Fica registado que e desvio deliberado, nao imitacao:
ver o comentario em `rodar_periodicos`.

### Confirmado a funcionar (30/08, 17:04)

Dez tiques de +10 de mana, com o numero a sair em todos. Falta so o rumo, que difere do
oficial em tres bytes por o jogador se virar entre o pedido e a resposta.

UM ERRO DE PROCESSO, para nao se repetir: a linha `pack_into("<H", p, 22, alvo)` existe em
DUAS funcoes -- `build_dano_skill` e `build_dano_no_jogador`. Um substituir sem limite meteu
o bloco novo nas duas, e a segunda nem tem `pedido`. Deu `NameError` a matar a thread do
mundo inteira: nada regenerava, nada andava. Ao trocar codigo por texto, contar as
ocorrencias ANTES de escrever.

## 55. As trilhas de monstro estavam coladas -- e a ferramenta lia metade das capturas

O jogador em 30/08: *"nossa trilha ta tudo bugadas, a ferramenta nao foi precisa"*. Estava
certo, e havia tres defeitos independentes.

### 1. A ferramenta nunca cortava

O `coletar()` agrupava por `(captura, entidade, especie)` e somava tudo. Sempre que o bicho
saia do campo de visao e voltava -- ou o id de entidade era reaproveitado -- os passos
continuavam a ir para a MESMA trilha. Medido nas 55 trilhas gravadas:

    54 das 55 com buraco de tempo acima de 3 s; o maior de 9872 s (2h45)
    30 das 55 com salto de posicao acima de 20 u; o maior de 151 u

O `_aparar_buracos` do servidor encolhia o RELOGIO mas deixava as posicoes, portanto a
costura virava uma marcha em linha recta de 151 unidades. Na tela: o monstro congela e
depois parte a direito.

Cortar rende MAIS material, nao menos -- 2044 pedacos limpos de 60 passos ou mais, contra
55 trilhas coladas.

### 2. Metade das capturas estava de fora

A ferramenta so lia `C:\RoshHook\traffic*.log` (180 MB). A pasta `CapturaKaros` do Desktop,
que o jogador ja tinha dito ser "tudo do original tbm", tem 130 MB que nunca foram lidos --
so o `karos_20260827_0838.log` tem 108 MB. Com as duas: 310 MB, 60 ficheiros.

Resultado: 10 especies com trilha -> **14**. A Fada passou de 2 trilhas para 6.

### 3. O emprestimo era sempre da mesma especie

    minhas = todas.get(sorted(todas)[0])     # o id mais baixo, sempre

Todo o monstro sem trilha andava como um Urso furioso, que e dos mais rapidos (8,2-8,8 u/s
contra 4,4 da Aranha pequena).

O que os dados mostram e que os ids andam aos PARES, e o par e a mesma criatura em dois
niveis:

    1000814 Urso furioso 14   /  1000815 Urso furioso 15
    1001005 Filhote      5    /  1001006 Filhote      6
    1005002 Urso coruja  2    /  1005003 Urso coruja  3
    1006703 Cao da estepe 3   /  1006704 Cao da estepe 4

Quatro dos cinco monstros sem trilha tem irmao com trilha. Passou a emprestar por NOME
primeiro, e por nivel mais proximo quando nao ha irmao (so os Zombies, hoje).

### O tamanho real do problema

Das 69 especies com poste no mundo, **so 18 sao monstros**; as outras 51 sao NPCs parados,
que nao precisam de trilha. Dos 18, **13 tem trilha propria** e 5 emprestam. Isto e muito
menos mau do que o "56 sem trilha" que a ferramenta imprime.

### De caminho: o espiao ficou ligado

`ESPIAO_ENTIDADE = -1` grava TODAS as entidades para `captures/espiao_movimento.log`, que ja
vai em 19,6 GB. Nao e urgente -- ha 1,8 TB livres e o backup ja o exclui -- mas nao ha razao
para continuar ligado fora de uma sessao de diagnostico.

## 56. Os guardas andavam, e atacavam toda a gente

O jogador em 30/08: *"guardas ainda esta em movimento e sao agressivo, na verdade eles ataca
sem aviso quem ta de moral negativa"*.

### Porque andavam

O Guarda e o npc 44444444 (id nosso, nao do client). Na tabela `Npc` do proprio client ele e
`tipo=2` -- criatura, na mesma gaveta do Tirano sanguinario e da Fada -- e nao `tipo=1`, que
e o NPC parado. Por isso os 13 postes dele estao marcados `tipo = mob` e ele recebia trilha
emprestada como qualquer monstro.

A CORRECCAO NAO E `tipo = npc` NO POSTE, que era a ideia obvia: `tipo == "npc"` desliga o
passeio E a agressao, e o guarda tem de reagir. Ficou `anda = False` no Mob.ini, lido por
`especie_parada`.

Conferido de caminho, e estava tudo certo: os 46 postes marcados `npc` sao exactamente as
46 especies `tipo=1` do client (Melisa, Hudson, Caixa de correio...). Nao ha nenhum NPC de
cidade a passear.

### Porque atacavam toda a gente

`agressivo = True` no Mob.ini, que so tinha dois estados. Passou a ter tres: `pk` quer dizer
"ataca sem aviso, mas so quem tem moral negativa". A reputacao ja existia (`heroi.reputacao`,
+8 por abate, negativa ao matar jogador) e nunca tinha sido usada para nada.

O portao so vale enquanto o modo ainda nao comecou -- quem bateu no guarda continua a ser
perseguido, com moral positiva ou nao.

### A armadilha do `or` num booleano

`str(linha.get("anda") or "")` -- o `data_files` ja converte "False" para o booleano False, e
`False or ""` da string vazia. A leitura respondia "anda" a toda a gente, com o ficheiro
certo. Ausente e diferente de False e so o segundo trava o passeio; agora sao dois caminhos
separados.

## 57. Monstro no morro -- a coleira de altura

O jogador em 30/08: *"ja tinha javali em cima dos morro onde player nao chegam"*.

### Nao ha deriva nova

Primeiro, a pergunta dele antes desta: o corte das trilhas nao trouxe deriva. Alcance das
trilhas carregadas antes e depois, 14 especies:

    antes (coladas)   p10 75   p50 112   p90 190   max 245
    agora (cortadas)  p10 62   p50  98   p90 187   max 261

E a base continua na ancora, portanto nao ha soma de desvio entre voltas.

### O que causa o monstro no morro

A trilha foi gravada a volta de OUTRA ancora. Transplantada para um poste que tenha encosta
ao lado, leva o bicho encosta acima -- o mapa de alturas poe-no a altura certa do chao, o
que nao o impede de la ir.

MEDIDO no oficial, 401.058 posicoes de 1546 monstros, desvio face a altura mediana do
proprio bicho:

    p50 2,4    p90 9,8    p95 13,5    p99 22,7    max 78,0

Os nossos, poste a poste com o mapa de alturas:

    Lagarto do pantano 421   Velocista 424   Aranha pequena 309   Urso coruja 285

O DECLIVE NAO SERVE DE TRAVA, e ja tinha sido tentado em 29/08: medi-o agora em 326.974
passos e da p50 2,6 graus, p90 20,1, p99 42,1, max 81,8. O monstro do oficial sobe encostas
ingremes a vontade. O que ele nao faz e ACABAR longe da altura de onde partiu.

`BANDA_DE_ALTURA = 45`, com o porque de nao ser 23 escrito no proprio codigo: o numero do
oficial e medido a partir da mediana do bicho, o nosso centro e o poste, que nao tem de
estar no meio da area dele.

### O mapa mentia onde nao foi medido

Primeira versao encolhia a trilha do Velocista ao minimo. O perfil de altura dizia:

    (2711, 3779) 296.2  ->  337.6  ->  (2716, 3762) 545.3     em 18 unidades

Patamares planos com degraus -- a assinatura do `altura_em` a cair no recurso dos aneis, que
devolve a altura de uma celula longe quando nao ha medicao perto. Estavamos a encolher por
causa de terreno inventado. Criado o `altura_medida_em`, que devolve None em vez de
adivinhar, e a coleira passou a usar so esse.

Resultado: **9 por cento das trilhas encolhidas**, o resto intocado, e o pior caso de cada
especie abaixo de 45.

### De caminho: duas coisas escritas e nunca ligadas

`Entidade.raio` (o raio de perambulacao do proprio client) e `_escala_da_trilha` existem,
tem comentario a explicar o defeito que corrigem, e NAO SAO CHAMADOS EM LADO NENHUM. Por
isso todo o mob perambula pelo alcance da trilha mesmo onde o client declara raio 10, que e
o caso de 231 dos 472 postes.

Nao liguei: mudaria o movimento de todos de uma vez, e nao esta estabelecido se a coluna
`raio` do client e raio de PASSEIO ou de NASCIMENTO. Se for de nascimento, ligar isso deixa
metade do mundo praticamente parado. Fica para medir.

## 58. A coluna `raio` do client e de NASCIMENTO, nao de perambulacao

Medido a pedido do jogador em 30/08, depois de eu ter avisado que ligar o `raio` a escala da
trilha era arriscado por nao se saber o que ele significa. Ainda bem que se mediu: a
hipotese que estava escrita no codigo esta ERRADA.

### Como se mediu, e o que nao deu

Primeira tentativa: casar cada monstro do oficial com o poste dele pelo `entidade_id` do
NpcSpawn. Nao serve -- das 518 entidades declaradas, UMA aparece nas capturas. O
`entidade_id` e o indice da linha do client, nao o id que o servidor da ao monstro.

O que funcionou foi medir uma grandeza que NAO depende do poste: a dispersao propria do
bicho, ou seja a maior distancia entre uma posicao dele e a mediana das dele. Essa nao
herda o vies de "escolhi o poste mais perto". 1953 segmentos limpos, de cinco capturas.

### O resultado

    raio    segs   dispersao do bicho      distancia ao poste
             (p50 / p90 / max)             (p50 / p90 / max)
     10      990   68 / 131 / 197          22 /  56 / 156
     20       63   63 /  83 / 150          21 /  38 /  89
     30      218   68 / 129 / 151          21 /  53 / 161
    150       99   64 / 118 / 140          43 /  86 / 190
    200       44   55 /  76 / 135          56 / 148 / 241
    250       75   58 /  85 / 129         121 / 264 / 264
    300      451   64 / 115 / 153         119 / 178 / 1109

A DISPERSAO NAO MEXE -- 55 a 68 em todos os grupos, de raio 10 a raio 500. A distancia ao
poste acompanha o raio quase linearmente.

Conclusao: o raio diz ONDE O MONSTRO NASCE em volta do ponto, nao ate onde anda depois.

### O que isto evitou

`_escala_da_trilha(trilha, self.raio)` teria posto 231 dos 472 postes a andar num circulo de
10 unidades -- metade do mundo praticamente parado, para corrigir um defeito que nao existe.
O comentario em `Entidade.raio` afirmava que "bichos que deviam ficar no posto vagueavam 10x
mais longe"; era hipotese, nao medicao, e ficou corrigido no proprio codigo.

O `db.spawns()` ja usava o raio da maneira certa desde sempre: espalha por ele os monstros
extra de cada poste (518 linhas declaram 1223 monstros). O primeiro de cada poste fica no
ponto exacto, que e o que o client conhece.

### De lambuja: quanto anda um monstro do oficial

Dispersao propria, 1953 segmentos: p50 64, p90 ~120, max ~200. As nossas trilhas tem alcance
p50 98 medido a partir do INICIO, que e cerca do dobro da dispersao face a mediana -- ou
seja andamos aproximadamente o que ele anda. Nao ha excesso de perambulacao para corrigir.

## 59. O javali que "fugia", e a cadencia do golpe

### Nao fugia -- desistia

Nao ha IA de fuga ligada para o javali: `IA_FOGEM` tem a Fada e tres ids de treino, mais
nada. E o relogio da cacada (`TEMPO_MAX_CACADA`) esta em infinito.

O que se via era o `DESISTE_DO_JOGADOR = 200`: passado esse afastamento o bicho larga e
volta ao poste, e volta mais depressa do que anda -- na tela le-se como fuga.

Faltava provar QUANDO isso disparava, e nao havia registo da provocacao. Acrescentado, e o
log respondeu:

    PROVOCADO por 20110 a  9 de distancia (5 do poste)
    PROVOCADO por 20110 a 18 de distancia (45 do poste)
    PROVOCADO por 20110 a 23 de distancia (83 do poste)
    DESISTIU: alvo a 200, alem dos 200

Provocacao sempre a 9-31 unidades, corpo a corpo; as desistencias todas acima de 200, ou
seja quando o jogador ja se tinha afastado. Comportamento normal, e o jogador confirmou:
"ja nao corre".

FICA A LICAO: a pergunta era "ligaste a IA?" e a resposta so apareceu depois de por a
decisao no diario. Tres hipoteses (fuga com pouca vida, relogio da cacada, corda) foram
descartadas por leitura de codigo em menos tempo do que teria levado a discuti-las.

### A cadencia por omissao era a mais lenta do jogo

    INTERVALO_GOLPE = 4.21   <- era tambem a omissao

Esse 4,21 saiu de UM monstro: a entidade 952, 19 golpes seguidos. A distribuicao real, em
3.335 intervalos, vai de 2,39 (Sedento de sangue) a 4,19 (Tirano sanguinario), com mediana
2,98. Ou seja 12 das 18 especies do nosso mundo batiam ao ritmo do bicho mais lento que
existe.

Separado em `CADENCIA_OMISSAO = 2.98`. Nao e por especie e sabe-se porque: no MobAlcance.ini
a coluna `cad_n` diz quantos intervalos sustentam cada linha, e das 18 especies do mundo so
o Tirano sanguinario tem numero proprio. As outras usam a mediana ate haver amostra.

## 60. Renascimento por especie -- e porque a medicao anterior nao dava numero

Comecou por ser uma pergunta sobre a Fada e rendeu a tabela toda.

### O metodo antigo estava a emparelhar a coisa errada

O `tools/medir_respawn.py` de 26/08 emparelhava por GEOMETRIA: dois nascimentos seguidos em
cima do mesmo poste, com ids de entidade diferentes, na premissa de que "quem renasce recebe
um id novo". Achou 16 renascimentos em 37 capturas, todos de uma especie, com p25 108 s e
p75 708 s. Espalhamento desse tamanho nao e uma constante mal medida -- e sinal de que os
pares nao eram renascimentos.

Foi por isso que o servidor ficou com `SEGUNDOS_RESPAWN = 15.0` para toda a fauna.

Emparelhando pelo HP zero -- `08 06` com HP 0 e o `04 02` seguinte da MESMA entidade -- saem
**5.340 renascimentos**, e a premissa antiga cai: o id de entidade e o mesmo antes e depois
da morte.

### A prova esta na tensao da distribuicao

    Tirano sanguinario     6,0 s    172 mortes    espalhamento 0,1
    Aranha pequena         7,0 s   2730 mortes    espalhamento 0,1
    Urso coruja            9,0 s     95 mortes    espalhamento 0,1
    Javali-de-sabre       15,0 s    527 mortes    espalhamento 0,0
    Lagarto do pantano    22,0 s    148 mortes    espalhamento 0,1
    Velocista do pantano  24,0 s     74 mortes    espalhamento 0,1
    Espadachim aguia      70,0 s    761 mortes    espalhamento 0,1
    Guardiao Estripador   77,0 s     43 mortes    espalhamento 0,1
    Cao branco            85,0 s    282 mortes    espalhamento 0,1

Nao ha emparelhamento errado que produza p25, mediana e p75 iguais ate a decima em 2.730
mortes de aranha. Um par ao acaso espalha.

Os 15 s que estavam no servidor calharam ser os do Javali-de-sabre e do Filhote de urso. A
aranha demorava o dobro do que devia; o cao branco renascia 5,7 vezes depressa demais.

### Cinco especies ficam de fora, de proposito

Peekskin (197 s de espalhamento), Sedento de sangue (320), Cao da estepe 1006703 (423) e os
dois Estripadores (2081 e 4542). Nessas a mediana nao resume nada e usar o numero seria pior
do que a omissao. `ESPALHAMENTO_FIAVEL = 2.0` corta-as, e elas ficam na tabela com a coluna
a dizer porque -- para quem ler depois nao ter de voltar a captura.

### A Fada

ZERO renascimentos em 5.340. Nao e falha da medicao: o jogador explicou -- *"Fadas nao tem
lugar fixo, e aleatoria"*, *"o respaw dela e mais de hr, pq dropa itens pra fazer asa"*. Nao
e um poste que renasce, e outra mecanica, e nao se inventou linha para ela.

Nota de caminho: os ficheiros `.npc` do mapa NAO tem tempo de renascimento. Os campos por
documentar do registo ([16:20], [24:28] e tudo acima de 40) estao a ZERO em todas as
especies conferidas, Fada incluida. Isso vive no servidor deles.

### A Fada: uma hora, e nunca no mesmo sitio

Pedido do jogador em 30/08, e e conhecimento dele sobre o oficial, nao medicao -- nao ha uma
unica morte de Fada nas 5.340 capturadas, porque nenhuma sessao gravada dura uma hora.

    respawn = 3600
    nasce_ao_acaso = True

Ficam no `Mob.ini`, que se edita a mao, e NAO no `MobRespawn.ini`, que a ferramenta
reescreve. O `respawn_de` passou a dar precedencia ao Mob.ini -- e o unico sitio do
servidor onde um valor escrito a mao ganha a uma medicao, e esta comentado porque.

A ZONA E O CONJUNTO DOS POSTES, nao um circulo a volta de um. Ela tem tres em Berneo --
(3696,3707), (8273,3517) e (2833,8117) -- e sortear sempre o mesmo poste fazia dela um bicho
de um sitio so, que e o contrario do pedido. O sorteio escolhe primeiro o poste, depois um
ponto dentro do raio dele (300), e repete ate oito vezes se calhar em sitio bloqueado.

A ANCORA MUDA, nao so a posicao. Foi a parte que quase escapou: a trilha e a corda usam a
ancora, portanto mudar so o `x/y` fazia a Fada nascer num sitio e depois atravessar o mapa
para ir passear a volta do poste antigo. Muda-se a ancora e limpa-se `_trilha`, para a
coleira de altura ser recalculada no terreno novo.

DE CAMINHO, UM ERRO APANHADO ANTES DE CORRER: escrevi `e.ancora_z = ...` e a `Entidade` nao
tem esse atributo -- so guarda x e y da ancora, e o z sai do mapa de alturas a cada passo.
Python aceita a atribuicao mas a LEITURA no mesmo sitio rebentava, e so no primeiro
renascimento de Fada, ou seja uma hora depois de arrancar.

## 61. A EXP por especie estava a ser medida com tres defeitos ao mesmo tempo

Comecou com o jogador a dizer *"nem todos mobs ta configurado exp"* e a gravar uma sessao
matando um de cada especie de Berneo. Os tres defeitos so apareceram porque essa captura
tinha resposta conhecida para comparar.

### 1. O campo [90:94] do 08 01 NAO e o nivel

Estava escrito aqui, desde 23/08, que ele guarda "nivel menos um". Nao guarda: na captura
das 22:49 ele ALTERNA entre 0 e 1 com a mesma experiencia e o mesmo nivel. E uma bandeira.

O nivel verdadeiro le-se do LIMIAR em `[77:85]`, que segue o Level.ini degrau a degrau --
48, 216, 520, 972, 1577, 2343, 3274 nos sete primeiros.

O estrago: o par exp/morte so era aceite com a bandeira igual dos dois lados, portanto
rejeitavam-se mortes dentro do mesmo nivel e aceitavam-se mortes que ATRAVESSAVAM um nivel.
Exactamente ao contrario do que se queria.

### 2. Contava mortes de OUTROS JOGADORES

A subida de EXP era emparelhada com qualquer morte a vista. O jogador apanhou isto de fora:
*"nao matei nenhuma aguia"* -- e a aguia estava na tabela.

O `08 06` diz quem causou a morte em `[23:27]`, campo decifrado hoje. Contando:

    captura de 27/08    174 mortes minhas   55 de outros jogadores   34 sem atacante
    captura de 30/08     12 mortes minhas    6 de outros jogadores    5 sem atacante

Com o filtro, a Aranha pequena passou de 12 para **24** -- que e o valor conhecido. Sem ele,
uma aranha morta por outro jogador partia o valor ao meio.

### 3. Mortes repetidas e eventos nao declarados

O oficial repete o `08 06` com HP zero varias vezes na mesma morte -- vi quatro no mesmo
milissegundo. Uma morte contava como quatro e a divisao devolvia um quarto.

E havia evento de EXP em capturas que ninguem tinha assinalado. Vem por dia (o jogador:
*"se foi hj tinha evento, se foi ontem tava sem"*), portanto a tabela `EVENTO` usa prefixo
de data e nao nome de ficheiro.

### A captura de referencia, e porque se acredita nela

`karos_20260830_2249.log`: um abate de cada especie, sozinho, com evento 3x declarado.
Depois das tres correccoes, CINCO das doze especies batem ao certo com valores obtidos por
outro caminho -- Aranha 24, Urso coruja 31, Filhote 92, Javali louco 152, Bico 176.

Cinco em cinco valida o metodo e o factor de uma so vez. As outras sete entram por isso:

    Cao da estepe 55    Javali-de-sabre 125*   Lagarto 223    Urso furioso 281
    Zombies 239         Velocista 239          Tirano 292

(*) fica nos 131 que o jogador leu no ecra: 5 por cento e menos do que a incerteza de uma
amostra so, e o numero dele nao passou por emparelhamento nenhum.

O Velocista saia a 684 pela mediana automatica -- 2,9 vezes o medido aqui, ou seja mais um
evento por declarar.

### Cobertura

Zero das 18 especies de Berneo ficam nos 24 da aranha. Doze tem medicao propria e seis
herdam do irmao de mesmo nome -- ver o `_irmao_com_exp`, criado hoje pela mesma razao que a
trilha: os ids andam aos pares e o par e a mesma criatura em dois niveis.

## 62. A velocidade da fantasia: o campo estava certo, faltava o client SABER

O jogador: *"ve se ta funcionando velocidade de movimento"*, com a fantasia do pe que da
+10 por cento.

### O que se mediu

    OFICIAL   ficha 65,0 -> anda 64,9    ficha 71,5 -> anda 71,3    ficha 99,0 -> anda 98,9
    NOSSO     ficha 65,0 -> anda 64,9    ficha 71,5 -> anda 64,4

O client segue o campo 82 do `08 02` ao certo -- no oficial. No nosso declara-se 71,5 e ele
anda a 65.

### Tres hipoteses eliminadas por medicao

1. **O campo 462.** Tinha 65,0 igual ao 82 e parecia um segundo campo de velocidade. Nao e:
   nas capturas do oficial o 82 sobe a 71,5, 99,0, 103,68, 136,66 e 150,33 e o 462 fica em
   65,0 nos 546 quadros. Coincidencia.

2. **A cadencia da ficha.** O oficial manda o `08 02` de 3,14 em 3,14 s (546 numa sessao de
   22 min); nos mandavamos TRES na sessao inteira. Igualou-se -- 23 fichas com intervalo
   3,17 s -- e a velocidade continuou em 64,4. Nao era isto, mas fica, que e o que ele faz.

3. **O registo do item.** Os `06 06` dos dois sao byte a byte da mesma forma, 158 bytes, com
   a opcao 60200 no offset 87 LE nos dois.

### O que sobra, e e a resposta

O `06 06` do OFICIAL tem uma versao com o byte [10] a ZERO -- o contentor do equipamento.
O nosso nunca tem: os quatro registos que mandamos dessa peca vao todos com [10]=1, mochila.

A razao e a ordem: no oficial ele EQUIPOU a peca durante a sessao, e o equipar manda um
`06 06` com contentor 0 que leva a opcao junto. No nosso a peca ja estava equipada a entrada,
e a entrada anuncia o equipamento pelo `02 05`, onde os quatro ids da fantasia vao num
vector compacto -- offsets 235..248 -- **sem opcao nenhuma**, tudo zeros a seguir.

Conferido tambem do lado dele: o `02 05` do oficial NAO leva codigos 60xxx. Ou seja nenhum
dos dois poe a opcao na ficha de personagem; a diferenca e que ele manda o `06 06` do
equipamento e nos nao.

O servidor sabe do bonus -- por isso a ficha diz 71,5 -- mas o CLIENT, que e quem move o
personagem, nunca soube.

### A correccao proposta, por fazer

Mandar um `06 06` por peca equipada DEPOIS da entrada no mundo, com contentor 0 e a opcao,
como o oficial faz ao equipar. Fica por fazer de proposito: mexer no fluxo de entrada ja
partiu a entrada no mundo antes (ver a nota do RAJADA_MAX), e isto merece ser feito com a
cabeca fresca e testado logo a seguir.

### Continuacao: oito hipoteses eliminadas, e nao resolvido

A investigacao da velocidade continuou com o jogador a testar em directo. Estado final:
**nao resolvido**, mas com o campo muito estreitado. O que ficou provado NAO ser:

1. **O campo 82.** E o certo -- no oficial o client segue-o ao decimo (65->64,9; 71,5->71,3;
   99->98,9). O nosso escreve o mesmo valor.
2. **O campo 462.** Coincidencia; no oficial fica em 65,0 enquanto o 82 vai a 150,33.
3. **A cadencia da ficha.** Igualada (3,17 s contra os 3,14 dele). Sem efeito.
4. **O registo do item.** Byte a byte da mesma forma, com `cont=0 slot=29 SLOT_EQUIP=29` e
   `opcao=60200` no offset 87 -- identico ao dele.
5. **O item.** Deu-se ao jogador o MESMO item com que o oficial andou a 71,3 (Traje do
   vencedor, pes, 999008088). Continuou a 65.
6. **O conjunto incompleto.** Ele tinha as quatro pecas do terno equipadas o tempo todo.
   E os bonus de conjunto sao efeitos a parte (60204 com 3 pecas, 60205 com 4).
7. **O servidor a devolver o movimento do proprio.** Nenhum dos dois manda `04 01` da
   propria entidade para o dono dela.
8. **O `05 09` de skill 65540.** O oficial manda um par de 3,2 em 3,2 s e nos nenhum, mas
   ele atravessa a sessao inteira (80960..82193) independente da fantasia -- nao e ele.

ERRO MEU PELO CAMINHO, e vale registar porque quase virou conclusao: li o contentor no byte
[10] do pacote quando o registo do item comeca em [11]. Com o offset errado parecia que nos
nunca mandavamos a peca como equipada, e cheguei a escrever isso como a resposta. Com o
offset certo os dois sao iguais. Um byte de deslocamento produziu uma explicacao completa e
falsa.

O QUE SOBRA para a proxima sessao: a diferenca ja nao esta em nenhum pacote que eu saiba
comparar isolado. O caminho e outro -- reproduzir a sessao do oficial do principio (entrada,
equipar, andar) e comparar as duas TRANSCRICOES inteiras em ordem, em vez de campo a campo.
Fica tambem por explicar o `06 46`, o unico opcode que so nos mandamos.

### A experiencia que reenquadra tudo: 200 na ficha, e o client anda a 64,4

Com `FORCAR_VELOCIDADE = 200`, o `08 02` saiu com 200,0 em 23 quadros e o jogador andou a
**64,4** -- o mesmo que com 65 e com 71,5.

O CAMPO 82 NAO MOVE O PERSONAGEM DO PROPRIO JOGADOR. E reflexo, nao causa.

Isso desfaz a leitura que vinha de tras. No oficial a ficha diz 71,5 e ele anda 71,3 -- eu
tinha lido isso como "o client obedece a ficha". Nao obedece: as duas coisas batem porque
AMBAS saem do mesmo item. Quem calcula a velocidade do proprio personagem e o CLIENT, a
partir do equipamento que ele conhece.

Passa a ser inutil procurar o que falta no `08 02`. O que interessa e o que o client sabe
sobre a PECA -- e ai o registo do item ja foi comparado byte a byte e sobram dois bytes por
explicar entre o nosso e o dele:

    registo[8]   oficial 0x12   nosso 0x11
    registo[16]  a quantidade -- 1 nos dois quando o registo e montado agora

O `registo[16]` explicou-se: o par do 06 06 leva o estado ANTIGO com quantidade 0 e o NOVO
com 1, e eu tinha comparado o registo errado do par. O `registo[8]` fica por explicar.

PROXIMO PASSO, para quem retomar: comparar `registo[8]` numa peca de fantasia que FUNCIONE
no oficial contra a mesma peca no nosso, com o cuidado de emparelhar o mesmo estado do par.
Um byte de diferenca no registo do item e agora a unica coisa conhecida que separa os dois.

### O problema nao e a fantasia: e TODA a velocidade

Medido no nosso servidor em 30/08, com o mesmo personagem na mesma sessao:

    ficha 65,0  (nada)        anda 62,5
    ficha 71,5  (fantasia)    anda 64,1
    ficha 99,0  (MONTARIA)    anda 64,5   <- n=17

A montaria e o caso limpo: 99 contra 65 sao 52 por cento, nao cabe em ruido de amostra. No
oficial a mesma montaria da ficha 99,0 e velocidade real 98,0, com n=26.

NO NOSSO SERVIDOR O CLIENT NUNCA PASSA DA BASE, venha o bonus de onde vier. Todas as horas
gastas a comparar a peca de fantasia foram no alvo errado: o defeito e do caminho inteiro da
velocidade, e a fantasia era so o exemplo que estava a mao.

Junte-se a isso o que ja estava provado -- forcar 200 na ficha nao muda nada -- e o que
sobra e: o client calcula a propria velocidade a partir de alguma coisa que, no nosso
servidor, ele nao tem. Mesmo binario, mesmas tabelas .lod (conferido por SHA-256: o AMo.exe
e o Axn.exe sao o MESMO ficheiro, e das 200+ tabelas so o string.lod_pt difere, que e o
nosso patch do nome).

DE CAMINHO, uma medicao que reduz a confianca no que se media antes: nas capturas oficiais
em que o jogador ENTROU ja com a peca vestida (14:33, 14:46, 15:05), a velocidade real com
a ficha em 71,5 deu 52,0 / 49,8 / 50,1 -- ou seja nem no oficial os 71,5 aparecem quando a
peca vem da entrada. So aparecem quando ele equipa DENTRO da sessao (19:52, deu 70,9).
Amostras pequenas (n=4 a 21), portanto vale como indicio e nao como facto.

PROXIMO ALVO: a montaria, e nao a fantasia. E o caso com maior diferenca (52 por cento),
com amostra boa dos dois lados, e o que o oficial manda ao montar ja esta capturado.

### Estado final de 30/08: problema bem definido, nao resolvido

    NOSSO      ficha 65,0   anda 62,5      ficha 71,5   anda 64,1 (p90 73,5)
               ficha 99,0   anda 63,4      ficha 200,0  anda 64,4
    OFICIAL    ficha 65,0   anda 64,9      ficha 71,5   anda 71,3
               ficha 99,0   anda 98,0  (n=26)

O client NUNCA passa da base no nosso servidor, venha o bonus de onde vier e diga a ficha o
que disser. No oficial passa. E o MESMO executavel (AMo.exe e Axn.exe tem SHA-256 igual) com
as MESMAS tabelas (das 200+ .lod so o string.lod_pt difere, que e o nosso patch do nome).

Hipoteses testadas e mortas, por ordem: campo 82 -> campo 462 -> cadencia da ficha -> registo
do item -> o proprio item -> conjunto incompleto -> eco do movimento -> 05 09 -> registo[8]
-> binario diferente -> tabelas diferentes -> ordem do login -> par de fichas depois da
entrada -> "aplica so na entrada" (entrar ja montado tambem nao da 99).

A pista do jogador que mais estreitou foi observacional e nao aparecia na mediana: entrando
com a peca houve passadas a 72 (p75 72,3); depois de a tirar, tecto em 65; reequipar nao
devolve. Ou seja ALGUMA COISA chega a ser aplicada e depois perde-se. Medir por mediana
escondeu isso -- as curvas e as passadas curtas puxam a distribuicao para baixo, e o
percentil alto e que mostra o tecto real. LICAO DE MEDICAO: para velocidade, olhar p75/p90,
nao a mediana.

O QUE FALTA TENTAR, para quem retomar: reproduzir a entrada do oficial byte a byte (mudando
so identidade e posicao) em vez de comparar pacote a pacote. Tudo o que se compara isolado
ja bate; o que nao se comparou foi a sessao inteira em ordem.
