> For the complete documentation index, see [llms.txt](https://claude.fractalgamestudios.es/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://claude.fractalgamestudios.es/bolas/game-info/sistema-de-skills-de-arma.md).

# Sistema de skills de arma

### 2026-08-04 (progresion de estadisticas por arma estilo Pokemon: XP por partida, niveles, puntos de skill, panel en el menu y mesa de mejora en la Lobby)

Nuevo `/goal`: un sistema para subir las estadisticas de las armas **al estilo Pokemon** — se sube nivel tras cada partida con una cantidad de XP configurable, y con parametros configurables tipo "x daño" o "x boost"; accesible **desde el menu** y ademas **desde algun sitio fisico** ("rollo mesa de encantamientos de Minecraft"). Lo de **combinar objetos queda EXPLICITAMENTE fuera** por peticion del usuario ("de momento no lo hagas eso de combinar cosas").

#### Punto de partida: el esqueleto existia pero no lo usaba nadie

Al explorar salio que media estructura ya estaba puesta desde hacia tiempo, pero **muerta**:

* `data.Balls[arma].Stats = { Healthpoints, Damage, Speed, Rotationspeed, Size }` ya se guardaba en el perfil de cada jugador… siempre a 0.
* `ServerStorage.Data.WeaponData.MaxStats` ya definia un **reparto distinto por arma** (la Espada llega a 5 en todo; el Unarmed a 8 en Vida/Daño/Velocidad y no tiene ni giro ni tamaño; el Arco no tiene tamaño). Justo el "cada arma sube distinto" que pide un sistema tipo Pokemon.
* **Nada subia esos numeros y nada los leia**: `BallFactory` creaba TODAS las bolas con 100 de vida, 45 de velocidad, 1 de daño y 2.5 de giro escritos a mano.
* `PlayerManager.addXP` (XP de CUENTA, no de arma) existe pero esta **roto y muerto**: llama a `GameSettings.GetLevelFromXP` y `GameSettings.Rewards.LevelUp`, que **no existen** en `GameSettings` (solo tiene `Places`). No lo llama nadie, asi que nunca ha petado. Este sistema nuevo es de progresion **por arma** y no lo toca.

Asi que el trabajo real fue: darle vida al esqueleto, hacer que los puntos SIRVAN de verdad en combate, y montar las dos entradas de interfaz.

#### Arquitectura

**`ReplicatedStorage.Shared.WeaponProgressionConfig`** (nuevo) — el archivo de balance, y el unico sitio con numeros. Vive en ReplicatedStorage a proposito: el servidor lo necesita para aplicar las stats y el cliente para poder ENSEÑAR que hace cada punto sin que el servidor le serialice textos. Contiene:

* `MaxLevel = 50`, `SkillPointsPerLevel = 1`.
* **Curva de XP**: `Base * (Growth ^ (N-1)) + Flat * (N-1)`, con `Base = 100`, `Growth = 1.18`, `Flat = 25`. Sale nivel 1→2 = 100 XP, 2→3 = 135, 5→6 = 254, 10→11 = 660.
* **XP por partida** (`MatchXP`): `Base = 40` por jugar, `PerKill = 25`, `WinBonus = 60`, `SurvivalBonus = 20`, `PerDeath = 0`, y un **multiplicador por modo** (`1v1` x1, `3v3` x1.15, `BattleRoyale` x1.3, `BossFight` x1.5).
* **Que hace cada punto** (`Stats`) — esto es el "x daño / x boost" que se pidio en el config. Cada estadistica declara `Mode` (`flat` = `Base + puntos*PerPoint`, o `multiplier` = `Base * (1 + puntos*PerPoint)`), su `Base`, su `PerPoint`, su etiqueta, su descripcion y su formato de texto:

| Estadistica  | Modo          | Base                | Por punto | Con 2 puntos |
| ------------ | ------------- | ------------------- | --------- | ------------ |
| Vida         | flat          | 100                 | +15       | 130 PV       |
| Daño         | multiplicador | x1                  | +0.20     | x1.40        |
| Velocidad    | flat          | 45                  | +3        | 51           |
| Vel. de giro | flat          | 2.5                 | +0.35     | 3.20         |
| Tamaño       | multiplicador | (radio de la arena) | +0.05     | x1.10        |

**`ServerStorage.Managers.WeaponProgression`** (nuevo) — la unica autoridad del sistema. Añade `Level`, `XP` y `SkillPoints` a cada entrada de arma del perfil (con migracion silenciosa: los perfiles viejos solo traen `Stats`, y se completan la primera vez que se tocan sin borrar nada). Expone: `AddXP` / `AwardMatchXP` (sube tantos niveles como toque de golpe), `SpendPoint` (valida en el SERVIDOR que se posea el arma, que la estadistica exista para ESA arma, que quede tope y que haya puntos), `BuildView` (lo que necesita la interfaz, con los textos ya formateados) y `GetEffectiveStats` (los numeros con los que arranca la bola en partida).

**El tope de cada estadistica sale de `WeaponData.MaxStats`**, que ya existia: no se ha inventado ninguna tabla nueva de topes, y una estadistica que un arma no tiene (tamaño en el Arco, giro en el Unarmed) simplemente **no se ofrece** en el panel.

#### Que los puntos SIRVAN: un punto unico para todo el daño del juego

Aplicar Vida/Velocidad/Giro/Tamaño fue directo (`BallFactory` los lee ahora de `GetEffectiveStats` en vez de tenerlos escritos a mano; el tamaño se aplica despues de resolver que arma lleva la bola, y escala tambien sus `Attachment`, que traian offsets calibrados para el radio base).

El **Daño** no: resulta que el daño se restaba en **seis sitios distintos** con seis copias del mismo bloque (`WeaponSystem` para el cuerpo a cuerpo, y luego `Unarmed`, `Bow`, `Staff`, el veneno de `Scythe` y el sangrado de `Katana` por su cuenta). Un multiplicador global habria que repetirlo seis veces y uno se quedaria olvidado.

Se creo **`ServerStorage.Classes.BallSystem.Damage`**, punto UNICO por el que pasa ahora todo el daño entre bolas: aplica el multiplicador (atributo `DamageMultiplier` de la bola atacante, que pone `BallFactory`), resta la vida y **atribuye la muerte**.

**Bug preexistente que esto arregla de paso**: solo `WeaponSystem` escribia `KilledByBallName`, asi que **ninguna muerte por flecha, bola de fuego, veneno, corte o golpe de Unarmed se le atribuia a nadie** — `ArenaManager` no podia contar esa eliminacion y la pantalla de resultados la daba por muerte sin asesino. (Se vio de refilon en la sesion de BossFight de esta misma fecha: el jefe aparecia con 5 kills habiendo matado a 6). Ademas era bloqueante para este sistema, porque la XP se paga **por eliminacion**: sin atribucion, media XP se perdia. Verificado tras el cambio: daño letal desde una bola deja `KilledByBallName` correctamente puesto.

#### XP al terminar cada partida

`ArenaManager` guarda con que arma juega cada participante al crear su bola (`participantWeapons`) — no se puede leer de la bola al final porque la de quien muere se destruye mucho antes. En `EndRound`, cada jugador real recibe una **copia propia** del payload de resultados con su campo `progression` (la XP depende de sus kills, de si gano y de si sobrevivio, asi que no puede ir en el payload compartido).

La pantalla de resultados (`MatchFlowUI`) muestra ahora una linea nueva: `Unarmed +145 XP · 206 / 575 para el nivel 10`, en dorado y con `¡NIVEL N! (+1 punto de skill)` cuando toca subida.

#### Interfaz: las dos entradas que se pidieron

1. **Desde el menu**: quinto boton del menu principal (`Front.Modules.HUD`). La rejilla paso de **2x2 a 2x3** — con 5 celdas la rejilla cuadrada dejaba el quinto boton fuera del recuadro. Icono: el `textureId` (`132028212433872`) del Decal `99384128856512`, un grafico de barras con una estrella — verificado con `get_asset_thumbnail` antes de usarlo, y usando el textureId y no el ID del Decal (trampa ya documentada en este mismo archivo).
2. **Desde la Lobby**: **`Workspace.Lobby.Interactables.SkillsTable`** (nueva), construida a base de parts: pedestal de obsidiana, losa morada, cuatro pilares en las esquinas, **libro flotante** con luz morada y glifos subiendo (`ParticleEmitter`), y cartel 3D "HABILIDADES DE ARMA". Colocada en `(-1389, 137.75, 733)` — altura del suelo medida con raycast contra `Lobby.plataform.floor`, en el mismo anillo que los demas interactables y sin solaparse con ninguno.

El libro es la part que lleva el `ProximityPrompt` ("Mesa de mejora / Habilidades", tecla E) y el tag `SkillsTerminal`. **`StarterPlayer.StarterPlayerScripts.Controllers.SkillsTerminalController`** (nuevo) lo descubre por tag — mismo patron que `CustomizationTerminalController`, asi que se puede poner otra mesa en cualquier sitio sin tocar codigo — y hace flotar y girar el libro **localmente** (visual puro: no hace falta ningun script de servidor en bucle solo para eso). A diferencia de la terminal de customizacion, **no toca la camara ni bloquea al jugador**: el panel de skills es solo interfaz.

Las dos entradas acaban en la misma vista, **`Front.Modules.WeaponSkills`** (`main`, registrada en `routes` y `RouterView`): selector `< Arma >` (la progresion es por arma, hay que poder mirar y subir cualquiera de las que tengas), nivel, barra de XP, contador de puntos sin gastar y una fila por estadistica con sus "pips" de puntos gastados, su valor actual, **el valor que tendria con un punto mas** (`130 PV → 145 PV`, `x1.40 → x1.60`) y el boton de subir. La navegacion desde la mesa pasa por `RemoteRouteBridge`, que es quien tiene acceso al router de React.

#### Anti-rebote al gastar puntos (bug encontrado en la propia prueba)

Probando el boton "+" con un clic simulado, **una sola pulsacion gasto los 7 puntos disponibles de golpe**. Causa: cada subida provoca un snapshot nuevo del servidor, y ese snapshot **re-monta** el boton; si el raton sigue pulsado, el boton recien montado se vuelve a activar bajo la misma pulsacion. Gastar un punto es irreversible, asi que se añadio un anti-rebote de `0.25s` en `BallsController.upgradeStat` (cliente): suficiente para gastar varios seguidos a proposito (4/segundo), pero no para vaciarlos sin querer. Tras el arreglo, verificado varias veces: **un clic = un punto, en la fila clicada**.

#### Validacion en playtest (flujo completo, no revision de codigo)

* **XP real de partida**: 1v1 ganada con 1 eliminacion → `40 + 25 + 60 + 20 = 145 XP` x1 (modo 1v1) = **145**, exactamente lo configurado. Nivel 1 → 2 (cuesta 100), sobran 45 XP, +1 punto de skill.
* **Los puntos llegan al combate**: con `Vida=2, Daño=2, Velocidad=2` guardados, la bola de la siguiente partida arranco con **`MaxHealth = 130`** (base 100), **`DamageMultiplier = 1.4`** (base 1) y **`Speed = 51`** (base 45). Confirmado tambien en pantalla: el HUD de combate mostraba `145 / 145` y `Velocidad 51` con 3 puntos de vida.
* **El multiplicador se aplica de verdad**: `Damage.Apply` con 10 de daño desde una bola con `x1.4` resto **14** de vida al objetivo.
* **Atribucion de muertes**: daño letal desde una bola deja `KilledByBallName` puesto — la parte que antes solo funcionaba en cuerpo a cuerpo.
* **Panel desde el menu** y **panel desde la mesa** (pulsando **E** de verdad delante de ella): los dos abren la misma vista, con nivel, XP, puntos y las tres estadisticas del Unarmed (no ofrece giro ni tamaño, correcto).
* **Gasto de puntos**: clic real en "Vida" → `Vida` paso de 2 a 3 y los puntos de 4 a 3. Log del servidor: `UpgradeStat ... ok=true`, una sola vez.
* **Regresion de las 9 armas**: partida de BattleRoyale con **un bot por cada arma** (Sword, Dagger, Bow, Spear, Scythe, Katana, Staff, Scepter, Unarmed) para ejercitar los seis caminos de daño reescritos. La pelea corrio hasta quedar 4 bolas **sin un solo error** en `get_runtime_logs`.

#### Fuera de alcance (por peticion explicita)

* **Combinar objetos** ("tal vez la posibilidad de combinar cosas pero de momento no lo hagas"): no implementado. La mesa esta preparada para ello — es un modelo con su propio controlador y su propia vista — pero hoy solo gasta puntos de skill.

#### Pendiente / avisos

1. **`PlayerManager.addXP` sigue roto** (XP de CUENTA, distinto de esta XP de arma): llama a `GameSettings.GetLevelFromXP` y `GameSettings.Rewards.LevelUp.Dollars`, que no existen. Hoy no rompe nada porque **no lo llama nadie**, pero petara en cuanto alguien lo use. O se completa `GameSettings`, o se borra.
2. **Los bots no progresan** (siempre `x1` y estadisticas base). Es lo correcto hoy — no tienen perfil — pero si algun dia se quiere que los bots escalen con el nivel del jugador, el sitio es `BotManager.createProfile`, dandoles `Stats` ya rellenas.
3. **El equilibrio esta sin ajustar a datos reales**: los valores de `WeaponProgressionConfig` son un primer reparto razonable (un arma a tope de Daño pega **x2** con 5 puntos, o **x2.6** con los 8 del Unarmed). Todo se toca en ese unico archivo sin recompilar nada mas.
4. **La mesa vive bajo `Lobby.Interactables`**, asi que el server AFK la retira junto con el resto de interactables (solo se salva Customization). Si se quiere poder subir skills estando AFK, hay que añadirla a la lista de excepciones de `AFKLobbyController`.

#### Archivos

**Nuevos:** `ReplicatedStorage.Shared.WeaponProgressionConfig`, `ServerStorage.Managers.WeaponProgression`, `ServerStorage.Classes.BallSystem.Damage`, `ReplicatedStorage.Front.Modules.WeaponSkills.WeaponSkills`, `StarterPlayer.StarterPlayerScripts.Controllers.SkillsTerminalController`, `Workspace.Lobby.Interactables.SkillsTable`.

**Tocados:** `ServerStorage.Classes.BallSystem.BallFactory` (stats efectivas + tamaño + `DamageMultiplier`), `ServerStorage.Classes.BallSystem.WeaponSystem` y los cinco modulos de `WeaponScaling` que restaban vida por su cuenta (`Unarmed`, `Bow`, `Staff`, `Scythe`, `Katana`) — todos pasan ya por `Damage.Apply`, `ServerScriptService.Managers.ArenaManager` (`participantWeapons` + XP en `EndRound`), `ServerScriptService.Controllers.BallsController` (progresion en el snapshot + remote `Balls/UpgradeStat`), `ServerScriptService.GameServer` (remote nuevo), `ServerStorage.Managers.BotManager` (arma/nombre opcionales, reutilizado del Boss), `ReplicatedStorage.Front.Controllers.BallsController` (tipos, `upgradeStat` con anti-rebote, refresco al acabar partida), `Front.Modules.HUD.HUD` (rejilla 2x3 + boton de skills), `Front.Shared.Theme` (`MenuAccents.Skills`), `Front.Router.routes` / `RouterView` / `RemoteRouteBridge` (vista nueva), `StarterPlayer.StarterPlayerScripts.MatchFlowUI` (linea de XP en resultados).


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://claude.fractalgamestudios.es/bolas/game-info/sistema-de-skills-de-arma.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
