Verifying a feature - rebaserHEAD/NexusForever GitHub Wiki
This guide checks a table-driven feature on a local server before it reaches stable: predict the outcome from the game tables, drive the feature from the server, compare what got saved against the prediction row by row, then look at it in game.
Before you start
What you'll have
A row-by-row comparison between what the server saved and what the game's own data says it should have saved, plus a look at the result from the player's side. A feature isn't done when it compiles, or when the logs look right. It's done when both of those hold.
How it works
Most of WildStar's behaviour is driven by its game tables, so the tables say what should happen. That gives every table-driven feature the same check.
flowchart TD
T["Game tables"] -->|script| P["Prediction"]
WC["Commands to the server"] --> DB[("Saved rows")]
P --> CMP{"Compare"}
DB --> CMP
CMP -->|match| G["Check in game"]
CMP -->|mismatch| FIX["Find which side is wrong"]
Run the check
Start clean
Delete the test character's rows for the feature, along with anything that would short-circuit the run. For the archive that meant its archive rows, its achievements and its titles: an achievement the character already has won't complete again. A local test database holds nothing worth keeping, so clear what you need.
[!TIP] Build in Debug. It's the only build that checks each game table model against its file at start-up.
Build the prediction from the tables
Write the prediction from the tables, never from the server code. If you derive it from the code you're testing, a mistake in the code shows up on both sides and the comparison passes anyway.
Export the tables you need to CSV and compute the prediction with a short script. For the archive, each article's predicted unlocked word is:
- bit 31, the moment anything unlocks the article;
- the slot bit of every entry whose rule names one of the granted achievements, in every article that holds that entry;
- the slot bits of the entries flagged to unlock with the article.
Galactic Archive messages and storage has the bit layout and the flags.
Drive the feature from the server
Use the web console with the test character's name in the Character box, so each command runs as that character (Using the web console). For a long run, script it over the console's socket instead of typing.
[!NOTE] Most commands answer nothing when they work, so the run itself proves nothing. The proof comes next.
Read what was saved
Query the character database. The Aspire host starts phpMyAdmin beside MySQL, and the dashboard links to it. For the archive:
SELECT articleId, HEX(unlockedFlags), HEX(viewedFlags)
FROM character_archive_article
WHERE id = <character id>
ORDER BY articleId;
The character's id is in the character table.
Compare
Compare every row with its prediction. A mismatch means one of the two is wrong, and either answer is worth having: a bug in the feature, or a gap in how we read the tables.
Check it in game
Logs and rows don't show what the player sees. Open the client and check the feature from the player's side: the panel lists what it should, counts update, and state holds across a world change and a fresh login.
When the client has to act
Some things only start from the client. A quest counts as complete only when the player hands it in, and the hand-in comes from the client. To set one up:
- Pick a quest the character's faction can hand in. A quest for the other faction shows no hand-in at all.
- Check that the quest's receiver has a spawn in the world database, or there's nobody to hand it in to.
- From the console:
teleport location <id>to the receiver's location, thenquest add <id>andquest achieve <id>. - Hand it in from the client.
Anything that needs two characters online, such as trading, needs two clients. Two run side by side on one machine.
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
| Nothing saved after a run | The character never saved, or the commands had no character | Check the console replies for "Invalid target", or run character save |
| Every row is off by the same bits | The prediction misses a rule, such as entries that unlock with the article | Check the prediction against the table flags |
| A quest shows no hand-in | The quest is for the other faction, or its receiver has no spawn | Pick another quest |
Example: the Galactic Archive run
The run behind #588:
| Step | Result |
|---|---|
| Start-up, Debug build | 251 articles and 847 unlock rules loaded |
| Prediction | Built from ArchiveEntryUnlockRule, ArchiveArticle and ArchiveEntry |
| Drive | 180 achievement grant commands, every achievement that unlocks something |
| Saved | 134 articles, opening 214 of their 464 entry slots; 63 articles complete |
| Compare | Every saved word matched its prediction: 0 mismatches |
| Quests | Two hand-ins in game, each updating the expected articles to the predicted words |
| In game | The panel's count of updated articles matched the saved rows, and read state held across world changes |
The same run was repeated, from a cleared character, after the code was reworked for review, with the same result.