Economy command: faults keyed by rule, record and message; L-productive by cycle members with a rising radius refused; removals listed, counted and applied; pending only for the recipe-less; swap failures named, read-only refused, file mode kept
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
a90ec3d450
commit
352a94fe21
2 changed files with 50 additions and 8 deletions
|
|
@ -22,8 +22,15 @@ public sealed class EconomyFault
|
|||
// A fault only because the chain has not written the record yet (a commodity with no recipe). Reported, never a refusal.
|
||||
public readonly bool Pending;
|
||||
|
||||
public EconomyFault(string invariant, string record, string message, bool pending = false)
|
||||
// L-productive only: every good the carrying cycle holds (sorted) and the spectral radius. The fault's identity is the member
|
||||
// set; a radius that rises is a worse fault of the same identity.
|
||||
public readonly IReadOnlyList<string> Members;
|
||||
public readonly double Radius;
|
||||
|
||||
public EconomyFault(string invariant, string record, string message, bool pending = false, IReadOnlyList<string> members = null, double radius = 0)
|
||||
{
|
||||
Members = members;
|
||||
Radius = radius;
|
||||
Pending = pending;
|
||||
Invariant = invariant;
|
||||
Record = record;
|
||||
|
|
@ -304,7 +311,8 @@ public static class EconomyChecks
|
|||
string Name(int i) => i == labour ? "labour" : names[i];
|
||||
var order = CycleOrder(final, n, component);
|
||||
faults.Add(new EconomyFault(LProductive, Name(order[0]),
|
||||
$"The goods cannot sustain themselves: spectral radius {rho:0.######} is not below 1, carried by {string.Join(" -> ", order.Select(Name))}."));
|
||||
$"The goods cannot sustain themselves: spectral radius {rho:0.######} is not below 1, carried by {string.Join(" -> ", order.Select(Name))}.",
|
||||
members: order.Select(Name).OrderBy(name => name, StringComparer.Ordinal).ToArray(), radius: rho));
|
||||
}
|
||||
return faults;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -118,11 +118,14 @@ public static class EconomyCommand
|
|||
var trial = Path.Combine(scratch, "Aetheria.cc");
|
||||
var fingerprint = Fingerprint(catalogPath);
|
||||
File.Copy(catalogPath, trial);
|
||||
HashSet<(string, string)> held;
|
||||
Dictionary<string, EconomyFault> held;
|
||||
HashSet<string> recipesHeld;
|
||||
Dictionary<string, byte[]> original;
|
||||
using (var cache = AetheriaStores.Open(catalogPath, registry: registry))
|
||||
{
|
||||
held = new HashSet<(string, string)>(EconomyChecks.Check(cache).Faults.Select(f => (f.Invariant, f.Record)));
|
||||
held = new Dictionary<string, EconomyFault>(StringComparer.Ordinal);
|
||||
foreach (var fault in EconomyChecks.Check(cache).Faults) held[Identity(fault)] = fault;
|
||||
recipesHeld = new HashSet<string>(cache.GetAll<RecipeData>().Select(r => cache.RefOf(r).Key.Value), StringComparer.Ordinal);
|
||||
original = Records(cache);
|
||||
}
|
||||
var touched = new List<string>();
|
||||
|
|
@ -161,13 +164,15 @@ public static class EconomyCommand
|
|||
foreach (var (id, bytes) in trialRecords)
|
||||
if (!original.TryGetValue(id, out var was)) status[KeyOf(id)] = "added";
|
||||
else if (!was.AsSpan().SequenceEqual(bytes)) status[KeyOf(id)] = "changed";
|
||||
foreach (var id in original.Keys)
|
||||
if (!trialRecords.ContainsKey(id)) status[KeyOf(id)] = "removed";
|
||||
var listed = touched.Concat(status.Keys.OrderBy(k => k, StringComparer.Ordinal).Where(k => !touched.Contains(k))).ToList();
|
||||
foreach (var key in listed) output.WriteLine($"{(status.TryGetValue(key, out var s) ? s : "unchanged")} {key}");
|
||||
output.WriteLine($"{status.Values.Count(v => v == "added")} added, {status.Values.Count(v => v == "changed")} changed, {listed.Count - status.Count} unchanged");
|
||||
output.WriteLine($"{status.Values.Count(v => v == "added")} added, {status.Values.Count(v => v == "changed")} changed, {status.Values.Count(v => v == "removed")} removed, {listed.Count - status.Count} unchanged");
|
||||
var report = EconomyChecks.Check(cache);
|
||||
PrintFaults(report, output);
|
||||
PrintDepth(report, output);
|
||||
var introduced = report.Faults.Where(fault => Shape.Contains(fault.Invariant) && !fault.Pending && !held.Contains((fault.Invariant, fault.Record))).ToArray();
|
||||
var introduced = report.Faults.Where(fault => Shape.Contains(fault.Invariant) && Introduced(fault, held, recipesHeld)).ToArray();
|
||||
if (introduced.Length > 0)
|
||||
{
|
||||
output.WriteLine($"\nThe batch is refused: {introduced.Length} shape faults the batches introduce.");
|
||||
|
|
@ -182,8 +187,24 @@ public static class EconomyCommand
|
|||
}
|
||||
}
|
||||
var staged = catalogPath + ".economy-" + Guid.NewGuid().ToString("N");
|
||||
File.Copy(trial, staged);
|
||||
try { File.Replace(staged, catalogPath, null); }
|
||||
try
|
||||
{
|
||||
if (new FileInfo(catalogPath).IsReadOnly)
|
||||
{
|
||||
output.WriteLine("
|
||||
The batches are refused: Aetheria.cc is read-only. Clear the read-only flag, or check it out, and run again. The catalog is unchanged.");
|
||||
return 1;
|
||||
}
|
||||
File.Copy(trial, staged);
|
||||
if (!OperatingSystem.IsWindows()) File.SetUnixFileMode(staged, File.GetUnixFileMode(catalogPath));
|
||||
File.Replace(staged, catalogPath, null);
|
||||
}
|
||||
catch (Exception e) when (e is UnauthorizedAccessException || e is IOException)
|
||||
{
|
||||
output.WriteLine($"
|
||||
The batches are refused: Aetheria.cc could not be replaced ({e.Message}). Something holds the file: close Unity and any other editor or tool that has it open, then run again. The catalog is unchanged.");
|
||||
return 1;
|
||||
}
|
||||
finally { if (File.Exists(staged)) File.Delete(staged); }
|
||||
output.WriteLine($"\nWrote {written} records to Aetheria.cc");
|
||||
return 0;
|
||||
|
|
@ -194,6 +215,19 @@ public static class EconomyCommand
|
|||
}
|
||||
}
|
||||
|
||||
// A fault is the same fault by rule, record and message, so fixing one fault on a record cannot hide a new one there. L-productive
|
||||
// is one fault per cycle: its identity is the cycle's member set, and the same cycle with a higher radius is a worse fault.
|
||||
private static string Identity(EconomyFault fault) =>
|
||||
fault.Members != null ? fault.Invariant + "|" + string.Join(",", fault.Members) : fault.Invariant + "|" + fault.Record + "|" + fault.Message;
|
||||
|
||||
private static bool Introduced(EconomyFault fault, Dictionary<string, EconomyFault> held, HashSet<string> recipesHeld)
|
||||
{
|
||||
// "No recipe" is pending for a commodity that had none; it refuses where the batch took away a recipe the catalog held.
|
||||
if (fault.Pending) return recipesHeld.Contains(RecipeData.KeyOf(new CultRecordKey(fault.Record)).Value);
|
||||
if (!held.TryGetValue(Identity(fault), out var was)) return true;
|
||||
return fault.Members != null && fault.Radius > was.Radius + 1e-9;
|
||||
}
|
||||
|
||||
private static readonly MessagePackSerializerOptions Wire = CultDocumentMessagePackSerialization.OptionsFor(typeof(ItemData).Assembly);
|
||||
|
||||
private static string Fingerprint(string path) => Convert.ToHexString(System.Security.Cryptography.SHA256.HashData(File.ReadAllBytes(path)));
|
||||
|
|
|
|||
Loading…
Reference in a new issue