Compare commits
7 Commits
v0.4.0-rc1
...
kimi-work
| Author | SHA1 | Date | |
|---|---|---|---|
| 217e3ac886 | |||
| f33b799551 | |||
| d8cb98a964 | |||
| e59c49e3bf | |||
| 9664783b17 | |||
| 3db1b73d64 | |||
| 367c7f4ac3 |
+21
-5
@@ -143,6 +143,11 @@ pub fn draw(sim: &Sim, player: DVec3, yaw: f32) -> Vec<Shape> {
|
||||
s.push(square(x, y, 1.5, 0xFFFFFFA0));
|
||||
}
|
||||
}
|
||||
// The player is below target blips: a close target must not disappear under the white arrow.
|
||||
// Minecraft's yaw 0 looks south (+z, down the scope).
|
||||
let face = yaw.to_radians();
|
||||
let tip = |a: f32, l: f32| pt(cx - (face + a).sin() * l, cy + (face + a).cos() * l);
|
||||
s.push(quad(tip(0.0, 6.0), tip(2.5, 4.0), pt(cx, cy), tip(-2.5, 4.0), 0xFFFFFFFF));
|
||||
let mut far = 0;
|
||||
// The air picture is what the systems' radars hold, as they last measured it (a track older than a second
|
||||
// is a forecast, dimmer); with no system deployed, the sky as it is. Nothing else is read (#93).
|
||||
@@ -176,11 +181,6 @@ pub fn draw(sim: &Sim, player: DVec3, yaw: f32) -> Vec<Shape> {
|
||||
}
|
||||
}
|
||||
}
|
||||
// The player: a white arrow the way they look.
|
||||
// Minecraft's yaw 0 looks south (+z, down the scope).
|
||||
let face = yaw.to_radians();
|
||||
let tip = |a: f32, l: f32| pt(cx - (face + a).sin() * l, cy + (face + a).cos() * l);
|
||||
s.push(quad(tip(0.0, 6.0), tip(2.5, 4.0), pt(cx, cy), tip(-2.5, 4.0), 0xFFFFFFFF));
|
||||
// Labels.
|
||||
s.push(text(format!("РЛС · {}", distance(RANGE)), cx, SIZE + 2.0, 0xFF80FF90, true));
|
||||
s.push(text("С".into(), cx, 3.0, 0xFF80FF90, true));
|
||||
@@ -189,3 +189,19 @@ pub fn draw(sim: &Sim, player: DVec3, yaw: f32) -> Vec<Shape> {
|
||||
}
|
||||
s
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::sim::{DroneKind, Out};
|
||||
|
||||
#[test]
|
||||
fn target_blips_are_drawn_over_the_player_arrow() {
|
||||
let mut sim = Sim::new(1);
|
||||
sim.launch(DroneKind::Shahed, DVec3::new(0.0, 64.0, -10.0), DVec3::ZERO, DVec3::ZERO, &mut Out::default());
|
||||
let shapes = draw(&sim, DVec3::ZERO, 0.0);
|
||||
let arrow = shapes.iter().rposition(|s| matches!(s, Shape::Quad(q) if q.color == 0xFFFFFFFF)).unwrap();
|
||||
let target = shapes.iter().rposition(|s| matches!(s, Shape::Fill(f) if f.color == 0xFFFF4040)).unwrap();
|
||||
assert!(target > arrow, "the player arrow covers the target blip");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -155,6 +155,8 @@ impl Recording {
|
||||
objects: Vec::new(),
|
||||
sam_tracks: Vec::new(),
|
||||
sam_index: HashMap::new(),
|
||||
sam_keys: 0,
|
||||
sam_step: STEP,
|
||||
ticks: Vec::new(),
|
||||
times: Vec::new(),
|
||||
quiet: 0,
|
||||
@@ -185,6 +187,8 @@ pub const STEP: u32 = 2;
|
||||
/// The longest recording (10 minutes) and how many are kept.
|
||||
const LONGEST: u32 = 12_000;
|
||||
const KEPT: usize = 3;
|
||||
/// SAM poses share a fixed memory budget; under pressure old poses are thinned and new ones slow down.
|
||||
const MAX_SAM_KEYS: usize = 32_768;
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Debug)]
|
||||
pub enum What {
|
||||
@@ -381,6 +385,8 @@ pub struct Recording {
|
||||
/// it pointed, whether it was still whole).
|
||||
pub sam_tracks: Vec<SamTrack>,
|
||||
sam_index: HashMap<u32, usize>,
|
||||
sam_keys: usize,
|
||||
sam_step: u32,
|
||||
/// The game's tick number as sampled with the clock: (recording tick, game tick). A replay
|
||||
/// shows the world's blocks as of this time.
|
||||
ticks: Vec<(u32, u64)>,
|
||||
@@ -589,14 +595,38 @@ impl Recording {
|
||||
objects: sim.targets.clone(),
|
||||
sam_tracks: Vec::new(),
|
||||
sam_index: HashMap::new(),
|
||||
sam_keys: 0,
|
||||
sam_step: STEP,
|
||||
ticks: Vec::new(),
|
||||
times: Vec::new(),
|
||||
quiet: 0,
|
||||
}
|
||||
}
|
||||
fn thin_sam_tracks(&mut self) {
|
||||
for track in &mut self.sam_tracks {
|
||||
let len = track.keys.len();
|
||||
let mut kept = 0;
|
||||
for read in (0..len).step_by(2) {
|
||||
track.keys[kept] = track.keys[read];
|
||||
kept += 1;
|
||||
}
|
||||
if len % 2 == 0 && len > 0 {
|
||||
track.keys[kept] = track.keys[len - 1];
|
||||
kept += 1;
|
||||
}
|
||||
track.keys.truncate(kept);
|
||||
track.keys.shrink_to_fit();
|
||||
}
|
||||
self.sam_keys = self.sam_tracks.iter().map(|track| track.keys.len()).sum();
|
||||
self.sam_step = self.sam_step.saturating_mul(2).min(LONGEST);
|
||||
}
|
||||
|
||||
/// Notes the systems' poses at the recording's tick `now`: a pose only when it changed, with
|
||||
/// the pose it held marked before it so a replay doesn't glide through the stillness.
|
||||
fn note_sams(&mut self, sams: &[Sam], now: u32) {
|
||||
if now % self.sam_step != 0 {
|
||||
return;
|
||||
}
|
||||
for sam in sams {
|
||||
let key = SamKey::of(sam, self.origin, now);
|
||||
let i = *self.sam_index.entry(sam.id).or_insert_with(|| {
|
||||
@@ -604,16 +634,21 @@ impl Recording {
|
||||
self.sam_tracks.len() - 1
|
||||
});
|
||||
let track = &mut self.sam_tracks[i];
|
||||
let before = track.keys.len();
|
||||
match track.keys.last().copied() {
|
||||
None => track.keys.push(key),
|
||||
Some(last) if last.same(&key) => {}
|
||||
Some(last) => {
|
||||
if last.at + STEP < now {
|
||||
track.keys.push(SamKey { at: now - STEP, ..last });
|
||||
if last.at + self.sam_step < now {
|
||||
track.keys.push(SamKey { at: now - self.sam_step, ..last });
|
||||
}
|
||||
track.keys.push(key);
|
||||
}
|
||||
}
|
||||
self.sam_keys += track.keys.len() - before;
|
||||
}
|
||||
if self.sam_keys > MAX_SAM_KEYS {
|
||||
self.thin_sam_tracks();
|
||||
}
|
||||
}
|
||||
/// Actual launch, if observed during this recording. Missing is not proof of no launch:
|
||||
@@ -1043,6 +1078,8 @@ mod tests {
|
||||
objects: Vec::new(),
|
||||
sam_tracks: Vec::new(),
|
||||
sam_index: HashMap::new(),
|
||||
sam_keys: 0,
|
||||
sam_step: STEP,
|
||||
ticks: Vec::new(),
|
||||
times: Vec::new(),
|
||||
quiet: 0,
|
||||
@@ -1169,6 +1206,29 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn full_recording_bounds_sam_keys_without_freezing_their_poses() {
|
||||
let mut r = recording();
|
||||
let mut sams: Vec<_> = (0..64)
|
||||
.map(|id| Sam::new(id, SamKind::Radar, DVec3::ZERO, 0.0))
|
||||
.collect();
|
||||
for now in (0..LONGEST).step_by(STEP as usize) {
|
||||
for sam in &mut sams {
|
||||
sam.dish = now as f64 * 0.01;
|
||||
}
|
||||
r.note_sams(&sams, now);
|
||||
}
|
||||
let keys: usize = r.sam_tracks.iter().map(|track| track.keys.len()).sum();
|
||||
let allocated: usize = r.sam_tracks.iter().map(|track| track.keys.capacity()).sum();
|
||||
assert_eq!(keys, r.sam_keys);
|
||||
assert!(keys <= MAX_SAM_KEYS, "a full recording retained {keys} SamKeys");
|
||||
assert!(allocated <= 2 * MAX_SAM_KEYS, "SamKey allocations reached {} MiB", allocated * size_of::<SamKey>() / (1024 * 1024));
|
||||
assert!(r.sam_step > STEP, "the rotating radars never engaged adaptive sampling");
|
||||
let last = r.sam_tracks[0].pose(LONGEST as f64).unwrap();
|
||||
assert!(last.at >= LONGEST - 64, "poses stopped at tick {}", last.at);
|
||||
assert!(last.dish > 100.0, "the final radar pose was not recorded");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_system_keeps_its_pose_through_stillness_and_moves_when_it_moved() {
|
||||
let mut r = recording();
|
||||
|
||||
+2
-2
@@ -63,8 +63,8 @@ runtime закреплённого движка. Используется вре
|
||||
|
||||
## Акустика и владение захватом (API 27)
|
||||
|
||||
Текущий pin: `278f356aa604fa21c2d15d0081823bbe0f891df2` (API 30; поверх API29 — нормали моделей
|
||||
и очистка неудачного захвата). Акустические
|
||||
Текущий pin: `ed751714e0d24d02062976ee615e994186284170` (API 30; staging, cascades,
|
||||
previous-frame motion и feature-gated DLSS SR backend #124). Акустические
|
||||
`render.acoustics-status` и `render.capture-owner-status` возвращают поколение,
|
||||
`available`, `enabled`, `request-accepted`, `prepared` и причину. В игре всё это показывает
|
||||
`/airdef capabilities`. Отсутствие запроса, выключение, ожидание backend и ошибка различаются.
|
||||
|
||||
+17
-6
@@ -58,8 +58,12 @@ cd .. && rm -f horizon-patches/*.patch && git -C horizon format-patch -q $(cat H
|
||||
возвращается. Рисуются секции террейна в коробке дальнего каскада (`hd_shadow_visible`) и сущности-тенеотбрасыватели
|
||||
(`shadow_casters`: без оверлеев 4/9/10/40 — 40 это светящиеся модели мода).
|
||||
2. **G-буфер** (свой depth): albedo `Rgba8Unorm` (a = эмиссия), нормаль `Rgba16Float` (мировая, из `dpdx/dpdy`),
|
||||
уровни света `Rgba8Unorm` (x = небо, y = блоки — сырые координаты лайтмапа /240). Террейн + дальние LOD-тайлы +
|
||||
сущности. Полупрозрачное в G-буфер **не** попадает (идёт ванильным путём); вода — отдельный путь, см. п. 3 и «Вода».
|
||||
уровни света `Rgba8Unorm` (x = небо, y = блоки — сырые координаты лайтмапа /240), motion `Rg16Float`
|
||||
(current→previous в render-пикселях, обе проекции с jitter). Террейн + дальние LOD-тайлы + сущности.
|
||||
Terrain/ванильные сущности пишут camera/static motion; `motion_models_pass` перезаписывает mod-модели
|
||||
previous-frame transforms с read-only depth и тем же alpha cutout (`7a73e8d26`). Ветер остаётся camera-only.
|
||||
`HORIZON_HD_DEBUG_MOTION=1` показывает R/G displacement и validity вместо обычного TAA-кадра.
|
||||
Полупрозрачное в G-буфер **не** попадает (идёт ванильным путём); вода — отдельный путь, см. п. 3 и «Вода».
|
||||
3. **Вода** (`water_pass`, только если на экране есть полупрозрачные секции, нет `frame.underwater` и не `HORIZON_HD_WATER=0`):
|
||||
те же секции ещё раз с `fs_water` (terrain.wgsl), который отбрасывает всё, кроме слоёв спрайтов `water_still`/`water_flow`
|
||||
(номера слоёв передаются в `Renderer::new` из `sprite_layers` и ставятся override-константами `WATER_STILL/WATER_FLOW`; стекло,
|
||||
@@ -86,7 +90,9 @@ cd .. && rm -f horizon-patches/*.patch && git -C horizon format-patch -q $(cat H
|
||||
5. **Пост-проход мода** `lit` → `fx` (`effects.wgsl`: взрывы, дым, вспышки, свечение; при HD его собственная
|
||||
«подсветка огнём» выключена — свет даёт HD).
|
||||
6. **TAA** — после пост-прохода мода (`fx`, либо `lit` без него), до bloom: история пинг-понгом, в альфе истории —
|
||||
дистанция для отбраковки дизокклюзии; телепорт > 64 блоков сбрасывает историю. Джиттер добавляется к
|
||||
дистанция для отбраковки дизокклюзии; телепорт > 64 блоков сбрасывает историю. Если world/G-buffer depth
|
||||
совпадают, history UV = current UV + motion/render_size; вода, прозрачность и небо используют прежний
|
||||
depth/camera fallback. Джиттер добавляется к
|
||||
`view_projection` только при HD (публичная матрица без джиттера); рука и HUD рисуются после tonemap и не дрожат.
|
||||
**TAAU**: если мир меньше окна (`HORIZON_HD_SCALE=0.67`), тот же проход (`fs_taau`, в таймингах `hd taau`) восстанавливает
|
||||
полное разрешение. История, bloom и tonemap — в разрешении окна. Для каждого пикселя выхода берётся 3×3 выборки мира
|
||||
@@ -245,8 +251,13 @@ HORIZON_HD_TAA=0 --out run-home-bench/out/taa-off` — A/B; `--baseline run-home
|
||||
TAA-проход восстанавливает полное разрешение: история в полном разрешении, текущий кадр семплируется с учётом
|
||||
джиттера (Catmull-Rom/Lanczos-веса по субпиксельной позиции), клиппинг окрестности как в TAA. Настройка
|
||||
масштаба (`HORIZON_HD_SCALE` или поле в `HdSettings`). Цель: при 0.67 картинка близка к нативной, FPS заметно
|
||||
выше. Альтернатива на Mac — MetalFX через wgpu-hal (нативный код, только Metal) — отдельной опцией потом. DLSS
|
||||
нереален для нашей цели (только NVIDIA через Streamline, нет в wgpu).
|
||||
выше. #124 развивает этот путь в общий temporal backend: `ed751714e` добавляет optional `dlss` feature
|
||||
и `HORIZON_DLSS=1`, previous-frame motion, NGX+HDR bootstrap и display↔linear conversion. Submit:
|
||||
world+linearize → NGX → delinearize; native TAAU остаётся fallback. По умолчанию DLSS рендерит world 2/3,
|
||||
`HORIZON_HD_SCALE` переопределяет это. Сборка требует `DLSS_SDK` (v310.9.1), `VULKAN_SDK`, clang и
|
||||
`--features dlss`; runtime DLL/SO и NVIDIA notices нужны рядом с приложением, не в Git. Windows `cargo check`
|
||||
прошёл; MinGW link несовместим с MSVC `.lib`, MSVC link и реальный RTX/Vulkan runtime ещё не проверены.
|
||||
Ванильные сущности/ветер остаются camera-only. MetalFX — отдельный Metal backend, FG отдельно.
|
||||
2. **Вода** — СДЕЛАНО (патч 19, см. «Вода» ниже). Исходное ТЗ: отдельный G-буфер-путь для воды (маска + нормаль с анимированными волнами), SSR (трассировка по
|
||||
глубине, откат на небо/туман), преломление (смещение по нормали), затухание по глубине воды (цвет биома),
|
||||
блики солнца (GGX-подобный), френель. Подводный вид не ломать (`frame.underwater`).
|
||||
@@ -404,7 +415,7 @@ ffmpeg 9.0.1. `/airdef replay export` с записанного налёта (4
|
||||
|
||||
## Цветные тени, рябь, кино-пост: что знать преемнику
|
||||
|
||||
- **Цветные тени.** Ближний каскад (c = 0) рисуется `fs_shadow_near` (листва `MAT_LEAVES` отброшена), потом проход `hd shadow colour` (`Hd::shadow_tint_pass`, цель `Shadows::tint`, очистка в белое) рисует листву (`shadow_tint_leaves`, твёрдые квады, `SHADOW_SOLID`) и полупрозрачные квады секций (`shadow_tint_glass`, по отсортированным индексам; вода не рисуется) тем же `fs_shadow_tint`: rgb умножаются (blend `Dst*src`), альфа = `(расстояние от света − 320)/160` с blend Min — ближайший такой квад. Приёмник в `shadow_tint` (`light.wgsl`, привязка 22) окрашивается, если квад ближе к свету, чем он; 8 точек Vogel радиусом 0.4 блока размывают листву в «пятна». Дальний каскад по-прежнему рисует листву полностью (на границе 36 блоков шов: ближе листва светлее). Цвет не доходит до лучей тумана, воды и дальнего каскада. Цена: `hd shadow colour` 0.25–0.35 мс (Mac, малая сцена).
|
||||
- **Цветные тени.** Ближний каскад (c = 0) рисуется `fs_shadow_near` (листва `MAT_LEAVES` отброшена), потом проход `hd shadow colour` (`Hd::shadow_tint_pass`, цель `Shadows::tint`, очистка в белое) рисует листву (`shadow_tint_leaves`, твёрдые квады, `SHADOW_SOLID`) и полупрозрачные квады секций (`shadow_tint_glass`, по отсортированным индексам; вода не рисуется) тем же `fs_shadow_tint`: rgb умножаются (blend `Dst*src`), альфа = `(расстояние от света − 320)/160` с blend Min — ближайший такой квад. Приёмник в `shadow_tint` (`light.wgsl`, привязка 22) окрашивается, если квад ближе к свету, чем он; 8 точек Vogel радиусом 0.4 блока размывают листву в «пятна». Дальний каскад по-прежнему рисует листву полностью; с `17382445a` near/far depth filters смешиваются на последних 6 блоках перед границей 36,8, а tint затухает по той же smoothstep — круглого шва нет. Цвет не доходит до лучей тумана, воды и дальнего каскада. Цена: `hd shadow colour` 0.25–0.35 мс (Mac, малая сцена).
|
||||
- **Рябь.** `ripple_gradient` берёт часы воды (`water.y`, часы фильма): частоты — целые циклы на 600 с, сетка 160 клеток на 256 блоков, поэтому рисунок не прыгает на обороте. Гаснет по размеру пикселя, только под открытым небом (`nt.a`). Амплитуда 0.012 не подбиралась.
|
||||
- **Кино-пост.** `Hd::film` между `taa` и `finish` (`HdSource::Film`), только при `film_quality()`; цели создаются при первом фильме. Motion blur — только от движения камеры (глубина + `prev_view_proj`, затвор 180°, мёртвая зона 1–2.5 px из-за джиттера; движущиеся объекты не размываются; нет после склейки/телепорта: `taa.w`). DoF — диск 24 отсчёта, фокус авто (гармоническое среднее 9 глубин у центра) или `HORIZON_HD_DOF_FOCUS`. Детерминизм: единственное время в шейдере — `water.y` (часы фильма) для шума. Проверять на реальном фильме `/airdef replay film`; `HORIZON_HD_FILM_QUALITY=1` принудительно включает качество фильма для проб. DoF в игре был слишком сильным и дорогим; параметры уменьшены без повторного снимка. Motion blur на снимке (поворот 40° за 1.5 с) не разобран.
|
||||
- Из Photon (MIT) ничего нового не переносилось: THIRD_PARTY_PHOTON.md не менялся.
|
||||
|
||||
@@ -50,7 +50,9 @@
|
||||
| #119 | Три read-only луча в известной геометрии ослабляют непрямой blast для корпуса/узлов/объектов; проёмы дают частичное воздействие; прямой контакт сохранён | Unknown/stale: прежний урон без защиты, нулевое ожидание; partial blocks считаются занятыми кубами; GUI-проверка разрушения укрытия ещё нужна |
|
||||
| #120 | Конечное горение, powered/coast, сопротивление и снижение доступного манёвра, один остаточный pk по фактической скорости; swept-встреча; события burnout/energy loss; согласованное прекращение факела/дыма/звука | Игровой профиль, без аэродинамики и реальных параметров; сравнительная выборка 12 рейдов и расширенная сетка 48 рейдов (6 вариантов × 8 seed, батареи/дальности/классы) записаны в measurements; без подтверждённого дефекта параметры не менялись; прослушивание остаётся |
|
||||
| #121 | On/Off в команде и карте, save/replay; Off не создаёт own measurements и сохраняет сетевой приём; раскрытие работающего радара наблюдателем; SEAD использует last-known и возраст | Условный наблюдатель по расстоянию, без радиотехнической модели и ошибки пеленга; выбор на карте ещё не проверен в GUI |
|
||||
| #122 | Воспроизведены два класса: tick fuel exhaustion на двух максимальных составах, HD (полный обход height-cache на каждом взрыве заменён удалением нужных ключей; max135.3M/300M) и остановка в UI event на втором крупном приказе (переснимок known_units на каждом дроне заменён одним снимком на приказ и O(журнал+группировка); max command 32.9M/1e9, `/stats` до/после совпал); нативные тесты эквивалентности и второго приказа; WASM fixture с 4 принятыми приказами; callback diagnostics/profile | Требуется приёмка на Windows; без подтверждённого build identity нельзя утверждать ту же причину пользовательского сеанса; FPS/GPU не измерялись |
|
||||
| #122 | Воспроизведены три класса: tick fuel exhaustion на двух максимальных составах, HD (полный обход height-cache на каждом взрыве заменён удалением нужных ключей; max135.3M/300M); остановка в UI event на втором крупном приказе (переснимок known_units на каждом дроне заменён одним снимком на приказ и O(журнал+группировка); max command 32.9M/1e9, `/stats` до/после совпал); OOM в `note_sams` (до: 384000 SamKey/27 MiB в одной полной записи 64 РЛС; после: бюджет 32768, адаптивное прореживание без остановки записи; WASM 24002 тика/64 РЛС/60 малых волн прошёл с прежним лимитом 64 MiB); нативные регрессии и callback diagnostics/profile | Требуется приёмка на Windows; без подтверждённого build identity нельзя утверждать ту же причину пользовательского сеанса; FPS/GPU не измерялись |
|
||||
| #124 | Motion + object transforms + feature-gated DLSS SR PoC: engine `ed751714e`; motion в render-пикселях, previous-frame mod-models, `HORIZON_HD_DEBUG_MOTION`; NGX+HDR device bootstrap, долгоживущий SDK/context, display↔linear conversion и ordered submit; native TAAU fallback. Windows `cargo check --features dlss` прошёл; 24 HD + layout, mods/no-mods, обычный Metal кадр проверены | Это PoC, не подтверждённая поддержка: MinGW link несовместим с MSVC `.lib`, MSVC link и реальный RTX/Vulkan runtime не проверялись; ванильные сущности/ветер camera-only; reactive/transparency, FSR3/MetalFX и FG отдельно |
|
||||
| #130 | Первый небольшой backport: upstream `f7b77fbc8` перенесён как engine `94b572cd2`; oversized staging allocations округляются до степени двойки и переиспользуются, копируются только фактические байты; 3 upload/GPU readback-теста прошли на Metal | Полная миграция на Nightly не начата; VSync/pacing, дальний рельеф и другие кандидаты остаются; Windows/Vulkan и долгий игровой RSS-профиль не проверялись |
|
||||
| #127 | Этапы 1–2. **Картина:** журнал раскрытий заменён картой контактов (`Intel.contacts`: последнее «пуск/излучение/глушение», место, время, метка эмиссии, наблюдённая гибель, счётчик увиденных пусков), уверенность — функция возраста (Confirmed/Probable/Doubtful, TTL 6000), угроза и цены читают только published-параметры и факты наблюдений (launcher-only без донора «слеп»), уничтожение учится только из BSA. **Structured intent:** `Intent` (Plan/Sead/React/Replace) и `Aim` (юнит, since, switches, confidence) отделены от random-выбора; throttle `SWITCH_HOLD=160`, `SWITCH_CAP=3`, `ASSIGN_PER_TICK=16`, `ASSIGN_PER_CONTACT=6`, цена смены `SWITCH_GAIN=120`; в полёте цель меняется только когда картинка её убила/забыла. **Durable queue:** автосейв пишет версионированную секцию `air 1` — дроны, очередь запуска, задания (id, дрон, цель, юнит, версия, приоритет, намерение, версия рельефа), контакты, RLE-рельеф; очередь ограничена 256, устаревшие/фантомные/дублирующие задания отменяются при загрузке (`JOB_TTL=1200`), картина задания восстанавливается на свою версию, старые saves читаются (пустое небо), сценарии воздух не несут, Live/Replay не смешиваются. **Неизвестная земля не бесплатна** (`Relief.unknown=6` у повторного плана). **Объяснимость:** `enemy_picture()` дополнен aims/pending reason/aimed_by/pending_jobs без русского текста в core. Метаморфные тесты скрытых изменений (включая нагрев/замок РЭБ), save/restore посреди незавершённой очереди, детерминизм seed, 40+ массовый ретаргет. Замеры: `docs/measurements/issue-127-intent-queue-{baseline,result}.json` | GUI поверх `enemy_picture()`, игровая приёмка сохранения налёта на Windows/Linux, оценка баланса ew-adaptive (kills 59→58/59→58/56→63, потери 3→3/3→4/4→2 — следствие throttle, параметры не менялись) |
|
||||
|
||||
По #127 реализованы карта контактов, структурированное намерение с throttle и durable-очередь:
|
||||
|
||||
+24
-3
@@ -7,10 +7,31 @@
|
||||
поэтому запись может сохраниться и без ручного копирования.
|
||||
Задача: [#122](https://gitea.maoniao.team/chexit/airdef/issues/122).
|
||||
|
||||
**Воспроизведены и исправлены две конкретные причины отключения в этом классе нагрузки.**
|
||||
**Воспроизведены и исправлены три конкретные причины отключения в этом классе нагрузки.**
|
||||
Первая — тик и кэш высот (`1f28f83`, ниже). Вторая — событие UI и второй приказ
|
||||
(`order_raid`, см. раздел «Второй налёт из UI: событие, не тик»). Без старого лога не
|
||||
утверждается, что Windows-сеанс имел именно их; GPU/FPS отдельно не измерялись.
|
||||
(`order_raid`, см. раздел «Второй налёт из UI: событие, не тик»). Третья — исчерпание
|
||||
64 MiB линейной памяти WASM полными ключами поз техники в реплеях. GPU/FPS отдельно не измерялись.
|
||||
|
||||
## OOM при записи поз техники
|
||||
|
||||
Точный лог Windows: `rust_oom` ← `RawVec<SamKey>::grow_one` ← `Recording::note_sams` ←
|
||||
`Sim::step` во время обычного `tick`, с оставшимся fuel. Это не повтор прежнего OutOfFuel.
|
||||
|
||||
До исправления нативная регрессия с 64 непрерывно вращающимися РЛС на полной записи 12000 тиков
|
||||
создавала 384000 `SamKey`: 27 MiB только в этих ключах, без учёта ёмкости `Vec`, траекторий и
|
||||
событий. Активная запись и три сохранённых реплея могли превысить лимит 64 MiB.
|
||||
|
||||
`Recording` теперь имеет общий бюджет 32768 ключей поз. При превышении старые ключи каждой
|
||||
трассы прореживаются вдвое без потери первой и последней позы, освобождённая ёмкость возвращается,
|
||||
а интервал следующих samples удваивается. Запись не обрывается: регрессия проверяет бюджет
|
||||
длины и реально выделенной ёмкости, а также наличие свежей позы в конце окна. Для обычных
|
||||
коротких записей частота не меняется; при нехватке памяти точность старого движения снижается.
|
||||
|
||||
Проверки: core release 233 + 8 сценариев (8 ignored), adapter 68 + 1 boundary + 5 сценариев;
|
||||
настоящий WASM contract 30 шагов. Дополнительный flat-world WASM-стресс: 64 РЛС, 60 волн по
|
||||
4 Shahed на 4000 блоках, 24002 тика и два полных окна записи, 185 шагов; max tick fuel
|
||||
137346889/300M, command 129151/1B, failures=0. Лимит памяти 64 MiB не увеличивался. Это
|
||||
синтетический host без GPU и не приёмка Windows GUI.
|
||||
|
||||
## Второй налёт из UI: событие, не тик
|
||||
|
||||
|
||||
+1
-1
Submodule horizon updated: 278f356aa6...ed751714e0
Reference in New Issue
Block a user