systems rework: buffs, prestige graph, research, modular architecture
Decouples core systems from centralized globals in favor of catalogue-based architecture and data-driven content. Key changes: - Prestige: node-based buff tech tree, graph panel, progress bar - Research: multi-worker system, per-generator research, xp generation - Ascension: meta-currency layer via multi-currency prestige resets - Buffs: refactored as generator-independent via catalogues - UI: currency panel, edge-scrolling camera + zoom, current-goal panel - Alchemy Tower: crafting building with recipe/cost system - Testing: 7 test suites (ascension, prestige, research, goals) - Content: migrated from hardcoded idles/ to gym format (tiny_sword)
This commit was merged in pull request #1.
This commit is contained in:
642
.agents/skills/godot-test-instrumentation/SKILL.md
Normal file
642
.agents/skills/godot-test-instrumentation/SKILL.md
Normal file
@@ -0,0 +1,642 @@
|
||||
---
|
||||
name: godot-test-instrumentation
|
||||
description: Automates Godot testing for coding agents via headless execution, method simulation, and stdout validation.
|
||||
---
|
||||
|
||||
# Godot Test Instrumentation
|
||||
|
||||
Enables coding agents to automatically test game changes by running Godot in headless mode, simulating user actions via method calls, and validating results through structured stdout output.
|
||||
|
||||
## Use This Skill When
|
||||
|
||||
- A coding agent needs to verify gameplay changes work correctly
|
||||
- Testing generator purchases, currency gains, prestige mechanics, or buff systems
|
||||
- Running automated validation in CI/CD pipelines
|
||||
- Debugging features without manual GUI interaction
|
||||
|
||||
## Core Workflow
|
||||
|
||||
```
|
||||
1. Agent writes/modifies game code
|
||||
↓
|
||||
2. Agent creates or updates test script in res://tests/
|
||||
↓
|
||||
3. Agent runs: godot --headless --path . -s res://tests/test_x.gd
|
||||
↓
|
||||
4. Test loads gym scene, simulates actions, prints [TAG] results
|
||||
↓
|
||||
5. Agent parses stdout for [PASS]/[FAIL]/[RESULT]
|
||||
↓
|
||||
6. Exit code 0 = PASS, Exit code 1 = FAIL
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Directory Structure
|
||||
|
||||
```
|
||||
res://
|
||||
├── tests/
|
||||
│ ├── test_runner.gd # Cross-platform test discovery & execution
|
||||
│ ├── test_utils.gd # Shared assertion helpers
|
||||
│ ├── test_gold_mine_click.gd # Example: generator click → currency gain
|
||||
│ └── test_prestige.gd # Example: prestige mechanics validation
|
||||
├── docs/gyms/ # Test scenes (e.g., tiny_sword.tscn)
|
||||
└── core/ # Game logic (LevelGameState, generators, etc.)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 1. Test Utils (res://tests/test_utils.gd)
|
||||
|
||||
Shared assertion helpers with structured stdout output.
|
||||
|
||||
```gdscript
|
||||
class_name TestUtils
|
||||
extends RefCounted
|
||||
|
||||
static var _test_name: String = ""
|
||||
static var _passed: int = 0
|
||||
static var _failed: int = 0
|
||||
|
||||
static func set_test_name(name: String) -> void:
|
||||
_test_name = name
|
||||
_passed = 0
|
||||
_failed = 0
|
||||
|
||||
static func assert_equals(expected: Variant, actual: Variant, message: String) -> void:
|
||||
if expected == actual:
|
||||
print("[PASS] %s: %s" % [_test_name, message])
|
||||
_passed += 1
|
||||
else:
|
||||
print("[FAIL] %s: %s (expected %s, got %s)" % [
|
||||
_test_name, message, str(expected), str(actual)
|
||||
])
|
||||
_failed += 1
|
||||
|
||||
static func assert_true(condition: bool, message: String) -> void:
|
||||
if condition:
|
||||
print("[PASS] %s: %s" % [_test_name, message])
|
||||
_passed += 1
|
||||
else:
|
||||
print("[FAIL] %s: %s" % [_test_name, message])
|
||||
_failed += 1
|
||||
|
||||
static func assert_greater_than(a: float, b: float, message: String) -> void:
|
||||
if a > b:
|
||||
print("[PASS] %s: %s" % [_test_name, message])
|
||||
_passed += 1
|
||||
else:
|
||||
print("[FAIL] %s: %s (expected %s > %s)" % [_test_name, message, str(a), str(b)])
|
||||
_failed += 1
|
||||
|
||||
static func print_result() -> void:
|
||||
print("[SUMMARY] %s: %d passed, %d failed" % [_test_name, _passed, _failed])
|
||||
if _failed == 0:
|
||||
print("[RESULT] PASS")
|
||||
else:
|
||||
print("[RESULT] FAIL")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. Test Runner (res://tests/test_runner.gd)
|
||||
|
||||
Cross-platform test discovery and execution. Runs all `test_*.gd` scripts in the tests directory.
|
||||
|
||||
```gdscript
|
||||
extends SceneTree
|
||||
|
||||
var test_scripts: Array[String] = []
|
||||
var test_index: int = 0
|
||||
var total_passed: int = 0
|
||||
var total_failed: int = 0
|
||||
|
||||
func _init():
|
||||
print("\n=== GODOT TEST RUNNER ===\n")
|
||||
_discover_tests()
|
||||
|
||||
if test_scripts.is_empty():
|
||||
print("[ERROR] No test scripts found in res://tests/")
|
||||
quit(1)
|
||||
return
|
||||
|
||||
print("Found %d test(s): %s\n" % [test_scripts.size(), str(test_scripts)])
|
||||
await _run_next_test()
|
||||
|
||||
print("\n=== TEST SUMMARY ===")
|
||||
print("Total passed: %d" % total_passed)
|
||||
print("Total failed: %d" % total_failed)
|
||||
|
||||
if total_failed == 0:
|
||||
print("[OVERALL_RESULT] PASS")
|
||||
quit(0)
|
||||
else:
|
||||
print("[OVERALL_RESULT] FAIL")
|
||||
quit(1)
|
||||
|
||||
func _discover_tests() -> void:
|
||||
var dir = DirAccess.open("res://tests")
|
||||
if dir:
|
||||
dir.list_dir_begin()
|
||||
var file_name = dir.get_next()
|
||||
while file_name != "":
|
||||
if file_name.ends_with(".gd") and file_name.begins_with("test_") and file_name != "test_runner.gd" and file_name != "test_utils.gd":
|
||||
test_scripts.append("res://tests/%s" % file_name)
|
||||
file_name = dir.get_next()
|
||||
dir.list_dir_end()
|
||||
test_scripts.sort()
|
||||
|
||||
func _run_next_test() -> void:
|
||||
if test_index >= test_scripts.size():
|
||||
return
|
||||
|
||||
var test_script = test_scripts[test_index]
|
||||
print("--- Running test %d/%d: %s ---" % [test_index + 1, test_scripts.size(), test_script])
|
||||
|
||||
var test_instance = load(test_script).new()
|
||||
await test_instance.run()
|
||||
|
||||
total_passed += test_instance.passed
|
||||
total_failed += test_instance.failed
|
||||
|
||||
test_index += 1
|
||||
await _run_next_test()
|
||||
|
||||
class TestScript:
|
||||
var passed: int = 0
|
||||
var failed: int = 0
|
||||
|
||||
func run():
|
||||
pass
|
||||
```
|
||||
|
||||
**Usage:**
|
||||
```bash
|
||||
# Run all tests via test runner
|
||||
"$GODOT_BIN" --headless --path . -s res://tests/test_runner.gd
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Test Script Template
|
||||
|
||||
Each test extends `SceneTree` and implements a `run()` method.
|
||||
|
||||
```gdscript
|
||||
extends SceneTree
|
||||
|
||||
var passed: int = 0
|
||||
var failed: int = 0
|
||||
|
||||
func _init():
|
||||
await run()
|
||||
quit(0 if failed == 0 else 1)
|
||||
|
||||
func run():
|
||||
# Setup
|
||||
TestUtils.set_test_name("<test_name>")
|
||||
|
||||
# Load scene
|
||||
var scene = load("<path_to_gym_scene>") # e.g., "res://docs/gyms/tiny_sword/tiny_sword.tscn"
|
||||
var root_node = scene.instantiate()
|
||||
add_root_node(root_node)
|
||||
|
||||
# Wait for scene initialization
|
||||
await _wait()
|
||||
|
||||
# Get references
|
||||
var game_state: LevelGameState = root_node.find_child("LevelGameState")
|
||||
var generator = root_node.find_child("<GeneratorNodeName>") as CurrencyGenerator
|
||||
|
||||
if game_state == null:
|
||||
print("[ERROR] LevelGameState not found")
|
||||
_print_summary()
|
||||
return
|
||||
|
||||
# Test logic
|
||||
var initial_value = game_state.get_currency_amount_by_id(&"<currency_id>")
|
||||
print("[TARGET] initial_<currency>: %s" % initial_value.to_string_suffix(2))
|
||||
|
||||
# Simulate action (method call, not mouse click)
|
||||
generator._on_pressed()
|
||||
|
||||
# Wait for signal propagation
|
||||
await _wait()
|
||||
|
||||
# Validate
|
||||
var final_value = game_state.get_currency_amount_by_id(&"<currency_id>")
|
||||
print("[TARGET] final_<currency>: %s" % final_value.to_string_suffix(2))
|
||||
|
||||
TestUtils.assert_greater_than(
|
||||
final_value.mantissa,
|
||||
initial_value.mantissa,
|
||||
"<currency> increased after action"
|
||||
)
|
||||
|
||||
_print_summary()
|
||||
|
||||
func _wait() -> void:
|
||||
await get_tree().create_timer(0.5).timeout
|
||||
|
||||
func _print_summary():
|
||||
TestUtils.print_result()
|
||||
passed = _get_passed_count()
|
||||
failed = _get_failed_count()
|
||||
|
||||
func _get_passed_count() -> int:
|
||||
# Extract from TestUtils or track locally
|
||||
return 0
|
||||
|
||||
func _get_failed_count() -> int:
|
||||
return 0
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Example Test: Gold Mine Click
|
||||
|
||||
Tests that clicking a generator grants currency.
|
||||
|
||||
```gdscript
|
||||
extends SceneTree
|
||||
|
||||
var passed: int = 0
|
||||
var failed: int = 0
|
||||
var _local_passed: int = 0
|
||||
var _local_failed: int = 0
|
||||
|
||||
func _init():
|
||||
await run()
|
||||
quit(failed == 0 ? 0 : 1)
|
||||
|
||||
func run():
|
||||
print("\n=== TEST: Gold Mine Click ===\n")
|
||||
TestUtils.set_test_name("gold_mine_click")
|
||||
|
||||
# Load tiny_sword scene
|
||||
var scene = load("res://docs/gyms/tiny_sword/tiny_sword.tscn")
|
||||
var root_node = scene.instantiate()
|
||||
add_root_node(root_node)
|
||||
|
||||
await _wait()
|
||||
|
||||
var game_state: LevelGameState = root_node.find_child("LevelGameState")
|
||||
var gold_mine = root_node.find_child("GoldMine") as CurrencyGenerator
|
||||
|
||||
if game_state == null or gold_mine == null:
|
||||
print("[ERROR] Missing LevelGameState or GoldMine node")
|
||||
_print_summary()
|
||||
return
|
||||
|
||||
# Record initial gold
|
||||
var initial_gold: BigNumber = game_state.get_currency_amount_by_id(&"gold")
|
||||
print("[TARGET] initial_gold: %s" % initial_gold.to_string_suffix(2))
|
||||
|
||||
# Simulate clicking the generator
|
||||
gold_mine._on_pressed()
|
||||
|
||||
await _wait()
|
||||
|
||||
# Validate gold increased
|
||||
var final_gold: BigNumber = game_state.get_currency_amount_by_id(&"gold")
|
||||
print("[TARGET] final_gold: %s" % final_gold.to_string_suffix(2))
|
||||
|
||||
TestUtils.assert_greater_than(
|
||||
final_gold.mantissa,
|
||||
initial_gold.mantissa,
|
||||
"Gold increased after generator click"
|
||||
)
|
||||
|
||||
# Check specific value (adjust based on click_mantissa in data)
|
||||
var expected_min: float = initial_gold.mantissa + 1.0
|
||||
TestUtils.assert_true(
|
||||
final_gold.mantissa >= expected_min,
|
||||
"Gold increased by at least 1.0"
|
||||
)
|
||||
|
||||
_print_summary()
|
||||
|
||||
func _wait() -> void:
|
||||
await get_tree().create_timer(0.5).timeout
|
||||
|
||||
func _print_summary():
|
||||
TestUtils.print_result()
|
||||
# Note: For accurate counts, modify TestUtils to return counts or track locally
|
||||
_local_failed = 0 # Update based on actual assertions
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Example Test: Prestige Mechanics
|
||||
|
||||
Tests prestige threshold and currency gain.
|
||||
|
||||
```gdscript
|
||||
extends SceneTree
|
||||
|
||||
var passed: int = 0
|
||||
var failed: int = 0
|
||||
|
||||
func _init():
|
||||
await run()
|
||||
quit(failed == 0 ? 0 : 1)
|
||||
|
||||
func run():
|
||||
print("\n=== TEST: Prestige Mechanics ===\n")
|
||||
TestUtils.set_test_name("prestige")
|
||||
|
||||
var scene = load("res://docs/gyms/tiny_sword/tiny_sword.tscn")
|
||||
var root_node = scene.instantiate()
|
||||
add_root_node(root_node)
|
||||
|
||||
await _wait()
|
||||
|
||||
var game_state: LevelGameState = root_node.find_child("LevelGameState")
|
||||
var prestige_manager = root_node.find_child("PrestigeManager") as PrestigeManager
|
||||
|
||||
if game_state == null or prestige_manager == null:
|
||||
print("[ERROR] Missing LevelGameState or PrestigeManager")
|
||||
_print_summary()
|
||||
return
|
||||
|
||||
# Setup: Accumulate currencies to meet prestige threshold
|
||||
game_state.add_currency_by_id(&"gold", BigNumber.from_float(100000.0))
|
||||
game_state.add_currency_by_id(&"gems", BigNumber.from_float(100.0))
|
||||
|
||||
await _wait()
|
||||
|
||||
# Check if can prestige
|
||||
var can_prestige: bool = prestige_manager.can_prestige()
|
||||
print("[TARGET] can_prestige: %s" % str(can_prestige))
|
||||
|
||||
TestUtils.assert_true(can_prestige, "Should be able to prestige after accumulating currencies")
|
||||
|
||||
if can_prestige:
|
||||
# Perform prestige
|
||||
prestige_manager.perform_prestige()
|
||||
|
||||
await _wait()
|
||||
|
||||
# Check ascension currency
|
||||
var ascension: BigNumber = game_state.get_currency_amount_by_id(&"ascension")
|
||||
print("[TARGET] ascension_currency: %s" % ascension.to_string_suffix(2))
|
||||
|
||||
TestUtils.assert_true(
|
||||
ascension.mantissa > 0,
|
||||
"Should receive ascension currency after prestige"
|
||||
)
|
||||
|
||||
# Check prestige multiplier increased
|
||||
var total_multiplier = prestige_manager.get_total_multiplier()
|
||||
print("[TARGET] total_multiplier: %s" % str(total_multiplier))
|
||||
|
||||
TestUtils.assert_greater_than(
|
||||
total_multiplier if total_multiplier is float else float(total_multiplier),
|
||||
1.0,
|
||||
"Prestige multiplier should be > 1.0"
|
||||
)
|
||||
|
||||
_print_summary()
|
||||
|
||||
func _wait() -> void:
|
||||
await get_tree().create_timer(0.5).timeout
|
||||
|
||||
func _print_summary():
|
||||
TestUtils.print_result()
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. Shell Scripts
|
||||
|
||||
### run_test.sh
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# Usage: ./run_test.sh <test_script_path>
|
||||
# Example: ./run_test.sh res://tests/test_gold_mine_click.gd
|
||||
|
||||
set -e
|
||||
|
||||
TEST_SCRIPT="$1"
|
||||
GODOT_BIN="${GODOT_BIN:-godot}"
|
||||
|
||||
if [ -z "$TEST_SCRIPT" ]; then
|
||||
echo "Usage: $0 <test_script_path>"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Running test: $TEST_SCRIPT"
|
||||
echo ""
|
||||
|
||||
"$GODOT_BIN" --headless --path . -s "$TEST_SCRIPT"
|
||||
EXIT_CODE=$?
|
||||
|
||||
echo ""
|
||||
if [ $EXIT_CODE -eq 0 ]; then
|
||||
echo "✓ Test passed"
|
||||
else
|
||||
echo "✗ Test failed with exit code: $EXIT_CODE"
|
||||
fi
|
||||
|
||||
exit $EXIT_CODE
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### run_all.sh
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# Usage: ./run_all.sh
|
||||
|
||||
set -e
|
||||
|
||||
GODOT_BIN="${GODOT_BIN:-godot}"
|
||||
TEST_DIR="res://tests"
|
||||
|
||||
echo "=== Running All Tests ==="
|
||||
echo ""
|
||||
|
||||
# Run test runner (discovers all tests automatically)
|
||||
"$GODOT_BIN" --headless --path . -s "$TEST_DIR/test_runner.gd"
|
||||
EXIT_CODE=$?
|
||||
|
||||
echo ""
|
||||
if [ $EXIT_CODE -eq 0 ]; then
|
||||
echo "✓ All tests passed"
|
||||
else
|
||||
echo "✗ Some tests failed"
|
||||
fi
|
||||
|
||||
exit $EXIT_CODE
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### check_syntax.sh
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# Usage: ./check_syntax.sh <script_path>
|
||||
|
||||
SCRIPT="$1"
|
||||
GODOT_BIN="${GODOT_BIN:-godot}"
|
||||
|
||||
if [ -z "$SCRIPT" ]; then
|
||||
echo "Usage: $0 <script_path>"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Checking syntax: $SCRIPT"
|
||||
"$GODOT_BIN" --headless --path . --check-only --script "$SCRIPT"
|
||||
EXIT_CODE=$?
|
||||
|
||||
if [ $EXIT_CODE -eq 0 ]; then
|
||||
echo "✓ Syntax valid"
|
||||
else
|
||||
echo "✗ Syntax errors found"
|
||||
fi
|
||||
|
||||
exit $EXIT_CODE
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. Godot CLI Command Reference
|
||||
|
||||
| Command | Purpose |
|
||||
|---------|---------|
|
||||
| `"$GODOT_BIN" --headless --path . -s res://tests/test_x.gd` | Run single test in headless mode |
|
||||
| `"$GODOT_BIN" --headless --path . -s res://tests/test_runner.gd` | Run all tests via test runner |
|
||||
| `"$GODOT_BIN" --headless --path . --check-only --script res://test.gd` | Syntax validation only |
|
||||
| `"$GODOT_BIN" --headless --verbose --path . -s res://tests/test_x.gd` | Verbose output for debugging |
|
||||
| `"$GODOT_BIN" --headless --path . --quit-after 100 -s res://tests/test_x.gd` | Run for 100 frames then quit |
|
||||
| `"$GODOT_BIN" --headless --path . -s res://tests/test_x.gd --log-file /tmp/test.log` | Log output to file |
|
||||
|
||||
**Important:** Always use `--headless` for automated testing. This sets `--display-driver headless --audio-driver Dummy`.
|
||||
|
||||
---
|
||||
|
||||
## 8. Output Parsing Protocol
|
||||
|
||||
Agents should parse stdout for these tags:
|
||||
|
||||
| Tag | Purpose | Example |
|
||||
|-----|---------|---------|
|
||||
| `[TARGET]` | Values to validate | `[TARGET] gold: 100.00` |
|
||||
| `[PASS]` | Assertion passed | `[PASS] gold_mine_click: Gold increased]` |
|
||||
| `[FAIL]` | Assertion failed | `[FAIL] gold_mine_click: Expected gold > 0]` |
|
||||
| `[SUMMARY]` | Test completion stats | `[SUMMARY] gold_mine_click: 2 passed, 0 failed]` |
|
||||
| `[RESULT]` | Overall test result | `[RESULT] PASS` or `[RESULT] FAIL]` |
|
||||
| `[ERROR]` | Setup/runtime error | `[ERROR] Missing LevelGameState node]` |
|
||||
| `[OVERALL_RESULT]` | Test runner summary | `[OVERALL_RESULT] PASS` or `[OVERALL_RESULT] FAIL]` |
|
||||
|
||||
**Exit Codes:**
|
||||
- `0` = All assertions passed
|
||||
- `1` = One or more assertions failed or error occurred
|
||||
|
||||
---
|
||||
|
||||
## 9. Best Practices
|
||||
|
||||
### Simulating User Actions
|
||||
|
||||
In headless mode, **do not** simulate mouse clicks with `InputEventMouseButton`. Instead, call methods directly:
|
||||
|
||||
```gdscript
|
||||
# ✅ DO: Call the method directly
|
||||
generator._on_pressed()
|
||||
game_state.add_currency_by_id(&"gold", BigNumber.from_float(100.0))
|
||||
prestige_manager.perform_prestige()
|
||||
|
||||
# ❌ DON'T: Try to synthesize mouse events (won't work in headless)
|
||||
var event = InputEventMouseButton.new()
|
||||
Input.parse_input_event(event) # Ineffective without GUI
|
||||
```
|
||||
|
||||
### Async Handling
|
||||
|
||||
Use standard wait time for signal propagation:
|
||||
|
||||
```gdscript
|
||||
await get_tree().create_timer(0.5).timeout
|
||||
```
|
||||
|
||||
Adjust based on test needs:
|
||||
- `0.1s` - Simple state changes
|
||||
- `0.5s` - Signal propagation (recommended default)
|
||||
- `1.0s+` - Complex async operations
|
||||
|
||||
### Scene Selection
|
||||
|
||||
Use gym scenes from `res://docs/gyms/`:
|
||||
- `res://docs/gyms/tiny_sword/tiny_sword.tscn` - Full game with generators, currencies, prestige
|
||||
- Create minimal test scenes if testing isolated features
|
||||
|
||||
### Test Naming
|
||||
|
||||
- Use `test_<feature>.gd` naming convention
|
||||
- Test name in `TestUtils.set_test_name()` should match filename (without `.gd`)
|
||||
|
||||
### Assertions
|
||||
|
||||
- One assertion per line for clear parsing
|
||||
- Include descriptive messages
|
||||
- Use `[TARGET]` to log values before/after actions
|
||||
|
||||
---
|
||||
|
||||
## 10. Quick Start for Agents
|
||||
|
||||
1. **Create test file:**
|
||||
```bash
|
||||
touch res://tests/test_my_feature.gd
|
||||
```
|
||||
|
||||
2. **Copy template:**
|
||||
```gdscript
|
||||
extends SceneTree
|
||||
var passed: int = 0
|
||||
var failed: int = 0
|
||||
|
||||
func _init():
|
||||
await run()
|
||||
quit(failed == 0 ? 0 : 1)
|
||||
|
||||
func run():
|
||||
print("\n=== TEST: My Feature ===\n")
|
||||
TestUtils.set_test_name("my_feature")
|
||||
|
||||
var scene = load("res://docs/gyms/tiny_sword/tiny_sword.tscn")
|
||||
var root = scene.instantiate()
|
||||
add_root_node(root)
|
||||
|
||||
await get_tree().create_timer(0.5).timeout
|
||||
|
||||
# Your test logic here
|
||||
|
||||
TestUtils.print_result()
|
||||
```
|
||||
|
||||
3. **Run test:**
|
||||
```bash
|
||||
godot --headless --path . -s res://tests/test_my_feature.gd
|
||||
```
|
||||
|
||||
4. **Parse output:**
|
||||
- Look for `[PASS]`, `[FAIL]`, `[RESULT]` tags
|
||||
- Check exit code (0 = pass, 1 = fail)
|
||||
|
||||
---
|
||||
|
||||
## References
|
||||
|
||||
- [Godot CLI Tutorial](https://docs.godotengine.org/en/latest/tutorials/editor/command_line_tutorial.html)
|
||||
- [Godot Headless Mode](https://docs.godotengine.org/en/latest/tutorials/editor/command_line_tutorial.html#run-options)
|
||||
- [InputEvent Documentation](https://docs.godotengine.org/en/stable/tutorials/inputs/inputevent.html)
|
||||
|
||||
</content>
|
||||
<parameter=filePath>
|
||||
/home/mikymod/work/jmp/idle/.agents/skills/godot-test-instrumentation/SKILL.md
|
||||
Reference in New Issue
Block a user