The final engine 6a53a626c with the API30 AirDef build was run in the
real game (macOS, Apple M5, Metal, --offscreen, a clean HORIZON_HOME and
copied test worlds), not only in the sandbox: the monitor and the film's
main view report ready with the picture actually drawn (screenshots show
the panel and picture area, not a registered target), an empty set-views
drops the request, a world change bumps the generation and clears it, and
AirDef's next submission prepares the camera again in the new world.
Fixes nothing: no defect showed. It also records what the run does not
prove (a human visual check, Windows/other GPUs, two runs with the same
local id on the GPU, a mod reload on the GPU), the per-tick
set-views/set-main-view ordering that can honestly read awaiting-frame
between tick and note (not flicker), and that a title trip back into the
same world keeps the request because the generation is per world.
Co-Authored-By: Kimi Code <noreply@moonshot.cn>
Pin the engine at 6a53a626c (API 30) and show render.camera-status: the
calling run's set-views cameras, whether the last noted frame was a world
frame, the request's world generation, each camera's reason and the run's
own main-view selection. As the other lines, this is a host milestone:
availability, request, preparation and why, not a GPU frame or a visible
picture. A unit test pins the line: readiness stays per camera and apart
from the run's main-view selection.
docs/ENGINE.md, the 4 November continuation and the status table now say
what prepared means (a picture encoded into the camera's target, or a
world frame drawn from it as this run's screen view, only for the run
whose selection the screen used), the per-run/per-world resets, and what
remains: GPU acceptance.
Verified: python3 tools/airdef.py check (gitlink, packer, mod needs 30,
fork 30, game 30); test-wasm 27 steps; airdef-mod 65 unit, 1 boundary and
5 scenarios; python3 -m unittest discover -s tools 28 tests.
Co-Authored-By: Kimi Code <noreply@moonshot.cn>
The old engine_voice returned early for flame == 0 with
(DRONE_LANCET, 0.8, 1.6), so the later `P1sun => 1.7` match arm was
unreachable. The catalog migration mistook that arm for the played value
and stored 1.7, which changed the interceptor drone's flight sound.
specs.json is back to 1.6 and specs_generated.rs is regenerated.
Adds missile_engine_voices_keep_their_played_values, pinning P1-SUN to
(DRONE_LANCET, 0.8, 1.6) and M48N6 to (MISSILE_FLIGHT, 1.0, 0.88).
The continuation and handoff text now describe the early return instead
of the dead arm, and tools/airdef_assets/catalog.py gets its final
newline back.
Verified: gen_catalog --check and test_gen_assets, core release
195 passed + 8 scenarios (7 ignored), adapter 64 unit + 1 boundary +
5 scenarios, WASM contract 27 steps.
catalog/specs.json is now the single copy of the numeric rows for
DroneSpec, MissileSpec and SamSpec: speed, guidance, warheads, models'
parts, RCS, seekers, launch and motor sounds, mount drives, gravity,
the Oreshnik split height and logistics limits, plus the named part
sets. tools/gen_catalog.py generates airdef-mod/src/sim/specs_generated.rs
(included by mod.rs, so the mod and airdef-core compile the same rows)
and unit_specs_generated.rs (included by units.rs). The hand-written
spec matches are gone; DroneKind::spec, MissileKind::spec and
SamKind::spec read the generated constants.
load_specs validates the schema, sound references, kind coverage against
content.json, named-part references and unused rows. gen_catalog.py
--check already runs in the CI generated-assets step, so a JSON/Rust
drift fails the build.
Every value was compared with the literals at 632061d field by field.
The only mismatch was the P1-SUN motor voice pitch in JSON (1.6 instead
of the actual 1.7, the flame == 0 branch in voices.rs); the JSON was
corrected and behaviour is unchanged. Save keys, raid order, RNG and
gameplay traces are untouched.
Verified: core release 194 passed + 8 scenarios (7 ignored), adapter
64 unit + 1 boundary + 5 scenarios, WASM contract 27 steps, catalog and
model generators fresh, asset/sound/model-bound/report tests pass.
Pairs of the same replay exported with acoustics on and off on the
scratch world (no HORIZON_NO_AUDIO): acoustics and capture ownership
both ready. The open field has no reverb send by design, so it shows
occlusion/air absorption only; the stone court's on-track is quieter
and darker, and its residual after a gain fit is low-passed and
stereo-decorrelated, i.e. the wet path. A clean reverb tail/RT60 in a
real MP4, echoes alone and listening remain open. No code changes.
Co-Authored-By: Kimi Code <noreply@moonshot.ai>
Engine 60cc7b11d removes a failed or cancelled take's video, raw sound
and log, carries the log's last lines in the error, and only creates
<name>.mp4 after a successful finish. A real failing encoder on the
scratch world left no temporaries and kept the earlier film byte for
byte; successful audio and no-audio control films still verified.
Co-Authored-By: Kimi Code <noreply@moonshot.ai>
A frame hook ends an active film or export when the world is gone. The game
stops ticking at the title screen but keeps rendering, so an export used to
keep recording the title with its playback clock frozen (a sec=10 request
produced 3898 frames) and went on after re-entering the world. Session
cleanup runs once with WorldChange and cancels the capture.
Real short exports on a scratch home and world (pin 57f8b2853, API 29,
Metal): h264+AAC both 2.07 s with non-silent audio, reported frames equal
ffprobe nb_frames; cancel, SDK refusal, encoder failure and restart all
leave no capture running; 1920x1080 works. The engine still leaves
*.audio.raw and *.ffmpeg.log behind on an encoder error (game/film.rs).
Co-Authored-By: Kimi Code <noreply@moonshot.ai>