diff --git a/.gitignore b/.gitignore
index c45c9ef..2dbf80a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -105,3 +105,6 @@ TestResults*.xml
# Coplay AI assistant (resolved via manifest git URL)
/Packages/Coplay/
/Packages/com.coplaydev.coplay/
+
+# Unity test runner output folder
+/TestResults/
diff --git a/Assets/Editor/SpeakerDatabaseSetup.cs b/Assets/Editor/SpeakerDatabaseSetup.cs
index 27f627e..e599ddf 100644
--- a/Assets/Editor/SpeakerDatabaseSetup.cs
+++ b/Assets/Editor/SpeakerDatabaseSetup.cs
@@ -13,8 +13,8 @@ namespace AjedrezPurgatorio.EditorTools
/// Unity -batchmode -projectPath . -executeMethod
/// AjedrezPurgatorio.EditorTools.SpeakerDatabaseSetup.PopulateAndAssign
///
- /// Nota: 'muerte' y 'ricardo' no tienen PNG de retrato todavía; se registran
- /// con portrait nulo para evitar warnings y facilitar añadir arte después.
+ /// Nota: 'ricardo' no tiene PNG de retrato todavía; se registra con
+ /// portrait nulo para evitar warnings y facilitar añadir arte después.
///
public static class SpeakerDatabaseSetup
{
@@ -24,7 +24,7 @@ namespace AjedrezPurgatorio.EditorTools
private static readonly (string speaker, string portrait)[] Entries =
{
("narrator", "narrator"),
- ("muerte", null),
+ ("muerte", "muerte"),
("ricardo", null),
("alfil", "alfil"),
("caballo", "caballo"),
diff --git a/Assets/Game/Audio/Music.meta b/Assets/Game/Audio/Music.meta
deleted file mode 100644
index 1328dc3..0000000
--- a/Assets/Game/Audio/Music.meta
+++ /dev/null
@@ -1,8 +0,0 @@
-fileFormatVersion: 2
-guid: be210525ade96b143bbf7a4b90d5e258
-folderAsset: yes
-DefaultImporter:
- externalObjects: {}
- userData:
- assetBundleName:
- assetBundleVariant:
diff --git a/Assets/Game/Audio/SFX/capture.wav b/Assets/Game/Audio/SFX/capture.wav
deleted file mode 100644
index e406091..0000000
Binary files a/Assets/Game/Audio/SFX/capture.wav and /dev/null differ
diff --git a/Assets/Game/Audio/SFX/capture.wav.meta b/Assets/Game/Audio/SFX/capture.wav.meta
deleted file mode 100644
index 94cbd25..0000000
--- a/Assets/Game/Audio/SFX/capture.wav.meta
+++ /dev/null
@@ -1,23 +0,0 @@
-fileFormatVersion: 2
-guid: bbee09e29e03488a87056057de3333d3
-AudioImporter:
- externalObjects: {{}}
- serializedVersion: 7
- defaultSettings:
- serializedVersion: 2
- loadType: 0
- sampleRateSetting: 0
- normalizedSampleRate: 44100
- compressionFormat: 1
- quality: 1
- conversionMode: 0
- platformSettingOverrides: {{}}
- forceToMono: 0
- normalize: 1
- preloadAudioData: 1
- loadInBackground: 0
- ambisonic: 0
- 3D: 1
- userData:
- assetBundleName:
- assetBundleVariant:
diff --git a/Assets/Game/Audio/SFX/check.wav b/Assets/Game/Audio/SFX/check.wav
deleted file mode 100644
index 9cf90ae..0000000
Binary files a/Assets/Game/Audio/SFX/check.wav and /dev/null differ
diff --git a/Assets/Game/Audio/SFX/check.wav.meta b/Assets/Game/Audio/SFX/check.wav.meta
deleted file mode 100644
index 0726fb9..0000000
--- a/Assets/Game/Audio/SFX/check.wav.meta
+++ /dev/null
@@ -1,23 +0,0 @@
-fileFormatVersion: 2
-guid: 39c30d32bdb442ee844d9c2204244d96
-AudioImporter:
- externalObjects: {{}}
- serializedVersion: 7
- defaultSettings:
- serializedVersion: 2
- loadType: 0
- sampleRateSetting: 0
- normalizedSampleRate: 44100
- compressionFormat: 1
- quality: 1
- conversionMode: 0
- platformSettingOverrides: {{}}
- forceToMono: 0
- normalize: 1
- preloadAudioData: 1
- loadInBackground: 0
- ambisonic: 0
- 3D: 1
- userData:
- assetBundleName:
- assetBundleVariant:
diff --git a/Assets/Game/Audio/SFX/checkmate.wav b/Assets/Game/Audio/SFX/checkmate.wav
deleted file mode 100644
index 9701c60..0000000
Binary files a/Assets/Game/Audio/SFX/checkmate.wav and /dev/null differ
diff --git a/Assets/Game/Audio/SFX/checkmate.wav.meta b/Assets/Game/Audio/SFX/checkmate.wav.meta
deleted file mode 100644
index f1dec66..0000000
--- a/Assets/Game/Audio/SFX/checkmate.wav.meta
+++ /dev/null
@@ -1,23 +0,0 @@
-fileFormatVersion: 2
-guid: 4d305733468c4b7199722466abeeace9
-AudioImporter:
- externalObjects: {{}}
- serializedVersion: 7
- defaultSettings:
- serializedVersion: 2
- loadType: 0
- sampleRateSetting: 0
- normalizedSampleRate: 44100
- compressionFormat: 1
- quality: 1
- conversionMode: 0
- platformSettingOverrides: {{}}
- forceToMono: 0
- normalize: 1
- preloadAudioData: 1
- loadInBackground: 0
- ambisonic: 0
- 3D: 1
- userData:
- assetBundleName:
- assetBundleVariant:
diff --git a/Assets/Game/Audio/SFX/invalid.wav b/Assets/Game/Audio/SFX/invalid.wav
deleted file mode 100644
index eeed3db..0000000
Binary files a/Assets/Game/Audio/SFX/invalid.wav and /dev/null differ
diff --git a/Assets/Game/Audio/SFX/invalid.wav.meta b/Assets/Game/Audio/SFX/invalid.wav.meta
deleted file mode 100644
index fe0b6ba..0000000
--- a/Assets/Game/Audio/SFX/invalid.wav.meta
+++ /dev/null
@@ -1,23 +0,0 @@
-fileFormatVersion: 2
-guid: 146caa30b3a549eb8fa8d1fbd296635d
-AudioImporter:
- externalObjects: {{}}
- serializedVersion: 7
- defaultSettings:
- serializedVersion: 2
- loadType: 0
- sampleRateSetting: 0
- normalizedSampleRate: 44100
- compressionFormat: 1
- quality: 1
- conversionMode: 0
- platformSettingOverrides: {{}}
- forceToMono: 0
- normalize: 1
- preloadAudioData: 1
- loadInBackground: 0
- ambisonic: 0
- 3D: 1
- userData:
- assetBundleName:
- assetBundleVariant:
diff --git a/Assets/Game/Audio/SFX/move.wav b/Assets/Game/Audio/SFX/move.wav
deleted file mode 100644
index e080caa..0000000
Binary files a/Assets/Game/Audio/SFX/move.wav and /dev/null differ
diff --git a/Assets/Game/Audio/SFX/move.wav.meta b/Assets/Game/Audio/SFX/move.wav.meta
deleted file mode 100644
index d279031..0000000
--- a/Assets/Game/Audio/SFX/move.wav.meta
+++ /dev/null
@@ -1,23 +0,0 @@
-fileFormatVersion: 2
-guid: bd7eca68231b48f198532c2a8f8b8ed3
-AudioImporter:
- externalObjects: {{}}
- serializedVersion: 7
- defaultSettings:
- serializedVersion: 2
- loadType: 0
- sampleRateSetting: 0
- normalizedSampleRate: 44100
- compressionFormat: 1
- quality: 1
- conversionMode: 0
- platformSettingOverrides: {{}}
- forceToMono: 0
- normalize: 1
- preloadAudioData: 1
- loadInBackground: 0
- ambisonic: 0
- 3D: 1
- userData:
- assetBundleName:
- assetBundleVariant:
diff --git a/Assets/Game/Audio/SFX/promotion.wav b/Assets/Game/Audio/SFX/promotion.wav
deleted file mode 100644
index f1a8848..0000000
Binary files a/Assets/Game/Audio/SFX/promotion.wav and /dev/null differ
diff --git a/Assets/Game/Audio/SFX/promotion.wav.meta b/Assets/Game/Audio/SFX/promotion.wav.meta
deleted file mode 100644
index bb3508a..0000000
--- a/Assets/Game/Audio/SFX/promotion.wav.meta
+++ /dev/null
@@ -1,23 +0,0 @@
-fileFormatVersion: 2
-guid: f7347cab1bf24c4a93f0594fab7b84d8
-AudioImporter:
- externalObjects: {{}}
- serializedVersion: 7
- defaultSettings:
- serializedVersion: 2
- loadType: 0
- sampleRateSetting: 0
- normalizedSampleRate: 44100
- compressionFormat: 1
- quality: 1
- conversionMode: 0
- platformSettingOverrides: {{}}
- forceToMono: 0
- normalize: 1
- preloadAudioData: 1
- loadInBackground: 0
- ambisonic: 0
- 3D: 1
- userData:
- assetBundleName:
- assetBundleVariant:
diff --git a/Assets/Game/Data/Resources/Dialogues/muerte_quotes.json b/Assets/Game/Data/Resources/Dialogues/muerte_quotes.json
new file mode 100644
index 0000000..da89fb3
--- /dev/null
+++ b/Assets/Game/Data/Resources/Dialogues/muerte_quotes.json
@@ -0,0 +1,26 @@
+{
+ "intro": [
+ "Mate perfecto... para el tablero. Aquí abajo, la partida sigue.",
+ "Llegaste al final del tablero. Bienvenido al principio del mío.",
+ "Todos los caminos del ajedrez terminan frente a mí. Tú llegaste caminando.",
+ "Tu reina no me mateó a mí. Solo despejó la mesa."
+ ],
+ "milestone": [
+ "El Purgatorio tiene hambre. Cada turno, una casilla menos.",
+ "El suelo que pisas ya fue. Apúrate.",
+ "No te apresuro. El vacío lo hace por mí.",
+ "Cada casilla que cae fue una partida más. Como la tuya."
+ ],
+ "playerWin": [
+ "...Bien jugado. El tablero es tuyo. Por ahora.",
+ "Me capturas hoy. Mañana seré tu siguiente jaque.",
+ "Toma tu victoria. Yo me quedo con el tiempo.",
+ "Nadie me había alcanzado sin perder algo primero. Vuelve pronto."
+ ],
+ "playerLost": [
+ "Te dije que nadie gana aquí. Solo algunos posponen.",
+ "Tu pieza descansa. ¿Quieres negociar por ella? Los dados no mienten.",
+ "El vacío también juega. Y siempre va conmigo.",
+ "Otra partida termina. Otra alma aprende."
+ ]
+}
diff --git a/Assets/Game/Data/Resources/Dialogues/muerte_quotes.json.meta b/Assets/Game/Data/Resources/Dialogues/muerte_quotes.json.meta
new file mode 100644
index 0000000..7860114
--- /dev/null
+++ b/Assets/Game/Data/Resources/Dialogues/muerte_quotes.json.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: f3b9c74e28d14a6b8e50a1c9d7f26b34
+TextScriptImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Game/Audio/SFX.meta b/Assets/Game/Data/Resources/Pieces.meta
similarity index 76%
rename from Assets/Game/Audio/SFX.meta
rename to Assets/Game/Data/Resources/Pieces.meta
index 07ce356..b44fb9b 100644
--- a/Assets/Game/Audio/SFX.meta
+++ b/Assets/Game/Data/Resources/Pieces.meta
@@ -1,5 +1,5 @@
fileFormatVersion: 2
-guid: 5b071f4614a3499c9929e8f8bbaa0091
+guid: 8a4d2c91b7e34f6095c1d2e8a6b3f740
folderAsset: yes
DefaultImporter:
externalObjects: {}
diff --git a/Assets/Game/Data/Resources/Pieces/la_muerte.png b/Assets/Game/Data/Resources/Pieces/la_muerte.png
new file mode 100644
index 0000000..513f5aa
Binary files /dev/null and b/Assets/Game/Data/Resources/Pieces/la_muerte.png differ
diff --git a/Assets/Game/Data/Resources/Pieces/la_muerte.png.meta b/Assets/Game/Data/Resources/Pieces/la_muerte.png.meta
new file mode 100644
index 0000000..75b6416
--- /dev/null
+++ b/Assets/Game/Data/Resources/Pieces/la_muerte.png.meta
@@ -0,0 +1,156 @@
+fileFormatVersion: 2
+guid: e1167710624d445aa3b7dea5b24b42a5
+TextureImporter:
+ internalIDToNameTable:
+ - first:
+ 213: 6587412096331147520
+ second: la_muerte_0
+ externalObjects: {}
+ serializedVersion: 13
+ mipmaps:
+ mipMapMode: 0
+ enableMipMap: 0
+ sRGBTexture: 1
+ linearTexture: 0
+ fadeOut: 0
+ borderMipMap: 0
+ mipMapsPreserveCoverage: 0
+ alphaTestReferenceValue: 0.5
+ mipMapFadeDistanceStart: 1
+ mipMapFadeDistanceEnd: 3
+ bumpmap:
+ convertToNormalMap: 0
+ externalNormalMap: 0
+ heightScale: 0.25
+ normalMapFilter: 0
+ flipGreenChannel: 0
+ isReadable: 0
+ streamingMipmaps: 0
+ streamingMipmapsPriority: 0
+ vTOnly: 0
+ ignoreMipmapLimit: 0
+ grayScaleToAlpha: 0
+ generateCubemap: 6
+ cubemapConvolution: 0
+ seamlessCubemap: 0
+ textureFormat: 1
+ maxTextureSize: 2048
+ textureSettings:
+ serializedVersion: 2
+ filterMode: 1
+ aniso: 1
+ mipBias: 0
+ wrapU: 1
+ wrapV: 1
+ wrapW: 1
+ nPOTScale: 0
+ lightmap: 0
+ compressionQuality: 50
+ spriteMode: 2
+ spriteExtrude: 1
+ spriteMeshType: 1
+ alignment: 0
+ spritePivot: {x: 0.5, y: 0.5}
+ spritePixelsToUnits: 100
+ spriteBorder: {x: 0, y: 0, z: 0, w: 0}
+ spriteGenerateFallbackPhysicsShape: 1
+ alphaUsage: 1
+ alphaIsTransparency: 1
+ spriteTessellationDetail: -1
+ textureType: 8
+ textureShape: 1
+ singleChannelComponent: 0
+ flipbookRows: 1
+ flipbookColumns: 1
+ maxTextureSizeSet: 0
+ compressionQualitySet: 0
+ textureFormatSet: 0
+ ignorePngGamma: 0
+ applyGammaDecoding: 0
+ swizzle: 50462976
+ cookieLightType: 0
+ platformSettings:
+ - serializedVersion: 4
+ buildTarget: DefaultTexturePlatform
+ maxTextureSize: 2048
+ resizeAlgorithm: 0
+ textureFormat: -1
+ textureCompression: 1
+ compressionQuality: 50
+ crunchedCompression: 0
+ allowsAlphaSplitting: 0
+ overridden: 0
+ ignorePlatformSupport: 0
+ androidETC2FallbackOverride: 0
+ forceMaximumCompressionQuality_BC6H_BC7: 0
+ - serializedVersion: 4
+ buildTarget: Standalone
+ maxTextureSize: 2048
+ resizeAlgorithm: 0
+ textureFormat: -1
+ textureCompression: 1
+ compressionQuality: 50
+ crunchedCompression: 0
+ allowsAlphaSplitting: 0
+ overridden: 0
+ ignorePlatformSupport: 0
+ androidETC2FallbackOverride: 0
+ forceMaximumCompressionQuality_BC6H_BC7: 0
+ - serializedVersion: 4
+ buildTarget: Android
+ maxTextureSize: 2048
+ resizeAlgorithm: 0
+ textureFormat: -1
+ textureCompression: 1
+ compressionQuality: 50
+ crunchedCompression: 0
+ allowsAlphaSplitting: 0
+ overridden: 0
+ ignorePlatformSupport: 0
+ androidETC2FallbackOverride: 0
+ forceMaximumCompressionQuality_BC6H_BC7: 0
+ spriteSheet:
+ serializedVersion: 2
+ sprites:
+ - serializedVersion: 2
+ name: la_muerte_0
+ rect:
+ serializedVersion: 2
+ x: 0
+ y: 0
+ width: 1024
+ height: 1024
+ alignment: 0
+ pivot: {x: 0, y: 0}
+ border: {x: 0, y: 0, z: 0, w: 0}
+ customData:
+ outline: []
+ physicsShape: []
+ tessellationDetail: -1
+ bones: []
+ spriteID: 009ede666603b6b50800000000000000
+ internalID: 6587412096331147520
+ vertices: []
+ indices:
+ edges: []
+ weights: []
+ outline: []
+ customData:
+ physicsShape: []
+ bones: []
+ spriteID:
+ internalID: 0
+ vertices: []
+ indices:
+ edges: []
+ weights: []
+ secondaryTextures: []
+ spriteCustomMetadata:
+ entries: []
+ nameFileIdTable:
+ la_muerte_0: 6587412096331147520
+ mipmapLimitGroupName:
+ pSDRemoveMatte: 0
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Game/Data/Resources/Portraits/muerte.png b/Assets/Game/Data/Resources/Portraits/muerte.png
new file mode 100644
index 0000000..d0d8d37
Binary files /dev/null and b/Assets/Game/Data/Resources/Portraits/muerte.png differ
diff --git a/Assets/Game/Data/Resources/Portraits/muerte.png.meta b/Assets/Game/Data/Resources/Portraits/muerte.png.meta
new file mode 100644
index 0000000..9b66b14
--- /dev/null
+++ b/Assets/Game/Data/Resources/Portraits/muerte.png.meta
@@ -0,0 +1,156 @@
+fileFormatVersion: 2
+guid: c1e6e27e3c8e40129f502081e37ff22d
+TextureImporter:
+ internalIDToNameTable:
+ - first:
+ 213: 7419852336102758402
+ second: muerte_0
+ externalObjects: {}
+ serializedVersion: 13
+ mipmaps:
+ mipMapMode: 0
+ enableMipMap: 0
+ sRGBTexture: 1
+ linearTexture: 0
+ fadeOut: 0
+ borderMipMap: 0
+ mipMapsPreserveCoverage: 0
+ alphaTestReferenceValue: 0.5
+ mipMapFadeDistanceStart: 1
+ mipMapFadeDistanceEnd: 3
+ bumpmap:
+ convertToNormalMap: 0
+ externalNormalMap: 0
+ heightScale: 0.25
+ normalMapFilter: 0
+ flipGreenChannel: 0
+ isReadable: 0
+ streamingMipmaps: 0
+ streamingMipmapsPriority: 0
+ vTOnly: 0
+ ignoreMipmapLimit: 0
+ grayScaleToAlpha: 0
+ generateCubemap: 6
+ cubemapConvolution: 0
+ seamlessCubemap: 0
+ textureFormat: 1
+ maxTextureSize: 2048
+ textureSettings:
+ serializedVersion: 2
+ filterMode: 1
+ aniso: 1
+ mipBias: 0
+ wrapU: 1
+ wrapV: 1
+ wrapW: 1
+ nPOTScale: 0
+ lightmap: 0
+ compressionQuality: 50
+ spriteMode: 2
+ spriteExtrude: 1
+ spriteMeshType: 1
+ alignment: 0
+ spritePivot: {x: 0.5, y: 0.5}
+ spritePixelsToUnits: 100
+ spriteBorder: {x: 0, y: 0, z: 0, w: 0}
+ spriteGenerateFallbackPhysicsShape: 1
+ alphaUsage: 1
+ alphaIsTransparency: 1
+ spriteTessellationDetail: -1
+ textureType: 8
+ textureShape: 1
+ singleChannelComponent: 0
+ flipbookRows: 1
+ flipbookColumns: 1
+ maxTextureSizeSet: 0
+ compressionQualitySet: 0
+ textureFormatSet: 0
+ ignorePngGamma: 0
+ applyGammaDecoding: 0
+ swizzle: 50462976
+ cookieLightType: 0
+ platformSettings:
+ - serializedVersion: 4
+ buildTarget: DefaultTexturePlatform
+ maxTextureSize: 2048
+ resizeAlgorithm: 0
+ textureFormat: -1
+ textureCompression: 1
+ compressionQuality: 50
+ crunchedCompression: 0
+ allowsAlphaSplitting: 0
+ overridden: 0
+ ignorePlatformSupport: 0
+ androidETC2FallbackOverride: 0
+ forceMaximumCompressionQuality_BC6H_BC7: 0
+ - serializedVersion: 4
+ buildTarget: Standalone
+ maxTextureSize: 2048
+ resizeAlgorithm: 0
+ textureFormat: -1
+ textureCompression: 1
+ compressionQuality: 50
+ crunchedCompression: 0
+ allowsAlphaSplitting: 0
+ overridden: 0
+ ignorePlatformSupport: 0
+ androidETC2FallbackOverride: 0
+ forceMaximumCompressionQuality_BC6H_BC7: 0
+ - serializedVersion: 4
+ buildTarget: Android
+ maxTextureSize: 2048
+ resizeAlgorithm: 0
+ textureFormat: -1
+ textureCompression: 1
+ compressionQuality: 50
+ crunchedCompression: 0
+ allowsAlphaSplitting: 0
+ overridden: 0
+ ignorePlatformSupport: 0
+ androidETC2FallbackOverride: 0
+ forceMaximumCompressionQuality_BC6H_BC7: 0
+ spriteSheet:
+ serializedVersion: 2
+ sprites:
+ - serializedVersion: 2
+ name: muerte_0
+ rect:
+ serializedVersion: 2
+ x: 0
+ y: 0
+ width: 256
+ height: 256
+ alignment: 0
+ pivot: {x: 0, y: 0}
+ border: {x: 0, y: 0, z: 0, w: 0}
+ customData:
+ outline: []
+ physicsShape: []
+ tessellationDetail: -1
+ bones: []
+ spriteID: c39a1f4d82b60e718000000000000000
+ internalID: 7419852336102758402
+ vertices: []
+ indices:
+ edges: []
+ weights: []
+ outline: []
+ customData:
+ physicsShape: []
+ bones: []
+ spriteID:
+ internalID: 0
+ vertices: []
+ indices:
+ edges: []
+ weights: []
+ secondaryTextures: []
+ spriteCustomMetadata:
+ entries: []
+ nameFileIdTable:
+ muerte_0: 7419852336102758402
+ mipmapLimitGroupName:
+ pSDRemoveMatte: 0
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Game/Data/Resources/SpeakerDatabase.asset b/Assets/Game/Data/Resources/SpeakerDatabase.asset
index 8f8de32..3afe358 100644
--- a/Assets/Game/Data/Resources/SpeakerDatabase.asset
+++ b/Assets/Game/Data/Resources/SpeakerDatabase.asset
@@ -16,7 +16,7 @@ MonoBehaviour:
- speakerName: narrator
portrait: {fileID: 5364273658523971450, guid: 737c4034b70db6d47978d8d288f51f59, type: 3}
- speakerName: muerte
- portrait: {fileID: 0}
+ portrait: {fileID: 7419852336102758402, guid: c1e6e27e3c8e40129f502081e37ff22d, type: 3}
- speakerName: ricardo
portrait: {fileID: 0}
- speakerName: alfil
diff --git a/Assets/Game/Scripts/Data/CampaignState.cs b/Assets/Game/Scripts/Data/CampaignState.cs
index d0c63c9..714a179 100644
--- a/Assets/Game/Scripts/Data/CampaignState.cs
+++ b/Assets/Game/Scripts/Data/CampaignState.cs
@@ -65,9 +65,9 @@ public class CampaignState : ScriptableObject
public void StartChapter(string chapterId)
{
_currentChapterPiecesLost = 0;
-
- // Inicializar piezas vivas para el capítulo
- // TODO: Poblar esto desde el board state de GameManager
+
+ // Las piezas vivas se sincronizan desde el tablero vía SyncFromBoard(),
+ // llamada por CampaignManager tras SetupBoard().
Debug.Log($"[CampaignState] Iniciando capítulo: {chapterId}");
}
diff --git a/Assets/Game/Scripts/Data/SpeakerDatabase.cs b/Assets/Game/Scripts/Data/SpeakerDatabase.cs
index 9c2708c..759319d 100644
--- a/Assets/Game/Scripts/Data/SpeakerDatabase.cs
+++ b/Assets/Game/Scripts/Data/SpeakerDatabase.cs
@@ -61,6 +61,27 @@ public class SpeakerDatabase : ScriptableObject
return null;
}
+ ///
+ /// Obtiene el portrait de un speaker sin loguear warning si falta.
+ /// Usado por sistemas runtime (PortraitRegistry) que comprueban existencia.
+ ///
+ public bool TryGetPortrait(string speakerName, out Sprite portrait)
+ {
+ portrait = null;
+
+ if (_speakerLookup == null)
+ {
+ BuildLookup();
+ }
+
+ if (string.IsNullOrEmpty(speakerName))
+ {
+ return false;
+ }
+
+ return _speakerLookup.TryGetValue(speakerName.ToLower(), out portrait) && portrait != null;
+ }
+
///
/// Verifica si existe un speaker en la base de datos.
///
diff --git a/Assets/Game/Scripts/Mono/Core/PortraitRegistry.cs b/Assets/Game/Scripts/Mono/Core/PortraitRegistry.cs
index 2f4a397..f17dc8a 100644
--- a/Assets/Game/Scripts/Mono/Core/PortraitRegistry.cs
+++ b/Assets/Game/Scripts/Mono/Core/PortraitRegistry.cs
@@ -30,6 +30,7 @@ public class PortraitRegistry : MonoBehaviour
Sprite[] sprites = Resources.LoadAll(_resourcesPortraitsFolder);
int registered = 0;
+ int filled = 0;
foreach (var sprite in sprites)
{
@@ -42,9 +43,15 @@ public class PortraitRegistry : MonoBehaviour
database.RegisterPortrait(sprite.name, sprite);
registered++;
}
+ else if (!database.TryGetPortrait(sprite.name, out Sprite current) || current == null)
+ {
+ // Speaker ya registrado en el asset pero sin arte: rellenarlo.
+ database.RegisterPortrait(sprite.name, sprite);
+ filled++;
+ }
}
_registered = true;
- Debug.Log($"[PortraitRegistry] {registered} retrato(s) registrado(s) desde Resources/{_resourcesPortraitsFolder}.");
+ Debug.Log($"[PortraitRegistry] {registered} retrato(s) registrado(s), {filled} hueco(s) rellenado(s) desde Resources/{_resourcesPortraitsFolder}.");
}
}
diff --git a/Assets/Game/Scripts/Mono/Purgatory/Duel/MuerteQuotes.cs b/Assets/Game/Scripts/Mono/Purgatory/Duel/MuerteQuotes.cs
index 500cc08..00ad7b3 100644
--- a/Assets/Game/Scripts/Mono/Purgatory/Duel/MuerteQuotes.cs
+++ b/Assets/Game/Scripts/Mono/Purgatory/Duel/MuerteQuotes.cs
@@ -1,12 +1,26 @@
-using System.Collections.Generic;
+using System;
using UnityEngine;
///
/// Frases de La Muerte durante el Duelo del Purgatorio.
/// Voz solemne según purgatorio-cosmology.md ("la Muerte", máscara formal).
+/// Los pools se cargan de Resources/Dialogues/muerte_quotes.json (data-driven
+/// según purgatorio-final.md); las entradas ausentes o un JSON faltante/inválido
+/// caen a los pools por defecto embebidos.
///
public static class MuerteQuotes
{
+ [Serializable]
+ private class QuotePools
+ {
+ public string[] intro = Array.Empty();
+ public string[] milestone = Array.Empty();
+ public string[] playerWin = Array.Empty();
+ public string[] playerLost = Array.Empty();
+ }
+
+ private const string ResourcePath = "Dialogues/muerte_quotes";
+
private static readonly string[] Intro =
{
"Mate perfecto... para el tablero. Aquí abajo, la partida sigue.",
@@ -39,15 +53,56 @@ public static class MuerteQuotes
"Otra partida termina. Otra alma aprende."
};
- public static string GetIntro() => Pick(Intro);
- public static string GetMilestone() => Pick(Milestone);
- public static string GetPlayerWin() => Pick(PlayerWin);
- public static string GetPlayerLost() => Pick(PlayerLost);
+ private static QuotePools _pools;
+ private static bool _loaded;
+
+ public static string GetIntro() => Pick(Resolve(p => p.intro, Intro));
+ public static string GetMilestone() => Pick(Resolve(p => p.milestone, Milestone));
+ public static string GetPlayerWin() => Pick(Resolve(p => p.playerWin, PlayerWin));
+ public static string GetPlayerLost() => Pick(Resolve(p => p.playerLost, PlayerLost));
+
+ ///
+ /// Descarta la caché del JSON (útil en tests o hot-reload de contenido).
+ ///
+ public static void Reload()
+ {
+ _loaded = false;
+ }
+
+ private static string[] Resolve(Func selector, string[] fallback)
+ {
+ EnsureLoaded();
+ string[] fromJson = selector(_pools);
+ return fromJson != null && fromJson.Length > 0 ? fromJson : fallback;
+ }
+
+ private static void EnsureLoaded()
+ {
+ if (_loaded)
+ return;
+
+ _loaded = true;
+ _pools = new QuotePools();
+
+ TextAsset asset = Resources.Load(ResourcePath);
+ if (asset == null)
+ return; // Sin JSON: los getters usan los pools embebidos.
+
+ try
+ {
+ JsonUtility.FromJsonOverwrite(asset.text, _pools);
+ }
+ catch (Exception ex)
+ {
+ Debug.LogError($"[MuerteQuotes] JSON inválido en {ResourcePath}: {ex.Message}. Usando frases por defecto.");
+ _pools = new QuotePools();
+ }
+ }
private static string Pick(string[] pool)
{
if (pool == null || pool.Length == 0)
return string.Empty;
- return pool[Random.Range(0, pool.Length)];
+ return pool[UnityEngine.Random.Range(0, pool.Length)];
}
}
diff --git a/Assets/Game/Scripts/Mono/Purgatory/Duel/PurgatoryDuelManager.cs b/Assets/Game/Scripts/Mono/Purgatory/Duel/PurgatoryDuelManager.cs
index b14a14d..313c9e5 100644
--- a/Assets/Game/Scripts/Mono/Purgatory/Duel/PurgatoryDuelManager.cs
+++ b/Assets/Game/Scripts/Mono/Purgatory/Duel/PurgatoryDuelManager.cs
@@ -175,12 +175,32 @@ public class PurgatoryDuelManager : MonoBehaviour
boss.hasMoved = true;
board[spawnPos.x, spawnPos.y] = boss;
- foreach (var spriteRenderer in bossObj.GetComponentsInChildren(true))
- spriteRenderer.color = new Color(0.55f, 0.07f, 0.07f);
+ ApplyBossAppearance(bossObj);
return boss;
}
+ ///
+ /// Arte de La Muerte: usa el sprite propio (Resources/Pieces/la_muerte)
+ /// si existe; si no, tiñe el Rey negro en rojo oscuro como placeholder.
+ ///
+ private static void ApplyBossAppearance(GameObject bossObj)
+ {
+ SpriteRenderer[] renderers = bossObj.GetComponentsInChildren(true);
+
+ Sprite muerteSprite = Resources.Load("Pieces/la_muerte");
+ if (muerteSprite != null)
+ {
+ foreach (var spriteRenderer in renderers)
+ spriteRenderer.sprite = muerteSprite;
+
+ return;
+ }
+
+ foreach (var spriteRenderer in renderers)
+ spriteRenderer.color = new Color(0.55f, 0.07f, 0.07f);
+ }
+
private static Vector2Int PickBossSpawn(Piece[,] board)
{
foreach (Vector2Int candidate in new[]
diff --git a/Assets/Scenes/Chapter2.unity b/Assets/Scenes/Chapter2.unity
index 5210746..d517876 100644
--- a/Assets/Scenes/Chapter2.unity
+++ b/Assets/Scenes/Chapter2.unity
@@ -7717,7 +7717,7 @@ MonoBehaviour:
_speakerNameText: {fileID: 1622539956}
_speakerPortrait: {fileID: 991001013}
_continueIndicator: {fileID: 1699531460}
- _speakerDatabase: {fileID: 0}
+ _speakerDatabase: {fileID: 11400000, guid: e35f84464de24b4cb0885058c4cdea56, type: 2}
_blinkSpeed: 0.5
_advanceDialogueAction: {fileID: 0}
--- !u!1 &321429398
diff --git a/Assets/Scenes/Chapter3.unity b/Assets/Scenes/Chapter3.unity
index c585af4..c0eb007 100644
--- a/Assets/Scenes/Chapter3.unity
+++ b/Assets/Scenes/Chapter3.unity
@@ -7234,7 +7234,7 @@ MonoBehaviour:
_speakerNameText: {fileID: 1622539956}
_speakerPortrait: {fileID: 991001013}
_continueIndicator: {fileID: 1699531460}
- _speakerDatabase: {fileID: 0}
+ _speakerDatabase: {fileID: 11400000, guid: e35f84464de24b4cb0885058c4cdea56, type: 2}
_blinkSpeed: 0.5
_advanceDialogueAction: {fileID: 0}
--- !u!1 &321429398
diff --git a/production/roadmap.md b/production/roadmap.md
index d2f8198..345ab03 100644
--- a/production/roadmap.md
+++ b/production/roadmap.md
@@ -107,3 +107,20 @@ Verificación de los 7 bloqueantes listados arriba:
**Pendiente tras esta auditoría**: correr los menús Tools > Purgatory UI en Unity (Ch1, Ch2, Ch3),
guardar escenas, verificar consola sin errores de compilación, y commitear.
+
+---
+
+## Actualización 2026-08-24 — Duelo del Purgatorio + La Muerte como personaje
+
+Commit base: `f7d5606` (Duelo del Purgatorio completo + SFX cableados + menú de opciones + migración
+de tests). Sobre esa base:
+
+| Ítem | Estado |
+|------|--------|
+| Commit del trabajo pendiente | RESUELTO — `f7d5606` |
+| La Muerte como speaker | RESUELTO — retrato `Portraits/muerte.png` (256×256, flat) cableado en SpeakerDatabase.asset con GUID/fileID deterministas; `PortraitRegistry` ahora rellena portraits null de speakers existentes; frases data-driven en `Dialogues/muerte_quotes.json` con fallback embebido |
+| Arte del Jefe (La Muerte) | RESUELTO — sprite propio `Resources/Pieces/la_muerte.png` (1024×1024, PPU 100); `SpawnBoss` lo intercambia con fallback al Rey tintado |
+| Limpieza menor | RESUELTO — TODO obsoleto en `CampaignState.StartChapter` eliminado (SyncFromBoard ya cubre), `/TestResults/` a .gitignore, duplicado `Assets/Game/Audio/` (metas YAML corruptos, sin referencias) eliminado |
+
+**Verificación**: Unity 6000.3.13f1 batch mode — assets importados, `SpeakerDatabaseSetup` OK
+(12 speakers, DialogueUIs actualizados en Ch2/Ch3), EditMode 200/200 pasados.