Fix alignment/ids output

This commit is contained in:
2026-05-31 08:17:41 +02:00
parent 7eeb32927d
commit 67c03fa6bd
3 changed files with 88 additions and 0 deletions
+5
View File
@@ -153,6 +153,11 @@ and column. Set `dataset: "*"` to apply an encoding to the same column name in
any native topdata dataset; an exact dataset rule takes precedence over the any native topdata dataset; an exact dataset rule takes precedence over the
wildcard rule for that column. Supported compact encodings include packed hex wildcard rule for that column. Supported compact encodings include packed hex
lists, external hex-list sidecar tables, alignment set masks, and ID hex lists. lists, external hex-list sidecar tables, alignment set masks, and ID hex lists.
Explicit `{"alignments": [...]}` and `{"ids": [...]}` objects are portable in
any native topdata dataset even without a column-specific rule; configured
rules still override when a column needs custom bounds, widths, or alignment
encoding mode. Plain `{"list": [...]}` objects remain config-driven because
numeric lists need table-specific output semantics.
Class spell availability can be authored as JSON spellbooks under Class spell availability can be authored as JSON spellbooks under
`topdata/data/classes/spellbooks/`. Each file names a `classes:*` row, and the `topdata/data/classes/spellbooks/`. Each file names a `classes:*` row, and the
+32
View File
@@ -4252,6 +4252,20 @@ func (r *valueResolver) resolveValue(row map[string]any, field string, value any
return tlkCompiledValue{Value: value}, nil return tlkCompiledValue{Value: value}, nil
} }
} }
if _, hasAlignments := typed["alignments"]; hasAlignments {
encoded, err := encodeConfiguredAlignmentSetMask(typed, defaultExplicitAlignmentSetMaskEncoding())
if err != nil {
return tlkCompiledValue{}, fmt.Errorf("field %s: %w", field, err)
}
return tlkCompiledValue{Value: encoded}, nil
}
if _, hasIDs := typed["ids"]; hasIDs {
value, err := r.encodeConfiguredIDHexList(typed, defaultExplicitIDHexListEncoding())
if err != nil {
return tlkCompiledValue{}, fmt.Errorf("field %s: %w", field, err)
}
return tlkCompiledValue{Value: value}, nil
}
if refID, ok := typed["id"]; ok { if refID, ok := typed["id"]; ok {
if refKey, ok := refID.(string); ok && strings.Contains(refKey, ":") { if refKey, ok := refID.(string); ok && strings.Contains(refKey, ":") {
id, ok := r.keyToID[refKey] id, ok := r.keyToID[refKey]
@@ -4413,6 +4427,24 @@ func encodeConfiguredAlignmentSetMask(raw any, encoding project.TopDataValueEnco
return "0x" + fmt.Sprintf("%0*X", encoding.HexWidth, mask), nil return "0x" + fmt.Sprintf("%0*X", encoding.HexWidth, mask), nil
} }
func defaultExplicitAlignmentSetMaskEncoding() project.TopDataValueEncodingConfig {
return project.TopDataValueEncodingConfig{
Mode: "alignment_set_mask",
Min: 0,
Max: 511,
HexWidth: 3,
}
}
func defaultExplicitIDHexListEncoding() project.TopDataValueEncodingConfig {
return project.TopDataValueEncodingConfig{
Mode: "id_hex_list",
Min: 0,
Max: 65535,
HexWidth: 4,
}
}
func (r *valueResolver) encodeConfiguredIDHexList(obj map[string]any, encoding project.TopDataValueEncodingConfig) (string, error) { func (r *valueResolver) encodeConfiguredIDHexList(obj map[string]any, encoding project.TopDataValueEncodingConfig) (string, error) {
mode := strings.TrimSpace(encoding.Mode) mode := strings.TrimSpace(encoding.Mode)
if mode != "id_hex_list" { if mode != "id_hex_list" {
+51
View File
@@ -586,6 +586,57 @@ func TestBuildNativeAppliesWildcardConfiguredValueEncodings(t *testing.T) {
} }
} }
func TestBuildNativeEncodesExplicitAlignmentAndIDListsInAnyDataset(t *testing.T) {
root := testProjectRoot(t)
mkdirAll(t, filepath.Join(root, "topdata", "data", "feat"))
mkdirAll(t, filepath.Join(root, "topdata", "data", "futuretable", "modules"))
writeFile(t, filepath.Join(root, "topdata", "base_dialog.json"), "{}\n")
writeFile(t, filepath.Join(root, "topdata", "data", "feat", "base.json"), `{
"output": "feat.2da",
"columns": ["LABEL"],
"rows": [
{"id": 2, "key": "feat:first_future", "LABEL": "FirstFuture"},
{"id": 17, "key": "feat:second_future", "LABEL": "SecondFuture"}
]
}`+"\n")
writeFile(t, filepath.Join(root, "topdata", "data", "feat", "lock.json"), `{
"feat:first_future": 2,
"feat:second_future": 17
}`+"\n")
writeFile(t, filepath.Join(root, "topdata", "data", "futuretable", "base.json"), `{
"output": "futuretable.2da",
"columns": ["Label", "AnyAlignmentMask", "AnyIDList"],
"rows": [
{"id": 0, "key": "futuretable:first", "Label": "First"}
]
}`+"\n")
writeFile(t, filepath.Join(root, "topdata", "data", "futuretable", "lock.json"), `{"futuretable:first":0}`+"\n")
writeFile(t, filepath.Join(root, "topdata", "data", "futuretable", "modules", "10_first.json"), `{
"overrides": [
{
"key": "futuretable:first",
"AnyAlignmentMask": {"alignments": ["lg", "tn", "ce"]},
"AnyIDList": {"ids": ["feat:first_future", {"id": "feat:second_future"}]}
}
]
}`+"\n")
proj := testProject(root)
result, err := BuildNativeWithOptions(proj, NativeBuildOptions{BuildWiki: false}, nil)
if err != nil {
t.Fatalf("BuildNativeWithOptions failed: %v", err)
}
raw, err := os.ReadFile(filepath.Join(result.Output2DADir, "futuretable.2da"))
if err != nil {
t.Fatalf("read futuretable.2da: %v", err)
}
got := string(raw)
if !strings.Contains(got, "0\tFirst\t0x111\t0x00020011\n") {
t.Fatalf("expected generic explicit list encodings in futuretable.2da, got:\n%s", got)
}
}
func TestBuildNativeAppliesConfiguredValueDefaults(t *testing.T) { func TestBuildNativeAppliesConfiguredValueDefaults(t *testing.T) {
root := testProjectRoot(t) root := testProjectRoot(t)
mkdirAll(t, filepath.Join(root, "topdata", "data", "racialtypes", "core", "modules")) mkdirAll(t, filepath.Join(root, "topdata", "data", "racialtypes", "core", "modules"))