Erinaldordso commited on
Commit
04681ce
·
verified ·
1 Parent(s): a625b1a

Recognize wood log species in verified Naldo mining tasks

Browse files

The initial Mine 1 wood log task rejected proven oak_log mining because wood_log was compared as a literal block ID. Resolve generic wood names to 1.19 log species and give the Creative critic the actual mining rules. Physical block evidence remains required; chat and inventory alone cannot prove mining. Three regression cases and 38 related checks passed.

tests/test_creative_mine_evidence.py ADDED
@@ -0,0 +1,101 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """A proven Creative mine without drops must pass; chat/give fakery must not.
2
+
3
+ Live case: Naldo mined oak_log (verified block change, inventory oak_log 1, no
4
+ drops in Creative) for "Mine 1 wood log", yet completed stayed 0: the generic
5
+ name matched nothing, so evidence read "Verified 0/1 distinct wood_log" and
6
+ both the model and the deny-only veto failed a real mine.
7
+ """
8
+ from __future__ import annotations
9
+
10
+ import sys
11
+ from pathlib import Path
12
+
13
+ ROOT = Path(__file__).resolve().parents[1]
14
+ sys.path.insert(0, str(ROOT))
15
+
16
+ import voyager_verification as verification
17
+ from voyager_creative_critic import CREATIVE_EVIDENCE_FACTS, VerifiedCreativeCritic
18
+
19
+
20
+ def _live_mine_events(log_name="oak_log"):
21
+ """Final observation shaped like the live Naldo attempt."""
22
+ return [("observe", {
23
+ "inventory": {log_name: 1, "netherite_axe": 1},
24
+ "status": {
25
+ "position": {"x": 33.38, "y": 67.0, "z": -7.52},
26
+ "equipment": ["netherite_helmet", "netherite_chestplate",
27
+ "netherite_leggings", "netherite_boots",
28
+ "netherite_axe", None],
29
+ "health": 20.0,
30
+ "food": 20.0,
31
+ },
32
+ "verified_actions": [
33
+ {"kind": "mine", "name": log_name, "positions": [[33, 66, -7]]}
34
+ ],
35
+ "step_start_position": {"x": 26.5, "y": 64.0, "z": -6.5},
36
+ })]
37
+
38
+
39
+ def test_proven_creative_mine_passes_without_drops():
40
+ # Every 1.19 log species satisfies the generic "wood log" objective.
41
+ for log_name in ("oak_log", "birch_log", "spruce_log", "jungle_log",
42
+ "acacia_log", "dark_oak_log", "mangrove_log"):
43
+ supported, evidence = verification.verify_task(
44
+ _live_mine_events(log_name), "Mine 1 wood log")
45
+ assert supported is True, f"{log_name}: {evidence}"
46
+ assert verification.mechanical_verdict(
47
+ _live_mine_events(log_name), "Mine 1 wood log") is True
48
+ # Stone is not wood: the alias must not become a wildcard.
49
+ supported, _ = verification.verify_task(
50
+ _live_mine_events("stone"), "Mine 1 wood log")
51
+ assert supported is False
52
+
53
+
54
+ def test_give_or_chat_alone_never_proves_a_mine():
55
+ # creativeGiveItem/chat fakery: inventory present, zero verified blocks.
56
+ events = [("observe", {
57
+ "inventory": {"oak_log": 1},
58
+ "status": {"position": {"x": 0.0, "y": 64.0, "z": 0.0}},
59
+ "verified_actions": [],
60
+ })]
61
+ supported, evidence = verification.verify_task(events, "Mine 1 wood log")
62
+ assert supported is False, evidence
63
+ assert verification.mechanical_verdict(events, "Mine 1 wood log") is False
64
+ # Obtaining is a different objective and still reads the inventory.
65
+ supported, _ = verification.verify_task(events, "Obtain 1 wood log")
66
+ assert supported is True
67
+
68
+
69
+ def test_wrapper_passes_real_mine_and_vetoes_fakery():
70
+ class Upstream:
71
+ def __init__(self):
72
+ self.calls = []
73
+
74
+ def check_task_success(self, *, events, task, context, chest_observation, max_retries=5):
75
+ self.calls.append({"context": context})
76
+ return True, "model says success"
77
+
78
+ upstream = Upstream()
79
+ critic = VerifiedCreativeCritic(upstream)
80
+ success, _ = critic.check_task_success(
81
+ events=_live_mine_events(), task="Mine 1 wood log",
82
+ context="ctx", chest_observation={}, max_retries=5)
83
+ assert success is True
84
+ context = upstream.calls[0]["context"]
85
+ assert "Independent world observation" in context
86
+ assert "Verified 1/1" in context
87
+ assert "no item drops" in context
88
+ assert CREATIVE_EVIDENCE_FACTS in context
89
+
90
+ upstream = Upstream()
91
+ critic = VerifiedCreativeCritic(upstream)
92
+ fake = [("observe", {
93
+ "inventory": {"oak_log": 1},
94
+ "status": {"position": {"x": 0.0, "y": 64.0, "z": 0.0}},
95
+ "verified_actions": [],
96
+ })]
97
+ success, reason = critic.check_task_success(
98
+ events=fake, task="Mine 1 wood log",
99
+ context="ctx", chest_observation={}, max_retries=5)
100
+ assert success is False, "chat/give fakery must not complete a Mine task"
101
+ assert "contradicts" in reason
voyager_creative_critic.py CHANGED
@@ -16,6 +16,19 @@ from __future__ import annotations
16
  from voyager_verification import mechanical_verdict, verify_task
