Testing and debugging
| What you want to do | How |
|---|---|
| View logs | Output Log panel — [your id] … (host.Log); exception details if isolated |
| View state | Plugin Manager · console plugins |
| Reload a fixed dll | Plugin Manager Reload (selected) |
| Test logic quickly | Unit tests with a fake INpWorld |
| Set breakpoints | Attach a debugger to the running editor |
| Automatically check down to the screen | AddShotScenario (development builds only; see the limitation below) |
The Output Log is a panel in the bottom area. If you closed it, open it from the Window menu. Plugin-related lines look like this:
| Line | Meaning |
|---|---|
[<id>] … | Text written with host.Log |
[Plugin <id>] <version> enabled — Menu 1 · Quick add 1 | Load succeeded, with the registration summary |
[Plugin] <name> attached — … | Attached to the editor |
[Plugin <id>] Load failed: <exception type>: <message> | Exception in Register or the constructor |
[Plugin <id>] Error → isolated — <where>: <exception type>: <message> | Exception in a callback. <where> appears as Menu <name> · Command <name> · Committed and so on. |
[Plugin <id>] Unloaded — <reason> | Unloaded by disabling · reloading · an error |
When a plugin is isolated, a notification Plugin "
Console commands
Section titled “Console commands”| Command | What it does |
|---|---|
plugins | Plugin list: id · version · state · error |
plugins reload | Rescans the folders and loads enabled plugins (plugins already on are not reloaded) |
Edit and reload
Section titled “Edit and reload”- Rebuild. The editor does not lock the dll, so you can build while it is running.
- Overwrite the dll (and pdb) in the plugin folder with the new one.
- In the Plugin Manager, select that row and click Reload (selected).
If you use the build output folder directly as a plugin folder, step 2 is unnecessary. Put the parent folder of the build output in the NP_EDITOR_PLUGINS environment variable. Each subfolder under it is one plugin.
NP_EDITOR_PLUGINS=D:\dev\np_plugins_outD:\dev\np_plugins_out\my_first_plugin\ ← plugin.json · MyFirstPlugin.dll · .pdbEnvironment variables are read only when the editor restarts.
Unit tests (fake INpWorld)
Section titled “Unit tests (fake INpWorld)”The SDK is interfaces only. So you can test plugin logic without the editor by using a small class that imitates INpWorld. Move the code that creates blocks into a function that takes an INpWorld.
using NP.Editor.Sdk;
/// <summary>Fake world for tests: cell → block state.</summary>public sealed class FakeWorld : INpWorld{ private readonly Dictionary<NpCell, string> _b = new(); public string? FilePath => null; public NpBox? Selection { get; set; } public NpBox? Bounds => null; public string GetBlock(int x, int y, int z) => _b.TryGetValue(new NpCell(x, y, z), out var s) ? s : "minecraft:air"; public bool IsAir(int x, int y, int z) => !_b.ContainsKey(new NpCell(x, y, z)); public IEnumerable<NpBlock> Blocks(NpBox? box = null) => _b.Where(kv => box is not { } bb || bb.Contains(kv.Key.X, kv.Key.Y, kv.Key.Z)) .Select(kv => new NpBlock(kv.Key.X, kv.Key.Y, kv.Key.Z, kv.Value)); public long CountBlocks() => _b.Count; public int Edit(string label, Action<INpEdit> build) { var e = new Ed(_b); build(e); return e.Changed; }
private sealed class Ed : INpEdit { private readonly Dictionary<NpCell, string> _b; public int Changed; public Ed(Dictionary<NpCell, string> b) => _b = b; public void Set(int x, int y, int z, string state) { var c = new NpCell(x, y, z); if (state == "minecraft:air") { if (_b.Remove(c)) Changed++; return; } if (!_b.TryGetValue(c, out var old) || old != state) { _b[c] = state; Changed++; } } public void Clear(int x, int y, int z) => Set(x, y, z, "minecraft:air"); }}
// Code under test (inside the plugin)public static class PillarBuilder{ public static int Build(INpWorld w, NpCell at) => w.Edit("3×3 기둥", e => { for (int y = 0; y < 6; y++) for (int x = 0; x < 3; x++) for (int z = 0; z < 3; z++) e.Set(at.X + x, at.Y + y, at.Z + z, "minecraft:stone_bricks"); });}An xUnit test example:
public class PillarTests{ [Fact] public void Pillar_is_3x3x6() { var w = new FakeWorld(); int n = PillarBuilder.Build(w, new NpCell(0, 1, 0)); Assert.Equal(54, n); Assert.Equal("minecraft:stone_bricks", w.GetBlock(2, 6, 2)); Assert.True(w.IsAir(0, 7, 0)); }}| Easy to test | How |
|---|---|
| Block placement calculations | Edit on a fake INpWorld, then check GetBlock |
| Command result text | Check the return value of NpCommand.Run(new[] { "6" }) |
| Exports such as CSV | Call NpFileFormat.Run(path) with a temporary path and check the file |
| World logic preview | Construct new NpSceneSim(...) directly and call Simulate |
A fake world cannot verify locking and undo behavior identical to the editor’s. Check by hand in the editor at the end.
Attaching a debugger
Section titled “Attaching a debugger”- Build the plugin in Debug.
- Put the
.dlland.pdbtogether in the plugin folder. The editor also loads the.pdbfrom the same folder. - Start the editor and enable the plugin.
- In your IDE, use Attach to Process and choose
NPEditor.exe(.NET / CoreCLR debugger). - Set breakpoints in your code. Execution stops when you click the menu item or run the command.
- If you stay stopped at a breakpoint for long, the editor screen freezes too, because every callback runs on the main thread.
- To break in
Register, attach first and then click Reload (selected).
Screen verification scenarios (AddShotScenario)
Section titled “Screen verification scenarios (AddShotScenario)”AddShotScenario registers a scenario for the editor’s screen verification runner. A scenario changes the world, places the camera, and records verdicts and screenshots.
host.AddShotScenario(new NpShot("myroad_smoke", async api =>{ api.World.Edit("시험", e => e.Set(0, 1, 0, "minecraft:stone")); api.LookAt(6, 6, -6, 0, 1, 0); // eye (6,6,-6) → looks at (0,1,0) await api.Frames(5); // wait 5 frames api.Check(api.World.GetBlock(0, 1, 0) == "minecraft:stone", "돌 놓임"); await api.Shot("smoke"); // save PNG}));The runner command line:
NPEditor.exe -- --np-shot=myroad_smoke --np-out=<result folder> --np-quit| Argument | Meaning |
|---|---|
--np-shot=<id>[,<id>…] | Scenarios to run |
--np-out=<folder> | Folder for PNGs and the result file (shot.json — pass/fail per scenario) |
--np-quit | Quit the editor when done |
- If a scenario throws, that verdict fails and the plugin is isolated.
- When a scenario starts,
WorldChanged(Reason = ShotScenario) fires.
Limitation: the screen verification runner is included only in editor development builds. It is not in the installed version. Also, in this mode the user plugin folder is not loaded automatically. So with the current installed version you cannot run external plugin scenarios. For external plugins, use unit tests and manual checks.
Manual checklist
Section titled “Manual checklist”| Check | Why |
|---|---|
| Enable → disable → enable | Menus and panels are not created twice or left behind |
| New world · open another world | State is cleared in WorldChanged |
| Use a feature once → Ctrl+Z once | It cleanly reverts in a single undo step |
| No selection · empty world | No exceptions when Selection · Bounds are null |
| Large world | Committed and live text do not stall the editor |