chore(cli): UI-debug apparatus — mock-selbar, dump-edges, crop, probe
Standalone AcDream.Cli subcommands built during the D.5.3a visual gate, kept as reusable UI/sprite/framebuffer debugging apparatus (alongside the existing export-ui-sprite / dump-sprite-sheet / render-vitals-mockup tools): - mock-selbar: composite the selected-object health bar (back + fill at fractions) - dump-edges: print a sprite's first/last column RGB at every row - crop: crop + nearest-upscale a region of a PNG (zoom into a framebuffer dump) - probe: print the RGB of a pixel block from a PNG Dev-only (reached via explicit args[0]); no game-runtime impact. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
8f627cce0e
commit
07965852e0
2 changed files with 141 additions and 0 deletions
|
|
@ -82,6 +82,45 @@ if (args.Length >= 1 && args[0] == "dump-font-atlas")
|
|||
return FontAtlasDump.Run(dfaDir, dfaFont, dfaSample, dfaOut);
|
||||
}
|
||||
|
||||
if (args.Length >= 1 && args[0] == "probe")
|
||||
{
|
||||
// probe <in.png> <x0> <y0> <x1> <y1>
|
||||
if (args.Length < 6) { Console.Error.WriteLine("usage: AcDream.Cli probe <in.png> <x0> <y0> <x1> <y1>"); return 2; }
|
||||
return VitalsMockup.Probe(args[1], int.Parse(args[2]), int.Parse(args[3]), int.Parse(args[4]), int.Parse(args[5]));
|
||||
}
|
||||
|
||||
if (args.Length >= 1 && args[0] == "crop")
|
||||
{
|
||||
// crop <in.png> <x> <y> <w> <h> <zoom> <out.png>
|
||||
if (args.Length < 8) { Console.Error.WriteLine("usage: AcDream.Cli crop <in.png> <x> <y> <w> <h> <zoom> <out.png>"); return 2; }
|
||||
return VitalsMockup.Crop(args[1],
|
||||
int.Parse(args[2]), int.Parse(args[3]), int.Parse(args[4]), int.Parse(args[5]), int.Parse(args[6]), args[7]);
|
||||
}
|
||||
|
||||
if (args.Length >= 1 && args[0] == "dump-edges")
|
||||
{
|
||||
string? deDir = args.ElementAtOrDefault(1) ?? Env.GetEnvironmentVariable("ACDREAM_DAT_DIR");
|
||||
string? deId = args.ElementAtOrDefault(2);
|
||||
if (string.IsNullOrWhiteSpace(deDir) || string.IsNullOrWhiteSpace(deId))
|
||||
{
|
||||
Console.Error.WriteLine("usage: AcDream.Cli dump-edges <dat-directory> <0xId>");
|
||||
return 2;
|
||||
}
|
||||
return VitalsMockup.DumpEdges(deDir, deId);
|
||||
}
|
||||
|
||||
if (args.Length >= 1 && args[0] == "mock-selbar")
|
||||
{
|
||||
string? msbDir = args.ElementAtOrDefault(1) ?? Env.GetEnvironmentVariable("ACDREAM_DAT_DIR");
|
||||
string msbOut = args.ElementAtOrDefault(2) ?? "selbar.png";
|
||||
if (string.IsNullOrWhiteSpace(msbDir))
|
||||
{
|
||||
Console.Error.WriteLine("usage: AcDream.Cli mock-selbar <dat-directory> [out.png]");
|
||||
return 2;
|
||||
}
|
||||
return VitalsMockup.MockSelBar(msbDir, msbOut);
|
||||
}
|
||||
|
||||
if (args.Length >= 1 && args[0] == "export-ui-sprite")
|
||||
{
|
||||
string? eusDatDir = args.ElementAtOrDefault(1) ?? Env.GetEnvironmentVariable("ACDREAM_DAT_DIR");
|
||||
|
|
|
|||
|
|
@ -192,6 +192,108 @@ public static class VitalsMockup
|
|||
return 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Composite the selected-object health bar (back-track 0x0600193E + red fill 0x0600193F)
|
||||
/// the same way the in-game UiMeter draws it: the 146px sprite mapped 1:1 into the 140px
|
||||
/// meter element (right 6px cropped), back drawn full, fill drawn over the left
|
||||
/// fraction*width. Rendered at several health fractions stacked so the end-caps / purple
|
||||
/// can be eyeballed offline (D.5.3a purple-end investigation).
|
||||
/// </summary>
|
||||
public static int MockSelBar(string datDir, string outPath)
|
||||
{
|
||||
if (!Directory.Exists(datDir)) { Console.Error.WriteLine($"error: dir not found: {datDir}"); return 2; }
|
||||
using var dats = new DatCollection(datDir, DatAccessType.Read);
|
||||
using var back = Load(dats, 0x0600193E);
|
||||
using var fill = Load(dats, 0x0600193F);
|
||||
|
||||
const int elemW = 140, zoom = 8, gap = 4;
|
||||
int elemH = Math.Min(back.Height, fill.Height);
|
||||
float[] fracs = { 1.0f, 0.9f, 0.7f, 0.5f, 0.0f };
|
||||
int rowH = elemH + gap;
|
||||
using var canvas = new Image<Rgba32>(elemW, rowH * fracs.Length, new Rgba32(20, 20, 24, 255));
|
||||
|
||||
for (int i = 0; i < fracs.Length; i++)
|
||||
{
|
||||
int y = i * rowH;
|
||||
float p = fracs[i];
|
||||
int backCrop = Math.Min(elemW, back.Width);
|
||||
using (var b = back.Clone(c => c.Crop(new Rectangle(0, 0, backCrop, elemH))))
|
||||
canvas.Mutate(c => c.DrawImage(b, new Point(0, y), 1f));
|
||||
int fillW = (int)MathF.Round(elemW * p);
|
||||
if (fillW > 0)
|
||||
{
|
||||
int fillCrop = Math.Min(fillW, fill.Width);
|
||||
using var f = fill.Clone(c => c.Crop(new Rectangle(0, 0, fillCrop, elemH)));
|
||||
canvas.Mutate(c => c.DrawImage(f, new Point(0, y), 1f));
|
||||
}
|
||||
}
|
||||
|
||||
canvas.Mutate(c => c.Resize(canvas.Width * zoom, canvas.Height * zoom, KnownResamplers.NearestNeighbor));
|
||||
canvas.SaveAsPng(outPath);
|
||||
Console.WriteLine($"wrote {outPath} — selbar composite, rows = health 1.0 / 0.9 / 0.7 / 0.5 / 0.0");
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// <summary>Print the RGB of a rectangular block of pixels from a PNG (framebuffer probe).</summary>
|
||||
public static int Probe(string inPath, int x0, int y0, int x1, int y1)
|
||||
{
|
||||
if (!File.Exists(inPath)) { Console.Error.WriteLine($"not found: {inPath}"); return 2; }
|
||||
using var img = Image.Load<Rgba32>(inPath);
|
||||
x0 = Math.Clamp(x0, 0, img.Width - 1); x1 = Math.Clamp(x1, 0, img.Width - 1);
|
||||
y0 = Math.Clamp(y0, 0, img.Height - 1); y1 = Math.Clamp(y1, 0, img.Height - 1);
|
||||
Console.WriteLine($"{inPath} {img.Width}x{img.Height} cols x={x0}..{x1}");
|
||||
for (int y = y0; y <= y1; y++)
|
||||
{
|
||||
var sb = new System.Text.StringBuilder($"y={y,4}: ");
|
||||
for (int x = x0; x <= x1; x++) { var p = img[x, y]; sb.Append($"{p.R:X2}{p.G:X2}{p.B:X2} "); }
|
||||
Console.WriteLine(sb.ToString());
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// <summary>Crop a region of a PNG and upscale (nearest) — for zooming into a framebuffer dump.</summary>
|
||||
public static int Crop(string inPath, int x, int y, int w, int h, int zoom, string outPath)
|
||||
{
|
||||
if (!File.Exists(inPath)) { Console.Error.WriteLine($"not found: {inPath}"); return 2; }
|
||||
using var img = Image.Load<Rgba32>(inPath);
|
||||
x = Math.Clamp(x, 0, img.Width - 1);
|
||||
y = Math.Clamp(y, 0, img.Height - 1);
|
||||
w = Math.Clamp(w, 1, img.Width - x);
|
||||
h = Math.Clamp(h, 1, img.Height - y);
|
||||
if (zoom < 1) zoom = 1;
|
||||
img.Mutate(c => c.Crop(new Rectangle(x, y, w, h)).Resize(w * zoom, h * zoom, KnownResamplers.NearestNeighbor));
|
||||
img.SaveAsPng(outPath);
|
||||
Console.WriteLine($"wrote {outPath} ({w * zoom}x{h * zoom}) from {inPath} region ({x},{y},{w},{h})");
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// <summary>Print the RGB of the first/last few columns of a sprite at every row, so the
|
||||
/// end-cap colors can be inspected (D.5.3a purple-end investigation).</summary>
|
||||
public static int DumpEdges(string datDir, string idText)
|
||||
{
|
||||
if (!Directory.Exists(datDir)) { Console.Error.WriteLine($"error: dir not found: {datDir}"); return 2; }
|
||||
uint id = ParseHex(idText);
|
||||
using var dats = new DatCollection(datDir, DatAccessType.Read);
|
||||
var rs = dats.Get<RenderSurface>(id);
|
||||
if (rs is null) { Console.Error.WriteLine($"no RenderSurface 0x{id:X8}"); return 1; }
|
||||
var pal = rs.DefaultPaletteId != 0 ? dats.Get<Palette>(rs.DefaultPaletteId) : null;
|
||||
var dec = SurfaceDecoder.DecodeRenderSurface(rs, pal);
|
||||
Console.WriteLine($"0x{id:X8} {rs.Format} {dec.Width}x{dec.Height}");
|
||||
int[] cols = { 0, 1, 2, 3, dec.Width - 4, dec.Width - 3, dec.Width - 2, dec.Width - 1 };
|
||||
foreach (int cx in cols)
|
||||
{
|
||||
if (cx < 0 || cx >= dec.Width) continue;
|
||||
var sb = new System.Text.StringBuilder();
|
||||
for (int y = 0; y < dec.Height; y++)
|
||||
{
|
||||
int i = (y * dec.Width + cx) * 4;
|
||||
sb.Append($"{dec.Rgba8[i]:X2}{dec.Rgba8[i + 1]:X2}{dec.Rgba8[i + 2]:X2} ");
|
||||
}
|
||||
Console.WriteLine($"x={cx,3}: {sb}");
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static int ExportSprite(string datDir, string idText, string outPath)
|
||||
{
|
||||
if (!Directory.Exists(datDir)) { Console.Error.WriteLine($"error: dir not found: {datDir}"); return 2; }
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue