Spaces:
Running on Zero
Running on Zero
Delete files harness/guard.py harness/teacher.yaml with huggingface_hub
Browse files- harness/guard.py +0 -133
- harness/teacher.yaml +0 -178
harness/guard.py
DELETED
|
@@ -1,133 +0,0 @@
|
|
| 1 |
-
"""Harness-boundary guard and repetition helpers, copied from Video-Coder's video_coder_harness/agent.py
|
| 2 |
-
(GuardedAgent) without the mini-swe-agent dependency. Apache-2.0, https://github.com/CYWang735/Video-Coder"""
|
| 3 |
-
|
| 4 |
-
import difflib
|
| 5 |
-
import re
|
| 6 |
-
import shlex
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
TOOLS = ("run_blender", "check_script")
|
| 10 |
-
# Shell-level rules: applied to the command with here-document bodies removed, so the Python written into main.py
|
| 11 |
-
# (comments like "frame -> /tmp", "x > 0") is not mistaken for shell redirections or commands.
|
| 12 |
-
_INSPECT = re.compile(rf"^\s*(?:sudo\s+)?(?:type|which|command\s+-v|cat|less|more|head|tail|sed|awk|file|readlink|"
|
| 13 |
-
rf"realpath|ls|stat|strings|od|xxd)\b[^;&|]*\b({'|'.join(TOOLS)})\b")
|
| 14 |
-
_ENV_CMD = re.compile(r"(?m)(?:^|[;&|(]|\$\()\s*(?:env|printenv|set|export\s+-p|declare\s+-[px])\s*(?:$|[|;&>)])")
|
| 15 |
-
# writes to an absolute path (redirection, tee, cp/mv/ln target, mkdir/touch), except /dev/null and /dev/std*
|
| 16 |
-
_ABS_WRITE = re.compile(r"(?:(?<![0-9&<])>>?|\btee(?:\s+-a)?|\b(?:mkdir|touch)(?:\s+-p)?)\s*['\"]?"
|
| 17 |
-
r"(/(?!dev/(?:null|stdout|stderr)\b)[^\s'\";|&]*)")
|
| 18 |
-
_ABS_TARGET = re.compile(r"\b(?:cp|mv|ln|install|rsync)\b[^\n;|&]*\s['\"]?(/(?!dev/null\b)[^\s'\";|&]+)['\"]?\s*(?:$|[;|&\n])")
|
| 19 |
-
# Whole-text rules (here-document bodies included, so `python3 - <<EOF ... open(...)` is covered too):
|
| 20 |
-
# harness variables and locations that hold credentials or lie outside the task directory.
|
| 21 |
-
_HARNESS_VAR = re.compile(r"\bVCSFT_")
|
| 22 |
-
_SENSITIVE = re.compile(r"(~/|\$\{?HOME\b|/home/|/root/|\.ssh/|\.netrc\b|\.config/|\.env\b|\bOPENAI_API_KEY\b|"
|
| 23 |
-
r"\b[A-Z0-9_]*(?:API_KEY|SECRET|TOKEN|PASSWORD)\b|\bsk-[A-Za-z0-9_-]{8,})")
|
| 24 |
-
_HEREDOC = re.compile(r"<<-?\s*(['\"]?)([A-Za-z_][A-Za-z0-9_]*)\1")
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
def strip_heredocs(command: str) -> str:
|
| 28 |
-
"""The shell text of a command without here-document bodies (the delimiter lines are dropped too)."""
|
| 29 |
-
lines, out, i = command.split("\n"), [], 0
|
| 30 |
-
while i < len(lines):
|
| 31 |
-
line = lines[i]
|
| 32 |
-
out.append(line)
|
| 33 |
-
i += 1
|
| 34 |
-
for m in _HEREDOC.finditer(line):
|
| 35 |
-
while i < len(lines) and lines[i].strip() != m.group(2):
|
| 36 |
-
i += 1
|
| 37 |
-
i += 1 # the closing delimiter
|
| 38 |
-
return "\n".join(out)
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
# Python code (also inside here-documents) that writes to an absolute path.
|
| 42 |
-
_PY_ABS_WRITE = [
|
| 43 |
-
re.compile(r"""\bopen\(\s*[rbuf]*(['"])(/[^'"]*)\1\s*,\s*[rbuf]*(['"])[^'"]*[wax+][^'"]*\3"""),
|
| 44 |
-
re.compile(r"""\bPath\(\s*(['"])(/[^'"]*)\1\s*\)\s*\.\s*(?:write_text|write_bytes|mkdir|touch|unlink|rename|replace)\b"""),
|
| 45 |
-
re.compile(r"""\bos\.(?:makedirs|mkdir|rename|replace|remove|unlink|rmdir)\(\s*(['"])(/[^'"]*)\1"""),
|
| 46 |
-
re.compile(r"""\bfilepath\s*=\s*[rbuf]*(['"])(/[^'"]*)\1"""),
|
| 47 |
-
]
|
| 48 |
-
_PARENT_DIR = re.compile(r"(?<![\w.])\.\./")
|
| 49 |
-
_SEPARATORS = set("();<>|&")
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
def in_dir(path: str, directory: str) -> bool:
|
| 53 |
-
"""True if `path` is `directory` itself or lies below it ('/w-other' is not inside '/w')."""
|
| 54 |
-
d = directory.rstrip("/")
|
| 55 |
-
return bool(d) and (path == d or path.startswith(d + "/"))
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
def _mask_workdir(text: str, workdir: str) -> str:
|
| 59 |
-
"""Replace the task directory by '.' in `text`, but not a longer sibling name such as '<workdir>-other'."""
|
| 60 |
-
if not workdir:
|
| 61 |
-
return text
|
| 62 |
-
return re.sub(re.escape(workdir.rstrip("/")) + r"(?![A-Za-z0-9_.-])", ".", text)
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
def _is_separator(token: str | None) -> bool:
|
| 66 |
-
return bool(token) and all(c in _SEPARATORS for c in token)
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
def _shell_path_violation(shell: str, workdir: str) -> str:
|
| 70 |
-
"""Rules on shell words: `..` path components and `cd` to somewhere outside the task directory."""
|
| 71 |
-
text = shell.replace("\\\n", " ").replace("\n", " ; ")
|
| 72 |
-
try:
|
| 73 |
-
lex = shlex.shlex(text, posix=True, punctuation_chars=True)
|
| 74 |
-
lex.whitespace_split = True
|
| 75 |
-
tokens = list(lex)
|
| 76 |
-
except ValueError: # unbalanced quotes etc.: the regex rules still apply
|
| 77 |
-
return ""
|
| 78 |
-
for i, tok in enumerate(tokens):
|
| 79 |
-
if tok == ".." or tok.startswith("../") or "/../" in tok or tok.endswith("/.."):
|
| 80 |
-
return "it reaches outside the task directory with '..'"
|
| 81 |
-
if tok in ("cd", "pushd") and (i == 0 or _is_separator(tokens[i - 1])):
|
| 82 |
-
nxt = tokens[i + 1] if i + 1 < len(tokens) else ""
|
| 83 |
-
target = "" if _is_separator(nxt) else nxt
|
| 84 |
-
if not target or target.startswith(("~", "$", "-")):
|
| 85 |
-
return f"`{tok}` without a path inside the task directory leaves it"
|
| 86 |
-
if target.startswith("/") and not target.startswith("/dev/") and not in_dir(target, workdir):
|
| 87 |
-
return f"it changes into a directory outside the task directory ({target})"
|
| 88 |
-
return ""
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
def harness_violation(command: str, harness_markers: list[str], workdir: str = "") -> str:
|
| 92 |
-
"""Reason why a command crosses the harness boundary, or "" if it is fine."""
|
| 93 |
-
for marker in harness_markers:
|
| 94 |
-
if marker and marker in command:
|
| 95 |
-
return f"it references the harness installation ({marker})"
|
| 96 |
-
if _HARNESS_VAR.search(command):
|
| 97 |
-
return "it reads the harness environment variables"
|
| 98 |
-
m = _SENSITIVE.search(_mask_workdir(command, workdir)) # the task directory itself is fine
|
| 99 |
-
if m:
|
| 100 |
-
return f"it references files or variables outside the task directory ({m.group(1)})"
|
| 101 |
-
if _PARENT_DIR.search(command):
|
| 102 |
-
return "it reaches outside the task directory with '../'"
|
| 103 |
-
for pattern in _PY_ABS_WRITE: # whole text: covers `python3 -c` and here-documents
|
| 104 |
-
for m in pattern.finditer(command):
|
| 105 |
-
path = m.group(2)
|
| 106 |
-
if not (path.startswith("/dev/") or in_dir(path, workdir)):
|
| 107 |
-
return f"it writes outside the task directory ({path})"
|
| 108 |
-
shell = strip_heredocs(command)
|
| 109 |
-
for line in shell.splitlines():
|
| 110 |
-
if _INSPECT.search(line):
|
| 111 |
-
return f"it inspects how {', '.join(TOOLS)} are implemented"
|
| 112 |
-
if _ENV_CMD.search(shell):
|
| 113 |
-
return "it reads the environment variables"
|
| 114 |
-
for pattern in (_ABS_WRITE, _ABS_TARGET):
|
| 115 |
-
for m in pattern.finditer(shell):
|
| 116 |
-
if not in_dir(m.group(1), workdir):
|
| 117 |
-
return f"it writes outside the task directory ({m.group(1)})"
|
| 118 |
-
return _shell_path_violation(shell, workdir)
|
| 119 |
-
|
| 120 |
-
|
| 121 |
-
BLOCKED = ("BLOCKED: this command was not executed because {reason}. Work only in the current directory and use "
|
| 122 |
-
"only check_script and run_blender; do not inspect or modify the harness or run Blender any other way. "
|
| 123 |
-
"For a quick experiment, write a small script in the current directory and run `run_blender "
|
| 124 |
-
"experiment.py`; its print() output appears in the blender log.")
|
| 125 |
-
REPEAT_WARNING = ("\n\nWARNING: this is the {n}th consecutive near-identical command with the same result. Repeating "
|
| 126 |
-
"it will not help: read the output, then change the script or your approach. The task ends after "
|
| 127 |
-
"{stop} repetitions.")
|
| 128 |
-
|
| 129 |
-
|
| 130 |
-
|
| 131 |
-
|
| 132 |
-
def _normalize_output(text: str) -> str:
|
| 133 |
-
return re.sub(r"\d+(?:\.\d+)?", "#", text or "")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
harness/teacher.yaml
DELETED
|
@@ -1,178 +0,0 @@
|
|
| 1 |
-
# Teacher trajectory generation: mini-swe-agent (tool-call mode) + OpenAI official API.
|
| 2 |
-
# The system/instance templates below become the SFT prompts, so the student must later run
|
| 3 |
-
# with the same templates. Changing them requires a new output directory.
|
| 4 |
-
|
| 5 |
-
harness:
|
| 6 |
-
# Blender version the scripts must target. Set this to the version you will execute/render with
|
| 7 |
-
# later; the prompt tells the teacher to use exactly this API. Required for non-dry runs.
|
| 8 |
-
blender_version: "4.5"
|
| 9 |
-
# true: the agent gets `run_blender` (real execution + render) and a sample is accepted only if the harness
|
| 10 |
-
# re-executes the final script from scratch and it renders a valid video. false: static checks only.
|
| 11 |
-
blender_available: true
|
| 12 |
-
bpy_python: ${WORK}/envs/bpy45/bin/python # Python 3.11 + bpy 4.5.14
|
| 13 |
-
bpy_libs: ${WORK}/envs/bpy45-mesa # libGL/X11 libs + Mesa llvmpipe EGL (Workbench on CPU)
|
| 14 |
-
exec_timeout: 600 # seconds per execution, render included
|
| 15 |
-
summary_samples: 6 # frames sampled for the scene summary shown to the agent
|
| 16 |
-
content_samples: 8 # frames sampled for the empty / static / out-of-view checks
|
| 17 |
-
# run_blender returns evenly spaced frames of the render as images, so the agent inspects its own result
|
| 18 |
-
# (needs a video_coder_harness.models.Visual* model class)
|
| 19 |
-
visual_feedback: true
|
| 20 |
-
preview_frames: 8
|
| 21 |
-
preview_width: 640
|
| 22 |
-
# optional VLM check of the final render (video_coder_harness/judge.py); off: acceptance uses deterministic checks only
|
| 23 |
-
judge:
|
| 24 |
-
enabled: false
|
| 25 |
-
model: openai/gpt-6-astra
|
| 26 |
-
frames: 8
|
| 27 |
-
width: 640
|
| 28 |
-
min_score: 8
|
| 29 |
-
attempts: 2
|
| 30 |
-
output_video: output.mp4
|
| 31 |
-
fps: 24
|
| 32 |
-
frame_count: 120 # shortest video: 120 frames = 5 s at 24 fps
|
| 33 |
-
max_frame_count: 240 # longest video: 240 frames = 10 s; the model chooses the length in between
|
| 34 |
-
resolution: [1280, 720]
|
| 35 |
-
script_name: main.py
|
| 36 |
-
|
| 37 |
-
model:
|
| 38 |
-
# Native Responses API. Do not use `litellm` (Chat Completions): for gpt-6 models litellm bridges it to the
|
| 39 |
-
# Responses API and drops the function_call items, so every step fails with "No tool calls found".
|
| 40 |
-
# The Responses path also carries the model's encrypted reasoning across turns.
|
| 41 |
-
model_class: video_coder_harness.models.VisualResponseModel # litellm_response + images in tool results (run_blender preview)
|
| 42 |
-
model_name: openai/gpt-6-astra # litellm "<provider>/<model id>"; confirm the exact id on your OpenAI account
|
| 43 |
-
cost_tracking: ignore_errors # litellm may not know the price of a new model; budget is enforced by max_calls
|
| 44 |
-
# litellm_model_registry: configs/model_registry.json # optional: register prices to get real cost numbers
|
| 45 |
-
model_kwargs:
|
| 46 |
-
drop_params: true
|
| 47 |
-
parallel_tool_calls: false # one command per step gives cleaner trajectories
|
| 48 |
-
# reasoning_effort: high
|
| 49 |
-
observation_template: |
|
| 50 |
-
{%- if output.output | length < 10000 -%}
|
| 51 |
-
{
|
| 52 |
-
"returncode": {{ output.returncode }},
|
| 53 |
-
"output": {{ output.output | tojson }}
|
| 54 |
-
{%- if output.exception_info %}, "exception_info": {{ output.exception_info | tojson }}{% endif %}
|
| 55 |
-
}
|
| 56 |
-
{%- else -%}
|
| 57 |
-
{
|
| 58 |
-
"returncode": {{ output.returncode }},
|
| 59 |
-
"output_head": {{ output.output[:5000] | tojson }},
|
| 60 |
-
"output_tail": {{ output.output[-5000:] | tojson }},
|
| 61 |
-
"elided_chars": {{ output.output | length - 10000 }},
|
| 62 |
-
"warning": "Output too long."
|
| 63 |
-
{%- if output.exception_info %}, "exception_info": {{ output.exception_info | tojson }}{% endif %}
|
| 64 |
-
}
|
| 65 |
-
{%- endif -%}
|
| 66 |
-
format_error_template: |
|
| 67 |
-
{% if finish_reason is defined and (finish_reason == "length" or (finish_reason == "tool_calls" and not has_tool_calls)) -%}
|
| 68 |
-
Your previous response reached the output token limit (finish_reason={{ finish_reason }}) before you produced a tool call, so it was cut off. Respond more concisely and finish with exactly one bash tool call.
|
| 69 |
-
{%- else -%}
|
| 70 |
-
Tool call error:
|
| 71 |
-
|
| 72 |
-
<error>
|
| 73 |
-
{{error}}
|
| 74 |
-
</error>
|
| 75 |
-
|
| 76 |
-
Every response must call the 'bash' tool exactly once with {"command": "..."}.
|
| 77 |
-
To finish, run `echo COMPLETE_TASK_AND_SUBMIT_FINAL_OUTPUT` on its own.
|
| 78 |
-
{%- endif %}
|
| 79 |
-
|
| 80 |
-
environment:
|
| 81 |
-
environment_class: local
|
| 82 |
-
timeout: 900 # per command; run_blender includes a full render
|
| 83 |
-
env:
|
| 84 |
-
PAGER: cat
|
| 85 |
-
MANPAGER: cat
|
| 86 |
-
PIP_PROGRESS_BAR: "off"
|
| 87 |
-
TQDM_DISABLE: "1"
|
| 88 |
-
|
| 89 |
-
agent:
|
| 90 |
-
step_limit: 30
|
| 91 |
-
cost_limit: 0 # per-task cost limit is meaningless without price data; use step_limit
|
| 92 |
-
wall_time_limit_seconds: 1800
|
| 93 |
-
max_consecutive_format_errors: 3
|
| 94 |
-
repeat_warn: 3 # warn from the 3rd consecutive near-identical command with the same result
|
| 95 |
-
repeat_stop: 6 # ... and end the task (exit status RepeatedActions) at the 6th
|
| 96 |
-
system_template: |
|
| 97 |
-
You are an expert Blender technical artist and Python programmer. You write Blender Python (bpy) scripts that build and animate 3D scenes and render them as untextured white-model (clay) videos. You work in a Linux shell through a bash tool.
|
| 98 |
-
instance_template: |
|
| 99 |
-
## Task
|
| 100 |
-
|
| 101 |
-
Write a Blender {{ blender_version }} Python script that builds the following scene and renders it as an animated white-model video:
|
| 102 |
-
|
| 103 |
-
<scene_description>
|
| 104 |
-
{{ task }}
|
| 105 |
-
</scene_description>
|
| 106 |
-
|
| 107 |
-
|
| 108 |
-
## Script contract
|
| 109 |
-
|
| 110 |
-
- Write exactly one self-contained script: `{{ script_name }}` in the current directory.
|
| 111 |
-
- It is executed headless as `blender --background --factory-startup --python {{ script_name }}` with Blender {{ blender_version }}, from the directory the video must be written to. Use only the Blender {{ blender_version }} API and the Python standard library; no external assets, files or add-ons.
|
| 112 |
-
- Start by removing every object in the default scene, then build the scene from scratch.
|
| 113 |
-
- Geometry: model with primitives, modifiers, `bmesh` or `mesh.from_pydata`. Name objects meaningfully. Keep the total scene under ~500k faces. Objects must not intersect unless the description implies contact.
|
| 114 |
-
{% if max_frame_count > frame_count -%}
|
| 115 |
-
- Animation: the video lasts {{ (frame_count / fps) | round | int }}-{{ (max_frame_count / fps) | round | int }} seconds at {{ fps }} fps, i.e. {{ frame_count }}-{{ max_frame_count }} frames; choose the length that suits the action. Set `scene.frame_start = 1` and `scene.frame_end` to that length, and animate with keyframes, drivers, constraints or physics so that the motion described actually happens within that range. Bake any physics simulation before rendering.
|
| 116 |
-
{%- else -%}
|
| 117 |
-
- Animation: the video has {{ frame_count }} frames at {{ fps }} fps. Set `scene.frame_start = 1` and `scene.frame_end = {{ frame_count }}`, and animate with keyframes, drivers, constraints or physics so that the motion described actually happens within that range. Bake any physics simulation before rendering.
|
| 118 |
-
{%- endif %}
|
| 119 |
-
- Camera: create a camera, assign it to `scene.camera`, and frame the action so that it stays in view for all frames (animate the camera if the description asks for camera motion).
|
| 120 |
-
- Seed any randomness (`random.seed(...)`) so the result is deterministic. Prefer `bpy.data` / low-level API over `bpy.ops` where the operator needs UI context.
|
| 121 |
-
- Do not import subprocess, socket, shutil, urllib or similar modules, and do not call os.system/eval/exec.
|
| 122 |
-
|
| 123 |
-
## White-model render (required, at the end of the script)
|
| 124 |
-
|
| 125 |
-
Every video in this dataset uses the same look, so configure exactly this and then render:
|
| 126 |
-
|
| 127 |
-
- `scene.render.engine = 'BLENDER_WORKBENCH'`
|
| 128 |
-
- `scene.display.shading`: `light = 'STUDIO'`, `color_type = 'SINGLE'`, `single_color = (0.8, 0.8, 0.8)`, `show_shadows = True`, `show_cavity = True`, `cavity_type = 'WORLD'`. No materials or textures are needed; Workbench studio lighting ignores scene lights.
|
| 129 |
-
- Resolution {{ resolution[0] }}x{{ resolution[1] }} at `resolution_percentage = 100`, `scene.render.fps = {{ fps }}`.
|
| 130 |
-
- Output: `scene.render.image_settings.file_format = 'FFMPEG'`, `scene.render.ffmpeg.format = 'MPEG4'`, `scene.render.ffmpeg.codec = 'H264'`, `scene.render.filepath = os.path.join(os.getcwd(), '{{ output_video }}')`.
|
| 131 |
-
- Last statement: `bpy.ops.render.render(animation=True)`.
|
| 132 |
-
- Write no other files: no .blend files, images or caches outside the render. Do not quit Blender.
|
| 133 |
-
|
| 134 |
-
## Tools
|
| 135 |
-
|
| 136 |
-
{% if blender_available -%}
|
| 137 |
-
Blender {{ blender_version }} is available through two commands (do not install anything):
|
| 138 |
-
|
| 139 |
-
```bash
|
| 140 |
-
check_script {{ script_name }} # fast static check: syntax, bpy import, camera, render call, forbidden calls
|
| 141 |
-
run_blender {{ script_name }} # executes the script exactly like the harness and renders {{ output_video }} (CPU, ~20-120 s)
|
| 142 |
-
```
|
| 143 |
-
|
| 144 |
-
`run_blender` reports the Python traceback if the script fails, whether {{ output_video }} has {% if max_frame_count > frame_count %}{{ frame_count }}-{{ max_frame_count }} frames matching the scene frame range{% else %}{{ frame_count }} frames{% endif %} at {{ resolution[0] }}x{{ resolution[1] }}, and a scene summary: object counts, animated objects, world positions of moving meshes at sampled frames, and meshes whose origin is outside the camera view. Use the numbers to verify that the motion, timing and framing match the description.{% if visual_feedback %} It also returns {{ preview_frames }} frames sampled evenly from the rendered video as images, with their frame numbers and times.{% endif %} It reports a problem when most frames are empty, nothing moves, or no object is in view.
|
| 145 |
-
{%- else -%}
|
| 146 |
-
Blender is NOT installed in this environment and must not be installed (no `pip install bpy`, no downloads). You cannot execute the script, so reason carefully about the Blender {{ blender_version }} API: exact operator names, property names and argument names.
|
| 147 |
-
The only verification tool is the static checker:
|
| 148 |
-
|
| 149 |
-
```bash
|
| 150 |
-
check_script {{ script_name }}
|
| 151 |
-
```
|
| 152 |
-
|
| 153 |
-
It checks syntax, required structure (bpy import, camera, render call) and forbidden calls. Passing it does not prove the script runs, so review the code yourself before submitting.
|
| 154 |
-
{%- endif %}
|
| 155 |
-
|
| 156 |
-
## Workflow
|
| 157 |
-
|
| 158 |
-
1. Think & Plan: Before generating any code, explicitly reason about the scene: objects, their dimensions and positions in meters, the motion over time (frame timing), and camera placement/framing.
|
| 159 |
-
2. Write `{{ script_name }}` with a heredoc: `cat <<'EOF' > {{ script_name }}` ... `EOF`.
|
| 160 |
-
3. Run `check_script {{ script_name }}` and fix every error.
|
| 161 |
-
{% if blender_available -%}
|
| 162 |
-
4. Run `run_blender {{ script_name }}`. Fix every error and problem it reports, and compare the scene summary against the description (positions, timing, clearances, camera framing).{% if visual_feedback %}
|
| 163 |
-
Then inspect the returned frames one by one against the description and write down every concrete problem you see: missing, unrecognizable or wrongly counted objects; wrong arrangement; motion that is missing, in the wrong order or at the wrong time; objects cut off, outside the frame or too small; objects passing through each other; wrong camera behavior. Fix the problems in the script and run `run_blender` again.{% endif %}
|
| 164 |
-
Repeat until it reports RUN OK{% if visual_feedback %} and the frames clearly show every element of the description{% else %} and the numbers match{% endif %}. If a command keeps giving the same result, change the script or your approach instead of repeating it; repeated identical commands end the task.
|
| 165 |
-
5. Submit with `echo COMPLETE_TASK_AND_SUBMIT_FINAL_OUTPUT` as a command on its own. You cannot continue after that. The harness then re-executes `{{ script_name }}` from scratch; the task only counts if it renders a valid video{% if judge_enabled %} and a reviewer who compares frames of the video against the description confirms that every element it describes (objects, arrangement, motion, camera) is clearly shown{% endif %}.
|
| 166 |
-
{%- else -%}
|
| 167 |
-
4. Re-read the script (`nl -ba {{ script_name }}`) and check API usage, coordinates, keyframe timing and camera framing against the description; fix problems with a rewrite or `sed -i`.
|
| 168 |
-
5. When the script is final and passes the check, submit with `echo COMPLETE_TASK_AND_SUBMIT_FINAL_OUTPUT` as a command on its own. You cannot continue after that.
|
| 169 |
-
{%- endif %}
|
| 170 |
-
|
| 171 |
-
## Command rules
|
| 172 |
-
|
| 173 |
-
- Each command runs in a fresh subshell in the task directory; `cd` and environment variables do not persist.
|
| 174 |
-
- Only create or modify files in the current directory.
|
| 175 |
-
- Use only `check_script` and `run_blender` to check and execute scripts. Do not inspect or modify the harness itself (how these commands are implemented, `VCSFT_*` environment variables, the Blender/bpy installation) and do not run Blender or bpy any other way; such commands are blocked.
|
| 176 |
-
- For a quick experiment, write a small script in the current directory and run `run_blender experiment.py`: whatever it `print()`s appears in the blender log. Delete experiment files before submitting.
|
| 177 |
-
|
| 178 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|