Make RustRaptor the repository root and track its playable bundle
No files matched your search
@@ -0,0 +1,32 @@
|
||||
# Cinder Hammer
|
||||
|
||||
An independent boss mod for Iron Coast mission 5, **Cinder Docks**. The furnace
|
||||
gunship patrols persistently at the top of the combat area. A pressure-warning
|
||||
sound precedes three molten projectiles fired six simulation ticks apart. The
|
||||
center mortar travels vertically; the two plasma rounds spread outward.
|
||||
|
||||
- Enemy: `iron_coast_boss_5:unit`; health 160, reward 1,800, logical size 96×64.
|
||||
- Mortar and plasma: damage 3, speed 4; a 54-tick attack cycle.
|
||||
- Death objective: `iron_coast_boss_5:defeated`, incremented only on actual death.
|
||||
- Manifest API 1, package version 1.0.0, no dependencies or overrides.
|
||||
|
||||
The package never completes a mission itself. A mission must spawn the enemy,
|
||||
declare this package as a dependency, and require its death objective. Each
|
||||
enemy instance stores its own attack clock and patrol direction in serializable
|
||||
Rhai state. Dead or removed enemies cannot issue attacks or movement commands.
|
||||
|
||||
SD PNGs are derived from the same transparent generated masters as the 8× HD
|
||||
PNGs. Both modes retain the same logical canvas and collision dimensions.
|
||||
`art/preview.png` shows the HD hull and exact SD silhouette enlarged for review.
|
||||
The generation prompts are in `prompts/`; `provenance.json` records tool mode
|
||||
and source hashes. All artwork and audio belong in Git with this mod.
|
||||
|
||||
Run `python3 build_assets.py` to reproduce the resized art and synthesized mono
|
||||
22,050 Hz, 16-bit PCM warning, shot and destruction sounds. Python and Pillow
|
||||
are required. `audio-recipe.json` and `art-qa.json` record file hashes and checks.
|
||||
|
||||
Run `cargo test --locked --manifest-path validation/Cargo.toml` from this folder
|
||||
for five asset-independent checks of attack timing, multiple-instance state,
|
||||
save serialization, dead-entity guards, patrol turns, actual simulation
|
||||
persistence, rewards and the single death objective. The simulation test also
|
||||
validates the real package-local PNGs and WAVs.
|
||||
@@ -0,0 +1,102 @@
|
||||
{
|
||||
"alpha": "Preserved RGBA; runtime alpha>=128 indexed transparency",
|
||||
"registration": "Whole-canvas proportional resize, fixed padding; single idle frame",
|
||||
"files": [
|
||||
{
|
||||
"name": "unit",
|
||||
"logical_size": [
|
||||
96,
|
||||
64
|
||||
],
|
||||
"hd_scale": 8,
|
||||
"origin": [
|
||||
0,
|
||||
0
|
||||
],
|
||||
"frames": 1,
|
||||
"master_sha256": "e138d470db1b0a9fa268b32ec8f9b1d9db3dcf41fb9c951acea609dbba5025db",
|
||||
"master_size": [
|
||||
1536,
|
||||
1024
|
||||
],
|
||||
"files": {
|
||||
"art/unit-sd.png": {
|
||||
"physical_size": [
|
||||
96,
|
||||
64
|
||||
],
|
||||
"opaque_bounds": [
|
||||
7,
|
||||
5,
|
||||
89,
|
||||
57
|
||||
],
|
||||
"transparent_pixels": 3690,
|
||||
"sha256": "506094c4c1d1ad15c3f58484aab8057b829a53dea59f598a8632e6236dc4e21d"
|
||||
},
|
||||
"art/unit-hd.png": {
|
||||
"physical_size": [
|
||||
768,
|
||||
512
|
||||
],
|
||||
"opaque_bounds": [
|
||||
58,
|
||||
38,
|
||||
710,
|
||||
453
|
||||
],
|
||||
"transparent_pixels": 235914,
|
||||
"sha256": "01bf000620057a06ca85b5b5d59e5e1ef56962726212b5f901916dfad83c86b1"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "projectile",
|
||||
"logical_size": [
|
||||
8,
|
||||
8
|
||||
],
|
||||
"hd_scale": 8,
|
||||
"origin": [
|
||||
0,
|
||||
0
|
||||
],
|
||||
"frames": 1,
|
||||
"master_sha256": "f16d4f48f61ffbdd1eac4102b856b5f3bfa3555dd0ae41784b9cd7499d52b955",
|
||||
"master_size": [
|
||||
1254,
|
||||
1254
|
||||
],
|
||||
"files": {
|
||||
"art/projectile-sd.png": {
|
||||
"physical_size": [
|
||||
8,
|
||||
8
|
||||
],
|
||||
"opaque_bounds": [
|
||||
2,
|
||||
3,
|
||||
6,
|
||||
6
|
||||
],
|
||||
"transparent_pixels": 56,
|
||||
"sha256": "c441ded21f3dcfa6666256ac2e5444b8de3e8ab3b52b22080b36a5ad4eacd3d8"
|
||||
},
|
||||
"art/projectile-hd.png": {
|
||||
"physical_size": [
|
||||
64,
|
||||
64
|
||||
],
|
||||
"opaque_bounds": [
|
||||
19,
|
||||
12,
|
||||
45,
|
||||
50
|
||||
],
|
||||
"transparent_pixels": 3490,
|
||||
"sha256": "39a7f5ed33f4d1e0d1683e7337b100ab4a04933f19b0939f2f26f4dfdbda588b"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
After Width: | Height: | Size: 406 KiB |
|
After Width: | Height: | Size: 801 KiB |
|
After Width: | Height: | Size: 2.9 KiB |
|
After Width: | Height: | Size: 168 B |
|
After Width: | Height: | Size: 2.5 MiB |
|
After Width: | Height: | Size: 403 KiB |
|
After Width: | Height: | Size: 8.2 KiB |
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"generator": "build_assets.py deterministic chirp/noise PCM synthesis; no recordings",
|
||||
"files": {
|
||||
"audio/warning.wav": {
|
||||
"sample_rate": 22050,
|
||||
"channels": 1,
|
||||
"bits": 16,
|
||||
"seed": 5501,
|
||||
"duration_seconds": 0.28,
|
||||
"samples": 6174,
|
||||
"peak": 23592,
|
||||
"rms": 7402.96485860664,
|
||||
"sha256": "dcf32a1fc3cdcb734e6aaa3ed0cfeac854e2b0c89b92a209d3b77c53dc820604"
|
||||
},
|
||||
"audio/shot.wav": {
|
||||
"sample_rate": 22050,
|
||||
"channels": 1,
|
||||
"bits": 16,
|
||||
"seed": 5502,
|
||||
"duration_seconds": 0.24,
|
||||
"samples": 5292,
|
||||
"peak": 23592,
|
||||
"rms": 4267.066582997498,
|
||||
"sha256": "16bb1b201c746b41b93baf6faa4e7612c90ebce2168d888d8da7e0e222157bd9"
|
||||
},
|
||||
"audio/death.wav": {
|
||||
"sample_rate": 22050,
|
||||
"channels": 1,
|
||||
"bits": 16,
|
||||
"seed": 5503,
|
||||
"duration_seconds": 0.95,
|
||||
"samples": 20948,
|
||||
"peak": 23592,
|
||||
"rms": 3232.3544431693276,
|
||||
"sha256": "91950d95cdd98a42359e9b65072904a15270a2a80308e1d93aa8a73295ee67d5"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Derive SD/HD PNGs from saved imagegen masters and synthesize local PCM WAVs.
|
||||
|
||||
Artwork is only resized and placed on padded transparent canvases. No generated
|
||||
shape is painted over or replaced. Re-run with Python 3 and Pillow.
|
||||
"""
|
||||
from pathlib import Path
|
||||
import hashlib
|
||||
import json
|
||||
import math
|
||||
import random
|
||||
import struct
|
||||
import wave
|
||||
|
||||
ROOT = Path(__file__).resolve().parent
|
||||
|
||||
|
||||
def digest(path):
|
||||
return hashlib.sha256(path.read_bytes()).hexdigest()
|
||||
|
||||
|
||||
def artwork():
|
||||
from PIL import Image
|
||||
records = []
|
||||
for name, logical, target in [
|
||||
("unit", (96, 64), (768, 512)),
|
||||
("projectile", (8, 8), (64, 64)),
|
||||
]:
|
||||
source_path = ROOT / "art" / f"{name}-generated-v1.png"
|
||||
source = Image.open(source_path).convert("RGBA")
|
||||
if source.width * logical[1] != source.height * logical[0]:
|
||||
raise ValueError(f"{name}: source and logical canvas aspect differ")
|
||||
alpha = source.getchannel("A")
|
||||
if alpha.getextrema()[0] != 0 or alpha.getextrema()[1] < 128:
|
||||
raise ValueError(f"{name}: generated master lacks real transparent alpha")
|
||||
hd = Image.new("RGBA", target, (0, 0, 0, 0))
|
||||
# A common, whole-canvas resize preserves orientation and relative detail.
|
||||
# Clear padding leaves all opaque art inside its original collision box.
|
||||
fit = (672, 448) if name == "unit" else (56, 56)
|
||||
resized = source.resize(fit, Image.Resampling.LANCZOS)
|
||||
hd.alpha_composite(resized, ((target[0] - fit[0]) // 2,
|
||||
(target[1] - fit[1]) // 2))
|
||||
sd = hd.resize(logical, Image.Resampling.BOX)
|
||||
result = {"name": name, "logical_size": list(logical), "hd_scale": 8,
|
||||
"origin": [0, 0], "frames": 1,
|
||||
"master_sha256": digest(source_path),
|
||||
"master_size": list(source.size), "files": {}}
|
||||
for mode, image in [("sd", sd), ("hd", hd)]:
|
||||
path = ROOT / "art" / f"{name}-{mode}.png"
|
||||
image.save(path)
|
||||
opaque = image.getchannel("A").point(lambda a: 255 if a >= 128 else 0)
|
||||
bounds = opaque.getbbox()
|
||||
if not bounds or bounds[0] == 0 or bounds[1] == 0 or bounds[2] == image.width or bounds[3] == image.height:
|
||||
raise ValueError(f"{name}-{mode}: opaque silhouette touches its canvas")
|
||||
result["files"][str(path.relative_to(ROOT))] = {
|
||||
"physical_size": list(image.size), "opaque_bounds": list(bounds),
|
||||
"transparent_pixels": sum(a < 128 for a in image.getchannel("A").get_flattened_data()),
|
||||
"sha256": digest(path),
|
||||
}
|
||||
records.append(result)
|
||||
# Review sheet exposes alpha edges and the exact SD silhouette at 4x.
|
||||
preview = Image.new("RGBA", (1280, 620), (24, 32, 40, 255))
|
||||
boss_hd = Image.open(ROOT / "art/unit-hd.png")
|
||||
preview.alpha_composite(boss_hd, (0, 0))
|
||||
boss_sd = Image.open(ROOT / "art/unit-sd.png").resize((384, 256), Image.Resampling.NEAREST)
|
||||
preview.alpha_composite(boss_sd, (848, 330))
|
||||
preview.alpha_composite(Image.open(ROOT / "art/projectile-hd.png"), (900, 20))
|
||||
preview.save(ROOT / "art/preview.png")
|
||||
(ROOT / "art-qa.json").write_text(json.dumps({
|
||||
"alpha": "Preserved RGBA; runtime alpha>=128 indexed transparency",
|
||||
"registration": "Whole-canvas proportional resize, fixed padding; single idle frame",
|
||||
"files": records,
|
||||
}, indent=2) + "\n")
|
||||
|
||||
|
||||
def audio():
|
||||
rate = 22050
|
||||
files = {}
|
||||
durations = {"warning": 0.28, "shot": 0.24, "death": 0.95}
|
||||
for name, duration in durations.items():
|
||||
seed = {"warning": 5501, "shot": 5502, "death": 5503}[name]
|
||||
rng = random.Random(seed)
|
||||
count = round(duration * rate)
|
||||
phase = low_noise = 0.0
|
||||
samples = []
|
||||
for index in range(count):
|
||||
time = index / rate
|
||||
position = index / (count - 1)
|
||||
noise = rng.uniform(-1, 1)
|
||||
low_noise = low_noise * 0.88 + noise * 0.12
|
||||
if name == "warning":
|
||||
frequency = 430 + 310 * position
|
||||
phase += 2 * math.pi * frequency / rate
|
||||
# Two separated furnace-pressure tones, distinct from a weapon hit.
|
||||
pulse = 0.0
|
||||
for start, end in [(0.02, 0.105), (0.155, 0.245)]:
|
||||
if start <= time <= end:
|
||||
pulse = max(0.0, min(1.0, (time - start) / 0.005,
|
||||
(end - time) / 0.005))
|
||||
value = (0.7 * math.sin(phase) + 0.12 * math.sin(2 * phase)) * pulse
|
||||
envelope = math.sin(math.pi * position) ** 2
|
||||
elif name == "shot":
|
||||
frequency = 70 + 550 * (1 - position) ** 2
|
||||
phase += 2 * math.pi * frequency / rate
|
||||
value = 0.59 * math.sin(phase) + 0.19 * low_noise + 0.09 * noise
|
||||
envelope = math.exp(-position * 6.0)
|
||||
else:
|
||||
frequency = 34 + 75 * (1 - position) ** 2
|
||||
phase += 2 * math.pi * frequency / rate
|
||||
value = 0.65 * low_noise + 0.18 * noise + 0.25 * math.sin(phase)
|
||||
envelope = math.exp(-position * 4.4)
|
||||
envelope *= min(1.0, time / 0.003)
|
||||
envelope *= max(0.0, min(1.0, (count - 1 - index) / (rate * 0.006)))
|
||||
samples.append(value * envelope)
|
||||
peak = max(abs(v) for v in samples)
|
||||
pcm = [round(v / peak * 0.72 * 32767) for v in samples]
|
||||
path = ROOT / "audio" / f"{name}.wav"
|
||||
with wave.open(str(path), "wb") as output:
|
||||
output.setnchannels(1)
|
||||
output.setsampwidth(2)
|
||||
output.setframerate(rate)
|
||||
output.writeframes(struct.pack("<" + "h" * len(pcm), *pcm))
|
||||
files[str(path.relative_to(ROOT))] = {
|
||||
"sample_rate": rate, "channels": 1, "bits": 16, "seed": seed,
|
||||
"duration_seconds": duration, "samples": len(pcm),
|
||||
"peak": max(abs(v) for v in pcm),
|
||||
"rms": (sum(v * v for v in pcm) / len(pcm)) ** 0.5,
|
||||
"sha256": digest(path),
|
||||
}
|
||||
(ROOT / "audio-recipe.json").write_text(json.dumps({
|
||||
"generator": "build_assets.py deterministic chirp/noise PCM synthesis; no recordings",
|
||||
"files": files,
|
||||
}, indent=2) + "\n")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
artwork()
|
||||
audio()
|
||||
print("Cinder Hammer: SD/HD artwork and three non-silent PCM sounds generated")
|
||||
@@ -0,0 +1,82 @@
|
||||
{
|
||||
"weapons": [
|
||||
{
|
||||
"id": "iron_coast_boss_5:mortar",
|
||||
"name": "Cinder Hammer furnace mortar",
|
||||
"damage": 3,
|
||||
"speed": 4,
|
||||
"cooldown": 54,
|
||||
"behavior": "straight",
|
||||
"target": "all",
|
||||
"art": {
|
||||
"png": "art/projectile-sd.png",
|
||||
"hd_png": "art/projectile-hd.png",
|
||||
"logical_width": 8,
|
||||
"logical_height": 8
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "iron_coast_boss_5:plasma",
|
||||
"name": "Cinder Hammer molten plasma",
|
||||
"damage": 3,
|
||||
"speed": 4,
|
||||
"cooldown": 54,
|
||||
"behavior": "straight",
|
||||
"target": "all",
|
||||
"art": {
|
||||
"png": "art/projectile-sd.png",
|
||||
"hd_png": "art/projectile-hd.png",
|
||||
"logical_width": 8,
|
||||
"logical_height": 8
|
||||
}
|
||||
}
|
||||
],
|
||||
"enemies": [
|
||||
{
|
||||
"id": "iron_coast_boss_5:unit",
|
||||
"name": "Cinder Hammer",
|
||||
"health": 160,
|
||||
"speed": 0,
|
||||
"reward": 1800,
|
||||
"ground": false,
|
||||
"boss": true,
|
||||
"cooldown": 54,
|
||||
"art": {
|
||||
"png": "art/unit-sd.png",
|
||||
"hd_png": "art/unit-hd.png",
|
||||
"logical_width": 96,
|
||||
"logical_height": 64
|
||||
},
|
||||
"script": "scripts/ai.rhai"
|
||||
}
|
||||
],
|
||||
"sounds": [
|
||||
{
|
||||
"id": "iron_coast_boss_5:warning",
|
||||
"name": "Cinder Hammer pressure warning",
|
||||
"file": "audio/warning.wav",
|
||||
"volume": 72,
|
||||
"pan": 127,
|
||||
"pitch": 128,
|
||||
"priority": 2
|
||||
},
|
||||
{
|
||||
"id": "iron_coast_boss_5:shot",
|
||||
"name": "Cinder Hammer furnace discharge",
|
||||
"file": "audio/shot.wav",
|
||||
"volume": 96,
|
||||
"pan": 127,
|
||||
"pitch": 128,
|
||||
"priority": 2
|
||||
},
|
||||
{
|
||||
"id": "iron_coast_boss_5:death",
|
||||
"name": "Cinder Hammer furnace rupture",
|
||||
"file": "audio/death.wav",
|
||||
"volume": 120,
|
||||
"pan": 127,
|
||||
"pitch": 128,
|
||||
"priority": 4
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
id = "iron_coast_boss_5"
|
||||
name = "Iron Coast Cinder Hammer"
|
||||
version = "1.0.0"
|
||||
api_version = 1
|
||||
description = "A furnace gunship boss for Cinder Docks: persistent patrol, audible heat warning and staggered mortar/plasma fire. Includes matching SD/HD sprites and synthesized sound effects."
|
||||
content = "content.json"
|
||||
scripts = ["scripts/ai.rhai"]
|
||||
@@ -0,0 +1,6 @@
|
||||
Use case: stylized-concept.
|
||||
Asset type: one transparent game projectile sprite, overhead view, for the Cinder Hammer furnace gunship in the Raptor Iron Coast campaign.
|
||||
Primary request: a compact heavy molten mortar projectile, centered circular armored charcoal-and-bronze shell ring with a blazing orange furnace core and a small dark-red edge. Crisp detailed painted sprite shading that remains a readable bright orange dot at 8 by 8 logical pixels. A pale hot-yellow small core, molten orange surrounding core, dark gunmetal outline. Compact circular shape with just a tiny tapered orange plasma tail above it; projectile travels DOWN. No detached particles.
|
||||
Composition/framing: one sprite only, centered square canvas, projectile fills 60% of canvas width, clear transparent padding on every edge. Orthographic view, not a scene, not a sprite sheet.
|
||||
Scene/backdrop: genuinely transparent RGBA including all padding, no surface, no background, no glow or shadow extending far beyond solid silhouette. No text, labels, logos, watermark or checkerboard pattern.
|
||||
Style: faithful industrial 1994 Raptor shot language with polished painted HD detail, strong simple silhouette and restrained small orange glow.
|
||||
@@ -0,0 +1,7 @@
|
||||
Use case: stylized-concept.
|
||||
Asset type: one transparent HD sprite for a top-down vertical scrolling shoot-em-up, faithful to 1994 Raptor but remastered in crisp detailed painted game-sprite shading.
|
||||
Primary request: Cinder Hammer, a distinct heavy industrial armored furnace gunship boss for the Iron Coast campaign. Orthographic straight overhead view, centered, nose and two blunt cannon muzzles facing DOWN toward bottom of image. Wide squat 3:2 silhouette: broad stepped angular armored wings, thick compact rectangular central furnace body, two large side engines near top rear, two forward mortar cannon pods near bottom wings, a central recessed furnace glow. Overall silhouette should fill about 84% width and 82% height of a landscape 3:2 canvas, leave clear transparent padding around all sides.
|
||||
Materials/color: gunmetal gray and muted bronze armored plates with rivets, dark steel heat grilles and furnace exhaust housings; restrained molten orange and red heat vents, no excessive bright bloom. Narrow cyan cockpit at upper center, small pale yellow hazard marks. Directional lighting from upper left, darker right/bottom plate edges, fine readable mechanical detail but strong simple outer silhouette.
|
||||
Scene/backdrop: fully transparent RGBA, no ground, no shadows outside silhouette, no atmospheric backdrop.
|
||||
Style: match classic Raptor top-down industrial craft vocabulary with modern carefully painted HD detail, no perspective depth, no cartoon outline, no photoreal scene, no pixel grid. This is ONE idle boss sprite, no sheet or other objects.
|
||||
Constraints: exact orthographic overhead camera; keep left and right wings symmetric; visible nose faces bottom, rear engines are at top; no external detached flames; no text, lettering, logo, watermark, frame, labels or fake checkerboard. True transparency. Canvas approximately 3:2.
|
||||
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"package": "iron_coast_boss_5",
|
||||
"image_generation": {
|
||||
"mode": "built-in image_gen",
|
||||
"transparent_background": true,
|
||||
"unit_prompt": "prompts/unit-hd-v1.txt",
|
||||
"projectile_prompt": "prompts/projectile-hd-v1.txt",
|
||||
"masters": [
|
||||
"art/unit-generated-v1.png",
|
||||
"art/projectile-generated-v1.png"
|
||||
]
|
||||
},
|
||||
"processing": "Whole-canvas proportional resize and transparent padding only; SD BOX derived from HD; no sprite repainting",
|
||||
"audio": "Deterministic procedural synthesis by build_assets.py; no recordings",
|
||||
"files": {
|
||||
"README.md": "bd408b9bb5f31958ecad85b5ee9f2f2c3332aaaa633d5022f18e8be8ca5898b6",
|
||||
"art/preview.png": "a952595930dbcb3f0c63fc1e064b744b60cb2bd85a3934f4e15e6b4b707b88de",
|
||||
"art/projectile-generated-v1.png": "f16d4f48f61ffbdd1eac4102b856b5f3bfa3555dd0ae41784b9cd7499d52b955",
|
||||
"art/projectile-hd.png": "39a7f5ed33f4d1e0d1683e7337b100ab4a04933f19b0939f2f26f4dfdbda588b",
|
||||
"art/projectile-sd.png": "c441ded21f3dcfa6666256ac2e5444b8de3e8ab3b52b22080b36a5ad4eacd3d8",
|
||||
"art/unit-generated-v1.png": "e138d470db1b0a9fa268b32ec8f9b1d9db3dcf41fb9c951acea609dbba5025db",
|
||||
"art/unit-hd.png": "01bf000620057a06ca85b5b5d59e5e1ef56962726212b5f901916dfad83c86b1",
|
||||
"art/unit-sd.png": "506094c4c1d1ad15c3f58484aab8057b829a53dea59f598a8632e6236dc4e21d",
|
||||
"art-qa.json": "2c9e91834a66e74a26eb381986e2ccb3f32595d5d28e83742e37126d0a93ca0f",
|
||||
"audio/death.wav": "91950d95cdd98a42359e9b65072904a15270a2a80308e1d93aa8a73295ee67d5",
|
||||
"audio/shot.wav": "16bb1b201c746b41b93baf6faa4e7612c90ebce2168d888d8da7e0e222157bd9",
|
||||
"audio/warning.wav": "dcf32a1fc3cdcb734e6aaa3ed0cfeac854e2b0c89b92a209d3b77c53dc820604",
|
||||
"audio-recipe.json": "e36836dc27322d388598127ae4e57280161a8cb7cda331163498dfaf0956a06f",
|
||||
"build_assets.py": "d0fd38749b47b5feceb1b1e9b93a0e29606121fa2660410b57442302fb30b1c3",
|
||||
"content.json": "f58d3566ff3f2aae2a45f408589c5b9ec67453fd628fa31bc4f3eed85bd8c5eb",
|
||||
"mod.toml": "9d67483df29ad894e4de7103b319ae9c2a1b82fce6d92aace6b473702177a1b1",
|
||||
"prompts/projectile-hd-v1.txt": "29be8882da6c8a80016b76222bc33d26bb94063edef47570def7e09f67e881cb",
|
||||
"prompts/unit-hd-v1.txt": "0c9cc47eead066d54853cf6d642c518684c1ca2e117d6f213b451c2250d0aa50",
|
||||
"scripts/ai.rhai": "8bc1515ebc2dac9cf2343e23cef6d0f80d449af43e40d12423aeaa0804a388d1",
|
||||
"validation/Cargo.lock": "48fdea0f5ef5b3117d50e53b690ac3f4736e4fc81ddea512ffd7f8e90c4c2873",
|
||||
"validation/Cargo.toml": "3f46046bc70a37db7859e3c2361e9e6d61c70c6d8344c6cdb9eb37baabe7bb66",
|
||||
"validation/src/lib.rs": "b8710178fac2e2918567f0d55da03cce6c280d2d09c59cedc00dcb1b8e8efd92",
|
||||
"validation-results.json": "cd85594adb7b84d2bfb448afa1d52f5e470173d48f4a31b5be7247b5d2c33ff1"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
// Persistent furnace gunship. Each instance has its own serializable clock.
|
||||
fn is_alive(event) {
|
||||
for unit in event.entities {
|
||||
if unit.entity == event.entity && unit.health > 0 { return true; }
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn initialize(state, key) {
|
||||
if !state.contains("units") { state.units = #{}; }
|
||||
if !state.units.contains(key) {
|
||||
state.units[key] = #{ age: 0, direction: 1, shots: 0 };
|
||||
}
|
||||
state
|
||||
}
|
||||
|
||||
fn on_spawn(event, state, random) {
|
||||
if !is_alive(event) { return #{ commands: [], state: state }; }
|
||||
let key = event.entity.to_string();
|
||||
state = initialize(state, key);
|
||||
// A pilot save contains previous script state; this is a fresh enemy instance.
|
||||
state.units[key] = #{ age: 0, direction: 1, shots: 0 };
|
||||
#{ commands: [#{ op: "velocity", entity: event.entity, vx: 0, vy: 2 }], state: state }
|
||||
}
|
||||
|
||||
fn on_tick(event, state, random) {
|
||||
// A lethal hit earlier in this tick must never create a ghost attack.
|
||||
if event.health <= 0 || !is_alive(event) { return #{ commands: [], state: state }; }
|
||||
if event.y < 24 {
|
||||
return #{ commands: [#{ op: "velocity", entity: event.entity, vx: 0, vy: 2 }], state: state };
|
||||
}
|
||||
let key = event.entity.to_string();
|
||||
state = initialize(state, key);
|
||||
let unit = state.units[key];
|
||||
unit.age += 1;
|
||||
if event.x >= 144 { unit.direction = -1; }
|
||||
if event.x <= 80 { unit.direction = 1; }
|
||||
let commands = [
|
||||
#{ op: "move", entity: event.entity, x: event.x, y: 24 },
|
||||
#{ op: "velocity", entity: event.entity, vx: unit.direction, vy: 0 }
|
||||
];
|
||||
// 12 ticks of warning before the first muzzle fires, then three shells six
|
||||
// ticks apart. The right/center/left lanes leave space to dodge the fan.
|
||||
let phase = unit.age % 54;
|
||||
if phase == 36 {
|
||||
commands.push(#{ op: "play_sound", sound: "iron_coast_boss_5:warning" });
|
||||
}
|
||||
if phase == 48 {
|
||||
commands.push(#{ op: "projectile", weapon: "iron_coast_boss_5:plasma", x: event.x + 24, y: event.y + 55, vx: -2, vy: 4, hostile: true });
|
||||
commands.push(#{ op: "play_sound", sound: "iron_coast_boss_5:shot" });
|
||||
unit.shots += 1;
|
||||
}
|
||||
if phase == 0 {
|
||||
commands.push(#{ op: "projectile", weapon: "iron_coast_boss_5:mortar", x: event.x + 44, y: event.y + 58, vx: 0, vy: 4, hostile: true });
|
||||
commands.push(#{ op: "play_sound", sound: "iron_coast_boss_5:shot" });
|
||||
unit.shots += 1;
|
||||
}
|
||||
if phase == 6 && unit.age > 6 {
|
||||
commands.push(#{ op: "projectile", weapon: "iron_coast_boss_5:plasma", x: event.x + 64, y: event.y + 55, vx: 2, vy: 4, hostile: true });
|
||||
commands.push(#{ op: "play_sound", sound: "iron_coast_boss_5:shot" });
|
||||
unit.shots += 1;
|
||||
}
|
||||
state.units[key] = unit;
|
||||
#{ commands: commands, state: state }
|
||||
}
|
||||
|
||||
fn on_hit(event, state, random) {
|
||||
state.hits = if state.contains("hits") { state.hits + 1 } else { 1 };
|
||||
#{ commands: [], state: state }
|
||||
}
|
||||
|
||||
fn on_death(event, state, random) {
|
||||
if state.contains("units") { state.units.remove(event.entity.to_string()); }
|
||||
state.destroyed = if state.contains("destroyed") { state.destroyed + 1 } else { 1 };
|
||||
let defeated = if event.objectives.contains("iron_coast_boss_5:defeated") { event.objectives["iron_coast_boss_5:defeated"] + 1 } else { 1 };
|
||||
#{ commands: [
|
||||
#{ op: "objective", key: "iron_coast_boss_5:defeated", value: defeated },
|
||||
#{ op: "effect", x: event.x + 24, y: event.y + 40 },
|
||||
#{ op: "effect", x: event.x + 64, y: event.y + 40 },
|
||||
#{ op: "play_sound", sound: "iron_coast_boss_5:death" }
|
||||
], state: state }
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"cli": {
|
||||
"command": "raptor-rust --data data --mods RustRaptor/mods --validate-mods iron_coast_boss_5",
|
||||
"result": "1 mod validated; no overrides; scripts compile; package-local PNGs and WAVs accepted"
|
||||
},
|
||||
"test_command": "cargo test --locked --offline --manifest-path RustRaptor/mods/iron_coast_boss_5/validation/Cargo.toml --target-dir RustRaptor/target",
|
||||
"tests": 5,
|
||||
"passed": 5,
|
||||
"actual_simulation": {
|
||||
"duration_ticks": 600,
|
||||
"boss_persistent": true,
|
||||
"death_damage": 160,
|
||||
"death_objective_count": 1,
|
||||
"death_reward": 1800,
|
||||
"post_death_ticks": 80,
|
||||
"ghost_attacks": 0
|
||||
},
|
||||
"art": "SD/8xHD share logical96x64 and8x8 canvases; true alpha and opaque silhouettes do not touch canvas boundaries",
|
||||
"sounds": "three non-silent mono22050Hz16-bit PCM WAVs, bounded peaks and faded boundaries"
|
||||
}
|
||||
@@ -0,0 +1,821 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "adler2"
|
||||
version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
|
||||
|
||||
[[package]]
|
||||
name = "ahash"
|
||||
version = "0.8.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"const-random",
|
||||
"getrandom 0.3.4",
|
||||
"once_cell",
|
||||
"version_check",
|
||||
"zerocopy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.104"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470"
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.13.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3ded4057c258ba199e2d26386d3af3780957ecaee6c4ef4041c6b4b8b97c0b06"
|
||||
|
||||
[[package]]
|
||||
name = "block-buffer"
|
||||
version = "0.10.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.20.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649"
|
||||
|
||||
[[package]]
|
||||
name = "bytemuck"
|
||||
version = "1.25.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "95832e849adfb21180ccb6826a99da14e5d266ae5c2e668e1602cf234f153797"
|
||||
|
||||
[[package]]
|
||||
name = "byteorder-lite"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4e7648175b45a9a48536d676f68d918270699102aa8dab5496df06904c914600"
|
||||
|
||||
[[package]]
|
||||
name = "const-random"
|
||||
version = "0.1.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359"
|
||||
dependencies = [
|
||||
"const-random-macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "const-random-macro"
|
||||
version = "0.1.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e"
|
||||
dependencies = [
|
||||
"getrandom 0.2.17",
|
||||
"once_cell",
|
||||
"tiny-keccak",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
version = "0.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crc32fast"
|
||||
version = "1.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "01a7799fd6b852db0e61728dde9a204c423b44d689dbd432522543614b490e78"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crunchy"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5"
|
||||
|
||||
[[package]]
|
||||
name = "crypto-common"
|
||||
version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "digest"
|
||||
version = "0.10.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
|
||||
dependencies = [
|
||||
"block-buffer",
|
||||
"crypto-common",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dyn-clone"
|
||||
version = "1.0.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555"
|
||||
|
||||
[[package]]
|
||||
name = "equivalent"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
|
||||
|
||||
[[package]]
|
||||
name = "fdeflate"
|
||||
version = "0.3.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c"
|
||||
dependencies = [
|
||||
"simd-adler32",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "flate2"
|
||||
version = "1.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6e634e2e0ebac1ee034020da1ca582e17ffe4e0f5e985823721e168928136dcb"
|
||||
dependencies = [
|
||||
"crc32fast",
|
||||
"miniz_oxide 0.9.1",
|
||||
"zlib-rs",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-core"
|
||||
version = "0.3.34"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e"
|
||||
|
||||
[[package]]
|
||||
name = "futures-task"
|
||||
version = "0.3.34"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd"
|
||||
|
||||
[[package]]
|
||||
name = "futures-util"
|
||||
version = "0.3.34"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-task",
|
||||
"pin-project-lite",
|
||||
"slab",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "generic-array"
|
||||
version = "0.14.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
|
||||
dependencies = [
|
||||
"typenum",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"wasi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.3.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"r-efi",
|
||||
"wasip2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.17.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
|
||||
|
||||
[[package]]
|
||||
name = "image"
|
||||
version = "0.25.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "85ab80394333c02fe689eaf900ab500fbd0c2213da414687ebf995a65d5a6104"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"byteorder-lite",
|
||||
"moxcms",
|
||||
"num-traits",
|
||||
"png",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.14.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iron-coast-cinder-hammer-validation"
|
||||
version = "1.0.0"
|
||||
dependencies = [
|
||||
"raptor-replica",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.106"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7883d941dae510fb2d978fc3fe018c71c9e2892fd38854de3e8b92c2e5ad9cc5"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"futures-util",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.190"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ce5d3ddc6d3fa000eb1536d85e147bfe31aacaba692ed6a876f95cb7c855be78"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
|
||||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.8.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316"
|
||||
dependencies = [
|
||||
"adler2",
|
||||
"simd-adler32",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b63fbc4a50860e98e7b2aa7804ded1db5cbc3aff9193adaff57a6931bf7c4b4c"
|
||||
dependencies = [
|
||||
"adler2",
|
||||
"simd-adler32",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "moxcms"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bb85c154ba489f01b25c0d36ae69a87e4a1c73a72631fc6c0eb6dde34a73e44b"
|
||||
dependencies = [
|
||||
"num-traits",
|
||||
"pxfm",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "no-std-compat"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b93853da6d84c2e3c7d730d6473e8817692dd89be387eb01b94d7f108ecb5b8c"
|
||||
dependencies = [
|
||||
"spin",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.21.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
|
||||
dependencies = [
|
||||
"portable-atomic",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-lite"
|
||||
version = "0.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
|
||||
|
||||
[[package]]
|
||||
name = "png"
|
||||
version = "0.18.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "60769b8b31b2a9f263dae2776c37b1b28ae246943cf719eb6946a1db05128a61"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"crc32fast",
|
||||
"fdeflate",
|
||||
"flate2",
|
||||
"miniz_oxide 0.8.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "portable-atomic"
|
||||
version = "1.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "05c8b63e8d9609db387f0324918f81d68fe27748f084ef092fb35954d0539a85"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.107"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pxfm"
|
||||
version = "0.1.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d55d956fa96f5ec02be2e13af0e20391a5aa83d6a074e3ad368959d0fab299ea"
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.47"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "r-efi"
|
||||
version = "5.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
|
||||
|
||||
[[package]]
|
||||
name = "raptor-replica"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"image",
|
||||
"rhai",
|
||||
"schemars",
|
||||
"semver",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2",
|
||||
"toml",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rhai"
|
||||
version = "1.26.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0334639972c0ea5a3fd366aa36116754a11431b619fec3ed559b3f73bcbcebf5"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"bitflags",
|
||||
"no-std-compat",
|
||||
"num-traits",
|
||||
"once_cell",
|
||||
"rhai_codegen",
|
||||
"serde",
|
||||
"smallvec",
|
||||
"smartstring",
|
||||
"thin-vec",
|
||||
"web-time",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rhai_codegen"
|
||||
version = "3.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3cd3a7535e50bf36857e7be7bec276d334e8c2dfa469c2201226fd01638ea5ca"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.119",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustversion"
|
||||
version = "1.0.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f"
|
||||
|
||||
[[package]]
|
||||
name = "schemars"
|
||||
version = "0.8.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3fbf2ae1b8bc8e02df939598064d22402220cd5bbcca1c76f7d6a310974d5615"
|
||||
dependencies = [
|
||||
"dyn-clone",
|
||||
"schemars_derive",
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "schemars_derive"
|
||||
version = "0.8.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32e265784ad618884abaea0600a9adf15393368d840e0222d101a072f3f7534d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"serde_derive_internals",
|
||||
"syn 2.0.119",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "semver"
|
||||
version = "1.0.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.229"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba"
|
||||
dependencies = [
|
||||
"serde_core",
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_core"
|
||||
version = "1.0.229"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.229"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 3.0.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive_internals"
|
||||
version = "0.29.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.119",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.151"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"memchr",
|
||||
"serde",
|
||||
"serde_core",
|
||||
"zmij",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_spanned"
|
||||
version = "0.6.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha2"
|
||||
version = "0.10.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures",
|
||||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "simd-adler32"
|
||||
version = "0.3.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea"
|
||||
|
||||
[[package]]
|
||||
name = "slab"
|
||||
version = "0.4.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.16.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f9395f0f0eee849a9b707b2f06bb92a6a422090e2123bb2ef8e87a0e61892a8e"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "smartstring"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3fb72c633efbaa2dd666986505016c32c3044395ceaf881518399d2f4127ee29"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"static_assertions",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "spin"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
|
||||
|
||||
[[package]]
|
||||
name = "static_assertions"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.119"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "3.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8593e8e72159ed2257d083c7a454a85cbf854f37a0966d8d483aff8c8a3ebcee"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thin-vec"
|
||||
version = "0.2.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d6a4b9ba8738cb4a4f399d37e266becfd475e75eb73425b87a05a2f2039ba63e"
|
||||
|
||||
[[package]]
|
||||
name = "tiny-keccak"
|
||||
version = "2.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237"
|
||||
dependencies = [
|
||||
"crunchy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.8.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
"toml_edit",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_datetime"
|
||||
version = "0.6.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_edit"
|
||||
version = "0.22.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
"toml_write",
|
||||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_write"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801"
|
||||
|
||||
[[package]]
|
||||
name = "typenum"
|
||||
version = "1.20.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d245f478577f809a851594d02313b640fb437e0bb33866753cff937863096954"
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.11.1+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
|
||||
|
||||
[[package]]
|
||||
name = "wasip2"
|
||||
version = "1.0.4+wasi-0.2.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487"
|
||||
dependencies = [
|
||||
"wit-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.129"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9bb54f33acc68fd454578d9820b0bde1a1a3d17aa17bb7b6595806d02886d409"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"once_cell",
|
||||
"rustversion",
|
||||
"wasm-bindgen-macro",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.129"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2e29d0c35b16e224a7eeb5cd2d25e3e1968fbd65604117b44d3b789d00ee8535"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"wasm-bindgen-macro-support",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro-support"
|
||||
version = "0.2.129"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6f501a8bc3719dba86ef8ae4728879c08001bea749eb1333ac5b91e040e2a6b7"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 3.0.6",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-shared"
|
||||
version = "0.2.129"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "23f0c9c52aa7cd7d77769a4cfe2a9adb1b331f489a41d912ce14513d5ab995c6"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "web-time"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb"
|
||||
dependencies = [
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "0.7.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen"
|
||||
version = "0.57.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e"
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.8.62"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "86502bf56ac7c77571a32e2647bb2a15894565e981fb2a48d7bde2d91c965a9d"
|
||||
dependencies = [
|
||||
"zerocopy-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy-derive"
|
||||
version = "0.8.62"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5457206954b06561e2608c7e19cf58b1926586d999c246eebe4502f7e2039d1a"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.119",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zlib-rs"
|
||||
version = "0.6.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b268e58e7c693d7c271f93ffc4ba3b380412554231c85bf61ca7af91042a4112"
|
||||
|
||||
[[package]]
|
||||
name = "zmij"
|
||||
version = "1.0.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b"
|
||||
@@ -0,0 +1,9 @@
|
||||
[package]
|
||||
name = "iron-coast-cinder-hammer-validation"
|
||||
version = "1.0.0"
|
||||
edition = "2024"
|
||||
publish = false
|
||||
|
||||
[dependencies]
|
||||
raptor-replica = { path = "../../..", default-features = false }
|
||||
serde_json = "1"
|
||||
@@ -0,0 +1,254 @@
|
||||
//! Meaningful callback checks without original assets or desktop services.
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use raptor_replica::modding::{Command, ScriptRuntime};
|
||||
use serde_json::{Value, json};
|
||||
|
||||
const OWNER: &str = "iron_coast_boss_5";
|
||||
const SCRIPT: &str = "scripts/ai.rhai";
|
||||
const KEY: &str = "iron_coast_boss_5/scripts/ai.rhai";
|
||||
|
||||
fn runtime() -> ScriptRuntime {
|
||||
let mut scripts = ScriptRuntime::new();
|
||||
scripts
|
||||
.add(OWNER, SCRIPT, include_str!("../../scripts/ai.rhai"))
|
||||
.unwrap();
|
||||
scripts
|
||||
}
|
||||
|
||||
fn event(entity: u64, tick: u64) -> Value {
|
||||
json!({"entity":entity,"tick":tick,"x":112,"y":24,"health":160,
|
||||
"entities":[{"entity":1,"health":160},{"entity":2,"health":160}],
|
||||
"objectives":{}})
|
||||
}
|
||||
|
||||
fn invoke(scripts: &mut ScriptRuntime, hook: &str, event: &Value) -> Vec<Command> {
|
||||
scripts.begin_tick();
|
||||
scripts.invoke(OWNER, SCRIPT, hook, event, 1024).unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn warnings_precede_the_staggered_54_tick_attack_cycle() {
|
||||
let mut scripts = runtime();
|
||||
invoke(&mut scripts, "on_spawn", &event(1, 0));
|
||||
let mut warnings = vec![];
|
||||
let mut shots = vec![];
|
||||
for tick in 1..=120 {
|
||||
let commands = invoke(&mut scripts, "on_tick", &event(1, tick));
|
||||
for command in commands {
|
||||
match command {
|
||||
Command::PlaySound { sound } if sound.ends_with(":warning") => {
|
||||
warnings.push(tick)
|
||||
}
|
||||
Command::Projectile {
|
||||
weapon,
|
||||
vx,
|
||||
vy,
|
||||
hostile,
|
||||
..
|
||||
} => {
|
||||
assert!(hostile);
|
||||
assert_eq!(vy, 4);
|
||||
shots.push((tick, weapon, vx));
|
||||
}
|
||||
Command::Complete | Command::Fail => {
|
||||
panic!("boss must not decide mission outcome")
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
assert_eq!(warnings, [36, 90]);
|
||||
assert_eq!(
|
||||
shots.iter().map(|x| x.0).collect::<Vec<_>>(),
|
||||
[48, 54, 60, 102, 108, 114]
|
||||
);
|
||||
assert_eq!(
|
||||
shots.iter().map(|x| x.2).collect::<Vec<_>>(),
|
||||
[-2, 0, 2, -2, 0, 2]
|
||||
);
|
||||
assert!(shots[1].1.ends_with(":mortar"));
|
||||
assert_eq!(scripts.states[KEY]["units"]["1"]["shots"], 6);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn simultaneous_enemies_keep_independent_serializable_clocks_and_cleanup() {
|
||||
let mut scripts = runtime();
|
||||
invoke(&mut scripts, "on_spawn", &event(1, 0));
|
||||
invoke(&mut scripts, "on_spawn", &event(2, 0));
|
||||
for tick in 1..=70 {
|
||||
invoke(&mut scripts, "on_tick", &event(1, tick));
|
||||
if tick <= 20 {
|
||||
invoke(&mut scripts, "on_tick", &event(2, tick));
|
||||
}
|
||||
}
|
||||
let serialized = serde_json::to_vec(&scripts.states).unwrap();
|
||||
let restored: std::collections::BTreeMap<String, Value> =
|
||||
serde_json::from_slice(&serialized).unwrap();
|
||||
assert_eq!(restored[KEY]["units"]["1"]["age"], 70);
|
||||
assert_eq!(restored[KEY]["units"]["2"]["age"], 20);
|
||||
let mut death = event(1, 71);
|
||||
death["objectives"] = json!({"iron_coast_boss_5:defeated":2});
|
||||
let commands = invoke(&mut scripts, "on_death", &death);
|
||||
assert!(commands.iter().any(|command| matches!(command,
|
||||
Command::Objective { key, value:3 } if key == "iron_coast_boss_5:defeated")));
|
||||
assert!(scripts.states[KEY]["units"].get("1").is_none());
|
||||
assert_eq!(scripts.states[KEY]["units"]["2"]["age"], 20);
|
||||
assert_eq!(scripts.states[KEY]["destroyed"], 1);
|
||||
// A new mission may reuse entity IDs; its attack age must begin at zero.
|
||||
invoke(&mut scripts, "on_spawn", &event(2, 0));
|
||||
assert_eq!(scripts.states[KEY]["units"]["2"]["age"], 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn removed_or_lethally_hit_enemies_emit_no_ghost_commands() {
|
||||
let mut scripts = runtime();
|
||||
invoke(&mut scripts, "on_spawn", &event(1, 0));
|
||||
let before = scripts.states.clone();
|
||||
let mut dead = event(1, 48);
|
||||
dead["health"] = json!(0);
|
||||
assert!(invoke(&mut scripts, "on_tick", &dead).is_empty());
|
||||
dead["health"] = json!(160);
|
||||
dead["entities"] = json!([]);
|
||||
assert!(invoke(&mut scripts, "on_tick", &dead).is_empty());
|
||||
assert!(invoke(&mut scripts, "on_spawn", &dead).is_empty());
|
||||
assert_eq!(scripts.states, before);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn entrance_and_patrol_turns_preserve_the_y24_band() {
|
||||
let mut scripts = runtime();
|
||||
let mut e = event(1, 0);
|
||||
e["y"] = json!(-24);
|
||||
let commands = invoke(&mut scripts, "on_tick", &e);
|
||||
assert!(matches!(
|
||||
commands[0],
|
||||
Command::Velocity {
|
||||
entity: 1,
|
||||
vx: 0,
|
||||
vy: 2
|
||||
}
|
||||
));
|
||||
e["y"] = json!(24);
|
||||
e["x"] = json!(144);
|
||||
let commands = invoke(&mut scripts, "on_tick", &e);
|
||||
assert!(
|
||||
commands
|
||||
.iter()
|
||||
.any(|c| matches!(c, Command::Velocity { vx: -1, vy: 0, .. }))
|
||||
);
|
||||
assert!(
|
||||
commands
|
||||
.iter()
|
||||
.any(|c| matches!(c, Command::Move { y: 24, .. }))
|
||||
);
|
||||
e["x"] = json!(80);
|
||||
let commands = invoke(&mut scripts, "on_tick", &e);
|
||||
assert!(
|
||||
commands
|
||||
.iter()
|
||||
.any(|c| matches!(c, Command::Velocity { vx: 1, vy: 0, .. }))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn real_simulation_keeps_the_boss_alive_until_damage_and_counts_death_once() {
|
||||
use raptor_replica::{
|
||||
content::{ContentPack, Registry},
|
||||
modding::{self, ActiveMods},
|
||||
persistence::Pilot,
|
||||
simulation::{Input, Outcome, World},
|
||||
};
|
||||
let pack: ContentPack = serde_json::from_value(json!({
|
||||
"weapons":[{"id":"base:test_gun","name":"Fixture gun","damage":1,"speed":8,"cooldown":2,"behavior":"straight","target":"all"}],
|
||||
"items":[{"id":"base:forward_guns","name":"Fixture guns","price":0,"max_count":1,"weapon":"base:test_gun"},
|
||||
{"id":"base:energy","name":"Shield","price":10000,"max_count":100}],
|
||||
"missions":[{"id":"base:sector_1_wave_1","name":"Boss validation","duration":2,"auto_complete":false}]
|
||||
})).unwrap();
|
||||
let mut base = Registry::default();
|
||||
base.apply(pack, "base").unwrap();
|
||||
base.validate().unwrap();
|
||||
let mods = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
|
||||
.parent()
|
||||
.unwrap()
|
||||
.parent()
|
||||
.unwrap();
|
||||
let discovery = modding::discover(mods);
|
||||
let mut active = ActiveMods::build(&base, &discovery, &[OWNER.into()], None).unwrap();
|
||||
let pilot = Pilot::default();
|
||||
let mut world =
|
||||
World::new(&"base:sector_1_wave_1".into(), &pilot, &active.registry).unwrap();
|
||||
world.player.x = 0;
|
||||
world
|
||||
.apply_commands(
|
||||
&[Command::Spawn {
|
||||
enemy: format!("{OWNER}:unit"),
|
||||
x: 112,
|
||||
y: -24,
|
||||
}],
|
||||
&active.registry,
|
||||
)
|
||||
.unwrap();
|
||||
let mut warning_count = 0;
|
||||
let mut shot_count = 0;
|
||||
for _ in 0..600 {
|
||||
world
|
||||
.step(Input::default(), &active.registry, &mut active.scripts)
|
||||
.unwrap();
|
||||
warning_count += world
|
||||
.mod_sounds
|
||||
.iter()
|
||||
.filter(|s| s.0.ends_with(":warning"))
|
||||
.count();
|
||||
shot_count += world
|
||||
.mod_sounds
|
||||
.iter()
|
||||
.filter(|s| s.0.ends_with(":shot"))
|
||||
.count();
|
||||
}
|
||||
assert_eq!(
|
||||
world.enemies.len(),
|
||||
1,
|
||||
"timer and scrolling cannot retire the boss"
|
||||
);
|
||||
assert_eq!(world.outcome, Outcome::Playing);
|
||||
assert_eq!(world.enemies[0].y, 24);
|
||||
assert!((80..=144).contains(&world.enemies[0].x));
|
||||
assert!(warning_count >= 10 && shot_count >= 30);
|
||||
assert!(!world.objectives.contains_key("iron_coast_boss_5:defeated"));
|
||||
let entity = world.enemies[0].entity;
|
||||
world
|
||||
.apply_commands(
|
||||
&[Command::Damage {
|
||||
entity,
|
||||
amount: 160,
|
||||
}],
|
||||
&active.registry,
|
||||
)
|
||||
.unwrap();
|
||||
world
|
||||
.step(Input::default(), &active.registry, &mut active.scripts)
|
||||
.unwrap();
|
||||
assert!(world.enemies.is_empty());
|
||||
assert_eq!(world.objectives["iron_coast_boss_5:defeated"], 1);
|
||||
assert_eq!(world.player.credits, pilot.credits + 1800);
|
||||
assert!(world.mod_sounds.iter().any(|s| s.0.ends_with(":death")));
|
||||
assert!(
|
||||
active.scripts.states[KEY]["units"]
|
||||
.get(entity.to_string())
|
||||
.is_none()
|
||||
);
|
||||
for _ in 0..80 {
|
||||
world
|
||||
.step(Input::default(), &active.registry, &mut active.scripts)
|
||||
.unwrap();
|
||||
assert!(!world.mod_sounds.iter().any(|s| s.0.ends_with(":shot")));
|
||||
}
|
||||
assert_eq!(world.objectives["iron_coast_boss_5:defeated"], 1);
|
||||
assert_eq!(
|
||||
world.outcome,
|
||||
Outcome::Playing,
|
||||
"boss never completes a mission itself"
|
||||
);
|
||||
}
|
||||
}
|
||||