Feature set 174. - #89
Merged
Merged
Conversation
Each game's enemy infobox names its fields differently and the alias lists only
spoke a subset, so whole columns read as empty for whole games. Final Fantasy V's
template was migrated to hyphenated names ("magic-defense", "drop-1",
"ps-abilities"), which left every one of its enemies with no magic defence, no
drops and nothing to steal; Final Fantasy XII states its combat stats only as a
level band and numbers its moves one field at a time.
Field names now match a hyphen or underscore wherever they carry a space, the
platform tags admit the same separator, the "min" forms of attack and defence are
read the way "hp min" already was, and magickname/technickname are collected.
vitality and spirit are scoped per game rather than added to the global lists.
They are the defence stats in Final Fantasy XV, whose template carries nothing
else, but Final Fantasy XII and IX list them beside a real defence, where reading
them reports a Banshee's defence as 46 when the article says 17. The game comes
from the article's own "| release =" line so the parser stays a pure function of
the wikitext.
Two robustness fixes in CleanFieldValue come with it, both found as markup sitting
in stored values:
- the leaked-assignment strip took a name of [a-zA-Z_][a-zA-Z_ ]*, and a
platform-tagged name does not start with a letter, so Final Fantasy III's
"| 3d common drop =" survived and was stored as a stolen item.
- the wikilink pattern allowed exactly one pipe, so Abaddon's
"[[Hi-Ether (Final Fantasy VI)||Hi-Ether]]" failed to match and kept its pipes.
The target is now matched greedily to the last pipe.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
BattleMath.Ratio divides offence by guard, which assumes the two are comparable numbers. They are not the same underlying stat in every game, and the enemy stat backfill made that visible: Final Fantasy XV publishes strength with a median of 4,080 and vitality with a median of 107, so a median enemy read as forty times stronger than another median enemy of its own bestiary and killed it in two turns instead of seven. It had been hidden only because XV published no defence at all, and the median-borrow happened to hand it the attack scale. Final Fantasy VI has the same fault in the other direction — a small attack against a 0-255 defence — and had every fight pinned at the MinRatio floor for seventeen turns, which predates the backfill. FillGapsWithGameMedians now rescales the guard stat so its median matches the offence it is measured against, per game and per stat pair. Scaling is monotonic, so which monsters are tougher than which is untouched; only where the bestiary as a whole sits changes. A game that publishes no guard stat already borrowed the offence median, which makes the factor exactly 1 and leaves that path alone. Every game's median pairing now sits at parity and runs about seven turns, and matchups still spread from four turns to seventeen depending on who meets whom. Sphere Hunter is unaffected either way: it rates stats as percentiles within a game, so scale cannot reach it. Its two counts are refreshed for the pool the backfill produced. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Enemy stat coverage: the parser now reads the per-game fields it was missing, and the battle model no longer misreads a game whose attack and defence are on different scales.
Why
Whole stat columns were empty for whole games, and it was never "the wiki doesn't say" — it was field names the parser didn't speak. Sampling six popular enemies per game and diffing the real field names against the alias lists found the gap game by game.
The data pass that came out of it filled 2,566 null cells across 951 rows and corrected 1,488 more where the parser disagreed with what was stored. Monsters now hold 37,840 non-null values across the 16 stat columns, up from 35,276, and residual wiki markup in stored values is down from 7 cells to 0.
Commits
Read the per-game enemy stat fields the parser was missingField names match a hyphen or underscore wherever they carry a space (Final Fantasy V's template was migrated to
magic-defense/drop-1/ps-abilitiesin 2026, which is why every FFV enemy read as having no magic defence, no drops and nothing to steal). Platform tags take the same separator, theminforms of attack and defence are read the wayhp minalready was, and FFXII's numberedmagickname/technicknamefields are collected.vitalityandspiritare scoped per game rather than added to the global lists. They are the defence stats in Final Fantasy XV, whose template carries nothing else — but XII and IX list them beside a real defence, where reading them globally reports a Banshee's defence as 46 when the article says 17. The game is taken from the article's own| release =line, so the parser stays a pure function of the wikitext.Two
CleanFieldValuerobustness fixes ride along, both found as markup sitting in stored values. These change what the dashboard's import writes:[a-zA-Z_][a-zA-Z_ ]*, and a platform-tagged name doesn't start with a letter, so FFIII's| 3d common drop =survived and was stored as a stolen item[[Hi-Ether (Final Fantasy VI)||Hi-Ether]]failed to match and kept its pipesPut each game's guard stat on the same scale as the offence it meetsBattleMath.Ratiodivides offence by guard, which assumes they're comparable numbers. They aren't the same underlying stat in every game, and accurate data exposed it: FFXV publishes strength at a median of 4,080 and vitality at 107, so a median enemy read as forty times stronger than another median enemy of its own bestiary. It had been hidden only because XV published no defence at all and the median-borrow happened to hand it the attack scale.FFVI has the same fault in the other direction — a small attack against a 0–255 defence — and had every fight pinned at the
MinRatiofloor. That one predates this work.FillGapsWithGameMediansnow rescales the guard stat so its median matches the offence it's measured against, per game and per stat pair. Scaling is monotonic, so which monsters are tougher than which is untouched.Median pairings now sit at parity at about seven turns, and sampling 4,000 random pairings per game still spreads from four turns to seventeen. The client displays ATK/DEF, so those numbers change — FFVI previously showed ATK 13 against DEF 110 and then took seventeen turns.
Sphere Hunter is unaffected: it rates stats as percentiles within a game, so scale can't reach it. It gains 37 draftable spheres because FFXV and FFV monsters used to share one median defence and now vary. Its two documented counts are refreshed.
Kupodle and Triple Triad are untouched. Both pools are
HybridCacheentries on a 10-minute expiration, so they rebuild themselves.Testing
560 tests pass, 16 new.
dotnet format --verify-no-changesand the Release build are clean.Before writing anything, a harness parsed all 951 affected articles through
WikiClient.ParseMonsterStatsand diffed every one of the 2,566 proposed cells against the reviewed set — 0 mismatches. Every write was guarded on the cell still being what the review saw, and revert SQL exists for both passes.Not in scope
The characters side is still thin (Job 139/2,071), and its release-prefix wildcard is pulling Revenant Wings jobs onto FFXII characters — Ashe reads as a Time Mage. FFVIII, FFXI and FFXIV remain unreachable by aliases: VIII stores level-scaling coefficients rather than scalars, and XI and XIV carry no stats infobox at all.
🤖 Generated with Claude Code