17
 
18
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
  class VerifiedCreativeCritic:
20
  def __init__(self, upstream_critic):
21
  self.upstream = upstream_critic
@@ -32,7 +45,7 @@ class VerifiedCreativeCritic:
32
  model_success, model_critique = self.upstream.check_task_success(
33
  events=events,
34
  task=task,
35
- context=f"{context}\nIndependent world observation: {evidence}",
36
  chest_observation=chest_observation,
37
  max_retries=max_retries,
38
  )
 
16
  from voyager_verification import mechanical_verdict, verify_task
17
 
18
 
19
+ # Facts the upstream critic prompt cannot know about this deployment. Appended
20
+ # to the context (never to critic.txt, never to an official method): Creative
21
+ # mining breaks blocks without drops, so only the verified-blocks line proves
22
+ # a mine; 'wood log' is any 1.19 log; obtaining is not mining.
23
+ CREATIVE_EVIDENCE_FACTS = (
24
+ "Creative mode facts: mining changes blocks but produces no item drops, so "
25
+ "a Mine task is proven only by the verified-blocks line above, never by "
26
+ "inventory deltas, chat messages or /give commands. 'wood log' means any "
27
+ "1.19 log (oak, spruce, birch, jungle, acacia, dark_oak, mangrove). "
28
+ "Obtaining an item and mining a block are different objectives."
29
+ )
30
+
31
+
32
  class VerifiedCreativeCritic:
33
  def __init__(self, upstream_critic):
34
  self.upstream = upstream_critic
 
45
  model_success, model_critique = self.upstream.check_task_success(
46
  events=events,
47
  task=task,
48
+ context=f"{context}\nIndependent world observation: {evidence}\n{CREATIVE_EVIDENCE_FACTS}",
49
  chest_observation=chest_observation,
50
  max_retries=max_retries,
51
  )
voyager_verification.py CHANGED
@@ -237,6 +237,24 @@ def task_history(tasks, limit=48):
237
  return ", ".join(entries) or "None"
238
 
239
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
240
  # The objective kinds verify_task() actually has a mechanical postcondition for.
241
  # Anything else (kill, use, breed, ...) returns None from mechanical_verdict, so
