1. Síntoma
Las mejoras de casco de Supervivencia no aparecían cuando el jugador tenía créditos “suficientes” respecto a la regla de doble precio. Las bajas etiquetadas de Supervivencia también inflaban créditos/XP. Cambiar la dificultad parecía la causa porque se publicó en el mismo periodo.
2. Impacto en el jugador
La progresión se sentía bloqueada o aleatoria. Se podía cobrar dos veces la misma baja y aun así fallar un check de casco que usaba precios de tienda. Elegir dificultad no cambiaba esas recompensas — ese es el diseño, y ahora está testeado.
3. Reproducción
Comparar ship.price con getSurvivalProgressionPrice(ship): los tests afirman price === progressionPrice * 8. findNextSurvivalShip debe seguir null en umbral − 1 y devolver el siguiente casco en el umbral (progressionPrice * 2). Conceder una baja etiquetada de Supervivencia y confirmar que combat.js omite crédito/combo ambiental si enemy.userData.aiGmSurvival está puesto. Cambiar Historia vs Extinción y comparar claims de hito.
4. Ownership del sistema
Campos de precio: data/ships.js (survivalProgressionPrice antes del ×8, price después). Umbral/hitos: survival-progression.js. Corte de pago por baja: play/three/combat.js y combat-window-shims.js. Perfiles de dificultad: combat-difficulty.js (diez ids). Las tablas de botín no deben importar dificultad.
5. Supuesto erróneo
Hay un solo “precio” en una nave. Las recompensas de baja pueden pasar todas por el mismo credit +=. La dificultad debería escalar recompensas “porque es más difícil”.
6. Causa raíz
El precio de tienda incluye SHIP_PRICE_GLOBAL_MULTIPLIER (8). La regla de doble precio de casco de Supervivencia debe usar survivalProgressionPrice pre-multiplicador. Las recompensas ambientales de combate seguían corriendo para enemigos ya pagados por el contrato de Supervivencia (userData.aiGmSurvival). La dificultad es un multiplicador de stats de combate, no de economía.
7. Arreglo mínimo
Mantener survivalProgressionPrice en el casco antes del ×8 global; filtrar findNextSurvivalShip con ese campo × 2. Omitir créditos ambientales, combo y XP cuando isKaelaSurvivalKill. Los créditos de hito (20k/40k/80k/160k en oleadas 5/10/15/20) se reclaman una vez por operación vía awardedWaves. No dejar que difficultyId cambie las funciones de baja/oleada/hito.
8. Test de regresión
tests/survival-progression-rewards.test.mjs: umbral pre-multiplicador, sin casco duplicado, hito una vez, Historia vs Extinción idénticos, las diez dificultades con XP/créditos idénticos, asserts de fuente en combat.js sobre el skip aiGmSurvival.
9. Validación de release
Log de desarrollo 20 ago 2026, v0.4.159 (umbral + bajas duplicadas + independencia de recompensa). La dificultad de diez niveles queda el mismo día como perfil aditivo aparte, no como escalador de recompensas.
10. Lección generalizada
Los campos de economía necesitan nombre por consumidor (tienda vs desbloqueo vs reparación). Un += compartido de créditos es cómo nacen las recompensas duplicadas. Dificultad, botín y progresión son tres perillas; acoplarlas en un prompt “balanceará” la equivocada.
Extracto simplificado
Simplificado / pseudocódigo para enseñar. No es un volcado de archivos de producción.
Dos precios en un casco, simplificado.
// Simplified teaching excerpt — not CONTRABAND production source.
let hull = basePrice;
if (tier >= 3) hull *= 3;
ship.survivalProgressionPrice = hull; // unlock / double-price rule
ship.price = hull * 8; // shipyard display
const unlockAt = ship.survivalProgressionPrice * 2;
if (enemy.userData.aiGmSurvival) {
// Survival contract already paid this kill — skip ambient credits / combo / XP
}Evidencia pública
- Log de desarrollo de CONTRABAND, 20 ago 2026, v0.4.159.
- survival-progression-rewards.test.mjs
- ships.js survivalProgressionPrice vs price; survival-progression.js; combat.js isKaelaSurvivalKill.