fix(vt): E metaf header + STATE/NAV fold markers, real byte identity
Item E (slice-1 fix round). SaveMeta/SaveNav emitted no header at all
and no per-STATE/per-NAV editor-fold comment pair, so proof (4)
(byte-identity against metaf's own canonical emission) could only ever
pass after stripping every "~~" line — which hid that metaf's own
ExportToMetAF DOES mechanically emit both:
- OutputText.metaHeader/navHeader (metaf_monolithic.py:365-434): a fixed
auto-completion-assistance banner, prepended to every meta/nav-only
file respectively.
- State.ExportToMetAF / Nav.ExportToMetAF (py:12050-12054,12463-12467):
every STATE:/NAV: block wrapped in "~~ {" ... "~~ }", unconditionally
(including the single-node Target/follow NAV case).
- Meta.ExportToMetAF (py:12775-12794): when any NAV exists, a blank
line, the exact separator
"~~========================= ONLY NAVS APPEAR BELOW THIS LINE =========================~~"
(no space beside either "~~"), and another blank line, before the
first NAV: block.
Changes:
- MetafSerializer: MetaHeaderLines/NavOutputHeaderLines — the header
text copied byte-for-byte from Fixtures/vtank/af/bella.af (meta) and
nav_ab.af (nav-only) rather than retyped from the Python source, per
the slice-1 contract. Joined with bare "\n" (matching metaf's own
multi-line string constant, written as ONE f.line entry) plus a
trailing "\n" — combined with this writer's own "\r\n" join separator,
reproduces the single blank line real output has between the header
and the first STATE:/NAV: line exactly.
- SaveMeta: prepends MetaHeader; wraps every STATE: block in "~~ {"/
"~~ }"; emits the "ONLY NAVS APPEAR BELOW..." separator only when at
least one embedded Nav tag exists.
- WriteNavBlock: wraps every NAV: block (both waypoint-list and
Target/follow modes) in "~~ {"/"~~ }".
- SaveNav: prepends NavOutputHeader; writes the sole nav under tag
"nav0" (matching metaf's Meta.GenerateUniqueNavTag counter starting at
0 — every committed nav_*.af fixture's tag is literally "nav0";
the prior tag "route" was a MossTank invention).
Proof (4) is now REAL byte identity (Assert.Equal(original, rewritten),
no comment stripping, no blank-line normalization) plus a new nav-only
case (WriterOutputMatchesMetafCanonicalEmissionNavOnly, against
nav_ab.af). Investigating the raw bytes of every candidate fixture
found real, PRE-EXISTING header divergence unrelated to this writer:
aphus.af/neftet.af/follower.af open "~~ {\r\n~~ " (CRLF) where a fresh
metaf conversion's header is bare-LF internally ("~~ {\n~~ ",
confirmed against augments.af/bella.af/gauntlet_leader.af/
empyrean_facility.af/example_sort_meta.af) — evidence of a re-save by
something other than metaf itself (e.g. a text editor normalizing every
line ending). hunting.af and lockandkey.af carry a wholly custom
hand-written banner instead of metaf's own. ByteIdenticalFixtureData is
narrowed to the five fixtures whose header IS metaf's fresh canonical
form (bella, gauntlet_leader, empyrean_facility, augments,
example_sort_meta — still five, per the slice-1 contract's floor);
aphus/neftet/hunting/follower/lockandkey keep exercising every other
proof (parse, parse-write-parse, and the ptl/tlk direct assertions from
the item-A fix) normally, with the header divergence documented at the
exclusion site rather than asserted away.
Three MossTankPanelTests assertions changed from StartsWith to Contains
("STATE: "/"NAV: ") since exported .af content is no longer the first
thing in the file.
Full MossTank suite: 570 -> 566 (5 fixtures dropped from the
byte-identity theory, replaced by 1 new nav-only fact: -4 net).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
e1d0c703f9
commit
8de69b9741
3 changed files with 180 additions and 53 deletions
|
|
@ -30,6 +30,92 @@ internal static class MetafSerializer
|
|||
{
|
||||
private const string Comment = "~~";
|
||||
|
||||
// The exact text metaf's own OutputText.metaHeader/navHeader constants
|
||||
// emit (metaf_monolithic.py:365-434), copied verbatim from a real
|
||||
// fixture rather than retyped from the Python source (Campaign VT
|
||||
// slice-1 fix round, item E) -- every line's tab count matters for byte
|
||||
// identity. Joined with bare "\n" (matching how metaf's own multi-line
|
||||
// string constant is written as ONE f.line entry containing embedded
|
||||
// newlines) plus one trailing "\n": the CRLF this writer's own
|
||||
// string.Join("\r\n", lines) inserts after that whole entry, combined
|
||||
// with the header's own trailing "\n", produces the single blank line
|
||||
// real metaf output has between the header and the first STATE:/NAV:
|
||||
// line -- confirmed byte-for-byte against Fixtures/vtank/af/bella.af
|
||||
// (meta) and nav_ab.af (nav-only).
|
||||
private static readonly string[] MetaHeaderLines =
|
||||
[
|
||||
"~~ {",
|
||||
"~~ FOR AUTO-COMPLETION ASSISTANCE: testvar getvar setvar touchvar clearallvars clearvar getcharintprop getchardoubleprop getcharquadprop getcharboolprop getcharstringprop getisspellknown getcancastspell_hunt getcancastspell_buff getcharvital_base getcharvital_current getcharvital_buffedmax getcharskill_traininglevel getcharskill_base getcharskill_buffed getplayerlandcell getplayercoordinates coordinategetns coordinategetwe coordinategetz coordinatetostring coordinateparse coordinatedistancewithz coordinatedistanceflat wobjectgetphysicscoordinates wobjectgetname wobjectgetobjectclass wobjectgettemplatetype wobjectgetisdooropen wobjectfindnearestmonster wobjectfindnearestdoor wobjectfindnearestbyobjectclass wobjectfindininventorybytemplatetype wobjectfindininventorybyname wobjectfindininventorybynamerx wobjectgetselection wobjectgetplayer wobjectfindnearestbynameandobjectclass actiontryselect actiontryuseitem actiontryapplyitem actiontrygiveitem actiontryequipanywand actiontrycastbyid actiontrycastbyidontarget chatbox chatboxpaste statushud statushudcolored uigetcontrol uisetlabel isfalse istrue iif randint cstr strlen getobjectinternaltype cstrf stopwatchcreate stopwatchstart stopwatchstop stopwatchelapsedseconds cnumber floor ceiling round abs getworldname getitemcountininventorybyname getheading getitemcountininventorybynamerx getheadingto actiontrygiveprofile vitae getfellowshipstatus getfellowshipname getfellowshipisopen getfellowshipisleader getfellowshipleaderid getfellowshipcanrecruit getfellowid getfellowshipcount getfellowshiplocked getfellowname getfellowshipisfull sin cos tan sqrt asin acos atan atan2 sinh cosh tanh vtsetmetastate getregexmatch echo chr ord wobjectgetid wobjectgethealth wobjectfindbyid wobjectgetintprop wobjectfindnearestbytemplatetype wobjectgetopencontainer testquestflag getquestktprogress isrefreshingquests getquestktrequired getqueststatus getisday getgamehour getgamehourname getisnight getgameday getgameticks getminutesuntilday getgamemonth getgamemonthname getminutesuntilnight getgameyear uisetvisible uiviewvisible uiviewexists getgvar touchgvar getpvar touchpvar setgvar cleargvar setpvar clearpvar testgvar clearallgvars testpvar clearallpvars dictgetitem dictcreate dicthaskey dictadditem dictkeys dictremovekey dictvalues dictclear dictsize dictcopy listgetitem listpop listcreate listcontains listremove listadd listindexof listremoveat listinsert listlastindexof listclear listcopy listcount listreverse",
|
||||
"",
|
||||
"~~\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t",
|
||||
"~~ File auto-generated by metaf, a program created by Eskarina of Morningthaw/Coldeve.\t",
|
||||
"~~\t\tGet metaf here: https://github.com/JJEII/metaf/\t\t\t\t\t\t\t\t\t",
|
||||
"~~\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t",
|
||||
"~~ All recognized structural designators:\t\t\t\t\t\t\t\t\t\t\t\t",
|
||||
"~~\t\tSTATE:\t\t\t\tDO:\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t",
|
||||
"~~\t\tIF:\t\t\t\t\tNAV:\t\t\t\t\t\t\t\t\t\t\t\t\t\t",
|
||||
"~~\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t",
|
||||
"~~ All recognized CONDITION (IF:) operation keywords:\t\t\t\t\t\t\t\t\t",
|
||||
"~~\t\tNever\t\t\t\tNavEmpty\t\t\tMobsInDist_Priority\t\tNot\t\t\t\t",
|
||||
"~~\t\tAlways\t\t\t\tDeath\t\t\t\tNeedToBuff\t\t\t\tPSecsInStateGE\t",
|
||||
"~~\t\tAll\t\t\t\t\tVendorOpen\t\t\tNoMobsInDist\t\t\tSecsOnSpellGE\t",
|
||||
"~~\t\tAny\t\t\t\t\tVendorClosed\t\tBlockE\t\t\t\t\tBuPercentGE\t\t",
|
||||
"~~\t\tChatMatch\t\t\tItemCountLE\t\t\tCellE\t\t\t\t\tDistToRteGE\t\t",
|
||||
"~~\t\tMainSlotsLE\t\t\tItemCountGE\t\t\tIntoPortal\t\t\t\tExpr\t\t\t",
|
||||
"~~\t\tSecsInStateGE\t\tMobsInDist_Name\t\tExitPortal\t\t\t\tChatCapture\t\t",
|
||||
"~~\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t",
|
||||
"~~ All recognized ACTION (DO:) operation keywords:\t\t\t\t\t\t\t\t\t\t",
|
||||
"~~\t\tNone\t\t\t\tEmbedNav\t\t\tChatExpr\t\t\t\tSetOpt\t\t\t",
|
||||
"~~\t\tSetState\t\t\tCallState\t\t\tSetWatchdog\t\t\t\tCreateView\t\t",
|
||||
"~~\t\tChat\t\t\t\tReturn\t\t\t\tClearWatchdog\t\t\tDestroyView\t\t",
|
||||
"~~\t\tDoAll\t\t\t\tDoExpr\t\t\t\tGetOpt\t\t\t\t\tDestroyAllViews\t",
|
||||
"~~\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t",
|
||||
"~~ All recognized NAV types:\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t",
|
||||
"~~\t\tcircular\t\t\tfollow\t\t\t\t\t\t\t\t\t\t\t\t\t\t",
|
||||
"~~\t\tlinear\t\t\t\tonce\t\t\t\t\t\t\t\t\t\t\t\t\t\t",
|
||||
"~~\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t",
|
||||
"~~ All recognized NAV NODE types:\t\t\t\t\t\t\t\t\t\t\t\t\t\t",
|
||||
"~~\t\tflw\t\t\t\t\tvnd\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t",
|
||||
"~~\t\tpnt\t\t\t\t\tptl\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t",
|
||||
"~~\t\trcl\t\t\t\t\ttlk\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t",
|
||||
"~~\t\tpau\t\t\t\t\tchk\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t",
|
||||
"~~\t\tcht\t\t\t\t\tjmp\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t",
|
||||
"~~\t\tprt (deprecated in VTank)\t\t\t\t\t\t\t\t\t\t\t\t\t\t",
|
||||
"~~ }\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t",
|
||||
];
|
||||
private static readonly string MetaHeader = string.Join("\n", MetaHeaderLines) + "\n";
|
||||
|
||||
private static readonly string[] NavOutputHeaderLines =
|
||||
[
|
||||
"~~ {",
|
||||
"~~ FOR AUTO-COMPLETION ASSISTANCE: testvar getvar setvar touchvar clearallvars clearvar getcharintprop getchardoubleprop getcharquadprop getcharboolprop getcharstringprop getisspellknown getcancastspell_hunt getcancastspell_buff getcharvital_base getcharvital_current getcharvital_buffedmax getcharskill_traininglevel getcharskill_base getcharskill_buffed getplayerlandcell getplayercoordinates coordinategetns coordinategetwe coordinategetz coordinatetostring coordinateparse coordinatedistancewithz coordinatedistanceflat wobjectgetphysicscoordinates wobjectgetname wobjectgetobjectclass wobjectgettemplatetype wobjectgetisdooropen wobjectfindnearestmonster wobjectfindnearestdoor wobjectfindnearestbyobjectclass wobjectfindininventorybytemplatetype wobjectfindininventorybyname wobjectfindininventorybynamerx wobjectgetselection wobjectgetplayer wobjectfindnearestbynameandobjectclass actiontryselect actiontryuseitem actiontryapplyitem actiontrygiveitem actiontryequipanywand actiontrycastbyid actiontrycastbyidontarget chatbox chatboxpaste statushud statushudcolored uigetcontrol uisetlabel isfalse istrue iif randint cstr strlen getobjectinternaltype cstrf stopwatchcreate stopwatchstart stopwatchstop stopwatchelapsedseconds cnumber floor ceiling round abs getworldname getitemcountininventorybyname getheading getitemcountininventorybynamerx getheadingto actiontrygiveprofile vitae getfellowshipstatus getfellowshipname getfellowshipisopen getfellowshipisleader getfellowshipleaderid getfellowshipcanrecruit getfellowid getfellowshipcount getfellowshiplocked getfellowname getfellowshipisfull sin cos tan sqrt asin acos atan atan2 sinh cosh tanh vtsetmetastate getregexmatch echo chr ord wobjectgetid wobjectgethealth wobjectfindbyid wobjectgetintprop wobjectfindnearestbytemplatetype wobjectgetopencontainer testquestflag getquestktprogress isrefreshingquests getquestktrequired getqueststatus getisday getgamehour getgamehourname getisnight getgameday getgameticks getminutesuntilday getgamemonth getgamemonthname getminutesuntilnight getgameyear uisetvisible uiviewvisible uiviewexists getgvar touchgvar getpvar touchpvar setgvar cleargvar setpvar clearpvar testgvar clearallgvars testpvar clearallpvars dictgetitem dictcreate dicthaskey dictadditem dictkeys dictremovekey dictvalues dictclear dictsize dictcopy listgetitem listpop listcreate listcontains listremove listadd listindexof listremoveat listinsert listlastindexof listclear listcopy listcount listreverse",
|
||||
"",
|
||||
"~~\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t",
|
||||
"~~ File auto-generated by metaf, a program created by Eskarina of Morningthaw/Coldeve.\t",
|
||||
"~~\t\tGet metaf here: https://github.com/JJEII/metaf/\t\t\t\t\t\t\t\t\t",
|
||||
"~~\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t",
|
||||
"~~ All recognized structural designators in a NAV-ONLY file:\t\t\t\t\t\t\t",
|
||||
"~~\t\tNAV:\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t",
|
||||
"~~\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t",
|
||||
"~~ All recognized NAV types:\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t",
|
||||
"~~\t\tcircular\t\t\tfollow\t\t\t\t\t\t\t\t\t\t\t\t\t\t",
|
||||
"~~\t\tlinear\t\t\t\tonce\t\t\t\t\t\t\t\t\t\t\t\t\t\t",
|
||||
"~~\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t",
|
||||
"~~ All recognized NAV NODE types:\t\t\t\t\t\t\t\t\t\t\t\t\t\t",
|
||||
"~~\t\tflw\t\t\t\t\tvnd\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t",
|
||||
"~~\t\tpnt\t\t\t\t\tptl\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t",
|
||||
"~~\t\trcl\t\t\t\t\ttlk\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t",
|
||||
"~~\t\tpau\t\t\t\t\tchk\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t",
|
||||
"~~\t\tcht\t\t\t\t\tjmp\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t",
|
||||
"~~\t\tprt (deprecated in VTank)\t\t\t\t\t\t\t\t\t\t\t\t\t\t",
|
||||
"~~ \t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t",
|
||||
"",
|
||||
"~~ \t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t",
|
||||
"~~ \t\t\t\tREMEMBER THAT NAV-ONLY FILES MUST CONTAIN EXACTLY ONE NAV!\t\t\t\t",
|
||||
"~~ }\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t",
|
||||
];
|
||||
private static readonly string NavOutputHeader = string.Join("\n", NavOutputHeaderLines) + "\n";
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Grammar building blocks (rx._D/_I/_H/_S/_L, metaf_monolithic.py:172-176)
|
||||
// ------------------------------------------------------------------
|
||||
|
|
@ -209,7 +295,7 @@ internal static class MetafSerializer
|
|||
public static string SaveMeta(MetaProfile profile)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(profile);
|
||||
var lines = new List<string>();
|
||||
var lines = new List<string> { MetaHeader };
|
||||
// EmbedNav's referenced Nav is a separate NAV: block elsewhere in
|
||||
// the file (metaf's own layout — see the class doc comment); the
|
||||
// tag itself carries no runtime meaning, but matching metaf's own
|
||||
|
|
@ -225,12 +311,27 @@ internal static class MetafSerializer
|
|||
.Where(static rule => rule.Enabled)
|
||||
.GroupBy(static rule => rule.State, StringComparer.Ordinal))
|
||||
{
|
||||
lines.Add($"STATE: {{{stateGroup.Key}}}");
|
||||
// State.ExportToMetAF (metaf_monolithic.py:12463-12467) wraps
|
||||
// every state in an editor-fold comment pair; this is metaf's
|
||||
// own mechanical emission, not the VirindiTank editor's, so it
|
||||
// is now reproduced for byte identity rather than stripped by
|
||||
// the test.
|
||||
lines.Add($"STATE: {{{stateGroup.Key}}} ~~ {{");
|
||||
foreach (MetaRule rule in stateGroup)
|
||||
{
|
||||
WriteCondition(lines, rule.Condition, "IF:", 1, ConditionDepth);
|
||||
WriteAction(lines, rule.Action, "DO:", 2, ActionDepth, embedTags);
|
||||
}
|
||||
lines.Add("~~ }");
|
||||
}
|
||||
if (embedTags.Count > 0)
|
||||
{
|
||||
// Meta.ExportToMetAF (metaf_monolithic.py:12784-12790): a blank
|
||||
// line, this exact separator (no space either side of the "~~"
|
||||
// delimiters), then another blank line, before any NAV: block.
|
||||
lines.Add(string.Empty);
|
||||
lines.Add("~~========================= ONLY NAVS APPEAR BELOW THIS LINE =========================~~");
|
||||
lines.Add(string.Empty);
|
||||
}
|
||||
foreach ((MetaAction action, string tag) in embedTags)
|
||||
{
|
||||
|
|
@ -622,8 +723,11 @@ internal static class MetafSerializer
|
|||
public static string SaveNav(NavigationSettings source)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(source);
|
||||
var lines = new List<string>();
|
||||
WriteNavBlock(lines, "route", source);
|
||||
// "nav0" matches metaf's own tag for the sole Nav in a stand-alone
|
||||
// nav-only conversion (Meta.GenerateUniqueNavTag starts its counter
|
||||
// at 0; every committed nav_*.af fixture's tag is literally "nav0").
|
||||
var lines = new List<string> { NavOutputHeader };
|
||||
WriteNavBlock(lines, "nav0", source);
|
||||
return string.Join("\r\n", lines) + "\r\n";
|
||||
}
|
||||
|
||||
|
|
@ -931,15 +1035,22 @@ internal static class MetafSerializer
|
|||
RouteMode.Once => "once",
|
||||
_ => throw new InvalidOperationException("unknown navigation mode."),
|
||||
};
|
||||
lines.Add($"NAV: {tag} {navType}");
|
||||
// Nav.ExportToMetAF (metaf_monolithic.py:12050-12054) wraps every
|
||||
// NAV: block in the same editor-fold comment pair State does,
|
||||
// unconditionally (including the single-"flw"-node Target/follow
|
||||
// case) — metaf's own mechanical emission, reproduced for byte
|
||||
// identity.
|
||||
lines.Add($"NAV: {tag} {navType} ~~ {{");
|
||||
if (source.Mode == RouteMode.Target)
|
||||
{
|
||||
lines.Add(
|
||||
$"\tflw {FormatHex(source.FollowTargetObjectId)} {{{source.FollowTargetName}}}");
|
||||
lines.Add("~~ }");
|
||||
return;
|
||||
}
|
||||
foreach (RouteWaypoint waypoint in source.Waypoints)
|
||||
lines.Add(RenderNavNode(waypoint));
|
||||
lines.Add("~~ }");
|
||||
}
|
||||
|
||||
private static string RenderNavNode(RouteWaypoint waypoint)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue