diff --git a/internal/topdata/metadata_wiki.go b/internal/topdata/metadata_wiki.go
index c836813..875fe5c 100644
--- a/internal/topdata/metadata_wiki.go
+++ b/internal/topdata/metadata_wiki.go
@@ -26,6 +26,8 @@ func normalizeWikiMetadataKey(key string) string {
return "canonical"
case "status", "wikistatus":
return "status"
+ case "progressionnotes", "progression_notes", "classprogressionnotes", "class_progression_notes":
+ return "progression_notes"
default:
return ""
}
diff --git a/internal/topdata/wiki_native_test.go b/internal/topdata/wiki_native_test.go
index 075fde9..e7e8f79 100644
--- a/internal/topdata/wiki_native_test.go
+++ b/internal/topdata/wiki_native_test.go
@@ -1506,7 +1506,7 @@ tables:
value: "{{range(HPMin, HPMax)}}"
- key: notes
label: Notes
- value: "{{if(Level == 4 && BonusFeat != \"0\", link(\"feat:weapon_specialization\", \"Weapon Specialization\") + \" first available.\", Notes)}}"
+ value: "{{if(Level == 4 && BonusFeat != \"0\", link(\"feat:weapon_specialization\", \"Weapon Specialization\") + \" first available.\" + if(present(Notes), \" \" + Notes, \"\"), Notes)}}"
empty: ""
allow_wiki_markup: true
`)
@@ -1521,6 +1521,13 @@ tables:
"FeatsTable": map[string]any{"table": "classes/feats:fighter"},
"HitDie": 10,
"SavingThrowTable": map[string]any{"table": "classes/saves:fortitude"},
+ "meta": map[string]any{
+ "wiki": map[string]any{
+ "progression_notes": map[string]any{
+ "4": "Manual level note.",
+ },
+ },
+ },
},
}
@@ -1538,7 +1545,7 @@ tables:
`
2 | `,
`[[Feats/Literate|Literate]], [[Feats/Simple_Weapon_Proficiency|Simple Weapon Proficiency]] | `,
`1-10 | `,
- `[[Feats/Weapon_Specialization|Weapon Specialization]] first available. | `,
+ `[[Feats/Weapon_Specialization|Weapon Specialization]] first available. Manual level note. | `,
} {
if !strings.Contains(got, expected) {
t.Fatalf("expected %q in rendered table:\n%s", expected, got)
diff --git a/internal/topdata/wiki_tables.go b/internal/topdata/wiki_tables.go
index 466681b..7e3c310 100644
--- a/internal/topdata/wiki_tables.go
+++ b/internal/topdata/wiki_tables.go
@@ -441,7 +441,14 @@ func (ctx *wikiContext) classProgressionRows(levels string, projections map[stri
if hitDie > 0 {
row["HPMax"] = level * hitDie
}
- row["Notes"] = ""
+ if !presentWikiValue(fieldValue(row, "Notes")) {
+ if note := ctx.classProgressionNote(classRow, level); note != "" {
+ row["Notes"] = note
+ }
+ }
+ if _, ok := row["Notes"]; !ok {
+ row["Notes"] = ""
+ }
row["HasSpellProgression"] = hasSpellProgression(classRow)
row["HasSpellsPerDay"] = hasPerDay
row["HasSpellsKnown"] = hasKnown
@@ -452,6 +459,38 @@ func (ctx *wikiContext) classProgressionRows(levels string, projections map[stri
return out, nil
}
+func (ctx *wikiContext) classProgressionNote(classRow map[string]any, level int) string {
+ meta, err := parseExistingMetadata(classRow)
+ if err != nil || meta == nil {
+ return ""
+ }
+ wiki, _ := meta["wiki"].(map[string]any)
+ if wiki == nil {
+ return ""
+ }
+ raw := wiki["progression_notes"]
+ if raw == nil {
+ return ""
+ }
+ switch typed := raw.(type) {
+ case map[string]any:
+ for _, key := range []string{strconv.Itoa(level), formatLevelNoteKey(level)} {
+ if value, ok := typed[key]; ok {
+ return strings.TrimSpace(ctx.resolveTextValue(value, nil))
+ }
+ }
+ case []any:
+ if level > 0 && level <= len(typed) {
+ return strings.TrimSpace(ctx.resolveTextValue(typed[level-1], nil))
+ }
+ }
+ return ""
+}
+
+func formatLevelNoteKey(level int) string {
+ return "level_" + strconv.Itoa(level)
+}
+
func classSpellProgressionByLevel(table *wikiTable) map[int]map[int]string {
out := map[int]map[int]string{}
if table == nil {