Debugging
Logs
ctx.log(...) writes to your plugin's Log tab in Plugins, with the time and the game it came from. You can pass it anything; objects print as they would in a console.
ctx.log('Joined', { room: 'jamaa_township.room_main', players: 12 });Uncaught errors are logged too, and the plugin shows as Stopped until you reload it.
Telling the player something isn't working
A log line is for you. When the player needs to know that your plugin is running but can't do one of its jobs, use ctx.snag:
ctx.snag(
'clothesOnHud',
'The HUD is not on screen yet, so there is nothing to hover over.'
);
ctx.snag('clothesOnHud', null);Phantom shows it beside the preference of that name wherever it's drawn, on your plugin's page, and once in your log. Pass null to clear it. See Saying what isn't working.
Refused calls
If the plugin does something it wasn't granted, the call rejects with PERMISSION_DENIED. The message names the scope it needed, like game:act, and the manifest line that asks for it. The SDK reference lists the scope for each call. Add it to the manifest's permissions ("game": "act", say) and allow it again.
Seeing what the game is doing
- Packets shows the messages between the game and Animal Jam as they happen. If your plugin is waiting for something that never comes, the packet log tells you whether the server sent it.
- Scripts is the quickest way to try a call before putting it in a plugin. It has the same SDK with autocomplete, and nothing to reload. See Scripts.
- The client's own code tells you what the game actually does. Animal Jam Classic is Flash, so its classes can be read.
packages/client-typesgenerates the TypeScript declarations Phantom is built against from the decompiled ActionScript, one file per class, and itsscripts/generate.tsputs that source under~/.cache/phantom-dev/client-1829-src/scripts/. If your plugin depends on how a screen behaves, read the class. Some settings you might add are already the game's own, like the name badgeMySettingskeeps. Drive the game's setting rather than adding a second one.
It worked, then stopped
If a listener or a label works until the player changes room, the call is fine. The client built a new HUD for the new room and yours is on the old one. Code that only works with a setTimeout and gets flaky on a slower computer has the same cause. Timing covers both: keep on a listener, phantom.aj.room.onLoaded instead of a wait, and phantom.flash.until for a value that isn't there yet.
Validate before you ship
npm run validateThis checks the manifest and lists the permissions your plugin asks for, worded as the player will see them. It also tells you whether a person will review the plugin for the store.
usePlugin() can't find the context
If a component throws usePlugin() found no plugin context, the composable it called comes from a second copy of @phantomclient/plugin. The frame gives the context to its own copy, and the other copy can't see it.
This happens when @phantomclient/plugin and @phantomclient/plugin/vue resolve to different installs, or a bundler pulls the package into your plugin instead of leaving it to the frame. Check there's only one @phantomclient/plugin under node_modules, and build with phantom-plugin build, which leaves it out for you. phantom-plugin dev already points both imports at the same install.