From cf3997bbce6947b9245c562686ad9786d2276082 Mon Sep 17 00:00:00 2001 From: wtclaude Date: Sun, 1 Nov 2026 02:15:18 -0600 Subject: [PATCH] fix(drill): place spots found up to 15 m from the navmesh; count only this run's Oxide errors (#22) Run 49: place.set.each was refused "4.4 m from the navmesh", because OnMesh searched 3 m and returned the unsnapped point; Place now searches 15 m. The drill reported run 48's four error lines again: oxideErrors read the whole day's Oxide log, and now reads only what was added after the drill started the rig. Refs #20, #22 Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY --- tools/RunicNpcTest.cs | 11 ++++++----- tools/drill/drill.js | 16 ++++++++++++---- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/tools/RunicNpcTest.cs b/tools/RunicNpcTest.cs index 7e1ad01..f2fdb27 100644 --- a/tools/RunicNpcTest.cs +++ b/tools/RunicNpcTest.cs @@ -1113,9 +1113,10 @@ namespace Oxide.Plugins private IEnumerator Place(Vector3 field) { - // A roamer's spot must be on the navmesh when it is set (D219), so both spots are put there. - Vector3 a = OnMesh(field + new Vector3(-40, 0, 0)); - Vector3 b = OnMesh(field + new Vector3(-40, 0, 30)); + // A roamer's spot must be on the navmesh when it is set (D219), so both spots are put there; the + // wider search because a fixed offset from the field can land a few metres off it (run 49: 4.4 m). + Vector3 a = OnMesh(field + new Vector3(-40, 0, 0), 15f); + Vector3 b = OnMesh(field + new Vector3(-40, 0, 30), 15f); StandIn(OnMesh(field + new Vector3(-40, 0, 110)), 8); string eachRefusal = Api("RunicNpc_SetPlacement", "t_each", Placement("t_each", a, 3, 20f, "each")); Check("place.set.each", eachRefusal == null, eachRefusal ?? ""); @@ -1569,11 +1570,11 @@ namespace Oxide.Plugins return floor; } - private static Vector3 OnMesh(Vector3 p) + private static Vector3 OnMesh(Vector3 p, float radius = 3f) { p.y = TerrainMeta.HeightMap.GetHeight(p); NavMeshHit hit; - return SampleNavmesh(p, 3f, out hit) ? hit.position : p; + return SampleNavmesh(p, radius, out hit) ? hit.position : p; } private static JObject Placement(string profile, Vector3 at, int count, float respawn, string mode) => diff --git a/tools/drill/drill.js b/tools/drill/drill.js index 8d62e53..1ea36d3 100644 --- a/tools/drill/drill.js +++ b/tools/drill/drill.js @@ -281,6 +281,7 @@ async function live(a) { for (const f of FRESH_DATA) await p.rm(rig, f) // already gone is fine: rm swallows it await p.write(rig, KITS_FILE, JSON.stringify(harnessKits(), null, 2)) + const logMark = await oxideLogMark(p, rig) log(`starting ${RIG} on ${branch}`) const started = Date.now() await p.power(rig, 'start') @@ -298,7 +299,7 @@ async function live(a) { const status = await waitStatus(p, rig, 30 * 60000) if (!status) { finding('RunicNPC did not load: `rnpc.status` never answered.') - result.oxideErrors = await oxideErrors(p, rig) + result.oxideErrors = await oxideErrors(p, rig, logMark) return } result.status = status @@ -322,7 +323,7 @@ async function live(a) { } if (!accepted) { finding('The test harness did not load (`rnt.run all` was not answered).') - result.oxideErrors = await oxideErrors(p, rig) + result.oxideErrors = await oxideErrors(p, rig, logMark) return } result.ran = true @@ -337,7 +338,7 @@ async function live(a) { } for (const f of result.harness.failed) finding(`Harness: ${f}`) log(`harness: ${result.harness.pass} pass, ${result.harness.fail} fail`) - result.oxideErrors = await oxideErrors(p, rig) + result.oxideErrors = await oxideErrors(p, rig, logMark) if (result.oxideErrors.length) finding(`RunicNPC logged ${result.oxideErrors.length} error line(s) in Oxide's log during the run.`) await p.command(rig, 'rnt.clear').catch(() => {}) } catch (e) { @@ -490,10 +491,17 @@ function summarise(lines) { } /** RunicNPC's and the harness's error lines from today's Oxide log. */ -async function oxideErrors(p, id) { +/** Where Oxide's log of today ends now, so oxideErrors reads only what a run adds (run 49 counted run 48's). */ +async function oxideLogMark(p, id) { + const day = now().slice(0, 10) + return { day, length: ((await p.readOr(id, `/oxide/logs/oxide_${day}.txt`, '')) || '').length } +} + +async function oxideErrors(p, id, since = null) { const day = now().slice(0, 10) const text = (await p.readOr(id, `/oxide/logs/oxide_${day}.txt`, '')) || '' return text + .slice(since && since.day === day ? since.length : 0) .split('\n') .filter((l) => /RunicNPC|RunicNpcTest/.test(l) && /\[Error\]|Exception|Failed|error CS/.test(l)) .slice(-40)