feat: La Muerte as speaker with custom art + data-driven quotes

- Portraits/muerte.png (256x256 flat style) wired into SpeakerDatabase.asset
  via deterministic GUID/fileID; SpeakerDatabaseSetup batch keeps it in sync
- PortraitRegistry now fills null portraits of existing speakers (was skip-only)
- MuerteQuotes loads pools from Resources Dialogues/muerte_quotes.json with
  embedded fallback per purgatorio-final.md
- Boss art: Resources/Pieces/la_muerte.png (1024px, PPU 100); SpawnBoss swaps
  the tinted king placeholder for the custom sprite when present
- Cleanup: stale TODO in CampaignState.StartChapter, /TestResults/ gitignored,
  duplicate Assets/Game/Audio removed (broken YAML metas, zero references)

Verified in Unity 6000.3.13f1 batch mode: clean import, 200/200 EditMode tests
This commit is contained in:
2026-08-24 20:07:42 -03:00
parent f7d56066c0
commit 088d70db6f
31 changed files with 487 additions and 165 deletions
+3
View File
@@ -105,3 +105,6 @@ TestResults*.xml
# Coplay AI assistant (resolved via manifest git URL) # Coplay AI assistant (resolved via manifest git URL)
/Packages/Coplay/ /Packages/Coplay/
/Packages/com.coplaydev.coplay/ /Packages/com.coplaydev.coplay/
# Unity test runner output folder
/TestResults/
+3 -3
View File
@@ -13,8 +13,8 @@ namespace AjedrezPurgatorio.EditorTools
/// Unity -batchmode -projectPath . -executeMethod /// Unity -batchmode -projectPath . -executeMethod
/// AjedrezPurgatorio.EditorTools.SpeakerDatabaseSetup.PopulateAndAssign /// AjedrezPurgatorio.EditorTools.SpeakerDatabaseSetup.PopulateAndAssign
/// ///
/// Nota: 'muerte' y 'ricardo' no tienen PNG de retrato todavía; se registran /// Nota: 'ricardo' no tiene PNG de retrato todavía; se registra con
/// con portrait nulo para evitar warnings y facilitar añadir arte después. /// portrait nulo para evitar warnings y facilitar añadir arte después.
/// </summary> /// </summary>
public static class SpeakerDatabaseSetup public static class SpeakerDatabaseSetup
{ {
@@ -24,7 +24,7 @@ namespace AjedrezPurgatorio.EditorTools
private static readonly (string speaker, string portrait)[] Entries = private static readonly (string speaker, string portrait)[] Entries =
{ {
("narrator", "narrator"), ("narrator", "narrator"),
("muerte", null), ("muerte", "muerte"),
("ricardo", null), ("ricardo", null),
("alfil", "alfil"), ("alfil", "alfil"),
("caballo", "caballo"), ("caballo", "caballo"),
-8
View File
@@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: be210525ade96b143bbf7a4b90d5e258
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
Binary file not shown.
-23
View File
@@ -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:
Binary file not shown.
-23
View File
@@ -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:
Binary file not shown.
-23
View File
@@ -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:
Binary file not shown.
-23
View File
@@ -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:
Binary file not shown.
-23
View File
@@ -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:
Binary file not shown.
-23
View File
@@ -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:
@@ -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."
]
}
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: f3b9c74e28d14a6b8e50a1c9d7f26b34
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -1,5 +1,5 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: 5b071f4614a3499c9929e8f8bbaa0091 guid: 8a4d2c91b7e34f6095c1d2e8a6b3f740
folderAsset: yes folderAsset: yes
DefaultImporter: DefaultImporter:
externalObjects: {} externalObjects: {}
Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

@@ -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:
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

@@ -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:
@@ -16,7 +16,7 @@ MonoBehaviour:
- speakerName: narrator - speakerName: narrator
portrait: {fileID: 5364273658523971450, guid: 737c4034b70db6d47978d8d288f51f59, type: 3} portrait: {fileID: 5364273658523971450, guid: 737c4034b70db6d47978d8d288f51f59, type: 3}
- speakerName: muerte - speakerName: muerte
portrait: {fileID: 0} portrait: {fileID: 7419852336102758402, guid: c1e6e27e3c8e40129f502081e37ff22d, type: 3}
- speakerName: ricardo - speakerName: ricardo
portrait: {fileID: 0} portrait: {fileID: 0}
- speakerName: alfil - speakerName: alfil
+3 -3
View File
@@ -65,9 +65,9 @@ public class CampaignState : ScriptableObject
public void StartChapter(string chapterId) public void StartChapter(string chapterId)
{ {
_currentChapterPiecesLost = 0; _currentChapterPiecesLost = 0;
// Inicializar piezas vivas para el capítulo // Las piezas vivas se sincronizan desde el tablero vía SyncFromBoard(),
// TODO: Poblar esto desde el board state de GameManager // llamada por CampaignManager tras SetupBoard().
Debug.Log($"[CampaignState] Iniciando capítulo: {chapterId}"); Debug.Log($"[CampaignState] Iniciando capítulo: {chapterId}");
} }
@@ -61,6 +61,27 @@ public class SpeakerDatabase : ScriptableObject
return null; return null;
} }
/// <summary>
/// Obtiene el portrait de un speaker sin loguear warning si falta.
/// Usado por sistemas runtime (PortraitRegistry) que comprueban existencia.
/// </summary>
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;
}
/// <summary> /// <summary>
/// Verifica si existe un speaker en la base de datos. /// Verifica si existe un speaker en la base de datos.
/// </summary> /// </summary>
@@ -30,6 +30,7 @@ public class PortraitRegistry : MonoBehaviour
Sprite[] sprites = Resources.LoadAll<Sprite>(_resourcesPortraitsFolder); Sprite[] sprites = Resources.LoadAll<Sprite>(_resourcesPortraitsFolder);
int registered = 0; int registered = 0;
int filled = 0;
foreach (var sprite in sprites) foreach (var sprite in sprites)
{ {
@@ -42,9 +43,15 @@ public class PortraitRegistry : MonoBehaviour
database.RegisterPortrait(sprite.name, sprite); database.RegisterPortrait(sprite.name, sprite);
registered++; 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; _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}.");
} }
} }
@@ -1,12 +1,26 @@
using System.Collections.Generic; using System;
using UnityEngine; using UnityEngine;
/// <summary> /// <summary>
/// Frases de La Muerte durante el Duelo del Purgatorio. /// Frases de La Muerte durante el Duelo del Purgatorio.
/// Voz solemne según purgatorio-cosmology.md ("la Muerte", máscara formal). /// 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.
/// </summary> /// </summary>
public static class MuerteQuotes public static class MuerteQuotes
{ {
[Serializable]
private class QuotePools
{
public string[] intro = Array.Empty<string>();
public string[] milestone = Array.Empty<string>();
public string[] playerWin = Array.Empty<string>();
public string[] playerLost = Array.Empty<string>();
}
private const string ResourcePath = "Dialogues/muerte_quotes";
private static readonly string[] Intro = private static readonly string[] Intro =
{ {
"Mate perfecto... para el tablero. Aquí abajo, la partida sigue.", "Mate perfecto... para el tablero. Aquí abajo, la partida sigue.",
@@ -39,15 +53,56 @@ public static class MuerteQuotes
"Otra partida termina. Otra alma aprende." "Otra partida termina. Otra alma aprende."
}; };
public static string GetIntro() => Pick(Intro); private static QuotePools _pools;
public static string GetMilestone() => Pick(Milestone); private static bool _loaded;
public static string GetPlayerWin() => Pick(PlayerWin);
public static string GetPlayerLost() => Pick(PlayerLost); 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));
/// <summary>
/// Descarta la caché del JSON (útil en tests o hot-reload de contenido).
/// </summary>
public static void Reload()
{
_loaded = false;
}
private static string[] Resolve(Func<QuotePools, string[]> 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<TextAsset>(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) private static string Pick(string[] pool)
{ {
if (pool == null || pool.Length == 0) if (pool == null || pool.Length == 0)
return string.Empty; return string.Empty;
return pool[Random.Range(0, pool.Length)]; return pool[UnityEngine.Random.Range(0, pool.Length)];
} }
} }
@@ -175,12 +175,32 @@ public class PurgatoryDuelManager : MonoBehaviour
boss.hasMoved = true; boss.hasMoved = true;
board[spawnPos.x, spawnPos.y] = boss; board[spawnPos.x, spawnPos.y] = boss;
foreach (var spriteRenderer in bossObj.GetComponentsInChildren<SpriteRenderer>(true)) ApplyBossAppearance(bossObj);
spriteRenderer.color = new Color(0.55f, 0.07f, 0.07f);
return boss; return boss;
} }
/// <summary>
/// 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.
/// </summary>
private static void ApplyBossAppearance(GameObject bossObj)
{
SpriteRenderer[] renderers = bossObj.GetComponentsInChildren<SpriteRenderer>(true);
Sprite muerteSprite = Resources.Load<Sprite>("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) private static Vector2Int PickBossSpawn(Piece[,] board)
{ {
foreach (Vector2Int candidate in new[] foreach (Vector2Int candidate in new[]
+1 -1
View File
@@ -7717,7 +7717,7 @@ MonoBehaviour:
_speakerNameText: {fileID: 1622539956} _speakerNameText: {fileID: 1622539956}
_speakerPortrait: {fileID: 991001013} _speakerPortrait: {fileID: 991001013}
_continueIndicator: {fileID: 1699531460} _continueIndicator: {fileID: 1699531460}
_speakerDatabase: {fileID: 0} _speakerDatabase: {fileID: 11400000, guid: e35f84464de24b4cb0885058c4cdea56, type: 2}
_blinkSpeed: 0.5 _blinkSpeed: 0.5
_advanceDialogueAction: {fileID: 0} _advanceDialogueAction: {fileID: 0}
--- !u!1 &321429398 --- !u!1 &321429398
+1 -1
View File
@@ -7234,7 +7234,7 @@ MonoBehaviour:
_speakerNameText: {fileID: 1622539956} _speakerNameText: {fileID: 1622539956}
_speakerPortrait: {fileID: 991001013} _speakerPortrait: {fileID: 991001013}
_continueIndicator: {fileID: 1699531460} _continueIndicator: {fileID: 1699531460}
_speakerDatabase: {fileID: 0} _speakerDatabase: {fileID: 11400000, guid: e35f84464de24b4cb0885058c4cdea56, type: 2}
_blinkSpeed: 0.5 _blinkSpeed: 0.5
_advanceDialogueAction: {fileID: 0} _advanceDialogueAction: {fileID: 0}
--- !u!1 &321429398 --- !u!1 &321429398
+17
View File
@@ -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), **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. 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.