242
  # the Critic keeps full authority over it.
@@ -289,20 +307,29 @@ def verify_task(events, task):
289
  return False, "Objective is not a simple quantified verb form; judge from the observation."
290
  kind, amount, name = spec
291
  if kind in {"mine", "place"}:
 
 
 
292
  positions = set()
 
293
  for evidence in final.get("verified_actions", []):
294
- if evidence.get("kind") != kind or evidence.get("name") != name:
295
  continue
 
296
  for position in evidence.get("positions", []):
297
  if isinstance(position, list) and len(position) == 3 and all(
298
  isinstance(v, (int, float)) and math.isfinite(v) for v in position
299
  ):
300
  positions.add(tuple(position))
 
 
 
301
  return len(positions) >= amount, (
302
- f"Verified {len(positions)}/{amount} distinct {name} blocks for {kind}."
303
  )
304
  if kind in {"obtain", "craft", "smelt", "cook"}:
305
- count = (final.get("inventory") or {}).get(name, 0)
 
306
  return isinstance(count, (int, float)) and count >= amount, (
307
  f"Live Inventory has {count}/{amount} {name}."
308
  )
 
237
  return ", ".join(entries) or "None"
238
 
239
 
240
+ # "Mine 1 wood log" names no species. In 1.19 a log is one of these overworld
241
+ # logs (stems are not logs). A generic name must match any of them, in mined
242
+ # blocks and in inventory alike; otherwise a real oak_log mine reads as
243
+ # "Verified 0/1 distinct wood_log" and both the model and the veto fail it.
244
+ WOOD_LOG_IDS = frozenset({
245
+ "oak_log", "spruce_log", "birch_log", "jungle_log",
246
+ "acacia_log", "dark_oak_log", "mangrove_log",
247
+ })
248
+ GENERIC_WOOD_NAMES = frozenset({"wood_log", "log", "logs", "wood", "woods"})
249
+
250
+
251
+ def _resolve_names(name):
252
+ """Expand a generic item name to the concrete 1.19 IDs it can mean."""
253
+ if name in GENERIC_WOOD_NAMES:
254
+ return sorted(WOOD_LOG_IDS)
255
+ return [name]
256
+
257
+
258
  # The objective kinds verify_task() actually has a mechanical postcondition for.
259
  # Anything else (kill, use, breed, ...) returns None from mechanical_verdict, so
260
  # the Critic keeps full authority over it.
 
307
  return False, "Objective is not a simple quantified verb form; judge from the observation."
308
  kind, amount, name = spec
309
  if kind in {"mine", "place"}:
310
+ # Mining is proven by changed blocks, never by inventory: Creative
311
+ # produces no drops, and a /give or chat message is not a mine.
312
+ wanted = _resolve_names(name)
313
  positions = set()
314
+ matched = set()
315
  for evidence in final.get("verified_actions", []):
316
+ if evidence.get("kind") != kind or evidence.get("name") not in wanted:
317
  continue
318
+ matched.add(evidence.get("name"))
319
  for position in evidence.get("positions", []):
320
  if isinstance(position, list) and len(position) == 3 and all(
321
  isinstance(v, (int, float)) and math.isfinite(v) for v in position
322
  ):
323
  positions.add(tuple(position))
324
+ detail = name if len(wanted) == 1 else (
325
+ f"{name} (as {', '.join(sorted(matched)) if matched else 'no matching log mined'})"
326
+ )
327
  return len(positions) >= amount, (
328
+ f"Verified {len(positions)}/{amount} distinct {detail} blocks for {kind}."
329
  )
330
  if kind in {"obtain", "craft", "smelt", "cook"}:
331
+ inventory = final.get("inventory") or {}
332
+ count = sum(inventory.get(item, 0) for item in _resolve_names(name))
333
  return isinstance(count, (int, float)) and count >= amount, (
334
  f"Live Inventory has {count}/{amount} {name}."
335
  )