fix(drill): place spots found up to 15 m from the navmesh; count only this run's Oxide errors (#22)
All checks were successful
PR Checks / plugin-checks (pull_request) Successful in 1m28s

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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
This commit is contained in:
2026-11-01 02:15:18 -06:00
parent 4fb935d9dd
commit cf3997bbce
2 changed files with 18 additions and 9 deletions

View File

@@ -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<string>("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) =>

View File

@@ -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)