docs: ADR-0000..0009 aceptados y registry de arquitectura

This commit is contained in:
2026-08-16 16:21:03 -03:00
parent 306c444016
commit 17e6e5a9b3
12 changed files with 2003 additions and 24 deletions
@@ -0,0 +1,301 @@
# ADR-0000: Patrones de Infraestructura Global
## Status
Accepted
## Date
2026-08-16 — when this ADR was written
## Last Verified
2026-08-16 — when this ADR was last confirmed accurate against the current
engine version and design. Update this date when you re-read and confirm it
is still correct, even if nothing changed.
## Decision Makers
Diseñador/director (usuario) + agente de arquitectura (opencode)
## Summary
ADR fundacional que formaliza la base de infraestructura que toda la base de
código ya usa de facto tras 5 sprints sin ADRs formales: (1) singleton
MonoBehaviour cross-escena con estilo canónico *Awake-explicit* +
`DontDestroyOnLoad`, (2) lógica de negocio en capa de servicios POCO sin
dependencia de Unity, (3) persistencia mediante ScriptableObject de estado
runtime + `JsonUtility` a `persistentDataPath`, (4) comunicación cross-sistema
por eventos C#, y (5) restricción un manager por responsabilidad (prohíbe
duplicados). Fija las convenciones que los ADR-0001..0008 (borradores en
`docs/architecture/architecture.md`) detallarán como archivos formales.
## Engine Compatibility
| Field | Value |
|-------|-------|
| **Engine** | Unity 6.3 LTS (6000.3.13f1) |
| **Domain** | Core / Scripting |
| **Knowledge Risk** | **HIGH** — versión post-cutoff (May 2025), verificada contra engine-reference |
| **References Consulted** | `docs/engine-reference/unity/VERSION.md`, `docs/engine-reference/unity/breaking-changes.md`, `docs/engine-reference/unity/deprecated-apis.md` |
| **Post-Cutoff APIs Used** | None (todas las APIs usadas son vigentes) |
| **Verification Required** | Re-validar si se migra a New Input System o Addressables (APIs deprecadas en uso — `Input` legacy, `Resources.Load` — aceptadas para el Vertical Slice, fuera del alcance de este ADR) |
> **Note**: Knowledge Risk es HIGH — este ADR debe re-validarse si el proyecto
> actualiza la versión del motor. Marcar como "Superseded" y escribir un ADR nuevo.
## ADR Dependencies
| Field | Value |
|-------|-------|
| **Depends On** | None (fundacional) |
| **Enables** | ADR consolidación `SceneTransitionManager` (Core/UI); ADR Save/Load completo; ADR Dead Kings System; ADR Legados |
| **Blocks** | Consolidación de managers duplicados; epics de infraestructura |
| **Ordering Note** | Debe aceptarse antes de los ADR específicos de managers |
## Context
### Problem Statement
El código se construyó de forma orgánica (Sprints 1-3 implementados) sin ADRs
formales. Coexisten **dos estilos de singleton** (Awake-explicit en 7 managers,
lazy-getter con auto-creación en 3) y hay un **manager duplicado**
(`SceneTransitionManager` en `Assets/Game/Scripts/Mono/Core/` y en
`Assets/Game/Scripts/Mono/UI/`). El coste de no decidir: cada nuevo manager
sigue un estilo distinto y los bugs de duplicación y de orden de ejecución se
pagan de nuevo en cada sistema.
### Current State
`docs/architecture/architecture.md` documenta los patrones dominantes como
mini-ADRs de borrador (ADR-001..008) que no tienen archivos formales ni
estatus. El registro `docs/registry/architecture.yaml` está vacío. Código
verificado:
- **Lazy-getter con auto-creación**: `AudioManager`, `SaveSystem`,
`UI\SceneTransitionManager`.
- **Awake-explicit** (`Instance { get; private set; }` + `DontDestroyOnLoad`):
`GameStateManager`, `Core\SceneTransitionManager`, `DialogueSystem`,
`CampaignManager`, `PurgatoryManager`, `GameManager`, `BoardManager`.
- **SO singleton lazy**: `DeadKingPool` (carga vía `Resources.Load<DeadKingPool>`).
### Constraints
- Motor fijado: Unity 6.3 LTS (6000.3.13f1).
- APIs deprecadas en uso (`Input` legacy en `DialogueUI`, `PauseMenuController`;
`Resources.Load` en `DeadKingPool`) se aceptan para el Vertical Slice.
- UGUI + TextMeshPro soportado (deprecated pero mantenido por Unity).
- Compatibilidad con sistemas existentes implementados: Board, Piece Movement,
Turn, Check/Checkmate, Castling, Promotion, En Passant, Draw Detection,
Piece Identity, Dice, Campaign, AI.
- Línea Atemporal II ("Gambito de Sombras") debe reutilizar esta infraestructura
sin duplicarla.
### Requirements
- Consistencia de patrón para todos los managers cross-escena.
- Lógica de negocio testeable sin dependencia de `UnityEngine` (test Edit Mode).
- Persistencia simple, portable y legible.
- Desacople entre sistemas (la UI no acopla a managers por nombre salvo vía
`Instance`/eventos).
- Un único dueño por responsabilidad (sin managers duplicados).
## Decision
### Arquitectura
```
┌─ MonoBehaviour Managers (cross-escena, DontDestroyOnLoad, Awake-explicit) ─┐
│ GameStateManager · CampaignManager · DialogueSystem · PurgatoryManager │
│ AudioManager · SaveSystem · SceneTransitionManager (único tras ADR de │
│ consolidación) │
└───────────────────────────────┬─────────────────────────────────────────────┘
│ eventos C# (Action<...>)
┌─────────────▼──────────────┐
│ Capa de Servicios POCO │ ← sin using UnityEngine
│ (lógica de negocio, │
│ unit-testable) │
└─────────────┬──────────────┘
│ SO de estado runtime (fuente de verdad)
┌─────────────▼──────────────┐
│ Persistencia: JsonUtility │→ persistentDataPath/*.json
│ + ScriptableObjects │
└────────────────────────────┘
```
### Key Interfaces
```
// Esqueleto canónico de manager cross-escena (Awake-explicit)
public class GameStateManager : MonoBehaviour
{
public static GameStateManager Instance { get; private set; }
private void Awake()
{
if (Instance != null && Instance != this)
{
Destroy(gameObject);
return;
}
Instance = this;
DontDestroyOnLoad(gameObject);
}
private void OnDestroy()
{
if (Instance == this) Instance = null;
}
}
// Comunicación cross-sistema por eventos C#
public static event Action<PieceIdentity> OnPieceCaptured; // prefijo On
// Persistencia: wrapper serializable explícito (JsonUtility no serializa
// [SerializeField] privados) → fichero JSON en persistentDataPath
[Serializable]
private class GameSaveData { public List<CampaignState> campaign = new(); }
```
### Implementation Guidelines
- Nuevos managers: copiar el esqueleto Awake-explicit (nunca lazy-getter).
- Migrar en tareas separadas (no bloquea el VS): `AudioManager`, `SaveSystem`,
`UI\SceneTransitionManager` a Awake-explicit.
- Eventos: desuscribirse en `OnDestroy`; convención de prefijo `On` (ej.
`OnPieceCaptured`).
- Servicios POCO: namespaces sin `MonoBehaviour`; exponer estado por
propiedades de solo lectura.
- `Resources.Load` aceptado para SO de configuración en el VS; centralizar la
carga en una API única para poder migrar a Addressables sin tocar consumidores.
## Alternatives Considered
### Alternative 1: Lazy-getter con auto-creación (estilo AudioManager)
- **Description**: getter crea un GameObject nuevo + `DontDestroyOnLoad` si
`_instance == null`. Robusto ante referencias tempranas.
- **Pros**: auto-cura ante referencias antes del Awake.
- **Cons**: oculta el ciclo de vida, fomenta ámbito global implícito, testing
más difícil, oculta errores de configuración de escena.
- **Estimated Effort**: menor (no hay migración de los 3 managers), pero
exige migrar los 7 managers Awake-explicit.
- **Rejection Reason**: la mayoría de la base ya usa Awake-explicit y es más
predecible con el ciclo de vida de Unity.
### Alternative 2: Híbrido con guarda de diagnóstico
- **Description**: base Awake-explicit + guarda en el getter que loguea un
error claro si se accede antes del Awake (sin auto-creación).
- **Pros**: diagnóstico temprano de referencias fuera de orden.
- **Cons**: coste de mantenimiento adicional sin beneficio real para el tamaño
del proyecto.
- **Estimated Effort**: similar al elegido + guarda extra por manager.
- **Rejection Reason**: no se eligió por simplicidad; la convención de
referencias tempranas (solo desde Awake/Start/OnEnable) cubre el caso.
### Alternative 3: Mega-ADR que formaliza todo a la vez
- **Description**: un solo ADR que documenta los 8 patrones de `architecture.md`.
- **Pros**: documentación rápida y centralizada.
- **Cons**: las decisiones específicas (IA, UI, persistencia de Dead Kings, etc.)
no tendrían historial ni estatus propios para su ciclo de vida.
- **Estimated Effort**: menor en redacción, mayor en mantenimiento.
- **Rejection Reason**: se prefieren ADRs atómicos; este ADR fija el umbrella y
los mini-ADRs 001-008 se promueven a archivos formales en sesiones siguientes.
### Alternative 4: Persistencia con BinaryFormatter / PlayerPrefs
- **Description**: serialización binaria o clave-valor para el estado guardado.
- **Pros**: PlayerPrefs es trivial de usar; BinaryFormatter era potente.
- **Cons**: BinaryFormatter deprecado/inseguro en .NET moderno; PlayerPrefs
inadecuado para datos estructurados de campaña.
- **Estimated Effort**: menor en lo inmediato.
- **Rejection Reason**: `JsonUtility` + ficheros JSON en `persistentDataPath`
es simple, portable y depurable.
## Consequences
### Positive
- Consistencia total del patrón de managers en toda la base.
- Testabilidad de la lógica de negocio (servicios POCO sin Unity).
- Desacople entre sistemas vía eventos; la UI no acopla managers por nombre.
- Fuente de verdad única de estado runtime (ScriptableObject) con serialización
explícita y portable.
### Negative
- Los 3 managers lazy requieren migración (riesgo bajo de regresiones de
inicialización).
- `Resources.Load` y `Input` legacy permanecen en el VS (deuda aceptada).
- JsonUtility no serializa `[SerializeField]` privados: exige wrappers
serializables explícitos por sistema.
### Neutral
- Eventos C# sin bus centralizado (no hay sistema de mensajes global).
- La convención "referenciar `Instance` solo en Awake/Start/OnEnable" pasa a
ser regla de revisión de código.
## Risks
| Risk | Probability | Impact | Mitigation |
|------|------------|--------|-----------|
| Migración lazy→Awake-explicit rompe orden de inicialización | Baja | Media | Guard de duplicado + convención de referencias tempranas; smoke test del VS |
| Orden de ejecución entre managers cross-escena | Media | Media | `DontDestroyOnLoad` en orden estable (GameStateManager primero); script execution order documentado |
| `Resources.Load` deprecado escala mal con contenido | Media | Media | API de carga centralizada para poder migrar a Addressables sin tocar consumidores |
| Eventos C# fugan suscripciones (memory leak) | Media | Baja | Regla de desuscripción en `OnDestroy`; revisión en code review |
## Performance Implications
| Metric | Before | Expected After | Budget |
|--------|--------|---------------|--------|
| CPU (frame time) | — | +despreciable (getters singleton, eventos C# sub-µs) | — |
| Memory | — | +coste de un `DontDestroyOnLoad` por manager (ya presente) | — |
| Load Time | — | sin cambio (persistencia solo en puntos de guardado) | — |
| Network (if applicable) | — | N/A | — |
## Migration Plan
1. Formalizar estancias en `docs/registry/architecture.yaml` (registry) —
fase 5 de este ADR.
2. Migrar `SaveSystem`, `AudioManager` a Awake-explicit (tareas de limpieza; sin
bloqueo del VS). `UI\SceneTransitionManager` eliminado con ADR-0009.
3. ✅ Crear ADR de consolidación del `SceneTransitionManager` duplicado (Core/UI)
— hecho: [ADR-0009](adr-0009-consolidacion-scenetransitionmanager.md).
4. ✅ Promover mini-ADRs 001-008 de `architecture.md` a archivos formales
— hecho: ADR-0001..0008 (esta sesión).
**Rollback plan**: los managers migrados son modificaciones locales reversibles
por git; el ADR se puede marcar Superseded si un cambio de motor lo invalida.
## Validation Criteria
- [ ] `grep -l "if (_instance == null)" Assets/Game/Scripts` devuelve 0 (sin
lazy-getters restantes).
- [ ] Todo manager cross-escena implementa el esqueleto Awake-explicit con
guard de duplicado.
- [ ] Cada servicio POCO compila sin referencia a `UnityEngine`.
- [ ] Nuevos sistemas se comunican por eventos y no referencian managers por
nombre salvo por `Instance`.
- [ ] Sin managers duplicados en la base (tras consolidación).
## GDD Requirements Addressed
| GDD Document | System | Requirement | How This ADR Satisfies It |
|-------------|--------|-------------|--------------------------|
| `design/gdd/systems-index.md` | 15 — Scene Management | Transiciones entre escenas (Sprint 1: SceneTransitionManager) | Estancia 5: un único dueño → consolida el duplicado; estilo canónico en estancia 1 |
| `design/gdd/systems-index.md` | 16 — Game State Manager | Estados (menú, partida, diálogo, purgatorio, game over) | Estancia 1 + 4: manager único Awake-explicit; transiciones de estado notificadas por eventos |
| `design/gdd/systems-index.md` | 17 — Save/Load | Persistencia parcial (CampaignState SO, sin serialización) | Estancia 3: SO runtime + JsonUtility a persistentDataPath completa el sistema |
| `design/gdd/systems-index.md` | 18 — HUD | HUD parcial (PieceTooltip) | Estancia 2 + 4: HUD consume estado vía servicios POCO + eventos, sin acoplar UI a managers |
> TR IDs: el `docs/architecture/tr-registry.yaml` está vacío; `/architecture-review`
> asignará los TR en su fase 8.
## Related
- `docs/architecture/architecture.md` — mini-ADRs 001-008 (borradores que este
ADR habilita formalizar como archivos formales).
- `docs/registry/architecture.yaml` — estancias registradas en fase 5
(singleton, persistencia, eventos, patrón prohibido de manager duplicado).
@@ -0,0 +1,191 @@
# ADR-0001: Service Layer POCO
## Status
Accepted
## Date
2026-08-16
## Last Verified
2026-08-16
## Decision Makers
Diseñador/director (usuario) + agente de arquitectura (opencode)
## Summary
`GameManager` concentraba 8 responsabilidades (~475 líneas) y era imposible de testear sin
instanciar una escena Unity completa. Se extrae la lógica de negocio del ajedrez a una capa de
servicios POCO (`CheckDetector`, `DrawDetector`, `MoveValidator`) sin dependencia de
`MonoBehaviour`, inyectados por constructor/dependencia en `GameManager.Awake()`.
## Engine Compatibility
| Field | Value |
|-------|-------|
| **Engine** | Unity 6.3 LTS (6000.3.13f1) |
| **Domain** | Core / Scripting |
| **Knowledge Risk** | HIGH — post-cutoff, verificada contra engine-reference |
| **References Consulted** | `VERSION.md`, `breaking-changes.md`, `deprecated-apis.md` |
| **Post-Cutoff APIs Used** | None (POCO C# estándar, .NET Standard 2.1) |
| **Verification Required** | None — la capa POCO no usa APIs del motor |
## ADR Dependencies
| Field | Value |
|-------|-------|
| **Depends On** | ADR-0000 (Accepted) — estancia 2: capa de servicios POCO |
| **Enables** | Test suite Edit Mode de servicios; reutilización por IA y futuros sistemas (hints, variantes) |
| **Blocks** | None |
| **Ordering Note** | None |
## Context
### Problem Statement
`GameManager` concentraba lógica de tablero, turnos, legalidad, jaque/mate, tablas, input y
coordinación (~475 líneas). Sin escena Unity completa era imposible testear la lógica de
ajedrez, y cualquier cambio de reglas requería tocar un MonoBehaviour acoplado al editor.
### Current State
Refactor ya aplicado (Sprints 1-2): `Assets/Game/Scripts/Mono/Core/Services/` contiene
`CheckDetector.cs`, `DrawDetector.cs` y `MoveValidator.cs` como POCOs. `GameManager` quedó
reducido a 3 responsabilidades.
### Constraints
- Los servicios POCO no deben referenciar `UnityEngine` (testeables en Edit Mode).
- Compatibilidad con el `GameManager` existente y con la IA (que comparte los servicios).
- Sin over-engineering: la capa es pequeña y no necesita interfaces por servicio.
### Requirements
- Testabilidad de la lógica de ajedrez sin escena.
- Reutilización de reglas por IA y futuros sistemas.
## Decision
### Architecture
```
GameManager (MonoBehaviour)
│ inyección en Awake()
CheckDetector ──► DrawDetector
▲ ▲
│ dependencia │
MoveValidator ────────┘
AIController (consume los mismos servicios)
```
### Key Interfaces
```
// Servicios POCO: sin MonoBehaviour, sin UnityEngine
public class CheckDetector {
public bool IsInCheck(BoardState board, Color side);
public bool IsCheckmate(BoardState board, Color side);
}
public class MoveValidator {
private readonly CheckDetector _checkDetector; // DI con validación null
public bool IsLegalMove(BoardState board, Move move, Color side);
}
public class DrawDetector {
public DrawType? Evaluate(BoardState board); // stalemate, repetición, 50-mov, material
}
```
### Implementation Guidelines
- Nuevos servicios: clases estáticas o de instancia sin herencia de `MonoBehaviour`;
estado por parámetro o inyección, nunca `Instance` global.
- El consumo desde MonoBehaviour se hace por inyección en `Awake()`.
- `MoveValidator` requiere `CheckDetector` no-null en constructor.
## Alternatives Considered
### Alternative 1: Mantener la lógica en GameManager
- **Description**: seguir con toda la lógica en el MonoBehaviour.
- **Pros**: sin refactor.
- **Cons**: intesteable, acoplada al editor, 8 responsabilidades.
- **Estimated Effort**: 0 (pero cada bug/cambio cuesta más).
- **Rejection Reason**: la causa directa del ADR.
### Alternative 2: Servicios como MonoBehaviour
- **Description**: componentes en la escena que ofrecen la lógica.
- **Pros**: accesibles por inspector.
- **Cons**: siguen requiriendo escena para testear.
- **Estimated Effort**: similar al elegido.
- **Rejection Reason**: no resuelve la testabilidad.
### Alternative 3: Utilidades estáticas
- **Description**: clases `static` con métodos puros.
- **Pros**: simples, sin estado.
- **Cons**: difícil inyectar dependencias (MoveValidator→CheckDetector) y testear en aislamiento.
- **Estimated Effort**: menor.
- **Rejection Reason**: la DI con instancias permite tests aislados y mocks.
## Consequences
### Positive
- Reglas de ajedrez 100% testeables sin Unity (tests Edit Mode).
- Lógica reutilizable por IA y futuros sistemas.
- `GameManager` reducido y legible.
### Negative
- Capa de indirección entre MonoBehaviour y lógica.
### Neutral
- Los servicios se exponen por métodos, no por estado global.
## Risks
| Risk | Probability | Impact | Mitigation |
|------|------------|--------|-----------|
| Servicios mutables compartidos entre GameManager e IA | Baja | Media | Estado por parámetro; servicios sin estado mutable persistente |
| Regresión tras refactor | Media | Media | Tests Edit Mode de servicios + smoke test del VS |
## Performance Implications
Sin impacto: los servicios son llamadas C# directas sin allocations adicionales relevantes
(valores de retorno simples, sin eventos por movimiento salvo los de ADR-0003).
## Migration Plan
Refactor ya ejecutado (Sprints 1-2). Cambios futuros de reglas se aplican en los POCOs.
**Rollback plan**: recuperar versión anterior de `GameManager` por git.
## Validation Criteria
- [ ] `CheckDetector`, `DrawDetector`, `MoveValidator` no importan `UnityEngine`.
- [ ] `GameManager` no contiene lógica de legalidad/jaque/tablas inline.
- [ ] Tests Edit Mode cubren: jaque, jaque mate, tablas (repetición/50-mov/ahogado/material), legalidad de movimiento.
## GDD Requirements Addressed
| GDD Document | System | Requirement | How This ADR Satisfies It |
|-------------|--------|-------------|--------------------------|
| `design/gdd/systems-index.md` | 1-8 — Board, Movement, Turn, Check/Checkmate, Castling, Promotion, En Passant, Draw | Reglas de ajedrez implementadas y verificables | Servicios POCO testeables sin escena |
| `design/gdd/systems-index.md` | 13 — AI System | IA consume las reglas para evaluar movimientos | Servicios compartidos por `AIController` |
## Related
- ADR-0000 (estancia 2) — capa de servicios POCO.
- ADR-0008 — `DrawDetector` implementa el hashing FEN-like.
- Código: `Assets/Game/Scripts/Mono/Core/Services/*.cs`, `GameManager.cs`.
@@ -0,0 +1,172 @@
# ADR-0002: Strategy Pattern IA
## Status
Accepted
## Date
2026-08-16
## Last Verified
2026-08-16
## Decision Makers
Diseñador/director (usuario) + agente de arquitectura (opencode)
## Summary
El sistema de IA necesita 3 dificultades escalables por capítulo. Se adopta el patrón
Strategy: interfaz `IAIStrategy` con `AIStrategyEasy` (random con prioridad a capturas),
`AIStrategyMedium` (1-ply evaluation) y `AIStrategyHard` (minimax alpha-beta depth 2, timeout
5s), seleccionadas por `AIController`.
## Engine Compatibility
| Field | Value |
|-------|-------|
| **Engine** | Unity 6.3 LTS (6000.3.13f1) |
| **Domain** | Core / Scripting |
| **Knowledge Risk** | HIGH — post-cutoff, verificada contra engine-reference |
| **References Consulted** | `VERSION.md`, `breaking-changes.md`, `deprecated-apis.md` |
| **Post-Cutoff APIs Used** | None |
| **Verification Required** | None — lógica pura C#, sin APIs nuevas del motor |
## ADR Dependencies
| Field | Value |
|-------|-------|
| **Depends On** | ADR-0000 (Accepted); ADR-0001 (Accepted requerido para compartir servicios) |
| **Enables** | Dificultad por capítulo; nuevas dificultades sin tocar el controlador |
| **Blocks** | Instanciación de `AIController` en escenas (ver Contexto — INCOMPLETO) |
| **Ordering Note** | La dificultad por capítulo desde `CampaignManager` es TODO pendiente (S4-005) |
## Context
### Problem Statement
Tres dificultades de IA deben poder escalarse por capítulo (el VS sube dificultad en cada
tablero: La Fábrica → El Hospital → La Corte del Juicio). Sin un patrón de selección, el
controlador crecería con condicionales por dificultad.
### Current State
`IAIStrategy` + las 3 estrategias implementadas (Sprint 2). **Completado (2026-08-16)**:
- `AIController` instanciado en `Chapter1.unity`.
- `CampaignManager.ApplyChapterAIConfiguration` aplica la dificultad del capítulo vía
`AIController.SetDifficulty()` (parsing de `AIConfig.difficulty``AIDifficulty`).
- `AIController` inicializa la estrategia solo si nadie la configuró antes del `Start`
(robustez de orden de ejecución) y reintenta la suscripción a `OnTurnChanged` si
`OnEnable` corrió antes que `GameManager.Awake`.
### Constraints
- Las estrategias deben poder evaluar posiciones sin depender de la UI.
- La selección debe ser configurable por capítulo.
### Requirements
- Extensibilidad: añadir dificultades sin modificar `AIController`.
- Selección de estrategia en tiempo de ejecución.
## Decision
### Architecture
```
AIController ──► IAIStrategy (seleccionada en runtime)
├── AIStrategyEasy (random + prioridad a capturas)
├── AIStrategyMedium (1-ply evaluation)
└── AIStrategyHard (minimax alpha-beta depth 2, timeout 5s)
```
### Key Interfaces
```
public interface IAIStrategy {
Move SelectMove(BoardState board, Color side, float timeoutSec);
}
```
### Implementation Guidelines
- El patrón debe respetar el estilo Awake-explicit de ADR-0000 si el controller es
MonoBehaviour cross-escena.
- Estrategias sin estado persistente entre turnos (o estado por partida, reseteable).
- Las estrategias consumen los servicios POCO de ADR-0001.
## Alternatives Considered
### Alternative 1: Condicionales switch/if por dificultad en AIController
- **Description**: `if (difficulty == Easy) ... else if (difficulty == Medium) ...`
- **Pros**: sin interfaces.
- **Cons**: el controlador crece con cada dificultad; OCP violado.
- **Estimated Effort**: menor al inicio, mayor a la larga.
- **Rejection Reason**: no extensible.
### Alternative 2: Dificultad como enum evaluada internamente
- **Description**: una sola clase con parámetros de dificultad.
- **Pros**: centraliza el algoritmo.
- **Cons**: acopla los 3 algoritmos distintos (random vs minimax) en una clase.
- **Estimated Effort**: similar.
- **Rejection Reason**: mezcla algoritmos radicalmente distintos.
## Consequences
### Positive
- Nuevas dificultades sin tocar el controlador.
- Cada estrategia se testea de forma aislada.
### Negative
- Sin deuda pendiente conocida tras el completado (2026-08-16). El patrón añade una clase
por dificultad (indirección ligera).
### Neutral
- Una clase por dificultad; ligero overhead de indirección.
## Risks
| Risk | Probability | Impact | Mitigation |
|------|------------|--------|-----------|
| `AIStrategyHard` (alpha-beta) excede el tiempo en posiciones complejas | Media | Media | Timeout de 5s + limitación de profundidad; abortar y devolver mejor movimiento encontrado |
| Orden de ejecución entre `AIController.Start` y la configuración del capítulo | Baja | Baja | `Start` inicializa solo si `_currentStrategy == null`; `CampaignManager` aplica la dificultad en `LoadChapter` |
## Performance Implications
`AIStrategyHard`: minimax depth 2 con alpha-beta, acotado por timeout de 5s. `Easy`/`Medium`
son ligeros. Sin impacto en frame time si el movimiento se computa en un turno del jugador IA
(no bloquea el Update).
## Migration Plan
Implementado (Sprint 2). Pendientes resueltos (2026-08-16): `AIController` instanciado en
`Chapter1.unity`; dificultad por capítulo cableada desde `CampaignManager`.
**Rollback plan**: la interfaz permite volver a una dificultad fija sin cambios de diseño.
## Validation Criteria
- [x] `AIController` instanciado en escenas y produce movimientos legales en partida.
- [x] La dificultad del capítulo N se selecciona desde `CampaignManager`.
- [ ] `Hard` nunca supera el timeout de 5s por movimiento (verificar en playtest).
- [ ] Nueva dificultad se añade implementando `IAIStrategy` sin modificar `AIController`.
## GDD Requirements Addressed
| GDD Document | System | Requirement | How This ADR Satisfies It |
|-------------|--------|-------------|--------------------------|
| `design/gdd/systems-index.md` | 13 — AI System | 3 dificultades escalables por capítulo | Strategy pattern permite selección runtime y extensión |
| `design/gdd/systems-index.md` | 2 — Piece Movement | La IA mueve piezas legalmente | Estrategias consumen servicios POCO de ADR-0001 |
## Related
- ADR-0000 (estancia 2 y 5).
- ADR-0001 — servicios compartidos.
- Código: `Assets/Game/Scripts/Mono/AI/`.
+182
View File
@@ -0,0 +1,182 @@
# ADR-0003: Event-Driven
## Status
Accepted
## Date
2026-08-16
## Last Verified
2026-08-16
## Decision Makers
Diseñador/director (usuario) + agente de arquitectura (opencode)
## Summary
La UI y los sistemas (Purgatorio, HUD) necesitan reaccionar a eventos del juego sin
acoplamiento directo. Se adoptan eventos `Action<T>` expuestos por `GameManager`
(`OnCheckmate`, `OnDraw`, `OnPieceMoved`, `OnTurnChanged`, `OnCheck`) como mecanismo de
comunicación cross-sistema, con `PurgatoryManager` suscrito a capturas y la UI escuchando
eventos.
## Engine Compatibility
| Field | Value |
|-------|-------|
| **Engine** | Unity 6.3 LTS (6000.3.13f1) |
| **Domain** | Core / Scripting |
| **Knowledge Risk** | HIGH — post-cutoff, verificada contra engine-reference |
| **References Consulted** | `VERSION.md`, `breaking-changes.md`, `deprecated-apis.md` |
| **Post-Cutoff APIs Used** | None (eventos C# estándar) |
| **Verification Required** | None |
## ADR Dependencies
| Field | Value |
|-------|-------|
| **Depends On** | ADR-0000 (Accepted) — estancia 4: comunicación por eventos C# |
| **Enables** | HUD completo (sistema 18), Purgatorio desacoplado del GameManager |
| **Blocks** | Suscripciones de `GameHUD` pendientes (ver Contexto — INCOMPLETO) |
| **Ordering Note** | None |
## Context
### Problem Statement
La UI (HUD, Purgatorio) y los sistemas reaccionan al estado de la partida. Referencias
directas entre UI y `GameManager` acoplan presentación con lógica y dificultan cambios.
### Current State
Eventos `Action<T>` en `GameManager`: `OnCheckmate`, `OnDraw`, `OnPieceMoved`,
`OnTurnChanged`, `OnCheck`. `PurgatoryManager` se suscribe a capturas. **Completado
(2026-08-16)**: añadidos `OnPieceCaptured(Piece)` y `OnCheckResolved`; `GameHUD` suscrito a
turno, captura, jaque y resolución de jaque.
### Constraints
- Mismo mecanismo de eventos en todo el proyecto (estancia ADR-0000).
- Sin bus de mensajes global (eventos C# estáticos, no mensajes enrutados).
### Requirements
- Desacople UI ↔ lógica.
- Suscriptores fáciles de añadir desde MonoBehaviour.
## Decision
### Architecture
```
GameManager (produce eventos)
│ OnCheckmate / OnDraw / OnPieceMoved / OnTurnChanged / OnCheck
│ OnPieceCaptured / OnCheckResolved (añadidos 2026-08-16)
├── PurgatoryManager (consumidor de capturas)
└── GameHUD / UI (consumidores)
```
### Key Interfaces
```
// Productor (GameManager) — eventos de instancia
public event Action<bool> OnTurnChanged;
public event Action<bool> OnCheck;
public event Action<bool> OnCheckmate;
public event Action<string> OnDraw;
public event Action<Piece, Vector2Int, Vector2Int> OnPieceMoved;
public event Action<Piece> OnPieceCaptured;
public event Action OnCheckResolved;
// Consumidor típico
private void OnEnable() { GameManager.Instance.OnPieceMoved += HandleMove; }
private void OnDisable() { GameManager.Instance.OnPieceMoved -= HandleMove; }
```
### Implementation Guidelines
- Prefijo `On` en el nombre del evento (convención ADR-0000).
- Desuscripción en `OnDisable`/`OnDestroy` (regla ADR-0000).
- `OnPieceCaptured` se emite para capturas normales y en passant; `OnCheckResolved` se emite
cuando el bando que estaba en jaque sale de él tras un movimiento legal.
## Alternatives Considered
### Alternative 1: Llamadas directas UI → GameManager
- **Description**: la UI llama métodos de `GameManager` para enterarse del estado.
- **Pros**: simple.
- **Cons**: acoplamiento UI ↔ lógica; la UI debe conocer el estado completo.
- **Estimated Effort**: menor.
- **Rejection Reason**: el acoplamiento que este ADR elimina.
### Alternative 2: Event bus global (UnityEvent / mensajes enrutados)
- **Description**: bus central que enruta mensajes por tipo.
- **Pros**: centraliza, desacopla emisor de consumidores.
- **Cons**: indirección adicional, difícil de depurar, over-engineering para el alcance.
- **Estimated Effort**: mayor.
- **Rejection Reason**: los eventos C# estáticos del ADR-0000 cubren el caso; ADR-0000
decidió no usar bus.
## Consequences
### Positive
- Desacople UI ↔ lógica.
- Consumidores se suscriben sin tocar el productor.
### Negative
- Sin deuda pendiente conocida tras el completado (2026-08-16). Los eventos de instancia
crean acoplamiento implícito productor↔consumidor (grep-able, pero no inyectable).
### Neutral
- Sin bus central; cada sistema declara sus propios eventos estáticos.
## Risks
| Risk | Probability | Impact | Mitigation |
|------|------------|--------|-----------|
| Fugas de suscripción (leaks) | Media | Baja | Desuscripción en `OnDisable`/`OnDestroy`; revisión en code review |
| Orden de suscripción entre consumidores | Baja | Baja | Los eventos solo notifican estado final, no mutaciones secuenciales |
| `GameHUD` desincronizado con el estado de capturas/jaque | Baja | Baja | Suscripciones activas desde 2026-08-16; verificación en playtest |
## Performance Implications
Eventos C# con 2-5 consumidores: overhead sub-microsegundo por notificación, solo en puntos
discretos (movimiento, turno, jaque, mate, tablas). Sin impacto en frame time.
## Migration Plan
Implementado (Sprints 1-2) + completado (2026-08-16): eventos añadidos y `GameHUD` suscrito.
Pendiente: test de integración formal (captura → Purgatorio + HUD reciben el evento).
**Rollback plan**: volver a llamadas directas solo si el desacople fallara (no previsto).
## Validation Criteria
- [x] `GameHUD` reacciona a capturas y a resolución de jaque vía eventos, sin referenciar
el estado de `GameManager` directamente.
- [x] Todos los suscriptores se desuscriben en `OnDisable`/`OnDestroy`.
- [ ] `PurgatoryManager` dispara el flujo de dados únicamente por evento de captura
(verificación de integración en playtest).
## GDD Requirements Addressed
| GDD Document | System | Requirement | How This ADR Satisfies It |
|-------------|--------|-------------|--------------------------|
| `design/gdd/systems-index.md` | 16 — Game State Manager | Transiciones de estado notificadas | Eventos de estado emitidos por GameManager |
| `design/gdd/systems-index.md` | 18 — HUD | HUD refleja el estado de la partida | Consumo por eventos, sin acoplar GameManager |
| `design/gdd/systems-index.md` | 10 — Dice System (Purgatorio) | Reaccionar a capturas | `PurgatoryManager` suscrito a capturas |
## Related
- ADR-0000 (estancia 4) — eventos C# canónicos.
- Código: `GameManager.cs`, `PurgatoryManager.cs`, `GameHUD.cs`.
@@ -0,0 +1,180 @@
# ADR-0004: Singleton dual
## Status
Accepted
## Date
2026-08-16
## Last Verified
2026-08-16
## Decision Makers
Diseñador/director (usuario) + agente de arquitectura (opencode)
## Summary
Managers globales accesibles desde cualquier script. Se adopta una variante dual:
`MonoBehaviour` con `DontDestroyOnLoad` para servicios con ciclo de vida de escena
(`CampaignManager`, `SaveSystem`, `AudioManager`) y `ScriptableObject` singleton para
persistencia + configuración en un solo asset (`DeadKingPool`). La variante MonoBehaviour
queda restringida al estilo canónico Awake-explicit de ADR-0000.
## Engine Compatibility
| Field | Value |
|-------|-------|
| **Engine** | Unity 6.3 LTS (6000.3.13f1) |
| **Domain** | Core / Scripting |
| **Knowledge Risk** | HIGH — post-cutoff, verificada contra engine-reference |
| **References Consulted** | `VERSION.md`, `breaking-changes.md`, `deprecated-apis.md` |
| **Post-Cutoff APIs Used** | None (`DontDestroyOnLoad`, `ScriptableObject` vigentes) |
| **Verification Required** | Re-validar si `Resources.Load` migra a Addressables (deprecated-apis) |
## ADR Dependencies
| Field | Value |
|-------|-------|
| **Depends On** | ADR-0000 (Accepted) — estancia 1: canónico Awake-explicit para la variante MonoBehaviour; estancia 3: persistencia |
| **Enables** | ADR-0005 (ScriptableObject como data container); acceso global a SaveSystem/AudioManager |
| **Blocks** | None |
| **Ordering Note** | Migración lazy→Awake-explicit de `SaveSystem` y `AudioManager` pendiente (deuda ADR-0000) |
## Context
### Problem Statement
Los managers globales (`CampaignManager`, `SaveSystem`, `AudioManager`, `DeadKingPool`) se
acceden desde cualquier script. Sin un patrón único, cada manager eligió un estilo distinto
(ver ADR-0000): la variante dual documenta ambas naturalezas y fija el estilo.
### Current State
- `MonoBehaviour` + `DontDestroyOnLoad`: `CampaignManager`, `SaveSystem`, `AudioManager`
(dos de ellos, `SaveSystem` y `AudioManager`, aún en estilo lazy-getter — por migrar).
- `ScriptableObject` singleton: `DeadKingPool` (persistencia + configuración en un asset,
cargado con `Resources.Load<DeadKingPool>`).
### Constraints
- Variante MonoBehaviour debe cumplir el esqueleto Awake-explicit de ADR-0000.
- `ScriptableObject` no tiene ciclo de vida de escena: apto para datos, no para servicios
con coroutines/Update.
### Requirements
- Acceso global simple y consistente.
- `DeadKingPool` accesible como asset desde editor y runtime.
## Decision
### Architecture
```
Singleton dual
├── MonoBehaviour + DontDestroyOnLoad (servicio con ciclo de vida)
│ CampaignManager · SaveSystem · AudioManager
│ └── estilo canónico Awake-explicit (ADR-0000)
└── ScriptableObject singleton (datos/persistencia en asset)
DeadKingPool
└── carga centralizada (Resources.Load hoy, Addressables si escala)
```
### Key Interfaces
```
// MonoBehaviour (canónico ADR-0000)
public static CampaignManager Instance { get; private set; }
private void Awake() { /* guard + DontDestroyOnLoad */ }
// ScriptableObject singleton
public static DeadKingPool Instance { get; }
// carga centralizada vía API única (Resources.Load actualmente)
```
### Implementation Guidelines
- Variante MonoBehaviour: esqueleto Awake-explicit con guard de duplicado y
`DontDestroyOnLoad` (ADR-0000).
- Variante ScriptableObject: carga centralizada en una API única para poder migrar a
Addressables sin tocar consumidores.
## Alternatives Considered
### Alternative 1: Solo MonoBehaviour
- **Description**: `DeadKingPool` como MonoBehaviour en una escena inicial.
- **Pros**: un solo patrón.
- **Cons**: la configuración del pool no es un asset editable en el editor.
- **Estimated Effort**: menor.
- **Rejection Reason**: pierde la edición de datos en editor de `ScriptableObject`.
### Alternative 2: Solo ScriptableObject
- **Description**: todos los managers como ScriptableObject.
- **Pros**: todo data-driven.
- **Cons**: sin ciclo de vida de escena (Update, coroutines, co-existence con escena).
- **Estimated Effort**: menor.
- **Rejection Reason**: los managers necesitan ciclo de vida de MonoBehaviour.
## Consequences
### Positive
- Acceso global simple.
- `DeadKingPool`: persistencia + configuración en un asset (editable en editor).
### Negative
- Acoplamiento implícito del patrón singleton, aceptado para el alcance del proyecto.
- Deuda de migración: `SaveSystem`/`AudioManager` aún en lazy-getter (ADR-0000).
- `Resources.Load` deprecado en Unity 6 (aceptado para el VS; API centralizada para migrar).
### Neutral
- Dos variantes de singleton conviven con reglas claras (qué es MonoBehaviour y qué es SO).
## Risks
| Risk | Probability | Impact | Mitigation |
|------|------------|--------|-----------|
| `Resources.Load` escala mal con contenido | Media | Media | Carga centralizada; migración a Addressables planificada |
| Duplicados de `DeadKingPool` (asset) por mala configuración | Baja | Baja | Singleton por asset con guard |
| Migración lazy→Awake-explicit de SaveSystem/AudioManager | Media | Media | Smoke test del VS tras migrar |
## Performance Implications
`Resources.Load` en el primer acceso de `DeadKingPool`: coste único de carga; cacheado en el
singleton. Sin impacto en frame time posterior.
## Migration Plan
1. Migrar `SaveSystem` y `AudioManager` a estilo Awake-explicit (deuda ADR-0000).
2. Mantener `DeadKingPool` como SO singleton; centralizar la carga.
3. (Futuro) Migrar `Resources.Load` a Addressables si el contenido escala.
**Rollback plan**: migraciones reversibles por git; el ADR se marca Superseded si un cambio
de motor invalida las APIs.
## Validation Criteria
- [ ] `SaveSystem` y `AudioManager` usan el esqueleto Awake-explicit.
- [ ] `DeadKingPool.Instance` es accesible desde editor y runtime sin duplicados.
- [ ] Consumidores de `DeadKingPool` no llaman `Resources.Load` directamente (API central).
## GDD Requirements Addressed
| GDD Document | System | Requirement | How This ADR Satisfies It |
|-------------|--------|-------------|--------------------------|
| `design/gdd/systems-index.md` | 14 — Dead Kings System | Pool de Reyes Muertos persistente | `DeadKingPool` SO singleton con persistencia + configuración |
| `design/gdd/systems-index.md` | 17 — Save/Load | Persistencia de campaña | `SaveSystem` global accesible desde cualquier sistema |
## Related
- ADR-0000 (estancias 1, 3 y 5) — canónico, persistencia, patrón prohibido de manager duplicado.
- ADR-0005 — ScriptableObjects como data container.
- Código: `CampaignManager.cs`, `SaveSystem.cs`, `AudioManager.cs`, `DeadKingPool.cs`.
@@ -0,0 +1,172 @@
# ADR-0005: ScriptableObject como data container
## Status
Accepted
## Date
2026-08-16
## Last Verified
2026-08-16
## Decision Makers
Diseñador/director (usuario) + agente de arquitectura (opencode)
## Summary
Los datos de diseño (identidades de 16-20 piezas, tablas de posición de IA, balance) deben
editarse sin recompilar. Se usan `PieceIdentity`, `AIPositionTables`, `BalanceConfig` y
`CampaignConfig` como ScriptableObjects; `CampaignState` y `DeadKingPool` se cargan desde
`Resources/`.
## Engine Compatibility
| Field | Value |
|-------|-------|
| **Engine** | Unity 6.3 LTS (6000.3.13f1) |
| **Domain** | Core / Scripting |
| **Knowledge Risk** | HIGH — post-cutoff, verificada contra engine-reference |
| **References Consulted** | `VERSION.md`, `breaking-changes.md`, `deprecated-apis.md` |
| **Post-Cutoff APIs Used** | None (`ScriptableObject`, `Resources` vigentes; `Resources` desaconsejado en favor de Addressables) |
| **Verification Required** | Re-validar migración `Resources.Load` → Addressables si el contenido escala |
## ADR Dependencies
| Field | Value |
|-------|-------|
| **Depends On** | ADR-0000 (Accepted) — estancia 3: SO de estado runtime como fuente de verdad; ADR-0004 (Proposed) |
| **Enables** | Data-driven sin recompilar; editor tools de balance |
| **Blocks** | None |
| **Ordering Note** | La serialización de `CampaignState` requiere capa JSON propia (limitación `JsonUtility` con objetos anidados) |
## Context
### Problem Statement
Datos de diseño de alta iteración (identidad de piezas, tablas de IA, balance, configuración
de campaña) no deben requerir recompilación ni edición de código.
### Current State
- `PieceIdentity`, `AIPositionTables`, `BalanceConfig`, `CampaignConfig`: ScriptableObjects.
- `CampaignState` y `DeadKingPool`: cargados desde `Resources/`.
- `BalanceConfig.LoadFromJSON()` es placeholder (valores default) — TODO pendiente.
### Constraints
- Editable en el inspector de Unity sin recompilar.
- La serialización de `CampaignState` (objetos anidados) excede a `JsonUtility`.
### Requirements
- Data-driven: los datos se editan en assets, no en código.
- Carga centralizada y reutilizable.
## Decision
### Architecture
```
ScriptableObjects (data containers)
├── PieceIdentity (identidad de 16-20 piezas)
├── AIPositionTables (tablas de posición IA)
├── BalanceConfig (balance, con LoadFromJSON pendiente)
├── CampaignConfig (configuración de campaña)
└── CampaignState / DeadKingPool → cargados desde Resources/
```
### Key Interfaces
```
// SOs como assets editables en el inspector
[CreateAssetMenu(menuName = "Ajedrez Purgatorio/...")]
public class PieceIdentity : ScriptableObject { ... }
// Carga centralizada (ADR-0004)
Resources.Load<DeadKingPool>("DeadKingPool");
```
### Implementation Guidelines
- Los SO de datos no deben contener lógica de negocio (solo datos); la lógica vive en la
capa de servicios POCO (ADR-0001).
- Serialización de `CampaignState`: wrapper `[Serializable]` propio + `JsonUtility`
(limitación de objetos anidados documentada en ADR-0000).
## Alternatives Considered
### Alternative 1: JSON externo puro
- **Description**: todos los datos en ficheros JSON cargados en runtime.
- **Pros**: editable fuera de Unity.
- **Cons**: sin edición visual ni validación en inspector; más infraestructura.
- **Estimated Effort**: mayor.
- **Rejection Reason**: perder la edición en inspector que Unity ofrece gratis.
### Alternative 2: Datos hardcodeados en C#
- **Description**: datos en constantes/clases estáticas.
- **Pros**: sin assets.
- **Cons**: requiere recompilar para cualquier ajuste; mezcla datos con código.
- **Estimated Effort**: menor.
- **Rejection Reason**: iteración de diseño lenta; contraviene el data-driven.
## Consequences
### Positive
- Data-driven y editable en editor.
- Iteración de balance/identidad sin recompilar.
### Negative
- La serialización de `CampaignState` requiere capa JSON propia (objetos anidados).
- `BalanceConfig.LoadFromJSON()` sigue siendo placeholder.
### Neutral
- Dos formas de carga: SOs referenciados en inspector + SOs desde `Resources/`.
## Risks
| Risk | Probability | Impact | Mitigation |
|------|------------|--------|-----------|
| SOs con lógica de negocio (mal uso del patrón) | Media | Media | Regla de revisión: SO = datos; lógica en servicios POCO |
| `Resources/` con muchos assets ralentiza build | Media | Media | Mantener solo SOs de estado runtime; migrar a Addressables si escala |
| `BalanceConfig.LoadFromJSON()` placeholder desincronizado | Media | Media | Completar la carga real antes de balance final |
## Performance Implications
SOs cargados una vez y referenciados; `Resources.Load` con cache por singleton. Sin impacto
en frame time.
## Migration Plan
Implementado (Sprints 1-2). Pendiente: completar `BalanceConfig.LoadFromJSON()` y mantener
la carga centralizada para la eventual migración a Addressables.
**Rollback plan**: los SOs son assets; revertir configuración es revertir assets por git.
## Validation Criteria
- [ ] Piezas, tablas de IA y balance se ajustan en el inspector sin recompilar.
- [ ] `CampaignState` se serializa a JSON (capa propia) y se restaura correctamente.
- [ ] `BalanceConfig` carga desde JSON real (sin placeholder) cuando el diseño lo requiera.
## GDD Requirements Addressed
| GDD Document | System | Requirement | How This ADR Satisfies It |
|-------------|--------|-------------|--------------------------|
| `design/gdd/systems-index.md` | 9 — Piece Identity System | Identidades de 16-20 piezas | `PieceIdentity` como SO editable |
| `design/gdd/systems-index.md` | 11 — Campaign System | Configuración y estado de campaña | `CampaignConfig`/`CampaignState` como SO |
| `design/gdd/systems-index.md` | 14 — Dead Kings System | Pool de Reyes Muertos | `DeadKingPool` como SO en Resources |
## Related
- ADR-0000 (estancia 3) — SO runtime + JsonUtility.
- ADR-0004 — singleton dual (DeadKingPool como SO).
- Código: `Assets/Game/Scripts/Data/`, `Assets/Game/Scripts/Mono/Data/`, `ScriptableObjects/`.
+162
View File
@@ -0,0 +1,162 @@
# ADR-0006: Overlay UI en lugar de escenas separadas
## Status
Accepted
## Date
2026-08-16
## Last Verified
2026-08-16
## Decision Makers
Diseñador/director (usuario) + agente de arquitectura (opencode)
## Summary
El flujo del Purgatorio (oferta → dados → resultado) debe interrumpir la partida sin perder
el contexto del tablero. Se implementa como Canvas overlay sobre la escena de juego en lugar
de escenas separadas, requiriendo pausar/resumir el gameplay (ADR-0007).
## Engine Compatibility
| Field | Value |
|-------|-------|
| **Engine** | Unity 6.3 LTS (6000.3.13f1) |
| **Domain** | UI |
| **Knowledge Risk** | HIGH — post-cutoff, verificada contra engine-reference |
| **References Consulted** | `VERSION.md`, `modules/ui.md`, `deprecated-apis.md` |
| **Post-Cutoff APIs Used** | None (UGUI + Canvas, deprecado pero soportado; TextMeshPro) |
| **Verification Required** | Re-validar si se migra de UGUI a UI Toolkit |
## ADR Dependencies
| Field | Value |
|-------|-------|
| **Depends On** | ADR-0000 (Accepted); ADR-0007 (Proposed) — pausa con `Time.timeScale` |
| **Enables** | Flujo de dados sin perder contexto del tablero; UX del VS |
| **Blocks** | None |
| **Ordering Note** | Requiere pausar/resumir gameplay (ADR-0007) |
## Context
### Problem Statement
El Purgatorio interrumpe la partida al capturar una pieza. Cargar una escena separada
destruiría el tablero y el estado de la partida (o exigiría serializarlo), degradando la UX.
### Current State
El flujo (PurgatoryOfferUI → DiceRollUI → DiceResultUI) está previsto como Canvas overlay;
`PurgatoryManager` + `DiceSystem` cableados en `Chapter1.unity` con las 3 UIs asignadas.
### Constraints
- El tablero y su estado deben permanecer intactos durante el flujo.
- El gameplay debe quedar pausado durante oferta/dados/resultado.
### Requirements
- Interrupción del tablero sin pérdida de contexto.
- Transiciones suaves oferta → dados → resultado.
## Decision
### Architecture
```
Chapter1.unity
└── Canvas (overlay)
├── PurgatoryOfferUI ──► DiceRollUI ──► DiceResultUI
└── (superpuesto sobre la escena de juego, sin descargarla)
```
### Key Interfaces
```
// Overlay controlado por PurgatoryManager
PurgatoryManager.ShowOffer(); // pausa gameplay (ADR-0007)
PurgatoryManager.ShowResult(); // resume gameplay (ADR-0007)
```
### Implementation Guidelines
- Overlays dentro de la misma escena; nunca `LoadScene` durante el flujo.
- Pausa/resume vía `Time.timeScale` (ADR-0007), no desactivando el tablero.
- El estado del tablero queda en memoria (no se serializa ni descarga).
## Alternatives Considered
### Alternative 1: Escenas separadas por paso del Purgatorio
- **Description**: cada pantalla (oferta, dados, resultado) es una escena.
- **Pros**: separación por escena.
- **Cons**: destruye el tablero, exige serializar estado, carga pesada, rompe la continuidad.
- **Estimated Effort**: mayor.
- **Rejection Reason**: pierde el contexto del tablero y degrada la UX.
### Alternative 2: Overlay único que conmuta paneles
- **Description**: un solo Canvas con todos los pasos visibles de forma condicional
(equivalente a lo adoptado, con variantes de implementación).
- **Pros**: igual que el elegido.
- **Cons**: paneles ocultos en memoria (mínimo).
- **Estimated Effort**: igual.
- **Rejection Reason**: no aplica — es la misma decisión.
## Consequences
### Positive
- El tablero y el estado permanecen intactos.
- Menor overhead de carga; UX continua.
- La pausa por `Time.timeScale` detiene física/animaciones automáticamente.
### Negative
- Los pasos del Purgatorio no se pueden cargar/testear de forma aislada como escenas.
- La escena de juego crece con paneles overlay.
### Neutral
- El flujo depende de `PurgatoryManager` para orquestar la secuencia.
## Risks
| Risk | Probability | Impact | Mitigation |
|------|------------|--------|-----------|
| La pausa por `Time.timeScale` deja coroutines vivas (WaitForSeconds congeladas) | Media | Media | ADR-0007: usar `WaitForSecondsRealtime` en overlays |
| El jugador interactúa con el tablero durante la oferta | Baja | Media | Bloquear input del tablero mientras el overlay está activo |
## Performance Implications
Overlays en memoria durante todo el tablero: coste fijo de Canvas (SetActive inactive);
sin cambio de carga de escena. Despreciable en el presupuesto.
## Migration Plan
Implementado (Sprint 3). Sin migración pendiente; validar bloqueo de input durante el overlay.
**Rollback plan**: N/A (implementación ya activa; revertir por git si fallara la UX).
## Validation Criteria
- [ ] Al capturar una pieza, la partida se pausa y el overlay aparece sin descargar el tablero.
- [ ] La secuencia oferta → dados → resultado se completa y el juego se reanuda.
- [ ] El input del tablero queda bloqueado mientras el overlay está activo.
## GDD Requirements Addressed
| GDD Document | System | Requirement | How This ADR Satisfies It |
|-------------|--------|-------------|--------------------------|
| `design/gdd/systems-index.md` | 10 — Dice System (Purgatorio) | Mini-juego de dados contra la Muerte al perder una pieza | Flujo overlay que interrumpe sin perder contexto |
| `design/gdd/systems-index.md` | 21 — Dice UI | 3 pantallas (Offer, Roll, Result) | Canvas overlay único con 3 pasos |
## Related
- ADR-0007 — pausa/resume con `Time.timeScale`.
- Código: `Assets/Game/Scripts/Mono/Purgatory/`, `Assets/Game/Scripts/Mono/UI/`.
@@ -0,0 +1,167 @@
# ADR-0007: Time.timeScale para pausas
## Status
Accepted
## Date
2026-08-16
## Last Verified
2026-08-16
## Decision Makers
Diseñador/director (usuario) + agente de arquitectura (opencode)
## Summary
La promoción de peón y el Purgatorio necesitan pausar el juego. Se usa `Time.timeScale = 0`
para pausar y las coroutines de los overlays usan `WaitForSecondsRealtime` para seguir
funcionando durante la pausa.
## Engine Compatibility
| Field | Value |
|-------|-------|
| **Engine** | Unity 6.3 LTS (6000.3.13f1) |
| **Domain** | Core / Scripting |
| **Knowledge Risk** | HIGH — post-cutoff, verificada contra engine-reference |
| **References Consulted** | `VERSION.md`, `breaking-changes.md`, `deprecated-apis.md` |
| **Post-Cutoff APIs Used** | None (`Time.timeScale`, `WaitForSecondsRealtime` vigentes) |
| **Verification Required** | Re-validar con el sistema de pausa del New Input System si se migra |
## ADR Dependencies
| Field | Value |
|-------|-------|
| **Depends On** | ADR-0000 (Accepted) |
| **Enables** | ADR-0006 (overlay del Purgatorio); pausa de promoción |
| **Blocks** | None |
| **Ordering Note** | Afecta a cualquier coroutine existente con `WaitForSeconds` (riesgo) |
## Context
### Problem Statement
La promoción de peón (elección de pieza) y el Purgatorio (oferta/dados/resultado) deben
pausar la partida. Sin pausa, la IA, los timers o la interacción seguirían activos.
### Current State
`Time.timeScale = 0` en `PurgatoryManager` (línea 85), `PauseMenuController` (94),
`PromotionUI` (53); `Time.timeScale = 1` para reanudar. Coroutines de UI con
`WaitForSecondsRealtime`.
### Constraints
- La pausa debe detener física, animaciones y coroutines basadas en `WaitForSeconds`.
- La UI (menú de pausa, overlays) debe seguir respondiendo.
### Requirements
- Pausar/resumir sin estado global adicional.
- Coroutines de UI vivas durante la pausa.
## Decision
### Architecture
```
Time.timeScale = 0 (pausa) Time.timeScale = 1 (resume)
│ ▲
├─ PurgatoryManager (oferta/dados) │
├─ PauseMenuController (pausa) │
└─ PromotionUI (elección de peón) │
└── coroutines de UI: WaitForSecondsRealtime (no se congelan)
```
### Key Interfaces
```
Time.timeScale = 0f; // pausa
yield return new WaitForSecondsRealtime(x); // coroutines de UI que siguen vivas
Time.timeScale = 1f; // resume
```
### Implementation Guidelines
- Coroutines de UI durante la pausa: siempre `WaitForSecondsRealtime`, nunca `WaitForSeconds`.
- Restaurar `timeScale` en `OnDestroy`/`OnDisable` del controlador de pausa si la escena se
descarga pausada.
- El resume debe restaurar el valor previo (no asumir siempre 1).
## Alternatives Considered
### Alternative 1: Flag `isPaused` manual con Update()
- **Description**: bool global que frena el Update de los sistemas.
- **Pros**: control fino.
- **Cons**: no detiene física ni animaciones automáticamente; hay que tocar cada sistema.
- **Estimated Effort**: mayor.
- **Rejection Reason**: no detiene el motor de forma global; propenso a olvidos.
### Alternative 2: Desactivar GameObjects de gameplay
- **Description**: SetActive(false) del tablero/sistemas al pausar.
- **Pros**: detiene todo lo desactivado.
- **Cons**: pierde estado/UI visible, más invasivo, difícil de orquestar.
- **Estimated Effort**: mayor.
- **Rejection Reason**: el overlay (ADR-0006) necesita el tablero visible.
## Consequences
### Positive
- Pausa global simple, sin estado adicional.
- Detiene física, animaciones y coroutines `WaitForSeconds`.
### Negative
- Riesgo: cualquier coroutine con `WaitForSeconds` normal se congela durante la pausa.
- `timeScale` es global: pausar un flujo pausa todos los demás.
### Neutral
- Pausa y resume se controlan por asignación directa de `Time.timeScale`.
## Risks
| Risk | Probability | Impact | Mitigation |
|------|------------|--------|-----------|
| Coroutines `WaitForSeconds` congeladas durante pausa | Media | Media | Regla: coroutines de UI con `WaitForSecondsRealtime`; grep en code review |
| `timeScale` queda en 0 si se descarga la escena pausada | Baja | Media | Restaurar en `OnDestroy`/`OnDisable` |
| Resume asume timeScale=1 y sobrescribe un valor previo | Baja | Baja | Guardar y restaurar el valor previo |
## Performance Implications
Sin impacto: `Time.timeScale` no introduce coste de procesamiento; solo congela el ciclo de
Update/animaciones/física.
## Migration Plan
Implementado (Sprints 1-3). Pendiente: auditar coroutines con `WaitForSeconds` normal que
pudieran congelarse durante pausas (grep + revisión).
**Rollback plan**: N/A — API vigente; revertir cambios concretos por git.
## Validation Criteria
- [ ] Durante la oferta del Purgatorio, la IA y los timers están detenidos (timeScale 0).
- [ ] El menú de pausa y los overlays siguen funcionando (WaitForSecondsRealtime).
- [ ] Al reanudar, `timeScale` vuelve al valor previo (1 en juego normal).
- [ ] No quedan coroutines con `WaitForSeconds` activas durante pausas.
## GDD Requirements Addressed
| GDD Document | System | Requirement | How This ADR Satisfies It |
|-------------|--------|-------------|--------------------------|
| `design/gdd/systems-index.md` | 6 — Pawn Promotion | Elección de pieza pausa la partida | `Time.timeScale = 0` en `PromotionUI` |
| `design/gdd/systems-index.md` | 10 — Dice System (Purgatorio) | Pausar durante oferta/dados | `Time.timeScale = 0` en `PurgatoryManager` |
## Related
- ADR-0006 — overlays que requieren la pausa.
- Código: `PurgatoryManager.cs`, `PauseMenuController.cs`, `PromotionUI.cs`.
@@ -0,0 +1,165 @@
# ADR-0008: Position hashing FEN-like
## Status
Accepted
## Date
2026-08-16
## Last Verified
2026-08-16
## Decision Makers
Diseñador/director (usuario) + agente de arquitectura (opencode)
## Summary
Para la detección de repetición triple se usa un hash de posición tipo FEN-string en lugar de
Zobrist hashing. El hash se genera desde el estado del tablero y la repetición se rastrea en
`DrawDetector` (capa POCO, ADR-0001).
## Engine Compatibility
| Field | Value |
|-------|-------|
| **Engine** | Unity 6.3 LTS (6000.3.13f1) |
| **Domain** | Core / Scripting |
| **Knowledge Risk** | HIGH — post-cutoff, verificada contra engine-reference |
| **References Consulted** | `VERSION.md`, `breaking-changes.md`, `deprecated-apis.md` |
| **Post-Cutoff APIs Used** | None (lógica pura C#) |
| **Verification Required** | None |
## ADR Dependencies
| Field | Value |
|-------|-------|
| **Depends On** | ADR-0000 (Accepted); ADR-0001 (Proposed) — `DrawDetector` como servicio POCO |
| **Enables** | Detección de tablas por triple repetición (sistema 8) |
| **Blocks** | None |
| **Ordering Note** | None |
## Context
### Problem Statement
La detección de tablas requiere reconocer cuando la misma posición ocurre 3 veces. Sin un
hash canónico de posición, comparar tableros completos es lento y propenso a error.
### Current State
Hash de posición tipo FEN-string implementado en `DrawDetector`. El FEN codifica: piezas por
fila, turno, derechos de enroque, en passant y contadores.
### Constraints
- El hash debe capturar los elementos que definen una posición legal (no solo las piezas).
- Suficiente para el volumen de partidas del juego (partidas de 30-60 min, no engine de alto
rendimiento).
### Requirements
- Identificar posiciones idénticas para repetición triple.
- Coste de computación aceptable por movimiento.
## Decision
### Architecture
```
DrawDetector (POCO, ADR-0001)
└─ FENHash(board) → string canónico (piezas + turno + enroque + en passant)
└─ Diccionario<FENHash, int> (conteo por posición)
└─ ≥3 → repetición triple (tablas)
```
### Key Interfaces
```
public static string FENHash(BoardState board); // hash canónico FEN-like
// DrawDetector mantiene un conteo por hash para repetición triple
```
### Implementation Guidelines
- El hash FEN debe incluir: disposición de piezas, turno, derechos de enroque, objetivo en
passant (no los contadores de 50-mov, que se rastrean aparte).
- Generar el hash solo al mover (no por frame).
- La comparación de posiciones se hace por igualdad de string (cultura invariante).
## Alternatives Considered
### Alternative 1: Zobrist hashing
- **Description**: tabla de números aleatorios por pieza/casilla, XOR combinado.
- **Pros**: O(1) incremental al mover; estándar en engines.
- **Cons**: colisiones teóricas (mitigadas con verificación); overkill para el volumen del juego.
- **Estimated Effort**: mayor.
- **Rejection Reason**: el volumen de partidas no justifica la complejidad; el FEN-string es
suficiente y más simple de depurar.
### Alternative 2: Comparación de tableros completos
- **Description**: comparar el array del tablero en cada movimiento contra el historial.
- **Pros**: sin hashing.
- **Cons**: O(n) por comparación, n piezas; histórico creciente.
- **Estimated Effort**: menor.
- **Rejection Reason**: lento y verboso frente al hash canónico.
## Consequences
### Positive
- Simplicidad y depurabilidad (el FEN es legible por humanos).
- Suficiente para el volumen de partidas del juego.
### Negative
- No incremental: regenerar el string completo por movimiento (O(n) con n piezas, coste
bajo para 32 piezas).
- No óptimo para rendimiento extremo (irrelevante aquí).
### Neutral
- La repetición se rastrea en `DrawDetector` con conteo por hash.
## Risks
| Risk | Probability | Impact | Mitigation |
|------|------------|--------|-----------|
| Colisión entre dos posiciones distintas con el mismo FEN | Muy baja | Baja | FEN canónico incluye turno/enroque/en passant; colisión requeriría estados idénticos |
| FEN sin normalizar (espacios/orden) produce falsos negativos | Baja | Media | Normalizar la generación (mismo orden de filas, formato único) y testearla |
## Performance Implications
Regenerar el FEN por movimiento: O(64) operaciones de cadena por movimiento — despreciable en
partidas de 30-60 min (~40-80 movimientos/partida). Sin impacto en frame time.
## Migration Plan
Implementado (Sprint 2) en `DrawDetector`. Sin migración pendiente; añadir test de
normalización del hash si no existe.
**Rollback plan**: N/A — servicio POCO; revertir por git.
## Validation Criteria
- [ ] La repetición triple se detecta con las tablas (posiciones idénticas 3 veces).
- [ ] Posiciones con mismas piezas pero distinto turno/enroque/en-passant NO cuentan como
repetición.
- [ ] El hash FEN es estable entre llamadas (normalizado).
## GDD Requirements Addressed
| GDD Document | System | Requirement | How This ADR Satisfies It |
|-------------|--------|-------------|--------------------------|
| `design/gdd/systems-index.md` | 8 — Draw Detection | Detección de tablas (incluye repetición triple) | FEN-hash + conteo en `DrawDetector` |
## Related
- ADR-0001 — `DrawDetector` como servicio POCO.
- Código: `Assets/Game/Scripts/Mono/Core/Services/DrawDetector.cs`.
@@ -0,0 +1,199 @@
# ADR-0009: Consolidación del SceneTransitionManager
## Status
Accepted
## Date
2026-08-16
## Last Verified
2026-08-16
## Decision Makers
Productor técnico, agentes de arquitectura e infraestructura (Claude Code Game Studios).
## Summary
Existen dos implementaciones de `SceneTransitionManager` con la misma responsabilidad (transiciones de escena con fade): una en `Core/` (estilo Awake-explicit, API `LoadScene`) y otra en `UI/` (lazy-getter, API `TransitionToScene`/`FadeOut`/`FadeIn`, sin consumidores en runtime). Se decide consolidar en una única clase: la versión `Core/` como canónica, eliminando la duplicada de `UI/` y actualizando la escena `Chapter1.unity` y el editor `ProjectSetup.cs`.
## Engine Compatibility
| Field | Value |
|-------|-------|
| **Engine** | Unity 6 (6000.3.13f1 — Unity 6.3 LTS) |
| **Domain** | Core / UI / Scripting |
| **Knowledge Risk** | HIGH — post-cutoff, verificado contra la documentación del motor |
| **References Consulted** | `docs/engine-reference/unity/VERSION.md`, `docs/engine-reference/unity/urp/deprecated-apis.md` |
| **Post-Cutoff APIs Used** | Ninguna nueva. `SceneManager.LoadSceneAsync`, `DontDestroyOnLoad`, `CanvasGroup`, `AnimationCurve` ya usados en la versión Core. |
| **Verification Required** | Transición MainMenu → Chapter1 con fade en runtime; guard de duplicado ante re-carga de escena. |
> **Note**: Knowledge Risk HIGH — revalidar si el proyecto actualiza de versión de motor.
## ADR Dependencies
| Field | Value |
|-------|-------|
| **Depends On** | ADR-0000 (estancia 1: singleton Awake-explicit; estancia 5: un-manager-por-responsabilidad), ADR-0004 (estilo singleton canónico) |
| **Enables** | Epics de infraestructura; eliminación de la deuda registrada en `architecture.yaml` (`duplicate_manager_per_responsibility`) |
| **Blocks** | Ninguno |
| **Ordering Note** | Debe implementarse junto con el borrado del duplicado de `UI/`; `ProjectSetup.cs` deja de compilar si se elimina la clase antes de actualizar sus referencias. |
## Context
### Problem Statement
La misma responsabilidad —transiciones entre escenas con fade— está implementada dos veces:
- `Assets/Game/Scripts/Mono/Core/SceneTransitionManager.cs` — clase global, singleton Awake-explicit (`Instance` asignado en `Awake` + `DontDestroyOnLoad` + guard de duplicado), API `LoadScene(string, Action)`, auto-crea `FadeCanvas`/`FadeImage` en runtime si no están referenciados. Usada por `GameStateManager` y presente en `MainMenu.unity` (cableada con `FadeCanvas` + `CanvasGroup` + `FadeImage`).
- `Assets/Game/Scripts/Mono/UI/SceneTransitionManager.cs` — clase en namespace `AjedrezPurgatorio.UI`, singleton lazy-getter (auto-creación de GameObject en el getter, patrón prohibido por ADR-0000), API `TransitionToScene`/`FadeOut`/`FadeIn`, crea su propio canvas con `sortingOrder` 9999. Sin ningún consumidor de su API en código de runtime.
El duplicado no solo incumple la estancia 5 de ADR-0000, sino que provoca desincronización: `FixErrors.cs` busca la clase global y no la encuentra en `Chapter1` (que instancia la versión `UI/`), por lo que la corrección de fade falla silenciosamente en esa escena. Además ambas pueden coexistir en escena → dobles transiciones y guard de duplicado con comportamiento distinto.
### Current State
| Escena | Implementación | Fade |
|--------|---------------|------|
| `MainMenu.unity` | `Core/SceneTransitionManager.cs` (guid `e1e89932918bf4f45b1b8b65506d6dd9`) | ✅ `FadeCanvas` + `CanvasGroup` + `FadeImage` cableados |
| `Chapter1.unity` | `AjedrezPurgatorio.UI.SceneTransitionManager` (guid `42a7f6937327de9469fc73fc1f0cae64`) | ⚠️ sin referencias de fade, API sin consumidores |
### Constraints
- `GameStateManager.cs:73` invoca `SceneTransitionManager.Instance.LoadScene(sceneName)` → la API canónica debe conservar `LoadScene`.
- `FixErrors.cs` y `ProjectSetup.cs` referencian las clases por tipo; `ProjectSetup.cs` usa la forma completamente cualificada `AjedrezPurgatorio.UI.SceneTransitionManager` y dejaría de compilar al eliminar la clase → debe actualizarse en el mismo changeset.
- `Chapter1.unity` referencia el MonoBehaviour por GUID de script → debe actualizarse el GUID en el mismo changeset.
- El fade es obligatorio en todas las transiciones (GDD S1 Scene Management) → la versión canónica debe auto-corregirse si falta el FadeCanvas.
### Requirements
- Un único `SceneTransitionManager` como dueño de las transiciones entre escenas.
- La clase canónica debe seguir el estilo Awake-explicit de ADR-0000/0004.
- La API pública debe cubrir al menos `LoadScene(string, Action)` (consumidor actual: `GameStateManager`).
- Transición de `Chapter1.unity` sin regenerar la escena ni perder los GameObjects existentes.
- El editor (`ProjectSetup.cs`, `FixErrors.cs`) debe apuntar a la clase canónica.
## Decision
Se adopta como única implementación la versión `Core/SceneTransitionManager.cs` (clase global, singleton Awake-explicit, auto-creación del fade). Se elimina `Assets/Game/Scripts/Mono/UI/SceneTransitionManager.cs` y su `.meta`. La API `TransitionToScene`/`FadeOut`/`FadeIn` de la versión eliminada no se re-implementa: no tiene consumidores en runtime y `FadeOut`/`FadeIn` están cubiertos internamente por la corrutina de transición.
### Architecture
```
┌─────────────────────────────┐
GameStateManager │ SceneTransitionManager │ (clase global única)
── LoadScene ──► │ ┌───────────────────────┐ │
│ │ Awake: Instance = this │ │── DontDestroyOnLoad
│ │ guard duplicado │ │
│ └───────────────────────┘ │
│ LoadScene(name, onComplete)│──► SceneManager.LoadSceneAsync
│ FadeRoutine(α 0→1→0) │──► CanvasGroup / FadeImage
└─────────────────────────────┘
│ auto-crea
FadeCanvas + CanvasGroup + FadeImage
(hijos del mismo GameObject, sortingOrder 999)
```
### Key Interfaces
```csharp
public static SceneTransitionManager Instance { get; private set; } // Awake-explicit
public void LoadScene(string sceneName, Action onComplete = null)
// Guard: rechaza si _isTransitioning.
// Fade out (α 0→1) → LoadSceneAsync → pausa 0.1s → fade in (α 1→0) → onComplete.
```
### Implementation Guidelines
- **No se modifica** la clase `Core/SceneTransitionManager.cs` salvo lo ya aprobado; es la fuente canónica.
- En `Chapter1.unity`, el MonoBehaviour existente (fileID `365129010`, GameObject `365129009`) se re-apunta al script de Core (guid `e1e89932918bf4f45b1b8b65506d6dd9`) con `_fadeDuration: 0.8` y las curvas EaseInOut serializadas; `_fadeCanvasGroup` y `_fadeImage` quedan en null (la clase auto-crea el `FadeCanvas` en `InitializeFadeCanvas`).
- En `ProjectSetup.cs`, sustituir las 3 referencias `AjedrezPurgatorio.UI.SceneTransitionManager` por la clase global `SceneTransitionManager`.
- Eliminar `Assets/Game/Scripts/Mono/UI/SceneTransitionManager.cs` **y** su `.meta` (guid `42a7f6937327de9469fc73fc1f0cae64`) para no dejar huérfano el GUID en el proyecto.
## Alternatives Considered
### Alternative 1: Mantener la versión `UI/` y eliminar la de `Core/`
- **Description**: Invertir el sentido: mantener la clase namespaced con su API `TransitionToScene`/`FadeOut`/`FadeIn` y migrar `GameStateManager`/`MainMenu` a ella.
- **Pros**: Su API separa fade de carga; `sortingOrder` superior.
- **Cons**: Usa el lazy-getter prohibido por ADR-0000 (requeriría reescribirla igualmente); `MainMenu.unity` y `FixErrors.cs` ya dependen de la clase global; su API no tiene consumidores. Más migración con menos beneficio.
- **Estimated Effort**: Mayor que la decisión elegida.
- **Rejection Reason**: Contradice ADR-0000 y supone re-escribir + migrar más código para un API sin uso.
### Alternative 2: Fusionar ambas en una tercera clase nueva
- **Description**: Crear una clase nueva que combine la API `LoadScene` + `FadeOut`/`FadeIn`.
- **Pros**: API completa en un solo lugar.
- **Cons**: Nuevo GUID de script → re-cablear `MainMenu` y `Chapter1` (y romper `FixErrors` que ya referencia el GUID global); añade superficie sin necesidad.
- **Estimated Effort**: Mayor (re-cableado de escenas + editor).
- **Rejection Reason**: La API extra no se usa; renunciar a ello evita el re-cableado completo y mantiene los GUIDs existentes.
## Consequences
### Positive
- Una única implementación de transiciones (cumple estancia 5 de ADR-0000).
- `FixErrors.cs` ahora encuentra el STM en `Chapter1` → la corrección de fade funciona en ambas escenas.
- Se elimina el único patrón lazy-getter pendiente de `SceneTransitionManager` (ADR-0004).
- `ProjectSetup.cs` y `Chapter1.unity` quedan coherentes con el estilo canónico.
### Negative
- Se pierde la API `TransitionToScene`/`FadeOut`/`FadeIn` (sin uso actual; se recupera en el futuro si un consumidor lo requiere).
- El fade en `Chapter1` no estará pre-cableado en el editor hasta que se ejecute `FixErrors` o se auto-cree en runtime (primer frame).
### Neutral
- `MainMenu.unity` no cambia (ya usaba la versión Core).
- El `sortingOrder` del fade pasa a ser 999 (Core) en lugar de 9999 (UI); suficiente por estar en el top de todas las capas de UI.
## Risks
| Risk | Probability | Impact | Mitigation |
|------|------------|--------|-----------|
| `ProjectSetup.cs` no compila tras eliminar la clase UI | Baja | Alto | Actualizar las 3 referencias en el mismo changeset; verificación de compilación batch |
| `Chapter1` sin fade visible (referencias null) | Media | Medio | El Core auto-crea `FadeCanvas` en runtime; `FixErrors` lo pre-cablea en editor |
| Guard de duplicado distinto si quedara una copia en otra escena | Baja | Medio | Borrado físico del `.meta` (guid huérfano desaparece); verificación con grep de GUID |
## Performance Implications
| Metric | Before | Expected After | Budget |
|--------|--------|---------------|--------|
| CPU (frame time) | 2 managers en escena (solo 1 en cada escena actual) | 1 manager, sin cambio funcional | Sin cambio |
| Memory | 2 clases + 2 posibles canvases | 1 clase, 1 canvas | Sin cambio |
| Load Time | Sin cambio | Sin cambio | Sin cambio |
## Migration Plan
1. **Escribir ADR-0009** (este documento) y actualizar `architecture.yaml` (deuda `duplicate_manager_per_responsibility` resuelta) y `architecture.md` (estado del STM + follow-up). Verificar: lectura de los archivos.
2. **Editar `Chapter1.unity`**: re-apuntar el MonoBehaviour `365129010` al script Core (guid `e1e89932...`) con campos serializados correctos. Verificar: grep de GUID UI en el proyecto = 0 resultados.
3. **Editar `ProjectSetup.cs`**: `AjedrezPurgatorio.UI.SceneTransitionManager``SceneTransitionManager` (líneas 487, 489, 605). Verificar: grep de `AjedrezPurgatorio.UI.SceneTransitionManager` = 0 resultados.
4. **Eliminar** `Assets/Game/Scripts/Mono/UI/SceneTransitionManager.cs` y `.meta`. Verificar: el archivo no existe y no quedan GUID `42a7f693...` en el repo.
5. **Compilar** en batch (`-batchmode -quit -nographics`), esperando exit 0 sin errores CS.
**Rollback plan**: `git checkout --` de los archivos modificados restaura la versión UI en `Chapter1` y `ProjectSetup.cs`; recuperar el archivo eliminado desde git (no se ha borrado si se commit ancla el estado previo).
## Validation Criteria
- [x] Un único `SceneTransitionManager` (clase global Core) en el proyecto; grep de `AjedrezPurgatorio.UI.SceneTransitionManager` = 0.
- [x] `Chapter1.unity` referencia el script Core (guid `e1e89932...`); grep del GUID UI en `Assets/` = 0.
- [x] Compilación batch Unity con exit 0 y sin `error CS`.
- [ ] Playtest: transición MainMenu → Chapter1 con fade visible (α negro 0→1→0) y sin dobles transiciones.
- [ ] Playtest: re-cargar escena no crea un segundo `SceneTransitionManager` (guard de duplicado).
## GDD Requirements Addressed
| GDD Document | System | Requirement | How This ADR Satisfies It |
|-------------|--------|-------------|--------------------------|
| `design/gdd/systems-index.md` | 15 — Scene Management | Transiciones entre escenas (Sprint 1: SceneTransitionManager) | Una única implementación canónica garantiza fades consistentes en todas las transiciones y elimina la desincronización del duplicado |
| `design/gdd/systems-index.md` | 16 — Game State Manager | Coordinación de estados con transición de escena | `GameStateManager` mantiene su llamada `LoadScene` sin cambios |
## Related
- Depende de: [ADR-0000 — Patrones de Infraestructura Global](adr-0000-patrones-infraestructura-global.md) (estancias 1 y 5)
- Relacionado: [ADR-0004 — Singleton dual](adr-0004-singleton-dual.md) (estilo canónico Awake-explicit)
- Código: `Assets/Game/Scripts/Mono/Core/SceneTransitionManager.cs` (canónico, se conserva), `Assets/Scenes/Chapter1.unity`, `Assets/Editor/ProjectSetup.cs`
+23 -20
View File
@@ -87,9 +87,13 @@ Assets/Game/Scripts/
## 4. Patrones Dominantes
> **Promovidos a ADRs formales** (2026-08-16): cada mini-ADR de esta sección tiene su
> archivo formal en `docs/architecture/adr-0000..0008.md` (Status `Proposed`, pendientes de
> aceptación). Los mini-ADRs aquí son el borrador base original y se mantienen como resumen.
Cada patrón se documenta como mini-ADR con contexto, decisión y consecuencias.
### 4.1 Service Layer POCO — **ADR-001**
### 4.1 Service Layer POCO — **ADR-001** → `docs/architecture/adr-0001-service-layer-poco.md`
**Contexto**: `GameManager` concentraba 8 responsabilidades (~475 líneas) y era imposible
de testear sin instanciar una escena Unity completa.
@@ -105,7 +109,7 @@ IA y futuros sistemas (hints, variantes de reglas).
**Código**: `Assets/Game/Scripts/Mono/Core/Services/*.cs`, `GameManager.cs`
### 4.2 Strategy Pattern — IA — **ADR-002**
### 4.2 Strategy Pattern — IA — **ADR-002** → `docs/architecture/adr-0002-strategy-pattern-ia.md`
**Contexto**: 3 dificultades de IA que deben poder escalarse por capítulo.
@@ -113,11 +117,11 @@ IA y futuros sistemas (hints, variantes de reglas).
`AIStrategyMedium` (1-ply evaluation) y `AIStrategyHard` (minimax alpha-beta depth 2, timeout
5s). `AIController` selecciona la estrategia.
**Consecuencias**: Extensible a nuevas dificultades sin tocar el controlador. ⚠️ **INCOMPLETO**:
la selección de dificultad por capítulo desde `CampaignManager` está como TODO (S4-005) y
`AIController` no está instanciado en ninguna escena.
**Consecuencias**: Extensible a nuevas dificultades sin tocar el controlador. **Completado**
(2026-08-16): `AIController` instanciado en `Chapter1.unity` y dificultad por capítulo cableada
desde `CampaignManager` (ver `adr-0002-strategy-pattern-ia.md`).
### 4.3 Event-Driven — **ADR-003**
### 4.3 Event-Driven — **ADR-003** → `docs/architecture/adr-0003-event-driven.md`
**Contexto**: UI y sistemas (Purgatorio, HUD) necesitan reaccionar a eventos del juego sin
acoplamiento directo.
@@ -125,10 +129,10 @@ acoplamiento directo.
**Decisión**: Eventos `Action<T>` en `GameManager` (`OnCheckmate`, `OnDraw`, `OnPieceMoved`,
`OnTurnChanged`, `OnCheck`). `PurgatoryManager` se suscribe a capturas; la UI escucha eventos.
**Consecuencias**: Desacople UI ↔ lógica. ⚠️ Faltan `OnPieceCaptured` y `OnCheckResolved`
que `GameHUD` espera (suscripciones comentadas como TODO).
**Consecuencias**: Desacople UI ↔ lógica. **Completado** (2026-08-16): añadidos
`OnPieceCaptured` y `OnCheckResolved`; `GameHUD` suscrito (ver `adr-0003-event-driven.md`).
### 4.4 Singleton dual — **ADR-004**
### 4.4 Singleton dual — **ADR-004** → `docs/architecture/adr-0004-singleton-dual.md`
**Contexto**: Managers globales accesibles desde cualquier script.
@@ -139,7 +143,7 @@ que `GameHUD` espera (suscripciones comentadas como TODO).
**Consecuencias**: Acceso global simple. El patrón singleton añade acoplamiento implícito,
aceptado para el alcance del proyecto.
### 4.5 ScriptableObject como data container — **ADR-005**
### 4.5 ScriptableObject como data container — **ADR-005** → `docs/architecture/adr-0005-scriptableobject-data-container.md`
**Contexto**: Datos de diseño (identidades de 16-20 piezas, tablas de posición IA, balance)
que deben editarse sin recompilar.
@@ -150,7 +154,7 @@ ScriptableObjects; `CampaignState` y `DeadKingPool` cargados desde `Resources/`.
**Consecuencias**: Data-driven y editable en editor. La serialización de `CampaignState`
requiere capa JSON propia para Save/Load (limitación de `JsonUtility` con objetos anidados).
### 4.6 Overlay UI en lugar de escenas separadas — **ADR-006**
### 4.6 Overlay UI en lugar de escenas separadas — **ADR-006** → `docs/architecture/adr-0006-overlay-ui.md`
**Contexto**: El Purgatorio debe interrumpir la partida sin perder el contexto del tablero.
@@ -159,7 +163,7 @@ la escena de juego, no escenas separadas.
**Consecuencias**: Mejor UX y menor overhead de carga. Requiere pausar/resumir gameplay.
### 4.7 `Time.timeScale` para pausas — **ADR-007**
### 4.7 `Time.timeScale` para pausas — **ADR-007** → `docs/architecture/adr-0007-timescale-pausas.md`
**Contexto**: Promoción y Purgatorio necesitan pausar el juego.
@@ -168,7 +172,7 @@ la escena de juego, no escenas separadas.
**Consecuencias**: Simple y sin estado global adicional. Riesgo: cualquier coroutine con
`WaitForSeconds` normal se congela.
### 4.8 Position hashing FEN-like — **ADR-008**
### 4.8 Position hashing FEN-like — **ADR-008** → `docs/architecture/adr-0008-position-hashing-fen-like.md`
**Contexto**: Detección de repetición triple.
@@ -242,14 +246,13 @@ Nueva partida → DeadKingPool.ShouldSpawnDeadKing(chapter) → Rey enemigo como
| DialogueSystem | ✅ `_campaignState` + 6 TextAssets |
| PieceIdentityManager | ✅ `_campaignState` |
| CampaignManager | ✅ |
| SceneTransitionManager (UI) | ⚠️ sin referencias de fade (versión duplicada) |
| AIController | ✅ instanciado (ADR-0002); dificultad aplicada por `CampaignManager` |
| GameHUD | ⚠️ `_pauseButton`, `_capturedPieceIconPrefab` null |
| DialogueUI | ⚠️ `_speakerPortrait`, `_speakerDatabase`, `_advanceDialogueAction` null |
| PauseMenuController | ⚠️ `_canvasGroup` null |
### Ausentes de escena (scripts existen)
-`GameStateManager` — previsto, pendiente de cablear
-`AIController` — IA no juega hasta instanciarlo
-`DeadKingInputUI` / `DeadKingRevealUI`
- ❌ Chapter2/3 scenes (no existen)
@@ -260,9 +263,8 @@ MainMenu + Chapter1 habilitadas. `SampleScene.unity` huérfana (no referenciada)
## 7. Problemas Conocidos
1. **SceneTransitionManager duplicado**`Core/SceneTransitionManager.cs` (usado en MainMenu)
y `UI/SceneTransitionManager.cs` (usado en Chapter1, sin fade). **Decisión: consolidar**
en una sola clase; `FixErrors.cs` asume campos que solo existen en la versión Core.
1. ~~**SceneTransitionManager duplicado**~~ — ✅ resuelto por ADR-0009: única clase Core,
`UI/SceneTransitionManager.cs` eliminada, `Chapter1.unity` y `ProjectSetup.cs` re-apuntados.
2. **`ProjectSetup.cs` no compila** — referencia `CameraFollow`, clase inexistente en el repo.
3. **Objetos basura en Chapter1** — GameObject suelto con SquareClick fuera del tablero
(-43.98, -0.72); GameManager/board en posiciones extrañas (34, 30); BoardParent vacío.
@@ -300,7 +302,7 @@ MainMenu + Chapter1 habilitadas. `SampleScene.unity` huérfana (no referenciada)
## 10. Follow-Up Work
**Inmediato**:
- [ ] Consolidar SceneTransitionManager (eliminar duplicado UI/)
- [x] Consolidar SceneTransitionManager (eliminar duplicado UI/) — ADR-0009
- [ ] Reparar `ProjectSetup.cs` (CameraFollow inexistente)
- [ ] Commit del trabajo sin commitear (escenas, prefabs, editor tools, assets movidos)
- [ ] Actualizar `PROJECT_STATUS.md`/`roadmap.md` (estado real: escenas ya existen)
@@ -311,7 +313,8 @@ MainMenu + Chapter1 habilitadas. `SampleScene.unity` huérfana (no referenciada)
- [ ] Crear Chapter2/3 y playtest completo documentado
**Largo plazo**:
- [ ] Generar ADRs formales (ADR-001..008 aquí son el borrador base)
- [x] Generar ADRs formales (ADR-001..008 promovidos a `docs/architecture/adr-0000..0008.md`, Status `Proposed`)
- [ ] Aceptar los ADRs formales y registrarlos en `docs/registry/architecture.yaml`
- [ ] Llenar `docs/architecture/tr-registry.yaml` con TR-IDs por sistema
- [ ] Control Manifest a partir de ADRs aceptados
+89 -4
View File
@@ -30,7 +30,7 @@
# Superseded entries: Grep pattern="status: superseded"
version: 1
last_updated: ""
last_updated: "2026-08-16"
# ─── STATE OWNERSHIP ─────────────────────────────────────────────────────────
# Who is the authoritative owner of each piece of shared game state.
@@ -71,7 +71,20 @@ state_ownership: []
#
# pattern options: signal | direct_call | event_bus | shared_resource | rpc | none
interfaces: []
interfaces:
- contract: cross_system_communication
status: active
pattern: signal # eventos C# (public static event Action<T>)
producer: all-systems
consumers:
- all-systems
adr: docs/architecture/adr-0000-patrones-infraestructura-global.md
signal_signature: "eventos C# con prefijo On, p.ej. OnPieceCaptured(T data); desuscripción en OnDestroy"
referenced_by:
- docs/architecture/adr-0000-patrones-infraestructura-global.md
- docs/architecture/adr-0003-event-driven.md
added: 2026-08-16
revised: ""
# Example:
#
@@ -131,7 +144,63 @@ performance_budgets: []
# Register an API decision when the choice is non-obvious and other ADRs might
# make a different choice for the same purpose without knowing this was decided.
api_decisions: []
api_decisions:
- purpose: cross_scene_manager_access
status: active
api: "MonoBehaviour singleton Awake-explicit: public static X Instance { get; private set; } asignado en Awake + DontDestroyOnLoad(gameObject) + guard de duplicado"
not: "lazy-getter con auto-creación de GameObject (if (_instance == null) en el getter)"
adr: docs/architecture/adr-0000-patrones-infraestructura-global.md
reason: "Estilo mayoritario (7 managers), predecible con el ciclo de vida de Unity; el lazy-getter oculta el ciclo de vida y fomenta ámbito global implícito."
referenced_by:
- docs/architecture/adr-0000-patrones-infraestructura-global.md
added: 2026-08-16
revised: ""
- purpose: save_load_serialization
status: active
api: "JsonUtility + ficheros JSON en persistentDataPath; ScriptableObject de estado runtime como fuente de verdad en memoria"
not: "BinaryFormatter ni PlayerPrefs para datos estructurados"
adr: docs/architecture/adr-0000-patrones-infraestructura-global.md
reason: "Serialización simple, portable y depurable; BinaryFormatter deprecado/inseguro; PlayerPrefs inadecuado para datos estructurados de campaña."
referenced_by:
- docs/architecture/adr-0000-patrones-infraestructura-global.md
added: 2026-08-16
revised: ""
- purpose: business_logic_implementation
status: active
api: "Servicios POCO sin dependencia de UnityEngine (p. ej. CheckDetector, DrawDetector, MoveValidator), inyectados por DI en los MonoBehaviour"
not: "lógica de negocio inline en MonoBehaviour ni utilidades estáticas con estado global"
adr: docs/architecture/adr-0001-service-layer-poco.md
reason: "Testabilidad sin escena Unity; reutilización por IA y futuros sistemas."
referenced_by:
- docs/architecture/adr-0001-service-layer-poco.md
- docs/architecture/adr-0008-position-hashing-fen-like.md
added: 2026-08-16
revised: ""
- purpose: pause_mechanism
status: active
api: "Time.timeScale (0/1) para pausas globales; coroutines de UI con WaitForSecondsRealtime"
not: "flag manual de pausa en Update ni SetActive(gameplay) para pausar"
adr: docs/architecture/adr-0007-timescale-pausas.md
reason: "Pausa global simple que detiene física/animaciones/coroutines WaitForSeconds sin estado adicional."
referenced_by:
- docs/architecture/adr-0006-overlay-ui.md
- docs/architecture/adr-0007-timescale-pausas.md
added: 2026-08-16
revised: ""
- purpose: interrupt_flow_ui
status: active
api: "Canvas overlay dentro de la escena de juego para flujos que interrumpen (Purgatorio: oferta→dados→resultado)"
not: "cargar escenas separadas para flujos que deben preservar el contexto del tablero"
adr: docs/architecture/adr-0006-overlay-ui.md
reason: "Preserva el contexto del tablero y el estado de partida en memoria; mejor UX y menor overhead de carga."
referenced_by:
- docs/architecture/adr-0006-overlay-ui.md
added: 2026-08-16
revised: ""
# Example:
#
@@ -157,7 +226,23 @@ api_decisions: []
# Register a pattern when an ADR explicitly bans it AND other ADRs might
# unknowingly use it (i.e., it's a tempting but wrong approach for this project).
forbidden_patterns: []
forbidden_patterns:
- pattern: duplicate_manager_per_responsibility
status: active
description: "Dos managers no pueden tener la misma responsabilidad. Deuda resuelta: SceneTransitionManager duplicado en Core/ y UI/ consolidado en ADR-0009 (única clase Core, UI/ eliminada)."
why: "Duplicación de responsabilidad provoca desincronización de estado, dobles transiciones y bugs difíciles de rastrear."
adr: docs/architecture/adr-0000-patrones-infraestructura-global.md
referenced_by:
- docs/architecture/adr-0009-consolidacion-scenetransitionmanager.md
added: 2026-08-16
revised: "2026-08-16"
- pattern: lazy_getter_singleton
status: active
description: "Los singleton cross-escena deben seguir el estilo Awake-explicit canónico; prohibido el getter con auto-creación de GameObject."
why: "El lazy-getter oculta el ciclo de vida del manager, fomenta ámbito global implícito y dificulta el testing."
adr: docs/architecture/adr-0000-patrones-infraestructura-global.md
added: 2026-08-16
# Example:
#