feat(asset-bridge): the 73 bodies action 0 could not see, and the ceiling that makes looking safe (Phase 6)
The catalogue asked every body for action 0 and reported the rest absent. 73 of this client's bodies have no art there and real art deeper — body 820's first drawn action is 23, and it is a horse — so they rendered as text on the bestiary. The catalogue now falls back to the first action that has art, and the key names that action (`body/820/a23`). 1,022 -> 1,095 rows. Walking the action axis is the one thing that can walk off the end of a body's slots, and the slots after a body's band are the NEXT BODY'S. Measured here: one action past the band, 643 of 795 legacy bodies return a fully validated picture and 452 of those are byte-identical to body+1's action 0 (body 1 action 22 is an ettin; body 3's is an imp, both confirmed by rendering them). Phase 0's validator cannot catch that — the record is real — so the ceiling refuses the ADDRESS, in ResolveAnimation where every caller already goes. The ceiling is the index banding, never `Animations.GetAnimLength`: for a body reaching file type 5 as id 34 that function answers 22 while the arithmetic gives 13, and the difference is nine actions of another creature's art. A fetch serves only the key the catalogue chose for that body. `body/820/a0` and `body/400/a2` come back `unsupported` with the chosen action alongside, never by decoding what was asked for. `EXTRACTOR_VERSION` 2 -> 3 (unchanged input, a different answer). Protocol stays 8 — `action` on a manifest/fetch row is additive. Deep frame keys and the bulk-fill switch that §16 planned for this phase were NOT built: the site displays still pictures, and a complete one-direction animation set measures 174,453 frames / 281.5 MB against no consumer (docs §11.2, org lead 2026-09-11). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016wDDVXWMDz82WqE1i969r4
This commit is contained in:
@@ -406,11 +406,123 @@ namespace Server.Custom.Bridge
|
||||
return false;
|
||||
}
|
||||
|
||||
int actions = ActionsOf(translated, fileType);
|
||||
|
||||
if (action >= actions)
|
||||
{
|
||||
// §4.10, measured in phase 6: this is the never-sweep rule again, one axis over.
|
||||
// A body's slots are contiguous and the next body's begin immediately after them,
|
||||
// so `index + action * 5` past the ceiling addresses ANOTHER BODY'S action — a
|
||||
// real record, at a real offset, that every check below passes. Measured on this
|
||||
// client: of 795 legacy bodies, 643 return a fully validated picture one action
|
||||
// past their band and **452 of those are byte-identical to body+1's action 0**.
|
||||
// Body 1 action 22 is an ettin; body 3 action 22 is an imp. Nothing downstream
|
||||
// can tell, which is why the refusal has to be here.
|
||||
reason = "body " + body + " has " + actions + " actions in file type " + fileType
|
||||
+ "; action " + action + " belongs to the next body";
|
||||
return false;
|
||||
}
|
||||
|
||||
index = AnimIndexOf(translated, fileType) + (action * 5) + direction;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// How many actions the index reserves for a body — the only safe ceiling, and it is
|
||||
/// the banding rather than the library's own answer.
|
||||
///
|
||||
/// <c>Animations.GetAnimLength</c> exists and looks like the right source. It is not:
|
||||
/// for a body reaching file type 5 as id 34 it answers **22** while
|
||||
/// <see cref="AnimIndexOf"/> puts that body in the 65-slot band, which is **13**. The
|
||||
/// two disagree on exactly one body of this client (reached by translation from body
|
||||
/// 276), and taking the larger number is nine actions of somebody else's art. So the
|
||||
/// count is derived from the same arithmetic that produces the offset, in the same
|
||||
/// file, where the two cannot drift apart.
|
||||
/// </summary>
|
||||
public static bool ActionCount(int body, out int actions, out int fileType, out string reason)
|
||||
{
|
||||
reason = null;
|
||||
actions = 0;
|
||||
fileType = 0;
|
||||
|
||||
if (body <= 0)
|
||||
{
|
||||
reason = "body " + body + " is not addressable";
|
||||
return false;
|
||||
}
|
||||
|
||||
int translated = body;
|
||||
int hue = 0;
|
||||
|
||||
try
|
||||
{
|
||||
Animations.Translate(ref translated, ref hue);
|
||||
fileType = BodyConverter.Convert(ref translated);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
reason = "body.def/bodyconv.def lookup failed: " + e.GetType().Name;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (AnimDataPath(fileType) == null)
|
||||
{
|
||||
reason = "bodyconv sends body " + body + " to file type " + fileType
|
||||
+ ", which this client does not have";
|
||||
return false;
|
||||
}
|
||||
|
||||
actions = ActionsOf(translated, fileType);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The banding of <see cref="AnimIndexOf"/>, read as an action count: a body's slots
|
||||
/// are five directions per action, so the band size divided by five is how many
|
||||
/// actions it owns.
|
||||
/// </summary>
|
||||
private static int ActionsOf(int body, int fileType)
|
||||
{
|
||||
return SlotsOf(body, fileType) / 5;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// How many index slots <see cref="AnimIndexOf"/>'s arithmetic gives this body. The
|
||||
/// bands are transcribed there and their sizes here, from the same source and in the
|
||||
/// same order, because a ceiling that disagrees with an offset is worse than no
|
||||
/// ceiling at all.
|
||||
/// </summary>
|
||||
private static int SlotsOf(int body, int fileType)
|
||||
{
|
||||
switch (fileType)
|
||||
{
|
||||
case 2:
|
||||
return body < 200 ? 110 : 65;
|
||||
|
||||
case 3:
|
||||
if (body < 300)
|
||||
return 65;
|
||||
|
||||
return body < 400 ? 110 : 175;
|
||||
|
||||
case 5:
|
||||
// Body 34's exclusion again — it is in the second band here, so it owns 13
|
||||
// actions and not 22. This is the one body `GetAnimLength` is wrong about.
|
||||
if (body < 200 && body != 34)
|
||||
return 110;
|
||||
|
||||
return body < 400 ? 65 : 175;
|
||||
|
||||
default: // 1 and 4 share their banding
|
||||
if (body < 200)
|
||||
return 110;
|
||||
|
||||
return body < 400 ? 65 : 175;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <c>Animations.GetFileIndex</c>'s own arithmetic, which is private. The banding is
|
||||
/// per file type and the boundaries differ between them, so this is transcribed rather
|
||||
|
||||
Reference in New Issue
Block a user