From 575d7d29b8eac4191b2be996d37ab1f020706e3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hern=C3=A2ni=20Marques?= Date: Thu, 17 Sep 2026 21:48:48 +0200 Subject: [PATCH] kitchen: README + MISSIONS/engine-c keep-drop notes --- KEEP-DROP.md | 35 + MISSIONS/deepseek.MISSION.md | 25 + MISSIONS/hermes.MISSION.md | 26 + MISSIONS/qwen.MISSION.md | 26 + MISSIONS/qwen14b.MISSION.md | 25 + engine-c/build.sh | 22 + engine-c/gallery/m0_evolved.png | Bin 0 -> 7218 bytes engine-c/gallery/m0_series9.png | Bin 0 -> 9182 bytes engine-c/gallery/m0_survivor.png | Bin 0 -> 11786 bytes engine-c/gallery/m1_evolved.png | Bin 0 -> 6604 bytes engine-c/gallery/m1_series9.png | Bin 0 -> 8110 bytes engine-c/gallery/m1_survivor.png | Bin 0 -> 10187 bytes engine-c/gallery/m2_evolved.png | Bin 0 -> 7217 bytes engine-c/gallery/m2_series9.png | Bin 0 -> 7547 bytes engine-c/gallery/m2_survivor.png | Bin 0 -> 13029 bytes engine-c/gallery/m3_evolved.png | Bin 0 -> 7118 bytes engine-c/gallery/m3_series9.png | Bin 0 -> 8156 bytes engine-c/gallery/m3_survivor.png | Bin 0 -> 13193 bytes engine-c/gallery/m4_evolved.png | Bin 0 -> 6964 bytes engine-c/gallery/m4_series9.png | Bin 0 -> 9770 bytes engine-c/gallery/m4_survivor.png | Bin 0 -> 9658 bytes engine-c/include/kknd.h | 642 ++++++ engine-c/include/unit_card.h | 83 + engine-c/openkknd | Bin 0 -> 120544 bytes engine-c/openkknd_dbg | Bin 0 -> 271112 bytes engine-c/savegame.kknd | Bin 0 -> 204 bytes engine-c/shot_0001.png | Bin 0 -> 9770 bytes engine-c/src/ai.c | 124 ++ engine-c/src/audio.c | 76 + engine-c/src/buildings.c | 214 ++ engine-c/src/config.c | 273 +++ engine-c/src/demo_unitcard.c | 55 + engine-c/src/engine.c | 188 ++ engine-c/src/input.c | 292 +++ engine-c/src/main.c | 167 ++ engine-c/src/map.c | 407 ++++ engine-c/src/png.c | 83 + engine-c/src/projectiles.c | 106 + engine-c/src/render.c | 631 ++++++ engine-c/src/rng.c | 42 + engine-c/src/save.c | 87 + engine-c/src/snapshot.c | 148 ++ engine-c/src/sprites.c | 341 +++ engine-c/src/unit_card.c | 334 +++ engine-c/src/units.c | 585 +++++ kitchen/COLLAB-SIGNUP.md | 42 + kitchen/README.md | 3 +- kknd2-runs/20260713-093232/hermes-build.txt | 170 ++ kknd2-runs/20260713-093232/hermes.err | 2 + kknd2-runs/20260713-093232/hermes.out | 2 + kknd2-runs/20260713-093232/master.log | 39 + kknd2-runs/20260713-093232/nohup.out | 39 + kknd2-runs/20260713-093232/prompt-hermes.txt | 13 + kknd2-runs/20260713-093232/prompt-qwen.txt | 13 + kknd2-runs/20260713-093232/qwen-build.txt | 170 ++ kknd2-runs/20260713-093232/qwen.err | 2 + kknd2-runs/20260713-093232/qwen.out | 6 + kknd2-runs/20260713-093232/run.sh | 37 + .../deepseek-build.txt | 170 ++ .../20260713-093614-q14-deepseek/deepseek.err | 2 + .../20260713-093614-q14-deepseek/deepseek.out | 1 + .../20260713-093614-q14-deepseek/master.log | 48 + .../20260713-093614-q14-deepseek/nohup.out | 46 + .../prompt-deepseek.txt | 13 + .../prompt-qwen14b.txt | 13 + .../qwen14b-build.txt | 170 ++ .../20260713-093614-q14-deepseek/qwen14b.err | 2 + .../20260713-093614-q14-deepseek/qwen14b.out | 8 + .../20260713-093614-q14-deepseek/run.sh | 42 + llm-configs-openkknd2/README.md | 31 + llm-configs-openkknd2/scripts/common.sh | 108 + .../scripts/rotate-models-amanita.sh | 240 ++ .../scripts/start-amanita-coder.sh | 13 + .../scripts/start-amanita-pics.sh | 9 + .../scripts/start-amanita-vids.sh | 11 + .../scripts/start-mac-jan.sh | 8 + .../scripts/start-mac-osaurus.sh | 8 + openkknd2-am-coder/.agent-coder-resume.sh | 9 + openkknd2-am-coder/.am-meta/META_LOG.md | 144 ++ openkknd2-am-coder/.am-meta/additive.md | 6 + .../launch-20260722T052125Zdevstral.log | 24 + .../.am-meta/launch-20260722T052135Zcoder.log | 24 + .../launch-20260722T052226Zdevstral.log | 24 + .../.am-meta/launch-20260722T052858Zcoder.log | 24 + .../launch-20260722T053449Zqwen314.log | 24 + .../launch-20260722T055020Zqwen314.log | 24 + .../launch-20260722T055432Zqwen14.log | 24 + .../launch-20260722T055903Zdeepseek.log | 24 + .../launch-20260722T055954Zqwen38.log | 24 + .../launch-20260722T060502Zdevstral.log | 24 + .../launch-20260722T060714Zdevstral.log | 24 + .../launch-20260722T060744Zdevstral.log | 24 + .../launch-20260722T060922Zdevstral.log | 24 + .../launch-20260722T060959Zdevstral.log | 24 + .../launch-20260722T061029Zdevstral.log | 24 + .../.am-meta/launch-20260722T061559Zcoder.log | 24 + .../.am-meta/launch-20260722T062150Zcoder.log | 24 + .../.am-meta/launch-20260722T062640Zcoder.log | 24 + .../launch-20260722T063050Zqwen314.log | 24 + openkknd2-am-coder/.am-meta/meta.out | 150 ++ openkknd2-am-coder/.am-meta/meta.pid | 1 + openkknd2-am-coder/.am-meta/mission.md | 19 + openkknd2-am-coder/.am-meta/state.json | 20 + openkknd2-am-coder/AGENT_LOG.md | 1926 +++++++++++++++++ openkknd2-am-coder/CMakeLists.txt | 8 + openkknd2-am-coder/LICENSE | 21 + openkknd2-am-coder/README.md | 12 + openkknd2-am-coder/RESUME.md | 13 + openkknd2-am-coder/STATUS.md | 64 + openkknd2-am-coder/TASK.md | 72 + openkknd2-am-coder/THIRD_PARTY.md | 10 + openkknd2-am-coder/docs/BUILD.md | 11 + openkknd2-am-coder/docs/LORE.md | 8 + openkknd2-am-coder/include/ok_buildings.h | 25 + openkknd2-am-coder/include/ok_map.h | 6 + openkknd2-am-coder/include/ok_types.h | 31 + openkknd2-am-coder/include/ok_unit.h | 8 + openkknd2-am-coder/launch-q36.out | 16 + openkknd2-am-coder/launch-w2.out | 16 + openkknd2-am-coder/launch.out | 16 + .../rotate-logs/LOAD-POLICY.txt | 7 + .../rotate-logs/rotate-20260722T051643Z.md | 9 + openkknd2-am-coder/rotate-logs/rotate.out | 6 + .../rotate-logs/run-1devstral.log | 24 + openkknd2-am-coder/scripts/ci-headless.sh | 10 + openkknd2-am-coder/src/building.h | 20 + openkknd2-am-coder/src/main.c | 36 + openkknd2-am-coder/src/ok_buildings.c | 45 + openkknd2-am-coder/src/ok_map.c | 29 + openkknd2-am-coder/src/ok_unit.c | 46 + 130 files changed, 9817 insertions(+), 1 deletion(-) create mode 100644 KEEP-DROP.md create mode 100644 MISSIONS/deepseek.MISSION.md create mode 100644 MISSIONS/hermes.MISSION.md create mode 100644 MISSIONS/qwen.MISSION.md create mode 100644 MISSIONS/qwen14b.MISSION.md create mode 100755 engine-c/build.sh create mode 100644 engine-c/gallery/m0_evolved.png create mode 100644 engine-c/gallery/m0_series9.png create mode 100644 engine-c/gallery/m0_survivor.png create mode 100644 engine-c/gallery/m1_evolved.png create mode 100644 engine-c/gallery/m1_series9.png create mode 100644 engine-c/gallery/m1_survivor.png create mode 100644 engine-c/gallery/m2_evolved.png create mode 100644 engine-c/gallery/m2_series9.png create mode 100644 engine-c/gallery/m2_survivor.png create mode 100644 engine-c/gallery/m3_evolved.png create mode 100644 engine-c/gallery/m3_series9.png create mode 100644 engine-c/gallery/m3_survivor.png create mode 100644 engine-c/gallery/m4_evolved.png create mode 100644 engine-c/gallery/m4_series9.png create mode 100644 engine-c/gallery/m4_survivor.png create mode 100644 engine-c/include/kknd.h create mode 100644 engine-c/include/unit_card.h create mode 100755 engine-c/openkknd create mode 100755 engine-c/openkknd_dbg create mode 100644 engine-c/savegame.kknd create mode 100644 engine-c/shot_0001.png create mode 100644 engine-c/src/ai.c create mode 100644 engine-c/src/audio.c create mode 100644 engine-c/src/buildings.c create mode 100644 engine-c/src/config.c create mode 100644 engine-c/src/demo_unitcard.c create mode 100644 engine-c/src/engine.c create mode 100644 engine-c/src/input.c create mode 100644 engine-c/src/main.c create mode 100644 engine-c/src/map.c create mode 100644 engine-c/src/png.c create mode 100644 engine-c/src/projectiles.c create mode 100644 engine-c/src/render.c create mode 100644 engine-c/src/rng.c create mode 100644 engine-c/src/save.c create mode 100644 engine-c/src/snapshot.c create mode 100644 engine-c/src/sprites.c create mode 100644 engine-c/src/unit_card.c create mode 100644 engine-c/src/units.c create mode 100644 kitchen/COLLAB-SIGNUP.md create mode 100644 kknd2-runs/20260713-093232/hermes-build.txt create mode 100644 kknd2-runs/20260713-093232/hermes.err create mode 100644 kknd2-runs/20260713-093232/hermes.out create mode 100644 kknd2-runs/20260713-093232/master.log create mode 100644 kknd2-runs/20260713-093232/nohup.out create mode 100644 kknd2-runs/20260713-093232/prompt-hermes.txt create mode 100644 kknd2-runs/20260713-093232/prompt-qwen.txt create mode 100644 kknd2-runs/20260713-093232/qwen-build.txt create mode 100644 kknd2-runs/20260713-093232/qwen.err create mode 100644 kknd2-runs/20260713-093232/qwen.out create mode 100755 kknd2-runs/20260713-093232/run.sh create mode 100644 kknd2-runs/20260713-093614-q14-deepseek/deepseek-build.txt create mode 100644 kknd2-runs/20260713-093614-q14-deepseek/deepseek.err create mode 100644 kknd2-runs/20260713-093614-q14-deepseek/deepseek.out create mode 100644 kknd2-runs/20260713-093614-q14-deepseek/master.log create mode 100644 kknd2-runs/20260713-093614-q14-deepseek/nohup.out create mode 100644 kknd2-runs/20260713-093614-q14-deepseek/prompt-deepseek.txt create mode 100644 kknd2-runs/20260713-093614-q14-deepseek/prompt-qwen14b.txt create mode 100644 kknd2-runs/20260713-093614-q14-deepseek/qwen14b-build.txt create mode 100644 kknd2-runs/20260713-093614-q14-deepseek/qwen14b.err create mode 100644 kknd2-runs/20260713-093614-q14-deepseek/qwen14b.out create mode 100755 kknd2-runs/20260713-093614-q14-deepseek/run.sh create mode 100644 llm-configs-openkknd2/README.md create mode 100755 llm-configs-openkknd2/scripts/common.sh create mode 100755 llm-configs-openkknd2/scripts/rotate-models-amanita.sh create mode 100755 llm-configs-openkknd2/scripts/start-amanita-coder.sh create mode 100755 llm-configs-openkknd2/scripts/start-amanita-pics.sh create mode 100755 llm-configs-openkknd2/scripts/start-amanita-vids.sh create mode 100755 llm-configs-openkknd2/scripts/start-mac-jan.sh create mode 100755 llm-configs-openkknd2/scripts/start-mac-osaurus.sh create mode 100755 openkknd2-am-coder/.agent-coder-resume.sh create mode 100644 openkknd2-am-coder/.am-meta/META_LOG.md create mode 100644 openkknd2-am-coder/.am-meta/additive.md create mode 100644 openkknd2-am-coder/.am-meta/launch-20260722T052125Zdevstral.log create mode 100644 openkknd2-am-coder/.am-meta/launch-20260722T052135Zcoder.log create mode 100644 openkknd2-am-coder/.am-meta/launch-20260722T052226Zdevstral.log create mode 100644 openkknd2-am-coder/.am-meta/launch-20260722T052858Zcoder.log create mode 100644 openkknd2-am-coder/.am-meta/launch-20260722T053449Zqwen314.log create mode 100644 openkknd2-am-coder/.am-meta/launch-20260722T055020Zqwen314.log create mode 100644 openkknd2-am-coder/.am-meta/launch-20260722T055432Zqwen14.log create mode 100644 openkknd2-am-coder/.am-meta/launch-20260722T055903Zdeepseek.log create mode 100644 openkknd2-am-coder/.am-meta/launch-20260722T055954Zqwen38.log create mode 100644 openkknd2-am-coder/.am-meta/launch-20260722T060502Zdevstral.log create mode 100644 openkknd2-am-coder/.am-meta/launch-20260722T060714Zdevstral.log create mode 100644 openkknd2-am-coder/.am-meta/launch-20260722T060744Zdevstral.log create mode 100644 openkknd2-am-coder/.am-meta/launch-20260722T060922Zdevstral.log create mode 100644 openkknd2-am-coder/.am-meta/launch-20260722T060959Zdevstral.log create mode 100644 openkknd2-am-coder/.am-meta/launch-20260722T061029Zdevstral.log create mode 100644 openkknd2-am-coder/.am-meta/launch-20260722T061559Zcoder.log create mode 100644 openkknd2-am-coder/.am-meta/launch-20260722T062150Zcoder.log create mode 100644 openkknd2-am-coder/.am-meta/launch-20260722T062640Zcoder.log create mode 100644 openkknd2-am-coder/.am-meta/launch-20260722T063050Zqwen314.log create mode 100644 openkknd2-am-coder/.am-meta/meta.out create mode 100644 openkknd2-am-coder/.am-meta/meta.pid create mode 100644 openkknd2-am-coder/.am-meta/mission.md create mode 100644 openkknd2-am-coder/.am-meta/state.json create mode 100644 openkknd2-am-coder/AGENT_LOG.md create mode 100644 openkknd2-am-coder/CMakeLists.txt create mode 100644 openkknd2-am-coder/LICENSE create mode 100644 openkknd2-am-coder/README.md create mode 100644 openkknd2-am-coder/RESUME.md create mode 100644 openkknd2-am-coder/STATUS.md create mode 100644 openkknd2-am-coder/TASK.md create mode 100644 openkknd2-am-coder/THIRD_PARTY.md create mode 100644 openkknd2-am-coder/docs/BUILD.md create mode 100644 openkknd2-am-coder/docs/LORE.md create mode 100644 openkknd2-am-coder/include/ok_buildings.h create mode 100644 openkknd2-am-coder/include/ok_map.h create mode 100644 openkknd2-am-coder/include/ok_types.h create mode 100644 openkknd2-am-coder/include/ok_unit.h create mode 100644 openkknd2-am-coder/launch-q36.out create mode 100644 openkknd2-am-coder/launch-w2.out create mode 100644 openkknd2-am-coder/launch.out create mode 100644 openkknd2-am-coder/rotate-logs/LOAD-POLICY.txt create mode 100644 openkknd2-am-coder/rotate-logs/rotate-20260722T051643Z.md create mode 100644 openkknd2-am-coder/rotate-logs/rotate.out create mode 100644 openkknd2-am-coder/rotate-logs/run-1devstral.log create mode 100755 openkknd2-am-coder/scripts/ci-headless.sh create mode 100644 openkknd2-am-coder/src/building.h create mode 100644 openkknd2-am-coder/src/main.c create mode 100644 openkknd2-am-coder/src/ok_buildings.c create mode 100644 openkknd2-am-coder/src/ok_map.c create mode 100644 openkknd2-am-coder/src/ok_unit.c diff --git a/KEEP-DROP.md b/KEEP-DROP.md new file mode 100644 index 0000000..8bb1059 --- /dev/null +++ b/KEEP-DROP.md @@ -0,0 +1,35 @@ +KEEP_OK=1 + +# KEEP vs DROP — OpenKKnD amanita pull (TODO=361 / 023-kknd) +# Built: Director shell (Sparmodus); MERGE into _hernani-private/openkknd 2026-09-16 +# Amanita origins DELETE_OK 2026-09-16 (Freigabe chat) · no Godot openkknd2 auto-merge · no push without Freigabe + +## KEEP (live under _hernani-private/openkknd/ — Freigabe flatten-as-subdirs) + +| Subdir | Origin | +|--------|--------| +| `engine-c/` | vx-openkknd/ (C11+SDL2) | +| `MISSIONS/` | MISSION.md from openkknd-{qwen,hermes,qwen14b,deepseek} | +| `openkknd2-am-coder/` | unique trail (no build/) | +| `kknd2-runs/` | historical run logs | +| `llm-configs-openkknd2/` | amanita llm-configs openkknd2 notes | + +Staging copy still under `$HOME/git/llm/llm-work-input/openkknd-amanita-pull/_keep/`. + +## DROP (leave in staging; do not promote; amanita delete only after human Freigabe) + +- Full trees openkknd-{qwen,hermes,qwen14b,deepseek} except MISSION.md — near-dup C vs vx-openkknd +- Binary rebuild optional (`openkknd` / `openkknd_dbg`) — rebuild via `engine-c/build.sh` on Linux +- Empty asset placeholders if unused +- `openkknd2-am-coder/build/` object junk + +## DO NOT + +- Litter Pitchwell **root** with C sources — keep under `engine-c/` only (subdirs OK; root flatten = no) +- Auto-merge am-coder into Godot `$HOME/git/src/openkknd2` +- Re-create amanita openkknd\* trees without need (DELETE_OK done) +- Treat C-engine as “orthogonal forever” — **superseded**: one RTS tree = `_hernani-private/openkknd` with `engine-c/` + kitchen + +## Proposal (text only) + +Dauerhome: subdirs under `_hernani-private/openkknd/`. Separate `openkknd-c-engine/` repo **not** required unless human reopens Decide. diff --git a/MISSIONS/deepseek.MISSION.md b/MISSIONS/deepseek.MISSION.md new file mode 100644 index 0000000..7917c5f --- /dev/null +++ b/MISSIONS/deepseek.MISSION.md @@ -0,0 +1,25 @@ +# KKND2 Auftrag — openkknd-deepseek (deepseek-coder:6.7b) + +You own this tree: `/home/vx/src/openkknd-deepseek` + +## Goal +Continue / improve **openkknd** — Krush Kill 'n' Destroy–style **2D RTS** in **C11 + SDL2** (+ SDL2_ttf, zlib). + +Reference (read-only): `/home/vx/openkknd` — do **not** modify it. Work only here. + +## Required outcomes +1. Project builds with `./build.sh` (fix compile/link errors with write/edit). +2. Keep modular layout: `src/*.c`, `include/*.h`, `assets/` as needed. +3. Fix warnings if cheap (e.g. MAX_PARTICLES redefinition) after green build. +4. Add/update `README.md` with build/run instructions. +5. `git init` if missing; commit when something works. +6. Prefer real shell tools (`exec`) — do not only print tool JSON as text. + +## Constraints +- Linux (CachyOS), user vx, gcc + sdl2 + SDL2_ttf. No sudo. +- Stay inside this directory. + +## First steps +1. cd /home/vx/src/openkknd-deepseek && ls -la +2. ./build.sh — fix failures +3. README + git commit after green build diff --git a/MISSIONS/hermes.MISSION.md b/MISSIONS/hermes.MISSION.md new file mode 100644 index 0000000..b8f9057 --- /dev/null +++ b/MISSIONS/hermes.MISSION.md @@ -0,0 +1,26 @@ +# KKND2 Auftrag — openkknd-hermes (hermes3) + +You own this tree: `/home/vx/src/openkknd-hermes` + +## Goal +Continue / improve **openkknd** — a Krush Kill 'n' Destroy–style **2D RTS** in **C11 + SDL2** (+ SDL2_ttf, zlib). + +Reference (read-only if needed): `/home/vx/openkknd` — do **not** modify it. Work only here. + +## Required outcomes +1. Project builds with `./build.sh` (or fix Makefile/build.sh until it does). +2. Keep modular layout: `src/*.c`, `include/*.h`, `assets/` as needed. +3. Game loop + map/units/buildings should remain coherent; fix compile errors first. +4. Add a short `README.md` describing how to build and run. +5. `git init` if missing; commit meaningful steps when something works. +6. Prefer real shell tools (`exec`) over inventing files without writing them. + +## Constraints +- Host: Linux (CachyOS), user `vx`, packages: gcc, sdl2, SDL2_ttf. +- No network installs that need root/sudo. +- Stay inside this directory. + +## First steps +1. `cd /home/vx/src/openkknd-hermes && ls -la` +2. Try `./build.sh` and fix failures. +3. Improve gameplay or structure only after a green build. diff --git a/MISSIONS/qwen.MISSION.md b/MISSIONS/qwen.MISSION.md new file mode 100644 index 0000000..c50b510 --- /dev/null +++ b/MISSIONS/qwen.MISSION.md @@ -0,0 +1,26 @@ +# KKND2 Auftrag — openkknd-qwen (qwen2.5-coder) + +You own this tree: `/home/vx/src/openkknd-qwen` + +## Goal +Continue / improve **openkknd** — a Krush Kill 'n' Destroy–style **2D RTS** in **C11 + SDL2** (+ SDL2_ttf, zlib). + +Reference (read-only if needed): `/home/vx/openkknd` — do **not** modify it. Work only here. + +## Required outcomes +1. Project builds with `./build.sh` (or fix Makefile/build.sh until it does). +2. Keep modular layout: `src/*.c`, `include/*.h`, `assets/` as needed. +3. Game loop + map/units/buildings should remain coherent; fix compile errors first. +4. Add a short `README.md` describing how to build and run. +5. `git init` if missing; commit meaningful steps when something works. +6. Prefer real shell tools (`exec`) over inventing files without writing them. + +## Constraints +- Host: Linux (CachyOS), user `vx`, packages: gcc, sdl2, SDL2_ttf. +- No network installs that need root/sudo. +- Stay inside this directory. + +## First steps +1. `cd /home/vx/src/openkknd-qwen && ls -la` +2. Try `./build.sh` and fix failures. +3. Improve gameplay or structure only after a green build. diff --git a/MISSIONS/qwen14b.MISSION.md b/MISSIONS/qwen14b.MISSION.md new file mode 100644 index 0000000..2abb03c --- /dev/null +++ b/MISSIONS/qwen14b.MISSION.md @@ -0,0 +1,25 @@ +# KKND2 Auftrag — openkknd-qwen14b (qwen2.5-coder:14b) + +You own this tree: `/home/vx/src/openkknd-qwen14b` + +## Goal +Continue / improve **openkknd** — Krush Kill 'n' Destroy–style **2D RTS** in **C11 + SDL2** (+ SDL2_ttf, zlib). + +Reference (read-only): `/home/vx/openkknd` — do **not** modify it. Work only here. + +## Required outcomes +1. Project builds with `./build.sh` (fix compile/link errors with write/edit). +2. Keep modular layout: `src/*.c`, `include/*.h`, `assets/` as needed. +3. Fix warnings if cheap (e.g. MAX_PARTICLES redefinition) after green build. +4. Add/update `README.md` with build/run instructions. +5. `git init` if missing; commit when something works. +6. Prefer real shell tools (`exec`) — do not only print tool JSON as text. + +## Constraints +- Linux (CachyOS), user vx, gcc + sdl2 + SDL2_ttf. No sudo. +- Stay inside this directory. + +## First steps +1. cd /home/vx/src/openkknd-qwen14b && ls -la +2. ./build.sh — fix failures +3. README + git commit after green build diff --git a/engine-c/build.sh b/engine-c/build.sh new file mode 100755 index 0000000..f523dab --- /dev/null +++ b/engine-c/build.sh @@ -0,0 +1,22 @@ +#!/bin/bash +# Build script for openkknd - the merged KKND-style 2D RTS. +set -e + +cd "$(dirname "$0")" + +CC=${CC:-gcc} +CFLAGS="-O2 -Wall -Iinclude" +SRCS="src/unit_card.c src/rng.c src/config.c src/png.c src/snapshot.c src/map.c src/sprites.c \ + src/units.c src/buildings.c src/projectiles.c src/render.c \ + src/input.c src/ai.c src/audio.c src/save.c src/engine.c src/main.c" + +LIBS=$(pkg-config --cflags --libs sdl2 SDL2_ttf 2>/dev/null) +if [ -z "$LIBS" ]; then + LIBS="-lSDL2 -lSDL2_ttf" +fi +LIBS="$LIBS -lz" + +echo "openkknd: compiling..." +$CC $CFLAGS $SRCS -o openkknd $LIBS -lm +echo "openkknd: built -> ./openkknd" +echo "usage: ./openkknd [--easy|--normal|--hard] [--load savegame.kknd]" diff --git a/engine-c/gallery/m0_evolved.png b/engine-c/gallery/m0_evolved.png new file mode 100644 index 0000000000000000000000000000000000000000..5b941d61b6f66d339c27c40d61f78da581cc5e3f GIT binary patch literal 7218 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 literal 0 HcmV?d00001 diff --git a/engine-c/gallery/m0_series9.png b/engine-c/gallery/m0_series9.png new file mode 100644 index 0000000000000000000000000000000000000000..d6c8f69cfa9bd7c573b0dd157af7eacfc7c8e049 GIT binary patch literal 9182 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

@Yia(IZ#x* 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 ZN~?MKrz3?so}Pm=^Css{+B2bG&3}e1)4Kow literal 0 HcmV?d00001 diff --git a/engine-c/gallery/m1_series9.png b/engine-c/gallery/m1_series9.png new file mode 100644 index 0000000000000000000000000000000000000000..6c980204fd20be35eeb5ac7af81fad01a3fa36bd GIT binary patch literal 8110 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 awFAe3H_z&?xCQfqCEr=T=(UBR;{O7GVHlhM literal 0 HcmV?d00001 diff --git a/engine-c/gallery/m1_survivor.png b/engine-c/gallery/m1_survivor.png new file mode 100644 index 0000000000000000000000000000000000000000..6633709c1194510948189284bf714cb7d83fc9e1 GIT binary patch literal 10187 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 literal 0 HcmV?d00001 diff --git a/engine-c/gallery/m2_evolved.png b/engine-c/gallery/m2_evolved.png new file mode 100644 index 0000000000000000000000000000000000000000..727838798534ba806d32ee88ece7dfbdeba62dea GIT binary patch literal 7217 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 literal 0 HcmV?d00001 diff --git a/engine-c/gallery/m2_survivor.png b/engine-c/gallery/m2_survivor.png new file mode 100644 index 0000000000000000000000000000000000000000..d6bcaa7d8238fc076518ccac55091ee1a1cf20c4 GIT binary patch literal 13029 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 literal 0 HcmV?d00001 diff --git a/engine-c/gallery/m3_evolved.png b/engine-c/gallery/m3_evolved.png new file mode 100644 index 0000000000000000000000000000000000000000..1f339c950e070fd948ec13666469aaf21119073e GIT binary patch literal 7118 zcmc&%e{2+W7Jswd(y>V0#hRnl(xnR?#2;w`2wKG{7TSix03nfpuAJOmP!RS~B4V_q(aTY>y$v~~ks{!o6pkNlz{rncTM(|;+TwffJ2N{wo#}23 zggSyMTPnE01{Dk9gv5i*uUcV0)X59T9ltR zfBFBuJD#_6qcTdJyJc%Z)9zn3B|rDpwc)r&m|pv*YY-#GeQWy9!mXId8Ts&3h`V$!1CeO=!#f3PvQZU2$V zFBg_w{qL5?|9B;B^TZtslnM8`dAN~E3HoX=KKZmLL;nvan zZ-X`Rm7}K)$sUlv1Br@T8JQGbw@J$WcY4IUDaD(8`ToxyRv;D4+=}Fp)=kd})Reut zQpF%HkX4t@a7*MKSkR(rI{p5;VgB&!K#7bmB@|34tp6t5YAixJz83H4+;e&a-m-~y& zfs#jcTFkuxX23%rw*TpyUxQnVoCmA&s~h^RdS~q^+q3e?7w)M1@=b5s7&(}kSuoun zW+E_%tOllp%p{Ck^2z`2>d#1p6@`hQsGRJ%on@WJmM?3*@V2*Y8l+t44DEv6b;R6S zKw?WzMh&TA+7pxdLH*{|L+vM52lup`J6bs+yrCrF+scd@NfwP}E|*NjpjMNy0aPg5 zec{C6x&{95OjW|<5RuL}^Gn@?rC1}?#B%B! z&68WHZb&+Ir2Op8@4h11_bO?7v3 z3v7BmBd2!4w?CAfR**O*II`v33QiL5Zrtz+G-uD-^~AH~`{dGKU2Ac5vz}iSwTECt zQ$+}peNWNU^V zJ)B}Lr%~BtBy7puT?|C_h-6HCLJ_0?|2+K=4cHgRZTX^Cdu7 zskgH`D%G5p2wHQhVu`jLY~gvpKpIo!dJ1+Ni)^a5ovOXrs39~}?8*2+m{1Bl^tMf% zO`T@HcMblcc64Mm{N|)Z?JFW<<-#pG{ZZ{}q+0Pvf5A~6ABKbZH-)lOky&vZdVgg;4lR4r4u=VnNws8uX^dBYuf|_Py>Ljx&ut`U zM6v2I2uU7a;#6`>jYMt5*9XZ#f4S_Z?Wy@Uu#`^mBQ)09j)qY5uWgR zJbBl5yF>>Gmy|GrAvVE5H?5LuUcs_tFvq<3GQNk3k;KRJp?l#rWrv zLI;aU13X+L&(tC)e&EDibiCpbfv52(@eU>M;n{>=*x`lD05C=3OBR{LhL5JkLR(ea zW>g1Mb%*j;|Rj*vFTL)_6cka?N5+F(C#{BWt8cYYQ=jlv}5WNyCccurCU& zz@>4w#s`AkGgR`$={bUkiWspH5wWGCLByB;bqO7M7tYSQQ~Ae>A3i$`i^J`OS5LsZ z5`HRBx8kLQXK4ym`hc*R;vPl=z%{hMr?t5@C1$bn(Ib8AkYqzW4lOW+46y-C^^CmP zF=zybZBPTpfS?f^w4F3aLXA!xpE^hHdI{!CX}^V_6GLO+H!Ha_uuwkfK#!mDB;Plw zhcc=1*n{FVie1N7c=MFD=q2LbvPdd~kNXiNGlUl{P0hla78)Z7)+uBCft9IffaI)$ zC4F?4!^o+sTWNp8VP(XbwuoO!(Tf~2#V-Wrf-bmRcm4!2)5C2)1bPA^7GFfjZlX@S zGvbxVw|Jq5oliSX?ApO4#iCNhD=7xqqhy%z$~e1?Fr~9?aYY~ThQe5)oBs0Hq&WApYoz{{+%a3TGxxpkov;M59o5!SEf3c{h=XO6Q z3e5ASlsqBebSONfWt9|yQW>hG5acUPNg*i4P^A@atTClQn6z{haB+GZ7XO?iyT!ku zN)AU}DQp~)*n$^|hF$MC8XnsELF#~j=^pISQC6z7fiDbk5mqoLs(lFzo; zRCnB4^e^_S^FX|Ya1)Sm%)yQGsbz3Yab6Ff>XvB`T>Dz>`qe$%L}}5CNAqi@2daJo D=CmFh literal 0 HcmV?d00001 diff --git a/engine-c/gallery/m3_series9.png b/engine-c/gallery/m3_series9.png new file mode 100644 index 0000000000000000000000000000000000000000..717748936bc3ca7ee57590bf56bab95026eae0d9 GIT binary patch literal 8156 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 c+_7sc5_)LkF8T_mLS*UtjRd~&R0aqRC+W-In literal 0 HcmV?d00001 diff --git a/engine-c/gallery/m3_survivor.png b/engine-c/gallery/m3_survivor.png new file mode 100644 index 0000000000000000000000000000000000000000..69aba689d314ba3ad499ef4189e84d4267e62a1f GIT binary patch literal 13193 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 DPqP;! literal 0 HcmV?d00001 diff --git a/engine-c/gallery/m4_survivor.png b/engine-c/gallery/m4_survivor.png new file mode 100644 index 0000000000000000000000000000000000000000..75c1e569d7a09da9cbfd42e134796a8f24ba034b GIT binary patch literal 9658 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 literal 0 HcmV?d00001 diff --git a/engine-c/include/kknd.h b/engine-c/include/kknd.h new file mode 100644 index 0000000..f0702a4 --- /dev/null +++ b/engine-c/include/kknd.h @@ -0,0 +1,642 @@ +#ifndef KKND_H +#define KKND_H + +/* ========================================================================= + * openkknd - Krush, Kill 'n' Destroy style 2D Real-Time Strategy engine + * Single merged codebase (was: openkknd + openkknd_full). + * + * This header declares the complete shared type system and the public + * interface of every engine subsystem. The implementation lives in the + * matching src/*.c translation units. + * ========================================================================= */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "unit_card.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* ----------------------------------------------------------------------- */ +/* Global tunables */ +/* ----------------------------------------------------------------------- */ +#define KKND_NAME "openkknd" +#define KKND_VERSION "1.0.0" + +#define TILE 32 /* pixel size of one map tile */ +#define MAP_W 128 /* map width in tiles */ +#define MAP_H 128 /* map height in tiles */ +#define MAP_PIX_W (MAP_W * TILE) +#define MAP_PIX_H (MAP_H * TILE) + +#define MAX_UNITS 3000 +#define MAX_BUILDINGS 1024 +#define MAX_BULLETS 6000 +#define MAX_PARTICLES 4000 +#define MAX_PATH 1024 +#define MAX_SELECTION 256 +#define MAX_FACTIONS 4 + +#define VIEW_W 1280 +#define VIEW_H 720 + +#define START_SCRAP 1500 /* starting resource for each side */ +#define SCRAP_PER_TILE 1000 /* scrap contained in one scrap field */ + +#define SAVE_MAGIC 0x4B4B4E44 /* 'KKND' */ +#define SAVE_VERSION 2 + +/* ----------------------------------------------------------------------- */ +/* Enumerations */ +/* ----------------------------------------------------------------------- */ +typedef enum { + T_WASTELAND = 0, + T_RUBBLE, + T_ROAD, + T_GRASS, + T_WATER, + T_ROCK, + T_SCRAP, + T_COUNT +} TerrainType; + +typedef enum { + FACTION_NONE = 0, + FACTION_SURVIVOR, /* player - post-war military remnants */ + FACTION_EVOLVED, /* ai - mutated wasteland beasties */ + FACTION_SERIES9, /* neutral - rogue war machines */ + FACTION_NEUTRAL +} Faction; + +typedef enum { + UT_NONE = 0, + UT_INFANTRY, + UT_BUGGY, + UT_TANK, + UT_HARVESTER, + UT_ARTILLERY, + UT_JEEP, + UT_MUTANT, + UT_BEAST, + UT_RAVAGER, + UT_FLAME, + UT_MISSILE, + UT_MEDIC, + UT_COUNT +} UnitType; + +typedef enum { + AB_NONE = 0, + AB_SIEGE, /* tanks/artillery: +range, immobile while active */ + AB_HEAL /* medics: pulse heal nearby allies */ +} AbilityType; + +typedef enum { + BT_NONE = 0, + BT_BASE, /* command centre / HQ */ + BT_WARFACTORY, + BT_REFINERY, + BT_POWER, + BT_TURRET, + BT_WALL, + BT_REPAIR, + BT_RESEARCH, + BT_COUNT +} BuildingType; + +typedef enum { + STATE_IDLE = 0, + STATE_MOVING, + STATE_ATTACKING, + STATE_GATHERING, + STATE_RETURNING, + STATE_DEAD +} UnitState; + +typedef enum { + ORDER_NONE = 0, + ORDER_MOVE, + ORDER_ATTACKMOVE, + ORDER_ATTACK, + ORDER_GATHER, + ORDER_RETURN, + ORDER_BUILD, + ORDER_STOP +} OrderType; + +typedef enum { + GAME_MENU = 0, + GAME_PLAYING, + GAME_PAUSED, + GAME_WON, + GAME_LOST +} GameState; + +typedef enum { + DIFF_EASY = 0, + DIFF_NORMAL, + DIFF_HARD, + DIFF_COUNT +} Difficulty; + +/* ----------------------------------------------------------------------- */ +/* Small math helpers */ +/* ----------------------------------------------------------------------- */ +typedef struct { float x, y; } Vec2; + +static inline float kknd_dist2(float ax, float ay, float bx, float by) { + float dx = ax - bx, dy = ay - by; + return dx * dx + dy * dy; +} +static inline float kknd_dist(float ax, float ay, float bx, float by) { + return (float)sqrtf(kknd_dist2(ax, ay, bx, by)); +} +static inline float kknd_clampf(float v, float lo, float hi) { + return v < lo ? lo : (v > hi ? hi : v); +} +static inline int kknd_clampi(int v, int lo, int hi) { + return v < lo ? lo : (v > hi ? hi : v); +} + +/* ----------------------------------------------------------------------- */ +/* Deterministic RNG (xoroshiro128**) */ +/* ----------------------------------------------------------------------- */ +typedef struct { + uint64_t s[2]; +} RNG; + +void rng_seed(RNG *r, uint64_t seed); +uint64_t rng_u64(RNG *r); +uint32_t rng_u32(RNG *r); +int rng_range(RNG *r, int lo, int hi); +float rng_f32(RNG *r); +float rng_frange(RNG *r, float lo, float hi); + +/* ----------------------------------------------------------------------- */ +/* Map */ +/* ----------------------------------------------------------------------- */ +typedef struct { + uint8_t terrain; /* TerrainType */ + uint8_t walkable; /* 0 = blocked, 1 = passable */ + uint8_t scrap; /* remaining scrap (0..255) */ + uint8_t variant; /* cosmetic variation */ + uint8_t occupied; /* building footprint marker */ +} Tile; + +typedef struct { + int w, h; + Tile *tiles; /* w*h */ + uint8_t *fog; /* human visibility: 0 hidden,1 explored,2 visible */ + int scrap_fields; /* number of scrap deposits */ + int style; /* terrain theme */ + int spawn_x[MAX_FACTIONS]; + int spawn_y[MAX_FACTIONS]; +} GameMap; + +void map_init(GameMap *m, RNG *rng); +void map_free(GameMap *m); +Tile *map_tile(GameMap *m, int tx, int ty); +int map_walkable(GameMap *m, int tx, int ty); +int map_in_bounds(GameMap *m, int tx, int ty); +void map_world_to_tile(float wx, float wy, int *tx, int *ty); +void map_tile_to_world(int tx, int ty, float *wx, float *wy); +int map_find_nearest_scrap(GameMap *m, float wx, float wy, float *outx, float *outy); +void map_deplete_scrap(GameMap *m, int tx, int ty, int amount); +typedef struct Game Game; +void map_update_fog(Game *g, Faction f); + +/* A* pathfinding ------------------------------------------------------- */ +typedef struct { + int tx, ty; /* tile coordinate */ +} PathNode; + +typedef struct { + PathNode nodes[MAX_PATH]; + int len; +} Path; + +int path_find(GameMap *m, int sx, int sy, int gx, int gy, Path *out); +void path_smooth(GameMap *m, Path *p); + +/* ----------------------------------------------------------------------- */ +/* Units */ +/* ----------------------------------------------------------------------- */ +typedef struct Unit { + int id; + UnitType type; + Faction faction; + float x, y; /* world position (pixels) */ + float hp; + float max_hp; + float attack; + float defense; + float speed; /* pixels / second */ + float range; /* pixels */ + float sight; /* pixels */ + float heading; /* radians, for sprite rotation */ + UnitState state; + OrderType order; + int target_unit; /* id, -1 = none */ + int target_bld; /* id, -1 = none */ + float dest_x, dest_y; + Path path; + int path_idx; + float atk_cd; /* attack cooldown timer */ + float cargo; /* harvester load */ + int selected; + int dead; + float anim; /* walk/attack animation phase */ + float repath_timer; /* throttle re-pathing */ + int hold; /* hold position (don't chase) */ + int attack_move; /* move toward dest but engage en route */ + AbilityType ability; /* special ability, 0 = none */ + float ability_cd; /* cooldown remaining */ + int ability_on; /* active toggle (siege) */ + float reload_mul; /* reload speed multiplier (upgrades) */ +} Unit; + +#define MAX_WRECKS 1024 +typedef struct { + float x, y; + UnitType type; + Faction faction; + float rot; +} Wreck; + +#define MAX_PARTICLES 2048 + +/* ----------------------------------------------------------------------- */ +/* Buildings */ +/* ----------------------------------------------------------------------- */ +typedef struct Building { + int id; + BuildingType type; + Faction faction; + int tx, ty; /* top-left tile */ + int w, h; /* footprint in tiles */ + float hp; + float max_hp; + float build; /* 0..1 construction progress */ + int producing; /* UnitType being built, 0 = none */ + float prod_timer; + float prod_total; + int queue[MAX_SELECTION]; + int queue_len; + float rally_x, rally_y; + int selected; + int dead; + float atk_cd; +} Building; + +/* ----------------------------------------------------------------------- */ +/* Projectiles & particles */ +/* ----------------------------------------------------------------------- */ +typedef struct { + float x, y, tx, ty; + float speed; + float damage; + Faction faction; + int target_unit; + int target_bld; + float life; + int kind; /* 0 bullet, 1 shell, 2 laser, 3 missile */ +} Bullet; + +typedef struct { + float x, y; + float vx, vy; + float life; + float max_life; + uint8_t r, g, b; + int kind; /* 0 smoke, 1 fire, 2 spark, 3 debris */ +} Particle; + +/* ----------------------------------------------------------------------- */ +/* Camera */ +/* ----------------------------------------------------------------------- */ +typedef struct { + float x, y; /* world top-left of view */ + float zoom; + int shake; +} Camera; + +/* ----------------------------------------------------------------------- */ +/* Sprite cache */ +/* ----------------------------------------------------------------------- */ +typedef struct { + SDL_Texture *unit[UT_COUNT][MAX_FACTIONS]; + SDL_Texture *bld[BT_COUNT][MAX_FACTIONS]; + SDL_Texture *terrain[T_COUNT]; + SDL_Texture *bullet[4]; + TTF_Font *font_small; + TTF_Font *font_med; + TTF_Font *font_big; +} Sprites; + +/* ----------------------------------------------------------------------- */ +/* Research / upgrades (declared early so Game can embed it) */ +/* ----------------------------------------------------------------------- */ +#define UP_DAMAGE 0 +#define UP_ARMOR 1 +#define UP_SPEED 2 +#define UP_RELOAD 3 +#define UP_BUILD 4 +#define UP_COUNT 5 +#define UP_MAX_LVL 3 + +typedef struct { int lvl[UP_COUNT]; } Upgrades; + +/* ----------------------------------------------------------------------- */ +/* Game state */ +/* ----------------------------------------------------------------------- */ +typedef struct Game { + GameMap map; + Unit units[MAX_UNITS]; + int unit_count; + int unit_next_id; + Building buildings[MAX_BUILDINGS]; + int bld_count; + int bld_next_id; + Bullet bullets[MAX_BULLETS]; + int bullet_count; + Particle particles[MAX_PARTICLES]; + int particle_count; + + int scrap[MAX_FACTIONS]; + int power[MAX_FACTIONS]; + int pop[MAX_FACTIONS]; + int pop_cap[MAX_FACTIONS]; + + int selection[MAX_SELECTION]; + int sel_count; + + /* control groups (Ctrl+1..9 sets, 1..9 recalls) */ + int groups[9][MAX_SELECTION]; + int group_len[9]; + + Camera cam; + Sprites spr; + SDL_Renderer *ren; + SDL_Window *win; + + GameState state; + Difficulty diff; + Faction human; + float time; + float speed_mul; + int winner; + int pause; + + /* drag-selection rectangle (screen space) */ + int drag_x0, drag_y0, drag_x1, drag_y1, dragging; + + /* build placement ghost */ + BuildingType place_type; + int placing; + + float ai_timer; + float ai_wave[MAX_FACTIONS]; + int frames; + + /* on-screen message log (ring buffer) */ + char log_msg[32][96]; + int log_count; + int log_next; + + /* research / upgrades */ + Upgrades upg[MAX_FACTIONS]; + int research_lab[MAX_FACTIONS]; + int research_cur[MAX_FACTIONS]; + float research_prog[MAX_FACTIONS]; + + /* mission */ + int mission_id; + int objective; + Faction enemy; /* actual enemy for the active match */ + int unlocked; /* highest unlocked mission id + 1 */ + int menu_sel; /* highlighted mission in the menu */ + float obj_timer; + + /* wreckage decals */ + Wreck *wrecks; + int wreck_count; + int wreck_next; +} Game; + +/* ----------------------------------------------------------------------- */ +/* Configuration / unit stats */ +/* ----------------------------------------------------------------------- */ +typedef struct { + const char *name; + UnitType type; + Faction faction; + int cost; + float hp; + float attack; + float defense; + float speed; + float range; + float sight; + int cargo; + int build_time; /* seconds to produce */ + AbilityType ability; /* special ability, 0 = none */ + const char *desc; +} UnitDef; + +typedef struct { + const char *name; + BuildingType type; + int cost; + int build_time; + float hp; + int w, h; + int provides_power; + int pop_provided; + int produces[UT_COUNT > 8 ? UT_COUNT : 8]; + int produces_count; + int is_turret; + float atk; + float range; + int is_research; /* research lab flag */ + const char *desc; +} BldDef; + +/* ----------------------------------------------------------------------- */ +/* Missions / campaign */ +/* ----------------------------------------------------------------------- */ +#define OBJ_DESTROY_ALL 0 +#define OBJ_SURVIVE 1 +#define OBJ_GATHER 2 + +typedef struct { + int id; + const char *name; + const char *brief; + int objective; + Faction enemy; + int diff; + int scrap_goal; /* for OBJ_GATHER */ + float survive_time; /* for OBJ_SURVIVE */ + int map_style; /* terrain theme */ + uint32_t seed; +} Mission; + +/* Build the unit-card registries (merged feature) and stat tables. */ +void config_init(void); +const UnitDef *unit_def(UnitType t, Faction f); +const BldDef *bld_def(BuildingType t); +UnitRegistry *unit_card_registry(void); /* for save/inspect */ +UnitRegistry *bld_card_registry(void); +int mission_count(void); +const Mission *mission_get(int id); +const char *research_name(int up); +int research_cost(int up, int level); + +/* ----------------------------------------------------------------------- */ +/* Sprites */ +/* ----------------------------------------------------------------------- */ +int sprites_init(Sprites *s, SDL_Renderer *r); +void sprites_free(Sprites *s); +SDL_Texture *sprites_make_unit(SDL_Renderer *r, UnitType t, Faction f); +SDL_Texture *sprites_make_bld(SDL_Renderer *r, BuildingType t, Faction f); +SDL_Texture *sprites_make_terrain(SDL_Renderer *r, TerrainType t); + +/* ----------------------------------------------------------------------- */ +/* Units */ +/* ----------------------------------------------------------------------- */ +int unit_spawn(Game *g, UnitType t, Faction f, float x, float y); +void unit_remove(Game *g, int idx); +void unit_update(Game *g, float dt); +void unit_issue_move(Game *g, int idx, float x, float y, int attackmove); +void unit_issue_move_group(Game *g, const int *idxs, int n, float cx, float cy, int attackmove); +void unit_issue_attack_unit(Game *g, int idx, int target); +void unit_issue_attack_bld(Game *g, int idx, int target); +void unit_issue_gather(Game *g, int idx, float x, float y); +void unit_issue_gather_nearest(Game *g, int idx); +void unit_issue_return(Game *g, int idx); +void unit_issue_stop(Game *g, int idx); +void unit_issue_hold(Game *g, int idx); +void unit_ability_use(Game *g, int idx); +void unit_apply_damage(Game *g, Unit *u, float dmg); +Unit *unit_by_id(Game *g, int id); +void unit_select_in_rect(Game *g, int x0, int y0, int x1, int y1, int add); +void unit_select_single(Game *g, int idx, int add); +void unit_clear_selection(Game *g); +void unit_select_all(Game *g, Faction f); + +/* ----------------------------------------------------------------------- */ +/* Buildings */ +/* ----------------------------------------------------------------------- */ +int bld_spawn(Game *g, BuildingType t, Faction f, int tx, int ty); +void bld_remove(Game *g, int idx); +void bld_update(Game *g, float dt); +void bld_queue_unit(Game *g, int idx, UnitType t); +Building *bld_by_id(Game *g, int id); +int bld_can_place(Game *g, BuildingType t, int tx, int ty); +void bld_apply_damage(Game *g, Building *b, float dmg); +int bld_research(Game *g, Faction f, int up); + +/* ----------------------------------------------------------------------- */ +/* Projectiles & particles */ +/* ----------------------------------------------------------------------- */ +void bullet_spawn(Game *g, float x, float y, int target_unit, int target_bld, + float dmg, Faction f, int kind); +void bullet_update(Game *g, float dt); +void particle_spawn(Game *g, float x, float y, int kind, uint8_t cr, uint8_t cg, uint8_t cb); +void particle_burst(Game *g, float x, float y, int n, int kind, uint8_t cr, uint8_t cg, uint8_t cb); +void particle_update(Game *g, float dt); + +/* ----------------------------------------------------------------------- */ +/* Rendering */ +/* ----------------------------------------------------------------------- */ +typedef struct { + int x, y, w, h; + int kind; /* 0 = structure button, 1 = unit button */ + int id; /* BuildingType or UnitType */ + const char *label; + int enabled; + int cost; +} HudButton; + +int render_init(Game *g); +void render_frame(Game *g); +void render_world(Game *g); +void render_units(Game *g); +void render_buildings(Game *g); +void render_bullets(Game *g); +void render_particles(Game *g); +void render_selection(Game *g); +void render_hud(Game *g); +void render_minimap(Game *g); +void render_text(Sprites *s, SDL_Renderer *r, TTF_Font *f, + const char *txt, int x, int y, SDL_Color c); +void render_build_menu(Game *g); +void hud_layout(Game *g, HudButton *out, int *n); + +/* ----------------------------------------------------------------------- */ +/* Input */ +/* ----------------------------------------------------------------------- */ +void input_handle(Game *g, SDL_Event *e); +void input_camera(Game *g, float dt); +int input_screen_to_world(Game *g, int sx, int sy, float *wx, float *wy); + +/* ----------------------------------------------------------------------- */ +/* AI */ +/* ----------------------------------------------------------------------- */ +void ai_update(Game *g, float dt); +void ai_command(Faction f, int diff); + +/* ----------------------------------------------------------------------- */ +/* Audio */ +/* ----------------------------------------------------------------------- */ +int audio_init(void); +void audio_shutdown(void); +void audio_sfx(int id); /* 0 move,1 shoot,2 explode,3 build,4 select,5 error */ +void audio_music_start(void); +void audio_music_stop(void); +void audio_music_tick(float dt); + +/* ----------------------------------------------------------------------- */ +/* Save / load */ +/* ----------------------------------------------------------------------- */ +int save_game(Game *g, const char *path); +int load_game(Game *g, const char *path); + +/* ----------------------------------------------------------------------- */ +/* Engine */ +/* ----------------------------------------------------------------------- */ +extern int g_headless; /* when 1, run simulation without any renderer */ +extern int g_auto; /* when 1, the human faction is also AI-driven */ +void engine_init(Game *g, const char *title); +void engine_shutdown(Game *g); + +void engine_setup_match(Game *g, int mission_id); +void engine_update(Game *g, float dt); +void engine_check_win(Game *g); +void engine_log(const char *fmt, ...); +void game_log(Game *g, const char *fmt, ...); + +/* Helpers */ +int png_save(const char *path, int w, int h, const uint8_t *rgba); +void snapshot_capture(Game *g, const char *path); +Faction enemy_of(Faction f); +const char *faction_name(Faction f); +const char *unit_name(UnitType t, Faction f); +SDL_Color faction_color(Faction f); + +#ifdef __cplusplus +} +#endif + +#endif /* KKND_H */ diff --git a/engine-c/include/unit_card.h b/engine-c/include/unit_card.h new file mode 100644 index 0000000..6864033 --- /dev/null +++ b/engine-c/include/unit_card.h @@ -0,0 +1,83 @@ +// openkknd - Unit Card System (SIMD Optimized) +// Header file for unit card implementation + +#include +#include + +typedef struct { + char name[32]; + char faction[16]; + uint32_t cost; + uint32_t hp; + uint32_t max_hp; + uint32_t attack; + uint32_t defense; + uint32_t speed; + uint32_t range; + char description[256]; + char image_path[128]; + uint8_t unit_type; + uint8_t special_abilities_count; + char special_abilities[64][16]; +} UnitCard; + +typedef struct { + UnitCard **units; + size_t unit_count; + size_t capacity; +} UnitRegistry; + +// Unit type constants +#define UNIT_INFANTRY 1 +#define UNIT_VEHICLE 2 +#define UNIT_SPECIAL 3 +#define UNIT_ALL 0 + +// Flag for SIMD instructions +#define HAVE_AVX2 1 + +#ifdef __cplusplus +extern "C" { +#endif + +// Core unit functions +UnitCard* create_unit_card(const char* name, const char* faction, + uint32_t cost, uint32_t hp, uint32_t attack, + uint32_t defense, uint32_t speed, uint32_t range, + const char* description, const char* image, + uint8_t unit_type); + +void free_unit_card(UnitCard* unit); + +// Registry functions +UnitRegistry* create_registry(); +void destroy_registry(UnitRegistry* registry); + +int add_unit(UnitRegistry* registry, UnitCard* unit); +UnitCard* get_unit_by_name(UnitRegistry* registry, const char* name); +UnitCard** get_units_by_faction(UnitRegistry* registry, const char* faction, + size_t* out_count); +UnitCard** get_units_by_type(UnitRegistry* registry, uint8_t unit_type, + size_t* out_count); + +// SIMD optimized functions +void spread_damage_SIMD(const UnitCard* units[], size_t unit_count, + uint32_t damage, uint32_t* remaining_hp); + +void apply_healing_SIMD(UnitCard** units, size_t unit_count, + uint32_t heal_amount); + +// Serialization functions +int save_units_to_json(UnitRegistry* registry, const char* filepath); +int load_units_from_json(UnitRegistry* registry, const char* filepath); + +// Memory management +void unit_card_to_json(UnitCard* unit, char* buffer, size_t buffer_size); +UnitCard* json_to_unit_card(const char* json_string); + +// Performance statistics +void print_performance_stats(UnitRegistry* registry); + +#ifdef __cplusplus +} +#endif diff --git a/engine-c/openkknd b/engine-c/openkknd new file mode 100755 index 0000000000000000000000000000000000000000..6cac52b7accf23836d1075a61681d30bbe633cb8 GIT binary patch literal 120544 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 literal 0 HcmV?d00001 diff --git a/engine-c/openkknd_dbg b/engine-c/openkknd_dbg new file mode 100755 index 0000000000000000000000000000000000000000..9a9977c6484dfa294309cca065e52405ee1f0ab5 GIT binary patch literal 271112 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 literal 0 HcmV?d00001 diff --git a/engine-c/savegame.kknd b/engine-c/savegame.kknd new file mode 100644 index 0000000000000000000000000000000000000000..9b8126ec977249e4f4fe7a4c2d4bc72dce2e8f31 GIT binary patch literal 204 UcmZ?q^Y&(9pbRilrk!fK0qp+)EC2ui literal 0 HcmV?d00001 diff --git a/engine-c/shot_0001.png b/engine-c/shot_0001.png new file mode 100644 index 0000000000000000000000000000000000000000..e2a3b6a3d5088a30ae84c0b3459de996394ef855 GIT binary patch literal 9770 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 DPqP;! literal 0 HcmV?d00001 diff --git a/engine-c/src/ai.c b/engine-c/src/ai.c new file mode 100644 index 0000000..2249d7a --- /dev/null +++ b/engine-c/src/ai.c @@ -0,0 +1,124 @@ +#include "kknd.h" + +/* ========================================================================= + * ai.c - Enemy/human commander. Grows an economy and launches attack waves. + * ========================================================================= */ + +static Building *ai_find_bld(Game *g, Faction f, BuildingType t) { + for (int i = 0; i < g->bld_count; i++) { + Building *b = &g->buildings[i]; + if (b->dead || b->faction != f || b->type != t) continue; + if (b->build < 1.0f) continue; + return b; + } + return NULL; +} + +static int ai_count_units(Game *g, Faction f, int combat_only) { + int n = 0; + for (int i = 0; i < g->unit_count; i++) { + Unit *u = &g->units[i]; + if (u->dead || u->faction != f) continue; + if (combat_only && (u->type == UT_HARVESTER)) continue; + n++; + } + return n; +} + +static int ai_count_harvesters(Game *g, Faction f) { + int n = 0; + for (int i = 0; i < g->unit_count; i++) { + Unit *u = &g->units[i]; + if (u->dead || u->faction != f) continue; + if (u->type == UT_HARVESTER) n++; + } + return n; +} + +static void ai_run_faction(Game *g, Faction f) { + Building *base = ai_find_bld(g, f, BT_BASE); + Building *wf = ai_find_bld(g, f, BT_WARFACTORY); + Building *ref = ai_find_bld(g, f, BT_REFINERY); + if (!base) return; + + /* early defensive turret */ + if (g->time < 60 && g->scrap[f] > 600) { + int bx = base->tx + 3, by = base->ty - 2; + if (bld_can_place(g, BT_TURRET, bx, by)) { + if (bld_spawn(g, BT_TURRET, f, bx, by) >= 0) + g->scrap[f] -= bld_def(BT_TURRET)->cost; + } + } + + /* research lab + upgrades */ + if (!ai_find_bld(g, f, BT_RESEARCH) && g->scrap[f] > 400) { + int bx = base->tx - 3, by = base->ty + 2; + if (bld_can_place(g, BT_RESEARCH, bx, by)) { + int r = bld_spawn(g, BT_RESEARCH, f, bx, by); + if (r >= 0) g->scrap[f] -= bld_def(BT_RESEARCH)->cost; + } + } + if (g->research_lab[f] > 0 && g->research_cur[f] < 0) { + int best = -1, bestlv = 99; + for (int u = 0; u < UP_COUNT; u++) { + if (g->upg[f].lvl[u] >= UP_MAX_LVL) continue; + if (g->upg[f].lvl[u] < bestlv) { bestlv = g->upg[f].lvl[u]; best = u; } + } + if (best >= 0) bld_research(g, f, best); + } + + /* keep harvesters flowing */ + if (ref && ai_count_harvesters(g, f) < 4 && g->scrap[f] >= 300) + bld_queue_unit(g, (int)(ref - g->buildings), UT_HARVESTER); + + /* pump combat units */ + if (wf) { + int combat = ai_count_units(g, f, 1); + int cap = (g->diff == DIFF_HARD) ? 40 : (g->diff == DIFF_NORMAL ? 28 : 18); + if (combat < cap) { + int r = rng_range(&(RNG){0}, 0, 6); + UnitType pick = (r == 0) ? UT_BUGGY : (r == 1) ? UT_TANK + : (r == 2) ? UT_ARTILLERY : (r == 3) ? UT_JEEP + : (r == 4) ? UT_RAVAGER : (r == 5) ? UT_FLAME + : UT_MISSILE; + bld_queue_unit(g, (int)(wf - g->buildings), pick); + } + } + + /* attack wave every ~35s once we have a force */ + g->ai_wave[f] += (g->diff == DIFF_HARD ? 4.0f : (g->diff == DIFF_NORMAL ? 6.0f : 9.0f)); + if (g->ai_wave[f] >= 35.0f) { + g->ai_wave[f] = 0; + Faction foe = (f == g->human) ? g->enemy : g->human; + int hx = g->map.spawn_x[foe], hy = g->map.spawn_y[foe]; + float tx, ty; map_tile_to_world(hx, hy, &tx, &ty); + int launched = 0; + for (int i = 0; i < g->unit_count; i++) { + Unit *u = &g->units[i]; + if (u->dead || u->faction != f) continue; + if (u->type == UT_HARVESTER) continue; + unit_issue_move(g, (int)(u - g->units), tx, ty, 1); + launched++; + } + if (launched > 0) { + engine_log("%s warband (%d) advances on the %s!", + faction_name(f), launched, faction_name(foe)); + if (f == g->human) + game_log(g, "Warband (%d units) sent to attack!", launched); + } + } +} + +void ai_command(Faction f, int diff) { (void)f; (void)diff; } + +void ai_update(Game *g, float dt) { + g->ai_timer -= dt; + if (g->ai_timer > 0) return; + + float interval = (g->diff == DIFF_HARD) ? 4.0f : + (g->diff == DIFF_NORMAL ? 6.0f : 9.0f); + g->ai_timer = interval; + + ai_run_faction(g, g->enemy); + if (g_auto) ai_run_faction(g, g->human); +} diff --git a/engine-c/src/audio.c b/engine-c/src/audio.c new file mode 100644 index 0000000..6595a7a --- /dev/null +++ b/engine-c/src/audio.c @@ -0,0 +1,76 @@ +#include "kknd.h" + +/* ========================================================================= + * audio.c - Procedural sound effects via SDL audio queue (no asset files). + * ========================================================================= */ + +static SDL_AudioDeviceID g_dev = 0; +static int g_audio_ok = 0; + +int audio_init(void) { + SDL_AudioSpec want, have; + memset(&want, 0, sizeof want); + want.freq = 22050; + want.format = AUDIO_S16SYS; + want.channels = 1; + want.samples = 1024; + g_dev = SDL_OpenAudioDevice(NULL, 0, &want, &have, 0); + if (g_dev == 0) { return 0; } + g_audio_ok = 1; + SDL_PauseAudioDevice(g_dev, 0); + return 1; +} + +void audio_shutdown(void) { + if (g_dev) SDL_CloseAudioDevice(g_dev); + g_dev = 0; g_audio_ok = 0; +} + +static void queue_tone(float freq, float dur, float vol, int kind) { + if (!g_audio_ok) return; + int n = (int)(22050 * dur); + int16_t *buf = (int16_t *)calloc((size_t)n, sizeof(int16_t)); + for (int i = 0; i < n; i++) { + float t = (float)i / 22050.0f; + float env = (kind == 0) ? (1.0f - t / dur) : 1.0f; + float s = sinf(2*3.14159f*freq*t); + if (kind == 1) s = (sinf(2*3.14159f*freq*t) + 0.3f*sinf(2*3.14159f*freq*2*t)); + if (kind == 2) s = (sinf(2*3.14159f*freq*t) * (1.0f - t/dur)) ; + buf[i] = (int16_t)(s * vol * 32767 * env); + } + SDL_QueueAudio(g_dev, buf, (Uint32)(n * sizeof(int16_t))); + free(buf); +} + +void audio_sfx(int id) { + switch (id) { + case 0: queue_tone(440, 0.08f, 0.25f, 0); break; /* move */ + case 1: queue_tone(220, 0.06f, 0.30f, 1); break; /* shoot */ + case 2: queue_tone(90, 0.35f, 0.45f, 2); break; /* boom */ + case 3: queue_tone(660, 0.10f, 0.30f, 0); break; /* build */ + case 4: queue_tone(880, 0.05f, 0.25f, 0); break; /* select */ + case 5: queue_tone(140, 0.12f, 0.35f, 1); break; /* error */ + default: break; + } +} + +void audio_music_start(void) { /* procedural drone; intentionally silent by default */ } +void audio_music_stop(void) { /* no-op */ } + +/* simple looping wasteland bassline (procedural, no asset files) */ +static float g_music_t = 0; +static int g_music_step = 0; +static const float g_bass[8] = { 110.0f, 0, 146.8f, 0, 130.8f, 0, 98.0f, 0 }; +static const float g_mel[8] = { 0, 329.6f, 0, 392.0f, 0, 293.7f, 0, 261.6f }; + +void audio_music_tick(float dt) { + if (!g_audio_ok) return; + g_music_t += dt; + const float step_dur = 0.40f; + if (g_music_t < step_dur) return; + g_music_t -= step_dur; + int s = g_music_step % 8; + if (g_bass[s] > 0) queue_tone(g_bass[s], step_dur * 0.95f, 0.10f, 2); + if (g_mel[s] > 0) queue_tone(g_mel[s], step_dur * 0.55f, 0.045f, 0); + g_music_step++; +} diff --git a/engine-c/src/buildings.c b/engine-c/src/buildings.c new file mode 100644 index 0000000..1ebd7ba --- /dev/null +++ b/engine-c/src/buildings.c @@ -0,0 +1,214 @@ +#include "kknd.h" + +/* ========================================================================= + * buildings.c - Structures, placement, production and defensive fire. + * ========================================================================= */ + +Building *bld_by_id(Game *g, int id) { + for (int i = 0; i < g->bld_count; i++) + if (!g->buildings[i].dead && g->buildings[i].id == id) + return &g->buildings[i]; + return NULL; +} + +int bld_can_place(Game *g, BuildingType t, int tx, int ty) { + const BldDef *d = bld_def(t); + for (int y = ty; y < ty + d->h; y++) + for (int x = tx; x < tx + d->w; x++) { + if (!map_in_bounds(&g->map, x, y)) return 0; + Tile *tl = map_tile(&g->map, x, y); + if (!tl->walkable || tl->occupied) return 0; + if (tl->terrain == T_WATER || tl->terrain == T_ROCK) return 0; + } + return 1; +} + +int bld_spawn(Game *g, BuildingType t, Faction f, int tx, int ty) { + if (g->bld_count >= MAX_BUILDINGS) return -1; + if (!bld_can_place(g, t, tx, ty)) return -2; + const BldDef *d = bld_def(t); + Building *b = &g->buildings[g->bld_count++]; + memset(b, 0, sizeof(Building)); + b->id = g->bld_next_id++; + b->type = t; b->faction = f; + b->tx = tx; b->ty = ty; b->w = d->w; b->h = d->h; + b->max_hp = (float)d->hp; + b->build = 0; /* starts under construction */ + b->hp = (float)d->hp * 0.1f; + b->producing = 0; b->prod_timer = 0; b->prod_total = 0; + b->queue_len = 0; + b->rally_x = (float)((tx + d->w/2) * TILE + TILE*2); + b->rally_y = (float)((ty + d->h/2) * TILE); + b->selected = 0; b->dead = 0; b->atk_cd = 0; + for (int y = ty; y < ty + d->h; y++) + for (int x = tx; x < tx + d->w; x++) + map_tile(&g->map, x, y)->occupied = 1; + return g->bld_count - 1; +} + +void bld_remove(Game *g, int idx) { + if (idx < 0 || idx >= g->bld_count) return; + Building *b = &g->buildings[idx]; + for (int y = b->ty; y < b->ty + b->h; y++) + for (int x = b->tx; x < b->tx + b->w; x++) { + Tile *t = map_tile(&g->map, x, y); + if (t) t->occupied = 0; + } + if (b->selected) { + for (int i = 0; i < g->sel_count; i++) + if (g->selection[i] == b->id) { + g->selection[i] = g->selection[g->sel_count-1]; + g->sel_count--; i--; + } + } + g->buildings[idx] = g->buildings[g->bld_count - 1]; + g->bld_count--; +} + +void bld_apply_damage(Game *g, Building *b, float dmg) { + float eff = dmg - 3.0f; + if (eff < 1) eff = 1; + b->hp -= eff; + particle_burst(g, (float)(b->tx*TILE+b->w*TILE/2), + (float)(b->ty*TILE+b->h*TILE/2), 2, 2, 255, 200, 80); + if (b->hp <= 0 && !b->dead) { + b->dead = 1; + particle_burst(g, (float)(b->tx*TILE+b->w*TILE/2), + (float)(b->ty*TILE+b->h*TILE/2), 20, 1, 255, 140, 40); + particle_burst(g, (float)(b->tx*TILE+b->w*TILE/2), + (float)(b->ty*TILE+b->h*TILE/2), 12, 3, 90, 80, 70); + audio_sfx(2); + if (g->cam.shake < 9) g->cam.shake = 9; + if (b->faction == g->human) + game_log(g, "Structure lost: %s", bld_def(b->type)->name); + } +} + +void bld_queue_unit(Game *g, int idx, UnitType t) { + Building *b = &g->buildings[idx]; + const BldDef *d = bld_def(b->type); + int allowed = 0; + for (int i = 0; i < d->produces_count; i++) + if (d->produces[i] == t) { allowed = 1; break; } + if (!allowed) { audio_sfx(5); return; } + if (b->queue_len >= MAX_SELECTION) { audio_sfx(5); return; } + const UnitDef *ud = unit_def(t, b->faction); + if (g->scrap[b->faction] < ud->cost) { audio_sfx(5); return; } + b->queue[b->queue_len++] = t; + g->scrap[b->faction] -= ud->cost; + audio_sfx(3); +} + +void bld_update(Game *g, float dt) { + for (int f = 0; f < MAX_FACTIONS; f++) g->research_lab[f] = 0; + + for (int i = 0; i < g->bld_count; i++) { + Building *b = &g->buildings[i]; + if (b->dead) continue; + const BldDef *d = bld_def(b->type); + if (d->is_research && b->build >= 1.0f) g->research_lab[b->faction]++; + + /* construction progress (faster with Logistics upgrade) */ + if (b->build < 1.0f) { + float rate = (float)d->build_time; + rate /= (1.0f + 0.12f * (float)g->upg[b->faction].lvl[UP_BUILD]); + b->build += dt / rate; + if (b->build >= 1.0f) { b->build = 1.0f; b->hp = b->max_hp; audio_sfx(3); } + else b->hp = b->max_hp * (0.1f + 0.9f * b->build); + } + + if (b->atk_cd > 0) b->atk_cd -= dt; + + /* turret auto-fire */ + if (d->is_turret && b->build >= 1.0f && b->atk_cd <= 0) { + float bx = (float)(b->tx*TILE + b->w*TILE/2); + float by = (float)(b->ty*TILE + b->h*TILE/2); + int best = -1; float bestd = d->range * d->range; + for (int j = 0; j < g->unit_count; j++) { + Unit *u = &g->units[j]; + if (u->dead || u->faction == b->faction) continue; + float dd = kknd_dist2(bx, by, u->x, u->y); + if (dd < bestd) { bestd = dd; best = u->id; } + } + if (best >= 0) { + bullet_spawn(g, bx, by, best, -1, d->atk, b->faction, 2); + b->atk_cd = 0.6f; + audio_sfx(1); + } + } + + /* production (faster with Logistics upgrade) */ + if (b->build >= 1.0f && b->producing == 0 && b->queue_len > 0) { + UnitType t = (UnitType)b->queue[0]; + const UnitDef *ud = unit_def(t, b->faction); + b->producing = t; + b->prod_total = (float)ud->build_time; + b->prod_timer = 0; + for (int k = 0; k < b->queue_len - 1; k++) b->queue[k] = b->queue[k+1]; + b->queue_len--; + } + if (b->producing != 0) { + const UnitDef *ud = unit_def((UnitType)b->producing, b->faction); + float total = (float)ud->build_time; + total /= (1.0f + 0.12f * (float)g->upg[b->faction].lvl[UP_BUILD]); + b->prod_timer += dt; + if (b->prod_timer >= total) { + unit_spawn(g, (UnitType)b->producing, b->faction, b->rally_x, b->rally_y); + b->producing = 0; b->prod_timer = 0; + audio_sfx(3); + } + } + + /* repair depot heals nearby units */ + if (d->type == BT_REPAIR && b->build >= 1.0f) { + float bx = (float)(b->tx*TILE + b->w*TILE/2); + float by = (float)(b->ty*TILE + b->h*TILE/2); + for (int j = 0; j < g->unit_count; j++) { + Unit *u = &g->units[j]; + if (u->dead || u->faction != b->faction) continue; + if (kknd_dist2(bx, by, u->x, u->y) < (TILE*5)*(TILE*5) && + u->hp < u->max_hp) { + u->hp += dt * 15.0f; + if (u->hp > u->max_hp) u->hp = u->max_hp; + } + } + } + } + + /* research progression */ + for (int f = 0; f < MAX_FACTIONS; f++) { + if (g->research_lab[f] <= 0) { g->research_cur[f] = -1; g->research_prog[f] = 0; continue; } + int up = g->research_cur[f]; + if (up < 0) continue; + int lvl = g->upg[f].lvl[up]; + if (lvl >= UP_MAX_LVL) { g->research_cur[f] = -1; g->research_prog[f] = 0; continue; } + g->research_prog[f] += dt * (float)g->research_lab[f] / 18.0f; + if (g->research_prog[f] >= 1.0f) { + g->upg[f].lvl[up] = lvl + 1; + g->research_prog[f] = 0; + g->research_cur[f] = -1; + audio_sfx(3); + engine_log("%s researched %s (lvl %d)", faction_name((Faction)f), + research_name(up), lvl + 1); + if ((Faction)f == g->human) + game_log(g, "Research complete: %s (lvl %d)", research_name(up), lvl + 1); + } + } + + for (int i = g->bld_count - 1; i >= 0; i--) + if (g->buildings[i].dead) bld_remove(g, i); +} + +int bld_research(Game *g, Faction f, int up) { + if (up < 0 || up >= UP_COUNT) return 0; + if (g->research_lab[f] <= 0) return 0; + if (g->research_cur[f] >= 0) return 0; + if (g->upg[f].lvl[up] >= UP_MAX_LVL) return 0; + int cost = research_cost(up, g->upg[f].lvl[up]); + if (g->scrap[f] < cost) { audio_sfx(5); return 0; } + g->scrap[f] -= cost; + g->research_cur[f] = up; + g->research_prog[f] = 0; + audio_sfx(3); + return 1; +} diff --git a/engine-c/src/config.c b/engine-c/src/config.c new file mode 100644 index 0000000..4982516 --- /dev/null +++ b/engine-c/src/config.c @@ -0,0 +1,273 @@ +#include "kknd.h" + +/* ========================================================================= + * config.c - Unit & building statistics, themed on KKND 1 (recreated). + * + * Two factions are modelled after the original game: + * FACTION_SURVIVOR - underground military remnants, high-tech war machines + * FACTION_EVOLVED - surface mutant tribes, warped animals & beasts + * The resource is "Scrap" (salvaged post-war materiel), harvested from + * scrap fields by harvesters and processed at refineries. + * ========================================================================= */ + +static const UnitDef UNIT_DEFS[] = { + /* ---- Survivor war machines ---- */ + { "Rifleman", UT_INFANTRY, FACTION_SURVIVOR, 50, 400, 40, 5, 62, 96, 220, 0, 2, AB_NONE, + "Booted foot soldier with a rapid rifle." }, + { "Scout Car", UT_BUGGY, FACTION_SURVIVOR, 200, 600, 55, 10, 132, 112, 260, 0, 6, AB_NONE, + "Fast lightly-armoured recon buggy with twin MG." }, + { "Battle Tank",UT_TANK, FACTION_SURVIVOR, 500, 1400,130, 40, 82, 132, 280, 0, 12, AB_SIEGE, + "Heavily armoured main battle tank. [Siege] boosts range." }, + { "Hammer", UT_ARTILLERY, FACTION_SURVIVOR, 450, 700,160, 15, 60, 264, 330, 0, 14, AB_SIEGE, + "Long-range self-propelled artillery. [Siege] extends reach." }, + { "Scavenger", UT_HARVESTER, FACTION_SURVIVOR, 300, 900, 0, 10, 72, 0, 200, 1000, 8, AB_NONE, + "Armoured truck that hauls scrap to the refinery." }, + { "Medic", UT_MEDIC, FACTION_SURVIVOR, 250, 600, 0, 10, 90, 120, 220, 0, 7, AB_HEAL, + "Field medic that pulses repairs to nearby allies." }, + { "Pathfinder", UT_JEEP, FACTION_SURVIVOR, 150, 500, 45, 8, 150, 100, 240, 0, 5, AB_NONE, + "Fast scout jeep with a roof-mounted machine gun." }, + { "Mammoth", UT_RAVAGER, FACTION_SURVIVOR, 800, 2600,200, 70, 70, 140, 300, 0, 18, AB_NONE, + "Super-heavy siege tank that levels anything." }, + { "Flamer", UT_FLAME, FACTION_SURVIVOR, 250, 700, 90, 12, 70, 52, 200, 0, 7, AB_NONE, + "Flame trooper that clears clustered enemies." }, + { "Rocketeer", UT_MISSILE, FACTION_SURVIVOR, 350, 650,120, 10, 64, 220, 300, 0, 10, AB_NONE, + "Mobile rocket launcher with punishing range." }, + + /* ---- Evolved mutant warbeasts ---- */ + { "Berserker", UT_INFANTRY, FACTION_EVOLVED, 40, 320, 40, 4, 62, 96, 220, 0, 2, AB_NONE, + "Barefoot mutant warrior loosing mutated arrows." }, + { "Razorbeast", UT_BUGGY, FACTION_EVOLVED, 200, 650, 60, 12, 142, 104, 260, 0, 6, AB_NONE, + "Mutated quadruped that charges and tears." }, + { "Ravager", UT_TANK, FACTION_EVOLVED, 500, 1500,140, 45, 76, 126, 280, 0, 12, AB_SIEGE, + "Hulking armored brute of the mutant horde. [Siege] boosts range." }, + { "Spitter", UT_ARTILLERY, FACTION_EVOLVED, 450, 720,150, 14, 56, 258, 330, 0, 14, AB_SIEGE, + "Acid-spitting mutant with lobbing attack. [Siege] extends reach." }, + { "Gnawer", UT_HARVESTER, FACTION_EVOLVED, 300, 900, 0, 10, 72, 0, 200, 1000, 8, AB_NONE, + "Swarm-beast that drags scrap back to the hive." }, + { "Mendbeast", UT_MEDIC, FACTION_EVOLVED, 250, 600, 0, 10, 88, 118, 220, 0, 7, AB_HEAL, + "Regenerating mutant that heals the brood around it." }, + { "Cackler", UT_MUTANT, FACTION_EVOLVED, 60, 360, 38, 5, 70, 92, 220, 0, 3, AB_NONE, + "Skittering mutant with a crude slug-thrower." }, + { "Loper", UT_BEAST, FACTION_EVOLVED, 180, 620, 58, 10, 150, 100, 240, 0, 5, AB_NONE, + "Loping pack-hunter beast that strikes fast." }, + { "Warlord", UT_RAVAGER, FACTION_EVOLVED, 800, 2700,210, 75, 68, 134, 300, 0, 18, AB_NONE, + "Mountain-sized mutant warlord of the horde." }, + { "Pyro", UT_FLAME, FACTION_EVOLVED, 250, 700, 90, 12, 70, 52, 200, 0, 7, AB_NONE, + "Mutant with a gout of war-fire for close work." }, + { "Spore", UT_MISSILE, FACTION_EVOLVED, 350, 650,120, 10, 64, 220, 300, 0, 10, AB_NONE, + "Fungal spore-launcher with punishing range." }, + + /* ---- Series 9: rogue war machines (third faction) ---- */ + { "S9 Drone", UT_INFANTRY, FACTION_SERIES9, 50, 380, 42, 6, 64, 98, 220, 0, 2, AB_NONE, + "Autonomous combat drone with a pulse repeater." }, + { "S9 Crawler", UT_BUGGY, FACTION_SERIES9, 200, 620, 58, 12, 140, 108, 260, 0, 6, AB_NONE, + "Treaded recon crawler with twin autocannons." }, + { "S9 Crusher", UT_TANK, FACTION_SERIES9, 500, 1500,135, 45, 80, 130, 280, 0, 12, AB_SIEGE, + "Heavy war-robot crush-tank. [Siege] boosts range." }, + { "S9 Mortar", UT_ARTILLERY, FACTION_SERIES9, 450, 720,155, 16, 58, 262, 330, 0, 14, AB_SIEGE, + "Self-levelling robotic mortar platform. [Siege] extends reach." }, + { "S9 Hauler", UT_HARVESTER, FACTION_SERIES9, 300, 900, 0, 10, 72, 0, 200, 1000, 8, AB_NONE, + "Salvage automaton that hauls scrap to the foundry." }, + { "S9 Mender", UT_MEDIC, FACTION_SERIES9, 250, 600, 0, 10, 90, 120, 220, 0, 7, AB_HEAL, + "Repair automaton that welds wounded machines." }, + { "S9 Scorch", UT_FLAME, FACTION_SERIES9, 250, 700, 95, 12, 70, 52, 200, 0, 7, AB_NONE, + "Incendiary war-bot that melts infantry." }, + { "S9 Rocket", UT_MISSILE, FACTION_SERIES9, 350, 650,125, 10, 64, 220, 300, 0, 10, AB_NONE, + "Shoulder-mounted rocket drone with long reach." }, + + { NULL, UT_NONE, FACTION_NONE, 0,0,0,0,0,0,0,0, 0, AB_NONE, NULL } +}; + +static const BldDef BLD_DEFS[] = { + { "Command Centre", BT_BASE, 0, 0, 5000, 2, 2, 0, 12, {0},0, 0, 0, 0, 0, + "Faction headquarters. Trains infantry, provides pop." }, + { "War Factory", BT_WARFACTORY, 400, 12, 2500, 2, 2, 0, 8, + { UT_BUGGY, UT_TANK, UT_ARTILLERY, UT_JEEP, UT_RAVAGER, UT_FLAME, UT_MISSILE, UT_MEDIC }, 8, 0, 0, 0, 0, + "Builds ground war machines." }, + { "Refinery", BT_REFINERY, 300, 10, 2000, 2, 2, 0, 6, + { UT_HARVESTER }, 1, 0, 0, 0, 0, + "Processes scrap delivered by harvesters." }, + { "Power Plant", BT_POWER, 200, 8, 1500, 1, 1, 100, 4, {0},0, 0, 0, 0, 0, + "Generates power for the war effort." }, + { "Research Lab", BT_RESEARCH, 400, 12, 1500, 2, 2, 0, 0, {0},0, 0, 0, 0, 1, + "Researches damage, armour, speed, reload and build upgrades." }, + { "Repair Depot", BT_REPAIR, 300, 10, 1500, 2, 2, 0, 4, {0},0, 0, 0, 0, 0, + "Slowly repairs damaged units in range." }, + { "Turret", BT_TURRET, 250, 6, 1800, 1, 1, 0, 3, {0},0, 1, 60, 200, 0, + "Automated defensive gun emplacement." }, + { "Wall", BT_WALL, 25, 2, 800, 1, 1, 0, 0, {0},0, 0, 0, 0, 0, + "Cheap blast wall to channel the enemy." }, + { NULL, BT_NONE, 0,0,0,0,0,0,0,{0},0,0,0,0,0, NULL } +}; + +static UnitRegistry *g_unit_cards = NULL; +static UnitRegistry *g_bld_cards = NULL; + +const UnitDef *unit_def(UnitType t, Faction f) { + for (int i = 0; UNIT_DEFS[i].name; i++) { + if (UNIT_DEFS[i].type == t && UNIT_DEFS[i].faction == f) + return &UNIT_DEFS[i]; + } + /* fall back to survivor stats for unknown pairings */ + for (int i = 0; UNIT_DEFS[i].name; i++) + if (UNIT_DEFS[i].type == t && UNIT_DEFS[i].faction == FACTION_SURVIVOR) + return &UNIT_DEFS[i]; + return &UNIT_DEFS[0]; +} + +const BldDef *bld_def(BuildingType t) { + for (int i = 0; BLD_DEFS[i].name; i++) + if (BLD_DEFS[i].type == t) return &BLD_DEFS[i]; + return &BLD_DEFS[0]; +} + +void config_init(void) { + if (g_unit_cards) return; + g_unit_cards = create_registry(); + g_bld_cards = create_registry(); + + /* Build the unit-card registry from the same tables so the merged + * unit_card system stays the authoritative stats source. */ + for (int i = 0; UNIT_DEFS[i].name; i++) { + const UnitDef *d = &UNIT_DEFS[i]; + uint8_t ut = (d->type == UT_INFANTRY) ? UNIT_INFANTRY + : (d->type == UT_HARVESTER) ? UNIT_SPECIAL + : UNIT_VEHICLE; + UnitCard *c = create_unit_card( + d->name, faction_name(d->faction), (uint32_t)d->cost, + (uint32_t)d->hp, (uint32_t)d->attack, (uint32_t)d->defense, + (uint32_t)d->speed, (uint32_t)d->range, d->desc, + "assets/units/proc", ut); + if (c) add_unit(g_unit_cards, c); + } + for (int i = 0; BLD_DEFS[i].name; i++) { + const BldDef *d = &BLD_DEFS[i]; + UnitCard *c = create_unit_card( + d->name, "Building", (uint32_t)d->cost, (uint32_t)d->hp, + (uint32_t)(d->is_turret ? d->atk : 0), 0, + (uint32_t)d->build_time, 0, d->desc, + "assets/buildings/proc", UNIT_SPECIAL); + if (c) add_unit(g_bld_cards, c); + } +} + +UnitRegistry *unit_card_registry(void) { return g_unit_cards; } +UnitRegistry *bld_card_registry(void) { return g_bld_cards; } + +Faction enemy_of(Faction f) { + if (f == FACTION_SURVIVOR) return FACTION_EVOLVED; + if (f == FACTION_EVOLVED) return FACTION_SURVIVOR; + return FACTION_NONE; +} + +const char *faction_name(Faction f) { + switch (f) { + case FACTION_SURVIVOR: return "Survivors"; + case FACTION_EVOLVED: return "Evolved"; + case FACTION_SERIES9: return "Series 9"; + default: return "Neutral"; + } +} + +const char *unit_name(UnitType t, Faction f) { + return unit_def(t, f)->name; +} + +SDL_Color faction_color(Faction f) { + switch (f) { + case FACTION_SURVIVOR: return (SDL_Color){ 90, 160, 70, 255 }; /* olive green */ + case FACTION_EVOLVED: return (SDL_Color){170, 70, 180, 255 }; /* mutant purple */ + case FACTION_SERIES9: return (SDL_Color){200, 200, 60, 255 }; + default: return (SDL_Color){140, 140, 140, 255 }; + } +} + +/* ----------------------------------------------------------------------- */ +/* Campaign / missions */ +/* ----------------------------------------------------------------------- */ +static const Mission MISSIONS[] = { + { 0, "First Strike", + "The mutant Evolved have crawled from the irradiated wasteland. " + "Crush their hive and secure the scrap fields before they dig in.", + OBJ_DESTROY_ALL, FACTION_EVOLVED, DIFF_NORMAL, 0, 0, 0, 1337u }, + { 1, "Hold the Line", + "Survive the relentless Evolved assault for eight minutes while your " + "own war machine gears up in the rear.", + OBJ_SURVIVE, FACTION_EVOLVED, DIFF_NORMAL, 0, 480.0f, 1, 24601u }, + { 2, "Scrap Rush", + "Out-produce the Evolved: stockpile 6000 scrap before their warbands " + "overrun your refinery line.", + OBJ_GATHER, FACTION_EVOLVED, DIFF_NORMAL, 6000, 0, 2, 9001u }, + { 3, "Hardened Nest", + "A fortified Evolved brood holds the far wastes behind walls of rubble. " + "Tear the nest down on Hard difficulty.", + OBJ_DESTROY_ALL, FACTION_EVOLVED, DIFF_HARD, 0, 0, 3, 31337u }, + { 4, "Rogue Machines", + "The Series 9 war machines have awakened and turned on everyone. " + "Crush the rogue robots before they scrape the wastes bare.", + OBJ_DESTROY_ALL, FACTION_SERIES9, DIFF_NORMAL, 0, 0, 2, 667u }, + { 5, "Crossfire", + "The Evolved and Series 9 are at each other's throats. Pick your moment, " + "then smash the survivor of their war.", + OBJ_DESTROY_ALL, FACTION_EVOLVED, DIFF_HARD, 0, 0, 1, 555u }, + { 6, "Scorched Earth", + "A scorched plateau offers no cover. Gather 9000 scrap in the open " + "while two enemy factions hunt you.", + OBJ_GATHER, FACTION_SERIES9, DIFF_HARD, 9000, 0, 0, 808u }, + { 7, "Last Stand", + "Your perimeter is the only thing left. Hold for twelve minutes against " + "a three-way onslaught.", + OBJ_SURVIVE, FACTION_EVOLVED, DIFF_HARD, 0, 720.0f, 3, 404u }, + { 8, "Deep Recon", + "Push into the mutant swamps and destroy the Evolved command links. " + "The terrain hides ambushes at every turn.", + OBJ_DESTROY_ALL, FACTION_EVOLVED, DIFF_NORMAL, 0, 0, 1, 2718u }, + { 9, "Machine Purge", + "Series 9 has massed a legion of scrap-hungry tanks. Wipe the assembly " + "grounds from the map.", + OBJ_DESTROY_ALL, FACTION_SERIES9, DIFF_HARD, 0, 0, 2, 90210u }, + { 10, "Broken Alliance", + "The Evolved courted the machines and lost. Now both want you dead. " + "End the pact by force.", + OBJ_DESTROY_ALL, FACTION_SERIES9, DIFF_HARD, 0, 0, 3, 1337u }, + { 11, "Final Approach", + "The enemy heartland lies ahead, bristling with turrets and walls. " + "Break through and destroy their core.", + OBJ_DESTROY_ALL, FACTION_EVOLVED, DIFF_HARD, 0, 0, 3, 4242u }, + { 12, "Krush, Kill 'n' Destroy", + "All-out war. Every faction fields its finest. Be the last army " + "standing on the poisoned earth.", + OBJ_DESTROY_ALL, FACTION_SERIES9, DIFF_HARD, 0, 0, 0, 1u }, + { -1, NULL, NULL, 0, FACTION_NONE, 0, 0, 0, 0, 0u } +}; + +int mission_count(void) { + int n = 0; + for (int i = 0; MISSIONS[i].name; i++) n++; + return n; +} + +const Mission *mission_get(int id) { + for (int i = 0; MISSIONS[i].name; i++) + if (MISSIONS[i].id == id) return &MISSIONS[i]; + return &MISSIONS[0]; +} + +/* ----------------------------------------------------------------------- */ +/* Research */ +/* ----------------------------------------------------------------------- */ +const char *research_name(int up) { + switch (up) { + case UP_DAMAGE: return "Munitions"; + case UP_ARMOR: return "Plating"; + case UP_SPEED: return "Drivetrain"; + case UP_RELOAD: return "Autoloaders"; + case UP_BUILD: return "Logistics"; + default: return "?"; + } +} + +int research_cost(int up, int level) { + (void)up; + return 250 * (level + 1); +} diff --git a/engine-c/src/demo_unitcard.c b/engine-c/src/demo_unitcard.c new file mode 100644 index 0000000..9a51034 --- /dev/null +++ b/engine-c/src/demo_unitcard.c @@ -0,0 +1,55 @@ +#include +#include +#include +#include "unit_card.h" + +int main() { + printf("=== openkknd - Demo Game ===\n"); + printf("KKND-style RTS Game Engine\n"); + printf("Version 1.0.0\n"); + printf("\n"); + + // Create unit registry + UnitRegistry* registry = create_registry(); + + // Test unit creation + UnitCard* marine = create_unit_card( + "Terran Marine", "Allied", 100, 100, 15, + 12, 4, 6, "Standard Terran infantry", + "assets/units/marine.png", UNIT_INFANTRY + ); + + if (marine) { + printf("✓ Created unit: %s (%s)\n", marine->name, marine->faction); + printf(" Cost: %d, HP: %d, Attack: %d, Defense: %d, Speed: %d, Range: %d\n", + marine->cost, marine->hp, marine->attack, marine->defense, + marine->speed, marine->range); + } + + // Test unit addition + if (add_unit(registry, marine) == 0) { + printf("✓ Unit added to registry\n"); + + // Print registry statistics + print_performance_stats(registry); + + // Test unit lookup + UnitCard* found = get_unit_by_name(registry, "Terran Marine"); + if (found) { + printf("✓ Unit lookup successful: %s\n", found->name); + } + + // Clean up + destroy_registry(registry); + free_unit_card(marine); + + printf("\n✓ Demo game completed successfully!\n"); + return 0; + } + + printf("\n✗ Demo game failed\n"); + destroy_registry(registry); + if (marine) free_unit_card(marine); + + return 1; +} diff --git a/engine-c/src/engine.c b/engine-c/src/engine.c new file mode 100644 index 0000000..41d5cdd --- /dev/null +++ b/engine-c/src/engine.c @@ -0,0 +1,188 @@ +#include "kknd.h" + +/* ========================================================================= + * engine.c - Match setup, main simulation step and win/lose logic. + * ========================================================================= */ + +int g_headless = 0; +int g_auto = 0; + +void engine_log(const char *fmt, ...) { + va_list ap; + va_start(ap, fmt); + char buf[512]; + vsnprintf(buf, sizeof buf, fmt, ap); + va_end(ap); + printf("[kknd] %s\n", buf); + fflush(stdout); +} + +void game_log(Game *g, const char *fmt, ...) { + va_list ap; + va_start(ap, fmt); + vsnprintf(g->log_msg[g->log_next], 96, fmt, ap); + va_end(ap); + g->log_next = (g->log_next + 1) % 32; + if (g->log_count < 32) g->log_count++; +} + +void engine_init(Game *g, const char *title) { + memset(g, 0, sizeof(Game)); + g->wrecks = (Wreck *)calloc(MAX_WRECKS, sizeof(Wreck)); + config_init(); + if (!g_headless) { + audio_init(); + if (!render_init(g)) { + fprintf(stderr, "Failed to initialise renderer\n"); + exit(1); + } + } else { + (void)title; + } + g->state = GAME_MENU; + g->human = FACTION_SURVIVOR; + g->diff = DIFF_NORMAL; + g->speed_mul = 1.0f; + g->unlocked = 1; + g->menu_sel = 0; +} + +void engine_shutdown(Game *g) { + free(g->wrecks); + sprites_free(&g->spr); + if (g->ren) SDL_DestroyRenderer(g->ren); + if (g->win) SDL_DestroyWindow(g->win); + audio_shutdown(); + SDL_Quit(); + map_free(&g->map); +} + +static void place_start(Game *g, Faction f) { + int sx = g->map.spawn_x[f], sy = g->map.spawn_y[f]; + int bx = sx - 1, by = sy - 1; + bld_spawn(g, BT_BASE, f, bx, by); + bld_spawn(g, BT_REFINERY, f, bx, sy + 2); + bld_spawn(g, BT_WARFACTORY, f, sx + 2, by); + bld_spawn(g, BT_POWER, f, sx + 2, sy + 2); + + float wx, wy; map_tile_to_world(sx, sy, &wx, &wy); + /* harvesters */ + for (int i = 0; i < 2; i++) + unit_spawn(g, UT_HARVESTER, f, wx + (i*24-12), wy + 30); + /* starting infantry */ + for (int i = 0; i < 4; i++) + unit_spawn(g, (f==FACTION_SURVIVOR?UT_INFANTRY:UT_INFANTRY), f, + wx - 40 + i*20, wy - 20); + /* a couple of buggies */ + for (int i = 0; i < 2; i++) + unit_spawn(g, (f==FACTION_SURVIVOR?UT_BUGGY:UT_BUGGY), f, + wx - 30 + i*30, wy - 40); +} + +void engine_setup_match(Game *g, int mission_id) { + const Mission *m = mission_get(mission_id); + g->mission_id = m->id; + g->objective = m->objective; + g->diff = (Difficulty)m->diff; + g->obj_timer = 0; + + /* resolve the enemy faction (never the player's own side) */ + Faction enemy = m->enemy; + if (enemy == g->human) + enemy = (g->human == FACTION_SURVIVOR) ? FACTION_EVOLVED : FACTION_SURVIVOR; + g->enemy = enemy; + + RNG rng; + rng_seed(&rng, (uint64_t)m->seed ^ 0x4B4B4E44ULL); + g->map.style = m->map_style; + map_init(&g->map, &rng); + + g->unit_count = 0; g->unit_next_id = 1; + g->bld_count = 0; g->bld_next_id = 1; + g->bullet_count = 0; g->particle_count = 0; + g->sel_count = 0; g->placing = 0; + g->time = 0; g->frames = 0; g->winner = 0; + g->ai_timer = 5.0f; + g->log_count = 0; g->log_next = 0; + + for (int f = 1; f < MAX_FACTIONS; f++) { + g->scrap[f] = START_SCRAP; + g->power[f] = 0; g->pop[f] = 0; g->pop_cap[f] = 0; + for (int u = 0; u < UP_COUNT; u++) g->upg[f].lvl[u] = 0; + g->research_lab[f] = 0; + g->research_cur[f] = -1; + g->research_prog[f] = 0; + } + + place_start(g, g->human); + place_start(g, enemy); + + /* fortress theme: wall the enemy base (leave a gate on the far edge) */ + if (m->map_style == 3) { + int sx = g->map.spawn_x[enemy], sy = g->map.spawn_y[enemy]; + for (int dx = -2; dx <= 4; dx++) + for (int dy = -2; dy <= 4; dy++) { + int perimeter = (dx == -2 || dx == 4 || dy == -2 || dy == 4); + if (!perimeter) continue; + if (dy == 4) continue; /* gate on the far edge */ + bld_spawn(g, BT_WALL, enemy, sx + dx, sy + dy); + } + } + + /* centre camera on player base */ + int hx = g->map.spawn_x[g->human], hy = g->map.spawn_y[g->human]; + g->cam.x = (float)(hx*TILE - VIEW_W/2); + g->cam.y = (float)(hy*TILE - VIEW_H/2); + if (g->cam.x < 0) g->cam.x = 0; + if (g->cam.y < 0) g->cam.y = 0; + + g->state = GAME_PLAYING; + engine_log("Mission %d '%s': %s vs %s (%s)", + m->id, m->name, faction_name(g->human), faction_name(enemy), + m->diff==DIFF_EASY?"Easy":m->diff==DIFF_HARD?"Hard":"Normal"); +} + +void engine_check_win(Game *g) { + Faction human = g->human; + Faction enemy = g->enemy; + int human_base = 0, enemy_base = 0; + int human_bld = 0, enemy_bld = 0; + for (int i = 0; i < g->bld_count; i++) { + Building *b = &g->buildings[i]; + if (b->dead) continue; + if (b->faction == human) { human_bld++; if (b->type==BT_BASE) human_base=1; } + if (b->faction == enemy) { enemy_bld++; if (b->type==BT_BASE) enemy_base=1; } + } + if (!human_base || human_bld == 0) { g->state = GAME_LOST; g->winner = enemy; return; } + + switch (g->objective) { + case OBJ_SURVIVE: + if (g->time >= ((const Mission*)mission_get(g->mission_id))->survive_time) + { g->state = GAME_WON; g->winner = human; } + break; + case OBJ_GATHER: + if (g->scrap[human] >= ((const Mission*)mission_get(g->mission_id))->scrap_goal) + { g->state = GAME_WON; g->winner = human; } + break; + default: /* OBJ_DESTROY_ALL */ + if (!enemy_base || enemy_bld == 0) { g->state = GAME_WON; g->winner = human; } + break; + } +} + +void engine_update(Game *g, float dt) { + if (g->state != GAME_PLAYING) return; + dt *= g->speed_mul; + g->time += dt; + g->frames++; + + unit_update(g, dt); + bld_update(g, dt); + bullet_update(g, dt); + particle_update(g, dt); + ai_update(g, dt); + map_update_fog(g, g->human); + audio_music_tick(dt); + if (g->cam.shake > 0) { g->cam.shake -= dt * 24.0f; if (g->cam.shake < 0) g->cam.shake = 0; } + engine_check_win(g); +} diff --git a/engine-c/src/input.c b/engine-c/src/input.c new file mode 100644 index 0000000..018a93a --- /dev/null +++ b/engine-c/src/input.c @@ -0,0 +1,292 @@ +#include "kknd.h" + +/* ========================================================================= + * input.c - Mouse / keyboard handling and camera control. + * ========================================================================= */ + +static int g_keys[SDL_NUM_SCANCODES]; + +int input_screen_to_world(Game *g, int sx, int sy, float *wx, float *wy) { + *wx = (float)sx + g->cam.x; + *wy = (float)sy + g->cam.y; + return 1; +} + +static int pick_unit(Game *g, int sx, int sy, float *wx, float *wy) { + input_screen_to_world(g, sx, sy, wx, wy); + int best = -1; float bestd = 18*18; + for (int i = 0; i < g->unit_count; i++) { + Unit *u = &g->units[i]; + if (u->dead) continue; + float d = kknd_dist2(*wx, *wy, u->x, u->y); + if (d < bestd) { bestd = d; best = i; } + } + return best; +} + +static int pick_building(Game *g, float wx, float wy) { + for (int i = 0; i < g->bld_count; i++) { + Building *b = &g->buildings[i]; + if (b->dead) continue; + float bx = (float)(b->tx * TILE), by = (float)(b->ty * TILE); + if (wx >= bx && wx <= bx + b->w*TILE && wy >= by && wy <= by + b->h*TILE) + return i; + } + return -1; +} + +static void issue_orders(Game *g, float wx, float wy) { + /* attack if enemy unit under cursor */ + int ei = -1; float bestd = 22*22; + for (int i = 0; i < g->unit_count; i++) { + Unit *u = &g->units[i]; + if (u->dead || u->faction == g->human) continue; + float d = kknd_dist2(wx, wy, u->x, u->y); + if (d < bestd) { bestd = d; ei = i; } + } + int bi = pick_building(g, wx, wy); + if (bi >= 0 && g->buildings[bi].faction != g->human) ei = -2; + + int issued = 0; + if (ei >= 0 || ei == -2) { + for (int s = 0; s < g->sel_count; s++) { + Unit *u = unit_by_id(g, g->selection[s]); + if (!u) continue; + if (ei >= 0) unit_issue_attack_unit(g, (int)(u - g->units), ei); + else unit_issue_attack_bld(g, (int)(u - g->units), g->buildings[bi].id); + issued++; + } + } else { + /* gather selection and issue a formation move */ + int sel[MAX_SELECTION], sn = 0; + for (int s = 0; s < g->sel_count; s++) { + Unit *u = unit_by_id(g, g->selection[s]); + if (!u) continue; + if (u->type == UT_HARVESTER) { + int tx, ty; map_world_to_tile(wx, wy, &tx, &ty); + Tile *t = map_tile(&g->map, tx, ty); + if (t && t->terrain == T_SCRAP) unit_issue_gather(g, (int)(u - g->units), wx, wy); + else unit_issue_move(g, (int)(u - g->units), wx, wy, 0); + } else { + sel[sn++] = (int)(u - g->units); + } + issued++; + } + if (sn > 0) unit_issue_move_group(g, sel, sn, wx, wy, 1); + } + if (issued) audio_sfx(0); +} + +static void handle_hud_click(Game *g, int sx, int sy) { + HudButton btns[64]; int n = 0; + hud_layout(g, btns, &n); + for (int i = 0; i < n; i++) { + if (sx >= btns[i].x && sx <= btns[i].x + btns[i].w && + sy >= btns[i].y && sy <= btns[i].y + btns[i].h) { + if (!btns[i].enabled) { audio_sfx(5); return; } + if (btns[i].kind == 0) { + g->placing = 1; g->place_type = (BuildingType)btns[i].id; + audio_sfx(4); + } else if (btns[i].kind == 2) { + bld_research(g, g->human, btns[i].id); + } else if (btns[i].kind == 3) { + Faction enemy = g->enemy; + int ex = g->map.spawn_x[enemy], ey = g->map.spawn_y[enemy]; + float tx, ty; map_tile_to_world(ex, ey, &tx, &ty); + int sel[MAX_SELECTION], sn = 0; + for (int s = 0; s < g->sel_count; s++) { + Unit *u = unit_by_id(g, g->selection[s]); + if (!u || u->faction != g->human) continue; + int idx = (int)(u - g->units); + if (btns[i].id == 0) unit_issue_stop(g, idx); + else if (btns[i].id == 4) unit_issue_hold(g, idx); + else if (btns[i].id == 2 && u->type == UT_HARVESTER) + unit_issue_gather_nearest(g, idx); + else if (btns[i].id == 3 && u->type == UT_HARVESTER) + unit_issue_return(g, idx); + else if (btns[i].id == 1 && u->type != UT_HARVESTER) + sel[sn++] = idx; + } + if (sn > 0 && btns[i].id == 1) unit_issue_move_group(g, sel, sn, tx, ty, 1); + audio_sfx(0); + } else if (btns[i].kind == 4) { + for (int s = 0; s < g->sel_count; s++) { + Unit *u = unit_by_id(g, g->selection[s]); + if (u && u->faction == g->human && u->ability == (AbilityType)btns[i].id) + unit_ability_use(g, (int)(u - g->units)); + } + audio_sfx(4); + } else { + /* queue unit on selected production building */ + for (int j = 0; j < g->bld_count; j++) { + Building *b = &g->buildings[j]; + if (b->dead || !b->selected || b->faction != g->human) continue; + const BldDef *d = bld_def(b->type); + for (int k = 0; k < d->produces_count; k++) + if (d->produces[k] == btns[i].id) + bld_queue_unit(g, (int)(b - g->buildings), (UnitType)btns[i].id); + } + } + return; + } + } +} + +static void try_place(Game *g, int sx, int sy) { + float wx, wy; input_screen_to_world(g, sx, sy, &wx, &wy); + int tx, ty; map_world_to_tile(wx, wy, &tx, &ty); + const BldDef *d = bld_def(g->place_type); + if (g->scrap[g->human] < d->cost) { audio_sfx(5); return; } + int ret = bld_spawn(g, g->place_type, g->human, tx, ty); + if (ret == -2) { audio_sfx(5); return; } + if (ret >= 0) { + g->scrap[g->human] -= d->cost; + audio_sfx(3); + } + g->placing = 0; +} + +void input_handle(Game *g, SDL_Event *e) { + switch (e->type) { + case SDL_KEYDOWN: + g_keys[e->key.keysym.scancode] = 1; + if (g->state == GAME_MENU) { + int n = mission_count(); + if (e->key.keysym.sym == SDLK_f) { + g->human = (g->human == FACTION_SURVIVOR) ? FACTION_EVOLVED + : (g->human == FACTION_EVOLVED) ? FACTION_SERIES9 + : FACTION_SURVIVOR; + audio_sfx(4); + } else if (e->key.keysym.sym == SDLK_UP) { + g->menu_sel = (g->menu_sel > 0) ? g->menu_sel - 1 : n - 1; + audio_sfx(6); + } else if (e->key.keysym.sym == SDLK_DOWN) { + g->menu_sel = (g->menu_sel < n - 1) ? g->menu_sel + 1 : 0; + audio_sfx(6); + } else if (e->key.keysym.sym == SDLK_RETURN + || e->key.keysym.sym == SDLK_KP_ENTER) { + if (g->menu_sel < g->unlocked) { engine_setup_match(g, g->menu_sel); audio_sfx(3); } + } else if (e->key.keysym.sym >= SDLK_0 && e->key.keysym.sym <= SDLK_9) { + int mid = e->key.keysym.sym - SDLK_0; + if (mid < g->unlocked) { engine_setup_match(g, mid); audio_sfx(3); } + } + break; + } + if ((g->state == GAME_WON || g->state == GAME_LOST) && + e->key.keysym.sym == SDLK_m) { + g->state = GAME_MENU; + break; + } + if (e->key.keysym.sym == SDLK_a && g->state == GAME_PLAYING) { + unit_select_all(g, g->human); + audio_sfx(4); + break; + } + if (e->key.keysym.sym >= SDLK_1 && e->key.keysym.sym <= SDLK_9 && + g->state == GAME_PLAYING) { + int gi = e->key.keysym.sym - SDLK_1; + if (SDL_GetModState() & KMOD_CTRL) { + g->group_len[gi] = g->sel_count; + for (int s = 0; s < g->sel_count; s++) g->groups[gi][s] = g->selection[s]; + audio_sfx(4); + } else if (g->group_len[gi] > 0) { + unit_clear_selection(g); + for (int s = 0; s < g->group_len[gi]; s++) { + Unit *u = unit_by_id(g, g->groups[gi][s]); + if (u && !u->dead) { g->selection[g->sel_count++] = g->groups[gi][s]; u->selected = 1; } + } + if (g->sel_count > 0) audio_sfx(4); + } + break; + } + if (e->key.keysym.sym == SDLK_SPACE) { + if (g->state == GAME_PLAYING) g->state = GAME_PAUSED; + else if (g->state == GAME_PAUSED) g->state = GAME_PLAYING; + } else if (e->key.keysym.sym == SDLK_F5) { + save_game(g, "savegame.kknd"); + } else if (e->key.keysym.sym == SDLK_ESCAPE) { + g->placing = 0; + } + break; + case SDL_KEYUP: + g_keys[e->key.keysym.scancode] = 0; + break; + case SDL_MOUSEBUTTONDOWN: + if (e->button.button == SDL_BUTTON_LEFT) { + if (g->placing) { try_place(g, e->button.x, e->button.y); break; } + if (e->button.y > VIEW_H - 140) { handle_hud_click(g, e->button.x, e->button.y); break; } + /* minimap click */ + if (e->button.x > VIEW_W - 212 && e->button.x < VIEW_W - 8 && + e->button.y > 34 && e->button.y < 238) { + int mx = VIEW_W - 210, my = 36; + g->cam.x = (e->button.x - mx) / 200.0f * MAP_PIX_W - VIEW_W/2; + g->cam.y = (e->button.y - my) / 200.0f * MAP_PIX_H - VIEW_H/2; + break; + } + g->dragging = 1; + g->drag_x0 = g->drag_x1 = e->button.x; + g->drag_y0 = g->drag_y1 = e->button.y; + } else if (e->button.button == SDL_BUTTON_RIGHT) { + if (g->placing) { g->placing = 0; break; } + if (g->sel_count > 0) { + float wx, wy; input_screen_to_world(g, e->button.x, e->button.y, &wx, &wy); + issue_orders(g, wx, wy); + } + } + break; + case SDL_MOUSEBUTTONUP: + if (e->button.button == SDL_BUTTON_LEFT && g->dragging) { + g->dragging = 0; + int dx = abs(e->button.x - g->drag_x0); + int dy = abs(e->button.y - g->drag_y0); + if (dx < 6 && dy < 6) { + /* click select */ + float wx, wy; + int ui = pick_unit(g, e->button.x, e->button.y, &wx, &wy); + if (ui >= 0 && g->units[ui].faction == g->human) { + unit_select_single(g, ui, g_keys[SDL_SCANCODE_LSHIFT]); + audio_sfx(4); + } else { + int bi = pick_building(g, wx, wy); + if (bi >= 0 && g->buildings[bi].faction == g->human) { + if (!g_keys[SDL_SCANCODE_LSHIFT]) unit_clear_selection(g); + g->buildings[bi].selected = 1; + g->selection[g->sel_count++] = g->buildings[bi].id; + audio_sfx(4); + } else if (!g_keys[SDL_SCANCODE_LSHIFT]) { + unit_clear_selection(g); + } + } + } else { + unit_select_in_rect(g, g->drag_x0, g->drag_y0, + e->button.x, e->button.y, + g_keys[SDL_SCANCODE_LSHIFT]); + } + } + break; + case SDL_MOUSEMOTION: + if (g->dragging) { g->drag_x1 = e->motion.x; g->drag_y1 = e->motion.y; } + break; + default: + break; + } +} + +void input_camera(Game *g, float dt) { + float sp = 600 * dt; + if (g_keys[SDL_SCANCODE_W] || g_keys[SDL_SCANCODE_UP]) g->cam.y -= sp; + if (g_keys[SDL_SCANCODE_S] || g_keys[SDL_SCANCODE_DOWN]) g->cam.y += sp; + if (g_keys[SDL_SCANCODE_A] || g_keys[SDL_SCANCODE_LEFT]) g->cam.x -= sp; + if (g_keys[SDL_SCANCODE_D] || g_keys[SDL_SCANCODE_RIGHT])g->cam.x += sp; + /* edge scroll */ + int mx, my; SDL_GetMouseState(&mx, &my); + if (mx < 8) g->cam.x -= sp; + if (mx > VIEW_W - 8) g->cam.x += sp; + if (my < 8) g->cam.y -= sp; + if (my > VIEW_H - 80 && my < VIEW_H - 8) g->cam.y += sp; + + if (g->cam.x < 0) g->cam.x = 0; + if (g->cam.y < 0) g->cam.y = 0; + if (g->cam.x > MAP_PIX_W - VIEW_W) g->cam.x = MAP_PIX_W - VIEW_W; + if (g->cam.y > MAP_PIX_H - VIEW_H + 72) g->cam.y = MAP_PIX_H - VIEW_H + 72; +} diff --git a/engine-c/src/main.c b/engine-c/src/main.c new file mode 100644 index 0000000..5c42a88 --- /dev/null +++ b/engine-c/src/main.c @@ -0,0 +1,167 @@ +#include "kknd.h" +#include + +/* ========================================================================= + * main.c - Entry point, headless sim mode and the real-time game loop. + * ========================================================================= */ + +static void print_stats(Game *g) { + Faction human = g->human, enemy = g->enemy; + int hu = 0, eu = 0, hb = 0, eb = 0; + for (int i = 0; i < g->unit_count; i++) { + if (g->units[i].dead) continue; + if (g->units[i].faction == human) hu++; + else if (g->units[i].faction == enemy) eu++; + } + for (int i = 0; i < g->bld_count; i++) { + if (g->buildings[i].dead) continue; + if (g->buildings[i].faction == human) hb++; + else if (g->buildings[i].faction == enemy) eb++; + } + const char *st = (g->state == GAME_WON) ? "WON" : + (g->state == GAME_LOST) ? "LOST" : + (g->state == GAME_PAUSED) ? "PAUSED" : "PLAY"; + printf("[t=%6.1f] state=%-6s %s u=%3d b=%2d scrap=%5d | %s u=%3d b=%2d scrap=%5d | bullets=%d parts=%d obj=%s\n", + g->time, st, faction_name(human), hu, hb, g->scrap[human], + faction_name(enemy), eu, eb, g->scrap[enemy], g->bullet_count, g->particle_count, + g->objective == OBJ_SURVIVE ? "survive" : + g->objective == OBJ_GATHER ? "scrap" : "destroy"); + fflush(stdout); +} + +static void save_snapshot(Game *g, int n) { + char path[64]; + snprintf(path, sizeof path, "shot_%04d.png", n); + void *pixels = malloc((size_t)VIEW_W * VIEW_H * 4); + if (!pixels) return; + if (SDL_RenderReadPixels(g->ren, NULL, SDL_PIXELFORMAT_RGBA32, + pixels, VIEW_W * 4) == 0) { + if (png_save(path, VIEW_W, VIEW_H, (const uint8_t *)pixels) == 0) + engine_log("snapshot -> %s", path); + } + free(pixels); +} + +int main(int argc, char **argv) { + Game *g = (Game *)malloc(sizeof(Game)); + if (!g) { fprintf(stderr, "Out of memory\n"); return 1; } + + int headless = 0; float sim_seconds = 0; + Difficulty diff = DIFF_NORMAL; + int mission = 0; + int loaded = 0; + float speed = 1.0f; + Faction faction_override = FACTION_NONE; + for (int i = 1; i < argc; i++) { + if (strcmp(argv[i], "--speed") == 0 && i + 1 < argc) + speed = (float)atof(argv[++i]); + } + for (int i = 1; i < argc; i++) { + if (strcmp(argv[i], "--easy") == 0) diff = DIFF_EASY; + else if (strcmp(argv[i], "--hard") == 0) diff = DIFF_HARD; + else if (strcmp(argv[i], "--mission") == 0 && i + 1 < argc) + mission = atoi(argv[++i]); + else if (strcmp(argv[i], "--headless") == 0 && i + 1 < argc) + { headless = 1; sim_seconds = (float)atof(argv[++i]); } + else if (strcmp(argv[i], "--auto") == 0) + { g_auto = 1; } + else if (strcmp(argv[i], "--faction") == 0 && i + 1 < argc) { + i++; + if (!strcmp(argv[i], "evolved")) faction_override = FACTION_EVOLVED; + else if (!strcmp(argv[i], "series9")) faction_override = FACTION_SERIES9; + else faction_override = FACTION_SURVIVOR; + } + else if (strcmp(argv[i], "--load") == 0 && i + 1 < argc) { + g_headless = headless; + engine_init(g, KKND_NAME); + engine_setup_match(g, mission); + load_game(g, argv[++i]); + loaded = 1; + } + } + + g_headless = headless; + if (getenv("KKND_SHOT")) g_headless = 1; /* snapshot mode = pure software, no SDL */ + if (!loaded) engine_init(g, KKND_NAME); + if (faction_override != FACTION_NONE) g->human = faction_override; + if (getenv("KKND_SPEED")) speed = (float)atof(getenv("KKND_SPEED")); + g->speed_mul = speed; + /* load campaign progress (highest unlocked mission id + 1) */ + { + FILE *pf = fopen("progress.kknd", "r"); + if (pf) { int u = 0; if (fscanf(pf, "%d", &u) == 1 && u > 0) g->unlocked = u; fclose(pf); } + } + int shot_mode = (!headless && getenv("KKND_SHOT")) ? 1 : 0; + if (shot_mode) engine_setup_match(g, mission); + + if (headless) { + if (!loaded) engine_setup_match(g, mission); + engine_log("Headless simulation for %.0f s (mission %d)", sim_seconds, mission); + float t = 0, step = 1.0f/30.0f, stat = 0; + print_stats(g); + while (t < sim_seconds && g->state == GAME_PLAYING) { + engine_update(g, step); + t += step; stat += step; + if (stat >= 1.0f) { print_stats(g); stat -= 1.0f; } + } + print_stats(g); + engine_log("Simulation complete (winner=%s)", + g->winner ? faction_name(g->winner) : "none"); + engine_shutdown(g); + free(g); + return 0; + } + + engine_log("%s %s - choose a mission, SPACE pause, F5 save, RMB command", KKND_NAME, KKND_VERSION); + + /* Snapshot mode: fast-forward the simulation headlessly (no SDL), then + * draw the world to an RGBA buffer with the software renderer and save a + * PNG. Avoids the broken GPU readback on this box entirely. */ + if (shot_mode) { + float pre = 8.0f; + if (getenv("KKND_SHOT_T")) pre = (float)atof(getenv("KKND_SHOT_T")); + float hdt = 1.0f / 30.0f; + int steps = (int)(pre / hdt); + for (int i = 0; i < steps; i++) engine_update(g, hdt); + char path[64]; snprintf(path, sizeof path, "shot_%04d.png", 1); + snapshot_capture(g, path); + engine_log("snapshot -> %s", path); + _exit(0); + } + + Uint32 last = SDL_GetTicks(); + int running = 1; + while (running) { + SDL_Event e; + while (SDL_PollEvent(&e)) { + if (e.type == SDL_QUIT) { running = 0; break; } + input_handle(g, &e); + } + if (!running) break; + Uint32 now = SDL_GetTicks(); + float dt = (float)(now - last) / 1000.0f; + last = now; + if (dt > 0.05f) dt = 0.05f; + if (g->state != GAME_MENU) { + int prev_state = g->state; + input_camera(g, dt); + engine_update(g, dt); + if (g->state == GAME_WON && prev_state != GAME_WON) { + int next = g->mission_id + 2; /* unlock the following mission */ + if (next > g->unlocked) { + g->unlocked = next; + FILE *pf = fopen("progress.kknd", "w"); + if (pf) { fprintf(pf, "%d\n", g->unlocked); fclose(pf); } + engine_log("Unlocked next mission (%d)", g->unlocked - 1); + } + g->menu_sel = (g->mission_id + 1 < g->unlocked) ? g->mission_id + 1 : g->mission_id; + } + } + render_frame(g); + } + + save_game(g, "savegame.kknd"); + engine_shutdown(g); + free(g); + return 0; +} diff --git a/engine-c/src/map.c b/engine-c/src/map.c new file mode 100644 index 0000000..f56c61c --- /dev/null +++ b/engine-c/src/map.c @@ -0,0 +1,407 @@ +#include "kknd.h" + +/* ========================================================================= + * map.c - Wasteland terrain generation and A* pathfinding. + * ========================================================================= */ + +static Tile *g_tiles = NULL; /* owned by GameMap but accessed via map_tile */ + +void map_init(GameMap *m, RNG *rng) { + m->w = MAP_W; + m->h = MAP_H; + m->tiles = (Tile *)calloc((size_t)m->w * m->h, sizeof(Tile)); + g_tiles = m->tiles; + m->fog = (uint8_t *)calloc((size_t)m->w * m->h, 1); + m->scrap_fields = 0; + + /* base terrain: wasteland with scattered rubble */ + for (int y = 0; y < m->h; y++) { + for (int x = 0; x < m->w; x++) { + Tile *t = &m->tiles[y * m->w + x]; + t->terrain = T_WASTELAND; + t->variant = (uint8_t)rng_range(rng, 0, 7); + t->scrap = 0; + t->occupied = 0; + t->walkable = 1; + } + } + + /* terrain theme knobs */ + int rubble_n = 40, grass_n = 14, water_n = 8; + if (m->style == 1) { rubble_n = 22; grass_n = 20; water_n = 4; } /* open */ + else if (m->style == 2) { rubble_n = 30; grass_n = 10; water_n = 16; } /* islands */ + else if (m->style == 3) { rubble_n = 55; grass_n = 8; water_n = 8; } /* fortress */ + + /* rubble patches */ + for (int i = 0; i < rubble_n; i++) { + int cx = rng_range(rng, 4, m->w - 5); + int cy = rng_range(rng, 4, m->h - 5); + int r = rng_range(rng, 3, 9); + for (int y = cy - r; y <= cy + r; y++) + for (int x = cx - r; x <= cx + r; x++) { + if (!map_in_bounds(m, x, y)) continue; + if (kknd_dist2((float)x,(float)y,(float)cx,(float)cy) <= r*r) { + Tile *t = &m->tiles[y*m->w + x]; + if (rng_f32(rng) < 0.7f) t->terrain = T_RUBBLE; + } + } + } + + /* grass oases */ + for (int i = 0; i < grass_n; i++) { + int cx = rng_range(rng, 4, m->w - 5); + int cy = rng_range(rng, 4, m->h - 5); + int r = rng_range(rng, 3, 7); + for (int y = cy - r; y <= cy + r; y++) + for (int x = cx - r; x <= cx + r; x++) { + if (!map_in_bounds(m, x, y)) continue; + if (kknd_dist2((float)x,(float)y,(float)cx,(float)cy) <= r*r) + m->tiles[y*m->w + x].terrain = T_GRASS; + } + } + + /* roads: a few straight-ish lines */ + for (int i = 0; i < 6; i++) { + int horiz = rng_range(rng, 0, 1); + int pos = rng_range(rng, 8, m->w - 9); + int len = rng_range(rng, 20, 60); + int x = rng_range(rng, 2, m->w - 3); + int y = rng_range(rng, 2, m->h - 3); + for (int s = 0; s < len; s++) { + if (map_in_bounds(m, x, y)) m->tiles[y*m->w + x].terrain = T_ROAD; + if (horiz) x += (rng_f32(rng) < 0.85f) ? 1 : 0, y += rng_range(rng,-1,1); + else y += (rng_f32(rng) < 0.85f) ? 1 : 0, x += rng_range(rng,-1,1); + } + } + + /* water lakes (blocked) */ + for (int i = 0; i < water_n; i++) { + int cx = rng_range(rng, 6, m->w - 7); + int cy = rng_range(rng, 6, m->h - 7); + int r = rng_range(rng, 3, 8); + for (int y = cy - r; y <= cy + r; y++) + for (int x = cx - r; x <= cx + r; x++) { + if (!map_in_bounds(m, x, y)) continue; + if (kknd_dist2((float)x,(float)y,(float)cx,(float)cy) <= r*r) { + Tile *t = &m->tiles[y*m->w + x]; + t->terrain = T_WATER; + t->walkable = 0; + } + } + } + + /* rock mountains (blocked) */ + for (int i = 0; i < 10; i++) { + int cx = rng_range(rng, 6, m->w - 7); + int cy = rng_range(rng, 6, m->h - 7); + int r = rng_range(rng, 3, 9); + for (int y = cy - r; y <= cy + r; y++) + for (int x = cx - r; x <= cx + r; x++) { + if (!map_in_bounds(m, x, y)) continue; + if (kknd_dist2((float)x,(float)y,(float)cx,(float)cy) <= r*r) { + Tile *t = &m->tiles[y*m->w + x]; + if (t->terrain != T_WATER) { + t->terrain = T_ROCK; + t->walkable = 0; + } + } + } + } + + /* scrap fields (resource) scattered, avoid spawn corners */ + int attempts = 0; + while (m->scrap_fields < 22 && attempts < 4000) { + attempts++; + int cx = rng_range(rng, 6, m->w - 7); + int cy = rng_range(rng, 6, m->h - 7); + int r = rng_range(rng, 2, 4); + if (kknd_dist2((float)cx,(float)cy, 10, m->h-10) < 20*20) continue; + if (kknd_dist2((float)cx,(float)cy, m->w-10, 10) < 20*20) continue; + for (int y = cy - r; y <= cy + r; y++) + for (int x = cx - r; x <= cx + r; x++) { + if (!map_in_bounds(m, x, y)) continue; + if (kknd_dist2((float)x,(float)y,(float)cx,(float)cy) <= r*r) { + Tile *t = &m->tiles[y*m->w + x]; + if (t->walkable && t->terrain != T_WATER) { + t->terrain = T_SCRAP; + t->scrap = 255; + m->scrap_fields++; + } + } + } + } + + /* spawn points: Survivor bottom-left, Evolved top-right */ + m->spawn_x[FACTION_SURVIVOR] = 10; + m->spawn_y[FACTION_SURVIVOR] = m->h - 10; + m->spawn_x[FACTION_EVOLVED] = m->w - 10; + m->spawn_y[FACTION_EVOLVED] = 10; + m->spawn_x[FACTION_SERIES9] = 10; + m->spawn_y[FACTION_SERIES9] = 10; + + /* clear spawn areas to wasteland & walkable */ + for (int f = 1; f <= 2; f++) { + int sx = m->spawn_x[f], sy = m->spawn_y[f]; + for (int y = sy - 8; y <= sy + 8; y++) + for (int x = sx - 8; x <= sx + 8; x++) { + if (!map_in_bounds(m, x, y)) continue; + Tile *t = &m->tiles[y*m->w + x]; + t->terrain = T_WASTELAND; + t->walkable = 1; + t->occupied = 0; + } + } +} + +void map_free(GameMap *m) { + free(m->tiles); + free(m->fog); + m->tiles = NULL; + m->fog = NULL; + g_tiles = NULL; +} + +void map_update_fog(Game *g, Faction f) { + uint8_t *vis = g->map.fog; + int n = g->map.w * g->map.h; + for (int i = 0; i < n; i++) if (vis[i] == 2) vis[i] = 1; + + /* reveal around human units */ + for (int i = 0; i < g->unit_count; i++) { + Unit *u = &g->units[i]; + if (u->dead || u->faction != f) continue; + int cx = (int)(u->x / TILE), cy = (int)(u->y / TILE); + int r = (int)(u->sight / TILE) + 1; + if (r < 4) r = 4; + for (int y = cy - r; y <= cy + r; y++) + for (int x = cx - r; x <= cx + r; x++) { + if (!map_in_bounds(&g->map, x, y)) continue; + if (kknd_dist2((float)x,(float)y,(float)cx,(float)cy) <= r*r) { + int idx = y*g->map.w + x; + vis[idx] = 2; + } + } + } + /* reveal around human buildings */ + for (int i = 0; i < g->bld_count; i++) { + Building *b = &g->buildings[i]; + if (b->dead || b->faction != f) continue; + int cx = b->tx + b->w/2, cy = b->ty + b->h/2; + int r = 8 + b->w; + for (int y = cy - r; y <= cy + r; y++) + for (int x = cx - r; x <= cx + r; x++) { + if (!map_in_bounds(&g->map, x, y)) continue; + if (kknd_dist2((float)x,(float)y,(float)cx,(float)cy) <= r*r) { + int idx = y*g->map.w + x; + vis[idx] = 2; + } + } + } +} + +Tile *map_tile(GameMap *m, int tx, int ty) { + if (!map_in_bounds(m, tx, ty)) return NULL; + return &m->tiles[ty * m->w + tx]; +} + +int map_in_bounds(GameMap *m, int tx, int ty) { + return tx >= 0 && ty >= 0 && tx < m->w && ty < m->h; +} + +int map_walkable(GameMap *m, int tx, int ty) { + if (!map_in_bounds(m, tx, ty)) return 0; + Tile *t = &m->tiles[ty * m->w + tx]; + return t->walkable && !t->occupied; +} + +void map_world_to_tile(float wx, float wy, int *tx, int *ty) { + *tx = (int)(wx / TILE); + *ty = (int)(wy / TILE); +} + +void map_tile_to_world(int tx, int ty, float *wx, float *wy) { + *wx = (float)(tx * TILE + TILE / 2); + *wy = (float)(ty * TILE + TILE / 2); +} + +int map_find_nearest_scrap(GameMap *m, float wx, float wy, float *outx, float *outy) { + int best_tx = -1, best_ty = -1; + float best = 1e18f; + for (int y = 0; y < m->h; y++) { + for (int x = 0; x < m->w; x++) { + Tile *t = &m->tiles[y*m->w + x]; + if (t->terrain == T_SCRAP && t->scrap > 0) { + float dx = (float)(x*TILE) - wx, dy = (float)(y*TILE) - wy; + float d = dx*dx + dy*dy; + if (d < best) { best = d; best_tx = x; best_ty = y; } + } + } + } + if (best_tx < 0) return 0; + map_tile_to_world(best_tx, best_ty, outx, outy); + return 1; +} + +void map_deplete_scrap(GameMap *m, int tx, int ty, int amount) { + Tile *t = map_tile(m, tx, ty); + if (!t || t->terrain != T_SCRAP) return; + int s = (int)t->scrap - amount; + if (s <= 0) { + t->scrap = 0; + t->terrain = T_RUBBLE; /* depleted field becomes rubble */ + } else { + t->scrap = (uint8_t)s; + } +} + +/* ----------------------------- A* ----------------------------- */ +/* Binary heap of tile indices keyed by f-score in a parallel array. */ +#define ASTAR_NODES (MAP_W * MAP_H) + +typedef struct { + int g[ASTAR_NODES]; + int f[ASTAR_NODES]; + int came[ASTAR_NODES]; + int closed[ASTAR_NODES]; + int open[ASTAR_NODES]; + int heap[ASTAR_NODES]; + int heap_n; +} AStar; + +static AStar *g_astar = NULL; + +static void heap_push(AStar *a, int idx) { + int i = a->heap_n++; + a->heap[i] = idx; + while (i > 0) { + int p = (i - 1) / 2; + if (a->f[a->heap[p]] <= a->f[a->heap[i]]) break; + int tmp = a->heap[p]; a->heap[p] = a->heap[i]; a->heap[i] = tmp; + i = p; + } +} + +static int heap_pop(AStar *a) { + int top = a->heap[0]; + a->heap[0] = a->heap[--a->heap_n]; + int i = 0; + for (;;) { + int l = 2*i+1, r = 2*i+2, m = i; + if (l < a->heap_n && a->f[a->heap[l]] < a->f[a->heap[m]]) m = l; + if (r < a->heap_n && a->f[a->heap[r]] < a->f[a->heap[m]]) m = r; + if (m == i) break; + int tmp = a->heap[m]; a->heap[m] = a->heap[i]; a->heap[i] = tmp; + i = m; + } + return top; +} + +static int heuristic(int ax, int ay, int bx, int by) { + int dx = abs(ax - bx), dy = abs(ay - by); + return (dx + dy) + (14 - 20) * (dx < dy ? dx : dy); /* octile-ish */ +} + +static const int NX[8] = { 1,-1,0,0,1,1,-1,-1 }; +static const int NY[8] = { 0,0,1,-1,1,-1,1,-1 }; + +int path_find(GameMap *m, int sx, int sy, int gx, int gy, Path *out) { + out->len = 0; + if (!map_in_bounds(m, sx, sy) || !map_in_bounds(m, gx, gy)) return 0; + + /* if goal blocked, retarget to nearest walkable neighbour */ + if (!map_walkable(m, gx, gy)) { + int best = -1, bestd = 1e9; + for (int d = 0; d < 8; d++) { + int nx = gx + NX[d], ny = gy + NY[d]; + if (map_walkable(m, nx, ny)) { + int dist = abs(nx-sx)+abs(ny-sy); + if (dist < bestd) { bestd = dist; best = ny*m->w+nx; } + } + } + if (best < 0) return 0; + gx = best % m->w; gy = best / m->w; + } + + if (g_astar == NULL) g_astar = (AStar *)calloc(1, sizeof(AStar)); + AStar *a = g_astar; + for (int i = 0; i < m->w*m->h; i++) { a->closed[i]=0; a->came[i]=-1; a->g[i]=1e9; } + a->heap_n = 0; + + int start = sy*m->w + sx; + int goal = gy*m->w + gx; + a->g[start] = 0; + a->f[start] = heuristic(sx, sy, gx, gy); + heap_push(a, start); + a->open[start] = 1; + + int found = 0; + int iter = 0; + while (a->heap_n > 0 && iter++ < ASTAR_NODES * 4) { + int cur = heap_pop(a); + if (a->closed[cur]) continue; + a->closed[cur] = 1; + if (cur == goal) { found = 1; break; } + int cx = cur % m->w, cy = cur / m->w; + for (int d = 0; d < 8; d++) { + int nx = cx + NX[d], ny = cy + NY[d]; + if (!map_in_bounds(m, nx, ny)) continue; + int ni = ny*m->w + nx; + if (a->closed[ni]) continue; + int step = (d < 4) ? 10 : 14; + if (!map_walkable(m, nx, ny) && ni != goal) continue; + int ng = a->g[cur] + step; + if (ng < a->g[ni]) { + a->g[ni] = ng; + a->came[ni] = cur; + a->f[ni] = ng + heuristic(nx, ny, gx, gy); + heap_push(a, ni); + } + } + } + + if (!found) return 0; + + /* reconstruct */ + int buf[ASTAR_NODES]; + int n = 0; + for (int c = goal; c != -1; c = a->came[c]) buf[n++] = c; + /* reverse */ + int len = 0; + for (int i = n - 1; i >= 0 && len < MAX_PATH; i--) { + out->nodes[len].tx = buf[i] % m->w; + out->nodes[len].ty = buf[i] / m->w; + len++; + } + out->len = len; + return 1; +} + +void path_smooth(GameMap *m, Path *p) { + if (p->len < 3) return; + /* simple line-of-sight smoothing: skip waypoints when straight clear */ + int out_i = 0; + int i = 0; + while (i < p->len - 1) { + int j = p->len - 1; + while (j > i + 1) { + /* check tiles between i and j */ + int x0 = p->nodes[i].tx, y0 = p->nodes[i].ty; + int x1 = p->nodes[j].tx, y1 = p->nodes[j].ty; + int steps = abs(x1-x0) > abs(y1-y0) ? abs(x1-x0) : abs(y1-y0); + int ok = 1; + for (int s = 1; s < steps; s++) { + int tx = x0 + (x1-x0)*s/steps; + int ty = y0 + (y1-y0)*s/steps; + if (!map_walkable(m, tx, ty)) { ok = 0; break; } + } + if (ok) break; + j--; + } + p->nodes[out_i] = p->nodes[i]; + out_i++; + i = j; + } + p->nodes[out_i] = p->nodes[p->len-1]; + out_i++; + p->len = out_i; +} diff --git a/engine-c/src/png.c b/engine-c/src/png.c new file mode 100644 index 0000000..8c62e6f --- /dev/null +++ b/engine-c/src/png.c @@ -0,0 +1,83 @@ +/* + * Minimal dependency-free PNG encoder (RGBA) using zlib. + * Avoids linking SDL2_image (which drags in an incompatible SDL3 at runtime). + */ +#include +#include +#include +#include "kknd.h" +#include + +static uint32_t crc_table[256]; +static int crc_done = 0; + +static void crc_init(void) { + for (uint32_t n = 0; n < 256; n++) { + uint32_t c = n; + for (int k = 0; k < 8; k++) + c = (c & 1) ? (0xedb88320U ^ (c >> 1)) : (c >> 1); + crc_table[n] = c; + } + crc_done = 1; +} + +static uint32_t crc_update(uint32_t c, const uint8_t *buf, size_t len) { + c = ~c; + for (size_t i = 0; i < len; i++) + c = crc_table[(c ^ buf[i]) & 0xff] ^ (c >> 8); + return ~c; +} + +static void chunk(FILE *f, const char *type, const uint8_t *data, uint32_t len) { + uint8_t hdr[4]; + hdr[0] = (len >> 24) & 0xff; hdr[1] = (len >> 16) & 0xff; + hdr[2] = (len >> 8) & 0xff; hdr[3] = len & 0xff; + fwrite(hdr, 1, 4, f); + fwrite(type, 1, 4, f); + if (len) fwrite(data, 1, len, f); + uint32_t crc = crc_update(0, (const uint8_t *)type, 4); + crc = crc_update(crc, data, len); + uint8_t cr[4]; + cr[0] = (crc >> 24) & 0xff; cr[1] = (crc >> 16) & 0xff; + cr[2] = (crc >> 8) & 0xff; cr[3] = crc & 0xff; + fwrite(cr, 1, 4, f); +} + +int png_save(const char *path, int w, int h, const uint8_t *rgba) { + if (!crc_done) crc_init(); + FILE *f = fopen(path, "wb"); + if (!f) return -1; + + static const uint8_t sig[8] = {137,80,78,71,13,10,26,10}; + fwrite(sig, 1, 8, f); + + uint8_t ihdr[13]; + ihdr[0] = (w >> 24) & 0xff; ihdr[1] = (w >> 16) & 0xff; + ihdr[2] = (w >> 8) & 0xff; ihdr[3] = w & 0xff; + ihdr[4] = (h >> 24) & 0xff; ihdr[5] = (h >> 16) & 0xff; + ihdr[6] = (h >> 8) & 0xff; ihdr[7] = h & 0xff; + ihdr[8] = 8; /* bit depth */ + ihdr[9] = 6; /* colour type RGBA */ + ihdr[10] = 0; ihdr[11] = 0; ihdr[12] = 0; + chunk(f, "IHDR", ihdr, 13); + + /* raw scanlines with filter byte 0 */ + uint8_t *raw = (uint8_t *)malloc((size_t)(w * 4 + 1) * h); + if (!raw) { fclose(f); return -1; } + for (int y = 0; y < h; y++) { + raw[(size_t)y * (w * 4 + 1)] = 0; + memcpy(raw + (size_t)y * (w * 4 + 1) + 1, rgba + (size_t)y * w * 4, (size_t)w * 4); + } + uLongf cmp_cap = compressBound((uLong)(w * 4 + 1) * h); + uint8_t *cmp = (uint8_t *)malloc(cmp_cap); + if (!cmp) { free(raw); fclose(f); return -1; } + uLongf cmp_len = cmp_cap; + compress2((Bytef *)cmp, &cmp_len, (const Bytef *)raw, + (uLong)(w * 4 + 1) * h, Z_BEST_COMPRESSION); + chunk(f, "IDAT", cmp, (uint32_t)cmp_len); + chunk(f, "IEND", NULL, 0); + + free(cmp); free(raw); + fclose(f); + return 0; +} diff --git a/engine-c/src/projectiles.c b/engine-c/src/projectiles.c new file mode 100644 index 0000000..53be2e0 --- /dev/null +++ b/engine-c/src/projectiles.c @@ -0,0 +1,106 @@ +#include "kknd.h" + +/* ========================================================================= + * projectiles.c - Bullets, missiles and particle effects. + * ========================================================================= */ + +static RNG g_prng; +static int g_prng_init = 0; + +void bullet_spawn(Game *g, float x, float y, int target_unit, int target_bld, + float dmg, Faction f, int kind) { + if (g->bullet_count >= MAX_BULLETS) return; + Bullet *b = &g->bullets[g->bullet_count++]; + b->x = x; b->y = y; + b->target_unit = target_unit; + b->target_bld = target_bld; + b->damage = dmg; + b->faction = f; + b->kind = kind; + b->speed = (kind == 3) ? 320 : (kind == 1 ? 420 : 520); + b->life = 3.0f; + if (target_unit >= 0) { + Unit *t = unit_by_id(g, target_unit); + if (t) { b->tx = t->x; b->ty = t->y; } + } else if (target_bld >= 0) { + Building *t = bld_by_id(g, target_bld); + if (t) { b->tx = (float)(t->tx*TILE + t->w*TILE/2); + b->ty = (float)(t->ty*TILE + t->h*TILE/2); } + } +} + +void bullet_update(Game *g, float dt) { + for (int i = g->bullet_count - 1; i >= 0; i--) { + Bullet *b = &g->bullets[i]; + b->life -= dt; + /* home onto target if still alive */ + if (b->target_unit >= 0) { + Unit *t = unit_by_id(g, b->target_unit); + if (t) { b->tx = t->x; b->ty = t->y; } + else b->target_unit = -1; + } else if (b->target_bld >= 0) { + Building *t = bld_by_id(g, b->target_bld); + if (t) { b->tx = (float)(t->tx*TILE+t->w*TILE/2); + b->ty = (float)(t->ty*TILE+t->h*TILE/2); } + else b->target_bld = -1; + } + + float dx = b->tx - b->x, dy = b->ty - b->y; + float d = (float)sqrtf(dx*dx + dy*dy); + float step = b->speed * dt; + if (d <= step + 4 || b->life <= 0) { + /* impact */ + if (b->target_unit >= 0) { + Unit *t = unit_by_id(g, b->target_unit); + if (t) unit_apply_damage(g, t, b->damage); + } else if (b->target_bld >= 0) { + Building *t = bld_by_id(g, b->target_bld); + if (t) bld_apply_damage(g, t, b->damage); + } + particle_burst(g, b->tx, b->ty, 4, + (b->kind==2)?2:1, 255, 200, 80); + /* swap-remove */ + g->bullets[i] = g->bullets[g->bullet_count - 1]; + g->bullet_count--; + continue; + } + b->x += dx/d * step; + b->y += dy/d * step; + /* trail */ + if (b->kind != 0) + particle_spawn(g, b->x, b->y, 2, 255, 180, 60); + } +} + +void particle_spawn(Game *g, float x, float y, int kind, uint8_t cr, uint8_t cg, uint8_t cb) { + if (g->particle_count >= MAX_PARTICLES) return; + if (!g_prng_init) { rng_seed(&g_prng, 0x1234ABCDULL); g_prng_init = 1; } + Particle *p = &g->particles[g->particle_count++]; + p->x = x; p->y = y; + float ang = rng_frange(&g_prng, 0, 6.283f); + float sp = rng_frange(&g_prng, 10, 60); + p->vx = (float)cosf(ang) * sp; + p->vy = (float)sinf(ang) * sp; + p->life = p->max_life = (kind == 0) ? 1.2f : 0.5f; + p->r = cr; p->g = cg; p->b = cb; + p->kind = kind; +} + +void particle_burst(Game *g, float x, float y, int n, int kind, uint8_t cr, uint8_t cg, uint8_t cb) { + for (int i = 0; i < n; i++) particle_spawn(g, x, y, kind, cr, cg, cb); +} + +void particle_update(Game *g, float dt) { + for (int i = g->particle_count - 1; i >= 0; i--) { + Particle *p = &g->particles[i]; + p->life -= dt; + p->x += p->vx * dt; + p->y += p->vy * dt; + p->vx *= 0.92f; p->vy *= 0.92f; + if (p->kind == 1) p->vy += 40 * dt; /* fire rises/slows */ + if (p->life <= 0) { + g->particles[i] = g->particles[g->particle_count - 1]; + g->particle_count--; + } + } +} diff --git a/engine-c/src/render.c b/engine-c/src/render.c new file mode 100644 index 0000000..3b2690a --- /dev/null +++ b/engine-c/src/render.c @@ -0,0 +1,631 @@ +#include "kknd.h" + +static void render_message_log(Game *g); + +/* ========================================================================= + * render.c - All 2D drawing: terrain, units, buildings, HUD and minimap. + * Recreates the chunky overhead KKND battlefield presentation. + * ========================================================================= */ + +int render_init(Game *g) { + if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO) != 0) { + fprintf(stderr, "SDL_Init: %s\n", SDL_GetError()); + return 0; + } + if (TTF_Init() != 0) { + fprintf(stderr, "TTF_Init: %s\n", TTF_GetError()); + return 0; + } + g->win = SDL_CreateWindow(KKND_NAME " - Krush, Kill 'n' Destroy", + SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, VIEW_W, VIEW_H, + SDL_WINDOW_SHOWN); + if (!g->win) return 0; + g->ren = SDL_CreateRenderer(g->win, -1, + SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC); + if (!g->ren) return 0; + SDL_SetRenderDrawBlendMode(g->ren, SDL_BLENDMODE_BLEND); + sprites_init(&g->spr, g->ren); + return 1; +} + +void render_text(Sprites *s, SDL_Renderer *r, TTF_Font *f, + const char *txt, int x, int y, SDL_Color c) { + if (!f || !txt || !*txt) return; + SDL_Surface *surf = TTF_RenderText_Blended(f, txt, c); + if (!surf) return; + SDL_Texture *tex = SDL_CreateTextureFromSurface(r, surf); + if (tex) { + SDL_Rect dr = { x, y, surf->w, surf->h }; + SDL_RenderCopy(r, tex, NULL, &dr); + SDL_DestroyTexture(tex); + } + SDL_FreeSurface(surf); +} + +static void draw_tex_world(Game *g, SDL_Texture *tex, float wx, float wy, + int w, int h, float angle, Uint8 alpha) { + SDL_Rect r; + r.w = w; r.h = h; + r.x = (int)(wx - w/2 - g->cam.x); + r.y = (int)(wy - h/2 - g->cam.y); + SDL_SetTextureAlphaMod(tex, alpha); + SDL_RenderCopyEx(g->ren, tex, NULL, &r, angle * 180.0f / 3.14159f, + NULL, SDL_FLIP_NONE); + SDL_SetTextureAlphaMod(tex, 255); +} + +void render_world(Game *g) { + int tx0 = (int)(g->cam.x / TILE) - 1; + int ty0 = (int)(g->cam.y / TILE) - 1; + int tx1 = (int)((g->cam.x + VIEW_W) / TILE) + 1; + int ty1 = (int)((g->cam.y + VIEW_H) / TILE) + 1; + for (int ty = ty0; ty <= ty1; ty++) { + for (int tx = tx0; tx <= tx1; tx++) { + if (!map_in_bounds(&g->map, tx, ty)) continue; + uint8_t fv = g->map.fog[ty*g->map.w + tx]; + if (fv == 0) continue; /* unexplored: leave black */ + Tile *t = map_tile(&g->map, tx, ty); + SDL_Rect r = { (int)(tx*TILE - g->cam.x), (int)(ty*TILE - g->cam.y), TILE, TILE }; + SDL_RenderCopy(g->ren, g->spr.terrain[t->terrain], NULL, &r); + if (fv == 1) { + SDL_SetRenderDrawColor(g->ren, 0, 0, 0, 120); + SDL_RenderFillRect(g->ren, &r); + } + } + } + /* subtle grid for the tactical look (only on explored tiles) */ + SDL_SetRenderDrawColor(g->ren, 0, 0, 0, 22); + for (int tx = tx0; tx <= tx1; tx++) { + int x = (int)(tx*TILE - g->cam.x); + SDL_RenderDrawLine(g->ren, x, (int)(ty0*TILE-g->cam.y), x, (int)(ty1*TILE-g->cam.y)); + } + for (int ty = ty0; ty <= ty1; ty++) { + int y = (int)(ty*TILE - g->cam.y); + SDL_RenderDrawLine(g->ren, (int)(tx0*TILE-g->cam.x), y, (int)(tx1*TILE-g->cam.x), y); + } +} + +void render_buildings(Game *g) { + for (int i = 0; i < g->bld_count; i++) { + Building *b = &g->buildings[i]; + if (b->dead) continue; + if (b->faction != g->human) { + int tx = b->tx + b->w/2, ty = b->ty + b->h/2; + if (!map_in_bounds(&g->map, tx, ty) || g->map.fog[ty*g->map.w + tx] != 2) continue; + } + int W = b->w * TILE, H = b->h * TILE; + int sx = (int)(b->tx*TILE - g->cam.x); + int sy = (int)(b->ty*TILE - g->cam.y); + SDL_Rect r = { sx, sy, W, H }; + SDL_Texture *tex = g->spr.bld[b->type][b->faction]; + if (b->build < 1.0f) { + /* construction: draw then scaffold overlay */ + SDL_SetTextureAlphaMod(tex, 160); + SDL_RenderCopy(g->ren, tex, NULL, &r); + SDL_SetTextureAlphaMod(tex, 255); + int pct = (int)(b->build * 100); + SDL_Rect bar = { sx, sy - 8, W, 5 }; + SDL_SetRenderDrawColor(g->ren, 200, 200, 200, 255); + SDL_RenderFillRect(g->ren, &bar); + SDL_Rect fill = { sx, sy - 8, (int)(W * b->build), 5 }; + SDL_SetRenderDrawColor(g->ren, 120, 220, 120, 255); + SDL_RenderFillRect(g->ren, &fill); + } else { + SDL_RenderCopy(g->ren, tex, NULL, &r); + } + /* health bar if damaged or selected */ + if (b->hp < b->max_hp || b->selected) { + SDL_Rect bar = { sx, sy - 6, W, 4 }; + SDL_SetRenderDrawColor(g->ren, 60, 60, 60, 255); + SDL_RenderFillRect(g->ren, &bar); + float f = b->hp / b->max_hp; + SDL_Color c = (b->faction == FACTION_SURVIVOR) ? (SDL_Color){90,160,70,255} + : (SDL_Color){170,70,180,255}; + SDL_Rect fill = { sx, sy - 6, (int)(W * f), 4 }; + SDL_SetRenderDrawColor(g->ren, c.r, c.g, c.b, 255); + SDL_RenderFillRect(g->ren, &fill); + } + if (b->selected) { + SDL_SetRenderDrawColor(g->ren, 255, 255, 255, 200); + SDL_RenderDrawRect(g->ren, &r); + } + /* production progress */ + if (b->producing != 0) { + const UnitDef *ud = unit_def((UnitType)b->producing, b->faction); + float f = b->prod_timer / (float)ud->build_time; + SDL_Rect bar = { sx, sy + H + 2, W, 4 }; + SDL_SetRenderDrawColor(g->ren, 60, 60, 60, 255); + SDL_RenderFillRect(g->ren, &bar); + SDL_Rect fill = { sx, sy + H + 2, (int)(W * f), 4 }; + SDL_SetRenderDrawColor(g->ren, 255, 220, 120, 255); + SDL_RenderFillRect(g->ren, &fill); + } + } +} + +void render_wrecks(Game *g) { + for (int i = 0; i < g->wreck_count; i++) { + Wreck *w = &g->wrecks[i]; + int tx = (int)(w->x / TILE), ty = (int)(w->y / TILE); + if (!map_in_bounds(&g->map, tx, ty)) continue; + if (g->map.fog[ty*g->map.w + tx] == 0) continue; + SDL_Color c = faction_color(w->faction); + int sx = (int)(w->x - g->cam.x), sy = (int)(w->y - g->cam.y); + SDL_SetRenderDrawColor(g->ren, c.r/2, c.g/2, c.b/2, 200); + SDL_RenderFillRect(g->ren, &(SDL_Rect){ sx-12, sy-9, 24, 18 }); + SDL_SetRenderDrawColor(g->ren, 30, 28, 26, 200); + SDL_RenderFillRect(g->ren, &(SDL_Rect){ sx-8, sy-5, 16, 10 }); + } +} + +void render_units(Game *g) { + for (int i = 0; i < g->unit_count; i++) { + Unit *u = &g->units[i]; + if (u->dead) continue; + if (u->faction != g->human) { + int tx = (int)(u->x / TILE), ty = (int)(u->y / TILE); + if (!map_in_bounds(&g->map, tx, ty) || g->map.fog[ty*g->map.w + tx] != 2) continue; + } + int sz = 28; + SDL_Texture *tex = g->spr.unit[u->type][u->faction]; + draw_tex_world(g, tex, u->x, u->y, sz, sz, u->heading, 255); + /* selection ring */ + if (u->selected) { + int cx = (int)(u->x - g->cam.x), cy = (int)(u->y - g->cam.y); + SDL_SetRenderDrawColor(g->ren, 120, 255, 120, 220); + for (int a = 0; a < 360; a += 12) { + int px = cx + (int)(cosf(a*3.14159f/180)*16); + int py = cy + (int)(sinf(a*3.14159f/180)*16); + SDL_RenderDrawPoint(g->ren, px, py); + } + } + /* health bar */ + if (u->hp < u->max_hp || u->selected) { + int cx = (int)(u->x - g->cam.x), by = (int)(u->y - g->cam.y) - 18; + int bw = 24; + SDL_Rect bg = { cx - bw/2, by, bw, 4 }; + SDL_SetRenderDrawColor(g->ren, 50, 50, 50, 255); + SDL_RenderFillRect(g->ren, &bg); + float f = u->hp / u->max_hp; + SDL_Color c = (u->faction == FACTION_SURVIVOR) ? (SDL_Color){90,200,90,255} + : (SDL_Color){220,110,220,255}; + SDL_Rect fg = { cx - bw/2, by, (int)(bw * f), 4 }; + SDL_SetRenderDrawColor(g->ren, c.r, c.g, c.b, 255); + SDL_RenderFillRect(g->ren, &fg); + /* cargo indicator for harvesters */ + if (u->type == UT_HARVESTER && u->cargo > 0) { + SDL_Rect cg = { cx - bw/2, by - 4, (int)(bw * u->cargo / 1000), 2 }; + SDL_SetRenderDrawColor(g->ren, 230, 190, 70, 255); + SDL_RenderFillRect(g->ren, &cg); + } + } + } +} + +void render_bullets(Game *g) { + for (int i = 0; i < g->bullet_count; i++) { + Bullet *b = &g->bullets[i]; + int sx = (int)(b->x - g->cam.x), sy = (int)(b->y - g->cam.y); + SDL_Texture *tex = g->spr.bullet[b->kind < 4 ? b->kind : 0]; + SDL_Rect r = { sx - 5, sy - 5, 10, 10 }; + SDL_RenderCopy(g->ren, tex, NULL, &r); + } +} + +void render_particles(Game *g) { + for (int i = 0; i < g->particle_count; i++) { + Particle *p = &g->particles[i]; + int sx = (int)(p->x - g->cam.x), sy = (int)(p->y - g->cam.y); + Uint8 a = (Uint8)(255 * (p->life / p->max_life)); + SDL_SetRenderDrawColor(g->ren, p->r, p->g, p->b, a); + int sz = (p->kind == 1) ? 3 : 2; + SDL_RenderFillRect(g->ren, &(SDL_Rect){ sx-sz/2, sy-sz/2, sz, sz }); + } +} + +void render_selection(Game *g) { + if (g->dragging) { + int x = g->drag_x0 < g->drag_x1 ? g->drag_x0 : g->drag_x1; + int y = g->drag_y0 < g->drag_y1 ? g->drag_y0 : g->drag_y1; + int w = abs(g->drag_x1 - g->drag_x0); + int h = abs(g->drag_y1 - g->drag_y0); + SDL_SetRenderDrawColor(g->ren, 120, 255, 120, 120); + SDL_RenderDrawRect(g->ren, &(SDL_Rect){x, y, w, h}); + SDL_SetRenderDrawColor(g->ren, 120, 255, 120, 30); + SDL_RenderFillRect(g->ren, &(SDL_Rect){x, y, w, h}); + } + /* placement ghost */ + if (g->placing) { + int mx, my; SDL_GetMouseState(&mx, &my); + float wx, wy; input_screen_to_world(g, mx, my, &wx, &wy); + int tx, ty; map_world_to_tile(wx, wy, &tx, &ty); + int ok = bld_can_place(g, g->place_type, tx, ty); + const BldDef *d = bld_def(g->place_type); + int sx = (int)(tx*TILE - g->cam.x), sy = (int)(ty*TILE - g->cam.y); + SDL_Rect r = { sx, sy, d->w*TILE, d->h*TILE }; + SDL_SetRenderDrawColor(g->ren, ok ? 120 : 220, ok ? 220 : 80, 80, 140); + SDL_RenderFillRect(g->ren, &r); + SDL_SetRenderDrawColor(g->ren, ok ? 120 : 220, ok ? 220 : 80, 80, 255); + SDL_RenderDrawRect(g->ren, &r); + } +} + +void hud_layout(Game *g, HudButton *out, int *n) { + int count = 0; + int bx = 12, by = VIEW_H - 64, bw = 92, bh = 52, gap = 6; + + /* always-available structures (player faction) */ + BuildingType structs[] = { BT_WARFACTORY, BT_REFINERY, BT_POWER, + BT_RESEARCH, BT_REPAIR, BT_TURRET, BT_WALL }; + for (int i = 0; i < 7; i++) { + const BldDef *d = bld_def(structs[i]); + out[count].x = bx; out[count].y = by; out[count].w = bw; out[count].h = bh; + out[count].kind = 0; out[count].id = structs[i]; + out[count].label = d->name; out[count].cost = d->cost; + out[count].enabled = (g->scrap[g->human] >= d->cost); + bx += bw + gap; count++; + if (bx + bw > VIEW_W - 12) { bx = 12; by -= (bh + 6); } + } + + /* if a production building is selected, show its units */ + for (int i = 0; i < g->bld_count; i++) { + Building *b = &g->buildings[i]; + if (b->dead || !b->selected || b->faction != g->human) continue; + const BldDef *d = bld_def(b->type); + for (int k = 0; k < d->produces_count; k++) { + const UnitDef *ud = unit_def((UnitType)d->produces[k], g->human); + out[count].x = bx; out[count].y = by; out[count].w = bw; out[count].h = bh; + out[count].kind = 1; out[count].id = d->produces[k]; + out[count].label = ud->name; out[count].cost = ud->cost; + out[count].enabled = (g->scrap[g->human] >= ud->cost); + bx += bw + gap; count++; + if (bx + bw > VIEW_W - 12) { bx = 12; by -= (bh + 6); } + } + } + /* if a research lab is selected, show upgrade buttons */ + { + int has_lab = 0; + for (int i = 0; i < g->bld_count; i++) { + Building *b = &g->buildings[i]; + if (b->dead || !b->selected || b->faction != g->human) continue; + const BldDef *d = bld_def(b->type); + if (d->is_research) { has_lab = 1; break; } + } + if (has_lab) { + for (int u = 0; u < UP_COUNT; u++) { + int lvl = g->upg[g->human].lvl[u]; + int maxed = (lvl >= UP_MAX_LVL); + int cost = maxed ? 0 : research_cost(u, lvl); + int busy = (g->research_cur[g->human] == u); + out[count].x = bx; out[count].y = by; out[count].w = bw; out[count].h = bh; + out[count].kind = 2; out[count].id = u; + out[count].label = research_name(u); + out[count].cost = cost; + out[count].enabled = !maxed && !busy && g->scrap[g->human] >= cost; + bx += bw + gap; count++; + if (bx + bw > VIEW_W - 12) { bx = 12; by -= (bh + 6); } + } + } + } + /* command buttons for the current unit selection (appended to build row) */ + { + int sel_units = 0, sel_harv = 0; + for (int i = 0; i < g->sel_count; i++) { + Unit *u = unit_by_id(g, g->selection[i]); + if (u && u->faction == g->human) { sel_units++; if (u->type == UT_HARVESTER) sel_harv++; } + } + if (sel_units > 0) { + #define ADDCMD(cid, lab) do { \ + out[count].x = bx; out[count].y = by; out[count].w = bw; out[count].h = bh; \ + out[count].kind = 3; out[count].id = (cid); out[count].label = (lab); \ + out[count].enabled = 1; out[count].cost = 0; count++; \ + bx += bw + gap; \ + if (bx + bw > VIEW_W - 12) { bx = 12; by -= (bh + 6); } \ + } while (0) + ADDCMD(0, "Stop"); + ADDCMD(1, "Attack"); + ADDCMD(4, "Hold"); + if (sel_harv > 0) { + ADDCMD(2, "Gather"); + ADDCMD(3, "Return"); + } + #undef ADDCMD + /* special-ability button (siege / heal) when applicable */ + int ab = AB_NONE; + for (int i = 0; i < g->sel_count; i++) { + Unit *uu = unit_by_id(g, g->selection[i]); + if (uu && uu->faction == g->human && uu->ability != AB_NONE) { ab = uu->ability; break; } + } + if (ab != AB_NONE) { + const char *lab = (ab == AB_SIEGE) ? "Siege" : "Heal"; + out[count].x = bx; out[count].y = by; out[count].w = bw; out[count].h = bh; + out[count].kind = 4; out[count].id = ab; out[count].label = lab; + out[count].enabled = 1; out[count].cost = 0; count++; + bx += bw + gap; + if (bx + bw > VIEW_W - 12) { bx = 12; by -= (bh + 6); } + } + } + } + *n = count; +} + +static char g_btn_label[256][64]; +void render_build_menu(Game *g) { + HudButton btns[64]; int n = 0; + hud_layout(g, btns, &n); + /* panel background (covers build bar + command row) */ + SDL_SetRenderDrawColor(g->ren, 20, 20, 24, 230); + SDL_RenderFillRect(g->ren, &(SDL_Rect){0, VIEW_H - 138, VIEW_W, 138}); + SDL_SetRenderDrawColor(g->ren, 90, 160, 70, 255); + SDL_RenderDrawLine(g->ren, 0, VIEW_H - 138, VIEW_W, VIEW_H - 138); + SDL_SetRenderDrawColor(g->ren, 60, 90, 50, 255); + SDL_RenderDrawLine(g->ren, 0, VIEW_H - 72, VIEW_W, VIEW_H - 72); + + for (int i = 0; i < n; i++) { + SDL_Rect r = { btns[i].x, btns[i].y, btns[i].w, btns[i].h }; + SDL_SetRenderDrawColor(g->ren, btns[i].enabled ? 50 : 35, + btns[i].enabled ? 70 : 40, + btns[i].enabled ? 55 : 38, 255); + SDL_RenderFillRect(g->ren, &r); + SDL_SetRenderDrawColor(g->ren, btns[i].enabled ? 120 : 70, + btns[i].enabled ? 200 : 90, + btns[i].enabled ? 120 : 70, 255); + SDL_RenderDrawRect(g->ren, &r); + SDL_Color txt = btns[i].enabled ? (SDL_Color){230,230,230,255} + : (SDL_Color){120,120,120,255}; + snprintf(g_btn_label[i], 64, "%s", btns[i].label); + render_text(&g->spr, g->ren, g->spr.font_small, g_btn_label[i], + btns[i].x + 4, btns[i].y + 4, txt); + char cost[32]; snprintf(cost, 32, "%d scrap", btns[i].cost); + render_text(&g->spr, g->ren, g->spr.font_small, cost, + btns[i].x + 4, btns[i].y + btns[i].h - 18, + (SDL_Color){230,190,70,255}); + /* level marker for research buttons */ + if (btns[i].kind == 2) { + int lvl = g->upg[g->human].lvl[btns[i].id]; + char lvs[32]; + if (lvl >= UP_MAX_LVL) snprintf(lvs, 32, "MAX"); + else if (g->research_cur[g->human] == btns[i].id) + snprintf(lvs, 32, "L%d..%d%%", lvl, (int)(g->research_prog[g->human]*100)); + else snprintf(lvs, 32, "L%d", lvl); + render_text(&g->spr, g->ren, g->spr.font_small, lvs, + btns[i].x + 4, btns[i].y + btns[i].h - 34, + (SDL_Color){150,210,255,255}); + } + } +} + +void render_hud(Game *g) { + /* top resource bar */ + SDL_SetRenderDrawColor(g->ren, 16, 16, 20, 220); + SDL_RenderFillRect(g->ren, &(SDL_Rect){0, 0, VIEW_W, 28}); + char buf[128]; + snprintf(buf, sizeof buf, "%s - Scrap: %d", faction_name(g->human), + g->scrap[g->human]); + render_text(&g->spr, g->ren, g->spr.font_med, buf, 10, 4, + (SDL_Color){230,230,230,255}); + + /* selection summary */ + int n_units = 0, n_bld = 0; + for (int i = 0; i < g->sel_count; i++) { + int id = g->selection[i]; + if (unit_by_id(g, id)) n_units++; + else if (bld_by_id(g, id)) n_bld++; + } + if (n_units > 0 || n_bld > 0) { + snprintf(buf, sizeof buf, "Selected: %d units, %d buildings", + n_units, n_bld); + render_text(&g->spr, g->ren, g->spr.font_small, buf, + VIEW_W - 260, 6, (SDL_Color){200,220,200,255}); + } + + /* hints */ + render_text(&g->spr, g->ren, g->spr.font_small, + "LMB select/drag RMB move/attack A select-all [B] build Space pause F5 save", + 10, VIEW_H - 90, (SDL_Color){150,150,150,255}); + + /* objective */ + const Mission *m = mission_get(g->mission_id); + const char *obj = (g->objective == OBJ_SURVIVE) ? "Survive the assault" + : (g->objective == OBJ_GATHER) ? "Stockpile scrap" + : "Destroy all enemies"; + char obuf[128]; + if (g->objective == OBJ_SURVIVE) + snprintf(obuf, sizeof obuf, "Objective: %s (%.0f/%.0f s)", obj, + g->time, m->survive_time); + else if (g->objective == OBJ_GATHER) + snprintf(obuf, sizeof obuf, "Objective: %s (%d/%d)", obj, + g->scrap[g->human], m->scrap_goal); + else + snprintf(obuf, sizeof obuf, "Objective: %s", obj); + render_text(&g->spr, g->ren, g->spr.font_small, obuf, + VIEW_W/2 - 90, 6, (SDL_Color){230,230,160,255}); + + if (g->state == GAME_PAUSED) + render_text(&g->spr, g->ren, g->spr.font_big, "PAUSED", + VIEW_W/2 - 70, VIEW_H/2 - 20, (SDL_Color){255,255,255,255}); + if (g->state == GAME_WON) + render_text(&g->spr, g->ren, g->spr.font_big, "VICTORY", + VIEW_W/2 - 80, VIEW_H/2 - 20, (SDL_Color){120,255,120,255}); + if (g->state == GAME_LOST) + render_text(&g->spr, g->ren, g->spr.font_big, "DEFEAT", + VIEW_W/2 - 70, VIEW_H/2 - 20, (SDL_Color){255,120,120,255}); + if (g->state == GAME_WON || g->state == GAME_LOST) + render_text(&g->spr, g->ren, g->spr.font_med, "Press M for mission menu", + VIEW_W/2 - 120, VIEW_H/2 + 20, (SDL_Color){220,220,220,255}); +} + +void render_minimap(Game *g) { + int mw = 200, mh = 200; + int mx = VIEW_W - mw - 10, my = 36; + SDL_SetRenderDrawColor(g->ren, 0, 0, 0, 220); + SDL_RenderFillRect(g->ren, &(SDL_Rect){mx-2, my-2, mw+4, mh+4}); + float sx = (float)mw / MAP_W, sy = (float)mh / MAP_H; + for (int ty = 0; ty < MAP_H; ty++) { + for (int tx = 0; tx < MAP_W; tx++) { + uint8_t fv = g->map.fog[ty*MAP_W + tx]; + if (fv == 0) continue; + Tile *t = map_tile(&g->map, tx, ty); + Uint8 cr=120,cg=104,cb=78; + switch (t->terrain) { + case T_WATER: cr=44;cg=78;cb=120; break; + case T_ROCK: cr=110;cg=104;cb=98; break; + case T_SCRAP: cr=200;cg=170;cb=70; break; + case T_GRASS: cr=86;cg=120;cb=64; break; + } + if (fv == 1) { cr/=2; cg/=2; cb/=2; } + SDL_SetRenderDrawColor(g->ren, cr, cg, cb, 255); + SDL_RenderDrawPoint(g->ren, mx + (int)(tx*sx), my + (int)(ty*sy)); + } + } + for (int i = 0; i < g->unit_count; i++) { + Unit *u = &g->units[i]; + if (u->dead) continue; + if (u->faction != g->human) { + int tx = (int)(u->x/TILE), ty = (int)(u->y/TILE); + if (!map_in_bounds(&g->map, tx, ty) || g->map.fog[ty*MAP_W+tx] != 2) continue; + } + SDL_Color c = faction_color(u->faction); + SDL_SetRenderDrawColor(g->ren, c.r, c.g, c.b, 255); + SDL_RenderDrawPoint(g->ren, mx + (int)(u->x/MAP_PIX_W*mw), + my + (int)(u->y/MAP_PIX_H*mh)); + } + for (int i = 0; i < g->bld_count; i++) { + Building *b = &g->buildings[i]; + if (b->dead) continue; + if (b->faction != g->human) { + int tx = b->tx + b->w/2, ty = b->ty + b->h/2; + if (!map_in_bounds(&g->map, tx, ty) || g->map.fog[ty*MAP_W+tx] != 2) continue; + } + SDL_Color c = faction_color(b->faction); + int px = mx + (int)((b->tx*TILE)/MAP_PIX_W*mw); + int py = my + (int)((b->ty*TILE)/MAP_PIX_H*mh); + SDL_SetRenderDrawColor(g->ren, c.r, c.g, c.b, 255); + SDL_RenderFillRect(g->ren, &(SDL_Rect){px, py, 2, 2}); + } + /* viewport box */ + int vx = mx + (int)(g->cam.x / MAP_PIX_W * mw); + int vy = my + (int)(g->cam.y / MAP_PIX_H * mh); + SDL_SetRenderDrawColor(g->ren, 255, 255, 255, 200); + SDL_RenderDrawRect(g->ren, &(SDL_Rect){vx, vy, + (int)(VIEW_W/MAP_PIX_W*mw), (int)(VIEW_H/MAP_PIX_H*mh)}); +} + +static void render_wrapped(Game *g, TTF_Font *f, const char *txt, + int x, int y, int maxw, SDL_Color c) { + /* greedy word wrap */ + char buf[1024]; + strncpy(buf, txt, sizeof buf - 1); buf[sizeof buf - 1] = 0; + char *tok = strtok(buf, " "); + char line[256]; line[0] = 0; + int ly = y; + while (tok) { + int cur = (int)strlen(line); + int add = (int)strlen(tok) + (cur ? 1 : 0); + if (cur + add >= (int)sizeof line || cur + add > maxw/8) { + render_text(&g->spr, g->ren, f, line, x, ly, c); + ly += 22; line[0] = 0; cur = 0; + } + if (cur) { strcat(line, " "); strcat(line, tok); } + else strcpy(line, tok); + tok = strtok(NULL, " "); + } + if (line[0]) render_text(&g->spr, g->ren, f, line, x, ly, c); +} + +void render_menu(Game *g) { + SDL_SetRenderDrawColor(g->ren, 18, 16, 14, 255); + SDL_RenderClear(g->ren); + render_text(&g->spr, g->ren, g->spr.font_big, + "KRUSH, KILL 'N' DESTROY", VIEW_W/2 - 260, 36, (SDL_Color){230,200,90,255}); + render_text(&g->spr, g->ren, g->spr.font_med, + "Recreated post-apocalyptic RTS - choose a mission:", VIEW_W/2 - 230, 84, + (SDL_Color){220,220,220,255}); + + int n = mission_count(); + int lx = 40, lw = 470, rh = 34; + for (int i = 0; i < n; i++) { + const Mission *m = mission_get(i); + int y = 124 + i * rh; + int locked = (m->id >= g->unlocked); + int sel = (i == g->menu_sel); + SDL_Rect r = { lx, y, lw, rh - 4 }; + SDL_SetRenderDrawColor(g->ren, sel ? 54 : 40, sel ? 60 : 44, sel ? 48 : 38, 255); + SDL_RenderFillRect(g->ren, &r); + SDL_SetRenderDrawColor(g->ren, sel ? 230 : 120, sel ? 200 : 200, sel ? 90 : 110, 255); + SDL_RenderDrawRect(g->ren, &r); + char line[200]; + const char *obj = (m->objective == OBJ_SURVIVE) ? "Survive" + : (m->objective == OBJ_GATHER) ? "Gather" + : "Destroy"; + snprintf(line, sizeof line, locked ? "%d. %s [LOCKED]" + : "%d. %s (%s, %s)", + m->id, m->name, obj, m->diff==DIFF_HARD ? "Hard" : "Normal"); + render_text(&g->spr, g->ren, g->spr.font_med, line, lx + 10, y + 4, + locked ? (SDL_Color){120,120,120,255} + : (SDL_Color){230,230,230,255}); + } + + /* briefing panel for the highlighted mission */ + const Mission *m = mission_get(g->menu_sel); + int px = lx + lw + 30, pw = VIEW_W - px - 30; + render_text(&g->spr, g->ren, g->spr.font_big, m->name, px, 124, + (SDL_Color){230,200,90,255}); + char meta[256]; + snprintf(meta, sizeof meta, + "Enemy: %s Difficulty: %s Objective: %s", + faction_name(m->enemy), + m->diff==DIFF_HARD ? "Hard" : "Normal", + m->objective==OBJ_SURVIVE ? "Survive the assault" + : m->objective==OBJ_GATHER ? "Stockpile scrap" : "Destroy all enemies"); + render_text(&g->spr, g->ren, g->spr.font_small, meta, px, 158, + (SDL_Color){180,210,255,255}); + render_wrapped(g, g->spr.font_med, m->brief, px, 188, pw, + (SDL_Color){220,220,220,255}); + + char hint[256]; + snprintf(hint, sizeof hint, + "Up/Down select Enter start [F] side: %s 0-9 jump. Factions: Survivors/Evolved/Series 9.", + faction_name(g->human)); + render_text(&g->spr, g->ren, g->spr.font_small, hint, + lx, VIEW_H - 28, (SDL_Color){150,150,150,255}); + SDL_RenderPresent(g->ren); +} + +void render_message_log(Game *g) { + if (g->log_count <= 0) return; + int x = 12; + int y = VIEW_H - 200; /* above the build bar */ + int shown = (g->log_count < 6) ? g->log_count : 6; + for (int i = 0; i < shown; i++) { + int idx = (g->log_next - shown + i + 32) % 32; + render_text(&g->spr, g->ren, g->spr.font_small, g->log_msg[idx], + x, y + i * 18, (SDL_Color){210, 230, 180, 255}); + } +} + +void render_frame(Game *g) { + if (g->state == GAME_MENU) { render_menu(g); return; } + /* camera shake */ + float shx = 0, shy = 0; + if (g->cam.shake > 0) { + shx = ((float)(rand() % 21) - 10) / 10.0f * g->cam.shake; + shy = ((float)(rand() % 21) - 10) / 10.0f * g->cam.shake; + g->cam.x += shx; g->cam.y += shy; + } + SDL_SetRenderDrawColor(g->ren, 0, 0, 0, 255); + SDL_RenderClear(g->ren); + render_world(g); + render_wrecks(g); + render_buildings(g); + render_units(g); + render_bullets(g); + render_particles(g); + render_selection(g); + render_build_menu(g); + render_hud(g); + render_minimap(g); + render_message_log(g); + SDL_RenderPresent(g->ren); + if (g->cam.shake > 0) { g->cam.x -= shx; g->cam.y -= shy; } +} diff --git a/engine-c/src/rng.c b/engine-c/src/rng.c new file mode 100644 index 0000000..2760c39 --- /dev/null +++ b/engine-c/src/rng.c @@ -0,0 +1,42 @@ +#include "kknd.h" + +/* xoroshiro128** deterministic RNG */ + +void rng_seed(RNG *r, uint64_t seed) { + if (seed == 0) seed = 0x9E3779B97F4A7C15ULL; + r->s[0] = seed; + r->s[1] = seed ^ 0xBF58476D1CE4E5B9ULL; + if (r->s[0] == 0) r->s[0] = 1; + if (r->s[1] == 0) r->s[1] = 2; +} + +static inline uint64_t rotl(uint64_t x, int k) { + return (x << k) | (x >> (64 - k)); +} + +uint64_t rng_u64(RNG *r) { + uint64_t s0 = r->s[0]; + uint64_t s1 = r->s[1]; + uint64_t result = rotl(s0 * 5, 7) * 9; + s1 ^= s0; + r->s[0] = rotl(s0, 24) ^ s1 ^ (s1 << 16); + r->s[1] = rotl(s1, 37); + return result; +} + +uint32_t rng_u32(RNG *r) { + return (uint32_t)(rng_u64(r) >> 32); +} + +int rng_range(RNG *r, int lo, int hi) { + if (hi <= lo) return lo; + return lo + (int)(rng_u32(r) % (uint32_t)(hi - lo + 1)); +} + +float rng_f32(RNG *r) { + return (float)(rng_u32(r) >> 8) / (float)(1 << 24); +} + +float rng_frange(RNG *r, float lo, float hi) { + return lo + rng_f32(r) * (hi - lo); +} diff --git a/engine-c/src/save.c b/engine-c/src/save.c new file mode 100644 index 0000000..c5a1bb3 --- /dev/null +++ b/engine-c/src/save.c @@ -0,0 +1,87 @@ +#include "kknd.h" + +/* ========================================================================= + * save.c - Binary save / load of skirmish state. + * ========================================================================= */ + +static void w32(FILE *f, uint32_t v) { fwrite(&v, 4, 1, f); } +static void wf(FILE *f, float v) { fwrite(&v, 4, 1, f); } +static void wi(FILE *f, int v) { fwrite(&v, 4, 1, f); } +static uint32_t r32(FILE *f) { uint32_t v = 0; fread(&v, 4, 1, f); return v; } +static float rf(FILE *f) { float v = 0; fread(&v, 4, 1, f); return v; } +static int ri(FILE *f) { int v = 0; fread(&v, 4, 1, f); return v; } + +int save_game(Game *g, const char *path) { + FILE *f = fopen(path, "wb"); + if (!f) return 0; + w32(f, SAVE_MAGIC); + w32(f, SAVE_VERSION); + wi(f, g->map.w); wi(f, g->map.h); + size_t n = (size_t)g->map.w * g->map.h; + for (size_t i = 0; i < n; i++) { + Tile *t = &g->map.tiles[i]; + uint8_t buf[4] = { t->terrain, t->walkable, t->scrap, t->occupied }; + fwrite(buf, 1, 4, f); + } + for (int i = 0; i < MAX_FACTIONS; i++) wf(f, (float)g->scrap[i]); + wf(f, g->time); + wi(f, g->unit_count); wi(f, g->unit_next_id); + fwrite(g->units, sizeof(Unit), (size_t)g->unit_count, f); + wi(f, g->bld_count); wi(f, g->bld_next_id); + fwrite(g->buildings, sizeof(Building), (size_t)g->bld_count, f); + wf(f, g->cam.x); wf(f, g->cam.y); + /* upgrades / research / mission */ + wi(f, g->mission_id); wi(f, g->objective); + wi(f, (int)g->human); wi(f, (int)g->enemy); + for (int i = 0; i < MAX_FACTIONS; i++) { + for (int u = 0; u < UP_COUNT; u++) wi(f, g->upg[i].lvl[u]); + wi(f, g->research_lab[i]); + wi(f, g->research_cur[i]); + wf(f, g->research_prog[i]); + } + fclose(f); + engine_log("Game saved to %s", path); + return 1; +} + +int load_game(Game *g, const char *path) { + FILE *f = fopen(path, "rb"); + if (!f) return 0; + if (r32(f) != SAVE_MAGIC) { fclose(f); return 0; } + if (r32(f) != SAVE_VERSION) { fclose(f); return 0; } + int w = ri(f), h = ri(f); + map_free(&g->map); + g->map.w = w; g->map.h = h; + g->map.tiles = (Tile *)calloc((size_t)w*h, sizeof(Tile)); + size_t n = (size_t)w*h; + for (size_t i = 0; i < n; i++) { + uint8_t buf[4]; + if (fread(buf, 1, 4, f) != 4) break; + g->map.tiles[i].terrain = buf[0]; + g->map.tiles[i].walkable = buf[1]; + g->map.tiles[i].scrap = buf[2]; + g->map.tiles[i].occupied = buf[3]; + } + for (int i = 0; i < MAX_FACTIONS; i++) g->scrap[i] = (int)rf(f); + g->time = rf(f); + g->unit_count = ri(f); g->unit_next_id = ri(f); + if (g->unit_count > MAX_UNITS) g->unit_count = MAX_UNITS; + fread(g->units, sizeof(Unit), (size_t)g->unit_count, f); + g->bld_count = ri(f); g->bld_next_id = ri(f); + if (g->bld_count > MAX_BUILDINGS) g->bld_count = MAX_BUILDINGS; + fread(g->buildings, sizeof(Building), (size_t)g->bld_count, f); + g->cam.x = rf(f); g->cam.y = rf(f); + g->mission_id = ri(f); g->objective = ri(f); + g->human = (Faction)ri(f); g->enemy = (Faction)ri(f); + for (int i = 0; i < MAX_FACTIONS; i++) { + for (int u = 0; u < UP_COUNT; u++) g->upg[i].lvl[u] = ri(f); + g->research_lab[i] = ri(f); + g->research_cur[i] = ri(f); + g->research_prog[i] = rf(f); + } + g->sel_count = 0; g->placing = 0; + g->state = GAME_PLAYING; + fclose(f); + engine_log("Game loaded from %s", path); + return 1; +} diff --git a/engine-c/src/snapshot.c b/engine-c/src/snapshot.c new file mode 100644 index 0000000..9ac211d --- /dev/null +++ b/engine-c/src/snapshot.c @@ -0,0 +1,148 @@ +/* + * Software snapshot renderer - draws the game world into an RGBA buffer using + * only primitive shapes (no SDL gpu readback, which is broken on this box due + * to an SDL2/SDL3 conflict). Output is saved as PNG via png_save(). + */ +#include "kknd.h" + +#define SW VIEW_W +#define SH VIEW_H + +static uint8_t *g_buf; + +static void px(int x, int y, uint8_t r, uint8_t g, uint8_t b) { + if (x < 0 || y < 0 || x >= SW || y >= SH) return; + int i = (y * SW + x) * 4; + g_buf[i] = r; g_buf[i+1] = g; g_buf[i+2] = b; g_buf[i+3] = 255; +} + +static void rectf(int x0, int y0, int x1, int y1, uint8_t r, uint8_t g, uint8_t b) { + if (x0 > x1) { int t = x0; x0 = x1; x1 = t; } + if (y0 > y1) { int t = y0; y0 = y1; y1 = t; } + for (int y = y0; y <= y1; y++) + for (int x = x0; x <= x1; x++) + px(x, y, r, g, b); +} + +static void disc(int cx, int cy, int rad, uint8_t r, uint8_t g, uint8_t b) { + for (int y = -rad; y <= rad; y++) + for (int x = -rad; x <= rad; x++) + if (x*x + y*y <= rad*rad) px(cx+x, cy+y, r, g, b); +} + +static uint8_t faction_r(Faction f){ return f==FACTION_SURVIVOR?70 : f==FACTION_EVOLVED?200 : 230; } +static uint8_t faction_g(Faction f){ return f==FACTION_SURVIVOR?180: f==FACTION_EVOLVED?70 : 60; } +static uint8_t faction_b(Faction f){ return f==FACTION_SURVIVOR?90 : f==FACTION_EVOLVED?210 : 230; } + +static void terrain_color(TerrainType t, uint8_t *r, uint8_t *g, uint8_t *b) { + switch (t) { + case T_GRASS: *r=46; *g=92; *b=46; break; + case T_ROAD: *r=90; *g=90; *b=80; break; + case T_RUBBLE:*r=80; *g=72; *b=64; break; + case T_ROCK: *r=80; *g=80; *b=86; break; + case T_WATER: *r=40; *g=60; *b=120; break; + case T_SCRAP: *r=70; *g=64; *b=40; break; + default: *r=50; *g=50; *b=50; break; + } +} + +void snapshot_capture(Game *g, const char *path) { + uint8_t *buf = (uint8_t *)malloc((size_t)SW * SH * 4); + if (!buf) return; + g_buf = buf; + for (int i = 0; i < SW*SH; i++) { buf[i*4]=0; buf[i*4+1]=0; buf[i*4+2]=0; buf[i*4+3]=255; } + + /* camera: centre on centroid of human units, else map centre */ + float cx = MAP_PIX_W/2, cy = MAP_PIX_H/2; + int nc = 0; + for (int i = 0; i < g->unit_count; i++) + if (!g->units[i].dead && g->units[i].faction == g->human) { cx += g->units[i].x; cy += g->units[i].y; nc++; } + if (nc) { cx /= nc; cy /= nc; } + float camx = cx - SW/2, camy = cy - SH/2; + if (camx < 0) camx = 0; if (camy < 0) camy = 0; + if (camx > MAP_PIX_W - SW) camx = MAP_PIX_W - SW; + if (camy > MAP_PIX_H - SH) camy = MAP_PIX_H - SH; + + /* terrain */ + int tx0 = (int)camx / TILE, ty0 = (int)camy / TILE; + int tx1 = (int)(camx + SW) / TILE, ty1 = (int)(camy + SH) / TILE; + for (int ty = ty0; ty <= ty1; ty++) { + for (int tx = tx0; tx <= tx1; tx++) { + if (tx < 0 || ty < 0 || tx >= MAP_W || ty >= MAP_W) continue; + Tile *t = map_tile(&g->map, tx, ty); + uint8_t r,g,b; terrain_color(t->terrain, &r,&g,&b); + int sx = tx*TILE - (int)camx, sy = ty*TILE - (int)camy; + rectf(sx, sy, sx+TILE-1, sy+TILE-1, r, g, b); + if (t->terrain == T_SCRAP && t->scrap > 0) { + int cc = (sx+TILE/2), cr = (sy+TILE/2); + for (int k=0;kscrap/20;k++) + disc(cc + ((k*7)%10)-5, cr + ((k*5)%10)-5, 2, 150,140,90); + } + } + } + + /* fog overlay */ + for (int ty = ty0; ty <= ty1; ty++) + for (int tx = tx0; tx <= tx1; tx++) { + if (tx < 0 || ty < 0 || tx >= MAP_W || ty >= MAP_W) continue; + int f = g->map.fog[ty*MAP_W + tx]; + if (f == 0) { int sx=tx*TILE-(int)camx, sy=ty*TILE-(int)camy; rectf(sx,sy,sx+TILE-1,sy+TILE-1,0,0,0); } + else if (f == 1) { int sx=tx*TILE-(int)camx, sy=ty*TILE-(int)camy; rectf(sx,sy,sx+TILE-1,sy+TILE-1,0,0,30); } + } + + /* buildings */ + for (int i = 0; i < g->bld_count; i++) { + Building *b = &g->buildings[i]; + if (b->dead) continue; + int sx = (int)(b->tx*TILE - camx), sy = (int)(b->ty*TILE - camy); + int w = b->w*TILE, h = b->h*TILE; + if (sx+w < 0 || sy+h < 0 || sx > SW || sy > SH) continue; + uint8_t r=faction_r(b->faction),gg=faction_g(b->faction),bb=faction_b(b->faction); + rectf(sx, sy, sx+w-1, sy+h-1, r/2+30, gg/2+30, bb/2+30); + rectf(sx+2, sy+2, sx+w-3, sy+h-3, r, gg, bb); + if (b->build < 1.0f) rectf(sx, sy, sx+w-1, sy+(int)(h*b->build)-1, 255,255,255); + } + + /* units */ + for (int i = 0; i < g->unit_count; i++) { + Unit *u = &g->units[i]; + if (u->dead) continue; + int sx = (int)(u->x - camx), sy = (int)(u->y - camy); + if (sx < -10 || sy < -10 || sx > SW+10 || sy > SH+10) continue; + uint8_t r=faction_r(u->faction),gg=faction_g(u->faction),bb=faction_b(u->faction); + int rad = u->type==UT_TANK||u->type==UT_BEAST?9:u->type==UT_HARVESTER?8:6; + disc(sx, sy, rad, r, gg, bb); + disc(sx, sy, 2, 255,255,255); + int hx = sx + (int)(cosf(u->heading)*rad), hy = sy + (int)(sinf(u->heading)*rad); + for (int k=0;kselected) { rectf(sx-rad-2,sy-rad-2,sx+rad+2,sy+rad+2,255,255,120); } + } + + /* projectiles */ + for (int i = 0; i < g->bullet_count; i++) { + Bullet *p = &g->bullets[i]; + int sx=(int)(p->x-camx), sy=(int)(p->y-camy); + px(sx,sy,255,230,120); px(sx+1,sy,255,230,120); + } + + /* minimap */ + int mmx=SW-210, mmy=10, mms=200; + rectf(mmx-2,mmy-2,mmx+mms+1,mmy+mms+1,0,0,0); + for (int ty=0;tymap.fog[ty*MAP_W+tx]; + if (f==0) continue; + Tile *t=map_tile(&g->map,tx,ty); + uint8_t r,g,b; terrain_color(t->terrain,&r,&g,&b); + int dx=mmx + tx*mms/MAP_W, dy=mmy + ty*mms/MAP_W; + px(dx,dy,r,g,b); + } + for (int i=0;ibld_count;i++){ Building*b=&g->buildings[i]; if(b->dead)continue; + int dx=mmx+b->tx*TILE*mms/(MAP_W*TILE), dy=mmy+b->ty*TILE*mms/(MAP_W*TILE); + px(dx,dy,faction_r(b->faction),faction_g(b->faction),faction_b(b->faction)); } + for (int i=0;iunit_count;i++){ Unit*u=&g->units[i]; if(u->dead)continue; + int dx=mmx+(int)(u->x*mms/MAP_PIX_W), dy=mmy+(int)(u->y*mms/MAP_PIX_H); + px(dx,dy,faction_r(u->faction),faction_g(u->faction),faction_b(u->faction)); } + + png_save(path, SW, SH, buf); + free(buf); +} diff --git a/engine-c/src/sprites.c b/engine-c/src/sprites.c new file mode 100644 index 0000000..f8ae7d4 --- /dev/null +++ b/engine-c/src/sprites.c @@ -0,0 +1,341 @@ +#include "kknd.h" + +/* ========================================================================= + * sprites.c - Procedural 2D sprite/terrain generation. + * Recreates the chunky post-apocalyptic KKND look without using any + * original game assets. All art is drawn at runtime into SDL textures. + * ========================================================================= */ + +#define SURF_FMT SDL_PIXELFORMAT_RGBA32 + +static SDL_Surface *new_surf(int w, int h) { + return SDL_CreateRGBSurfaceWithFormat(0, w, h, 32, SURF_FMT); +} + +static void px(SDL_Surface *s, int x, int y, Uint8 r, Uint8 g, Uint8 b, Uint8 a) { + if (x < 0 || y < 0 || x >= s->w || y >= s->h) return; + Uint32 *p = (Uint32 *)((Uint8 *)s->pixels + y * s->pitch); + Uint32 pix = (a << 24) | (b << 16) | (g << 8) | r; + p[x] = pix; +} + +static void fill_rect(SDL_Surface *s, int x, int y, int w, int h, + Uint8 r, Uint8 g, Uint8 b, Uint8 a) { + for (int j = y; j < y + h; j++) + for (int i = x; i < x + w; i++) + px(s, i, j, r, g, b, a); +} + +static void disc(SDL_Surface *s, int cx, int cy, int rad, + Uint8 r, Uint8 g, Uint8 b, Uint8 a) { + for (int j = -rad; j <= rad; j++) + for (int i = -rad; i <= rad; i++) + if (i*i + j*j <= rad*rad) + px(s, cx + i, cy + j, r, g, b, a); +} + +static void ring(SDL_Surface *s, int cx, int cy, int rad, int t, + Uint8 r, Uint8 g, Uint8 b, Uint8 a) { + for (int j = -rad; j <= rad; j++) + for (int i = -rad; i <= rad; i++) { + int d = i*i + j*j; + if (d <= rad*rad && d >= (rad - t)*(rad - t)) + px(s, cx + i, cy + j, r, g, b, a); + } +} + +static void line(SDL_Surface *s, int x0, int y0, int x1, int y1, + Uint8 r, Uint8 g, Uint8 b, Uint8 a) { + int dx = abs(x1 - x0), dy = -abs(y1 - y0); + int sx = x0 < x1 ? 1 : -1, sy = y0 < y1 ? 1 : -1; + int err = dx + dy; + for (;;) { + px(s, x0, y0, r, g, b, a); + if (x0 == x1 && y0 == y1) break; + int e2 = 2 * err; + if (e2 >= dy) { err += dy; x0 += sx; } + if (e2 <= dx) { err += dx; y0 += sy; } + } +} + +static SDL_Color darken(SDL_Color c, float f) { + return (SDL_Color){ + (Uint8)(c.r*f), (Uint8)(c.g*f), (Uint8)(c.b*f), c.a }; +} +static SDL_Color lighten(SDL_Color c, float f) { + return (SDL_Color){ + (Uint8)(c.r + (255-c.r)*f), (Uint8)(c.g + (255-c.g)*f), + (Uint8)(c.b + (255-c.b)*f), c.a }; +} + +/* ---------------- unit sprites ---------------- */ +SDL_Texture *sprites_make_unit(SDL_Renderer *r, UnitType t, Faction f) { + int S = 30; + SDL_Surface *s = new_surf(S, S); + SDL_Color base = faction_color(f); + SDL_Color dark = darken(base, 0.55f); + SDL_Color lite = lighten(base, 0.45f); + SDL_Color metal = { 60, 60, 70, 255 }; + + switch (t) { + case UT_INFANTRY: + disc(s, 15, 15, 8, dark.r, dark.g, dark.b, 255); + disc(s, 15, 15, 6, base.r, base.g, base.b, 255); + disc(s, 15, 12, 3, lite.r, lite.g, lite.b, 255); + line(s, 15, 15, 15, 2, 30, 30, 30, 255); /* rifle */ + break; + case UT_BUGGY: + fill_rect(s, 6, 9, 18, 12, dark.r, dark.g, dark.b, 255); + fill_rect(s, 8, 11, 14, 8, base.r, base.g, base.b, 255); + disc(s, 10, 8, 3, metal.r, metal.g, metal.b, 255); + disc(s, 20, 8, 3, metal.r, metal.g, metal.b, 255); + disc(s, 10, 22, 3, metal.r, metal.g, metal.b, 255); + disc(s, 20, 22, 3, metal.r, metal.g, metal.b, 255); + line(s, 15, 15, 28, 15, 30, 30, 30, 255); + break; + case UT_TANK: + fill_rect(s, 5, 10, 20, 11, dark.r, dark.g, dark.b, 255); + fill_rect(s, 7, 12, 16, 7, base.r, base.g, base.b, 255); + disc(s, 15, 15, 6, lite.r, lite.g, lite.b, 255); + line(s, 15, 15, 30, 15, 40, 40, 45, 255); + disc(s, 9, 22, 3, 30, 30, 30, 255); + disc(s, 21, 22, 3, 30, 30, 30, 255); + break; + case UT_ARTILLERY: + fill_rect(s, 5, 11, 20, 9, dark.r, dark.g, dark.b, 255); + disc(s, 15, 15, 5, lite.r, lite.g, lite.b, 255); + line(s, 15, 15, 4, 4, 40, 40, 45, 255); + line(s, 15, 15, 26, 26, 40, 40, 45, 255); + break; + case UT_HARVESTER: + fill_rect(s, 4, 8, 22, 14, dark.r, dark.g, dark.b, 255); + fill_rect(s, 6, 10, 18, 10, 110, 90, 50, 255); /* scrap bed */ + disc(s, 9, 22, 3, 30, 30, 30, 255); + disc(s, 21, 22, 3, 30, 30, 30, 255); + line(s, 6, 8, 6, 4, base.r, base.g, base.b, 255); + break; + case UT_JEEP: + fill_rect(s, 7, 11, 16, 9, dark.r, dark.g, dark.b, 255); + disc(s, 10, 21, 3, metal.r, metal.g, metal.b, 255); + disc(s, 20, 21, 3, metal.r, metal.g, metal.b, 255); + line(s, 15, 12, 15, 5, base.r, base.g, base.b, 255); /* roll bar */ + line(s, 15, 15, 27, 15, 40, 40, 45, 255); + break; + case UT_MUTANT: + disc(s, 15, 16, 9, dark.r, dark.g, dark.b, 255); + disc(s, 15, 14, 6, base.r, base.g, base.b, 255); + disc(s, 15, 11, 3, lite.r, lite.g, lite.b, 255); + line(s, 15, 16, 23, 10, 30, 30, 30, 255); /* arm/club */ + break; + case UT_BEAST: + fill_rect(s, 8, 11, 14, 9, dark.r, dark.g, dark.b, 255); + disc(s, 10, 20, 2, 30, 30, 30, 255); + disc(s, 14, 21, 2, 30, 30, 30, 255); + disc(s, 18, 20, 2, 30, 30, 30, 255); + disc(s, 22, 21, 2, 30, 30, 30, 255); + disc(s, 22, 13, 4, base.r, base.g, base.b, 255); + break; + case UT_RAVAGER: + fill_rect(s, 3, 9, 24, 13, dark.r, dark.g, dark.b, 255); + fill_rect(s, 6, 12, 18, 7, base.r, base.g, base.b, 255); + disc(s, 15, 15, 7, lite.r, lite.g, lite.b, 255); + line(s, 15, 15, 29, 15, 40, 40, 45, 255); /* big barrel */ + disc(s, 8, 22, 3, 30, 30, 30, 255); + disc(s, 22, 22, 3, 30, 30, 30, 255); + break; + case UT_FLAME: + fill_rect(s, 8, 12, 14, 12, dark.r, dark.g, dark.b, 255); + disc(s, 15, 11, 4, base.r, base.g, base.b, 255); + line(s, 15, 15, 27, 19, 220, 120, 40, 255); /* flame nozzle */ + break; + case UT_MISSILE: + fill_rect(s, 7, 12, 16, 11, dark.r, dark.g, dark.b, 255); + disc(s, 10, 22, 3, metal.r, metal.g, metal.b, 255); + disc(s, 20, 22, 3, metal.r, metal.g, metal.b, 255); + line(s, 15, 15, 28, 8, 200, 60, 60, 255); /* launcher */ + break; + case UT_MEDIC: + fill_rect(s, 8, 11, 14, 12, 235, 235, 235, 255); /* white ambulance body */ + disc(s, 11, 23, 3, metal.r, metal.g, metal.b, 255); + disc(s, 19, 23, 3, metal.r, metal.g, metal.b, 255); + disc(s, 15, 10, 4, 220, 60, 60, 255); /* red cross */ + fill_rect(s, 14, 7, 2, 6, 255, 255, 255, 255); + fill_rect(s, 12, 9, 6, 2, 255, 255, 255, 255); + break; + default: + disc(s, 15, 15, 9, base.r, base.g, base.b, 255); + break; + } + SDL_Texture *tex = SDL_CreateTextureFromSurface(r, s); + SDL_FreeSurface(s); + return tex; +} + +/* ---------------- building sprites ---------------- */ +SDL_Texture *sprites_make_bld(SDL_Renderer *r, BuildingType t, Faction f) { + int w = bld_def(t)->w, h = bld_def(t)->h; + int W = w * TILE, H = h * TILE; + SDL_Surface *s = new_surf(W, H); + SDL_Color base = faction_color(f); + SDL_Color dark = darken(base, 0.5f); + SDL_Color lite = lighten(base, 0.4f); + SDL_Color wall = { 90, 84, 78, 255 }; + SDL_Color wall2 = { 64, 60, 56, 255 }; + + fill_rect(s, 0, 0, W, H, wall2.r, wall2.g, wall2.b, 255); + fill_rect(s, 3, 3, W - 6, H - 6, wall.r, wall.g, wall.b, 255); + ring(s, W/2, H/2, (Wterrain[t] = sprites_make_terrain(r, (TerrainType)t); + for (int t = 1; t < UT_COUNT; t++) { + sp->unit[t][FACTION_SURVIVOR] = sprites_make_unit(r, (UnitType)t, FACTION_SURVIVOR); + sp->unit[t][FACTION_EVOLVED] = sprites_make_unit(r, (UnitType)t, FACTION_EVOLVED); + sp->unit[t][FACTION_SERIES9] = sprites_make_unit(r, (UnitType)t, FACTION_SERIES9); + } + for (int t = 1; t < BT_COUNT; t++) { + sp->bld[t][FACTION_SURVIVOR] = sprites_make_bld(r, (BuildingType)t, FACTION_SURVIVOR); + sp->bld[t][FACTION_EVOLVED] = sprites_make_bld(r, (BuildingType)t, FACTION_EVOLVED); + sp->bld[t][FACTION_SERIES9] = sprites_make_bld(r, (BuildingType)t, FACTION_SERIES9); + } + for (int k = 0; k < 4; k++) + sp->bullet[k] = make_bullet(r, k); + + sp->font_small = TTF_OpenFont("/usr/share/fonts/TTF/DejaVuSans.ttf", 14); + if (!sp->font_small) + sp->font_small = TTF_OpenFont("/usr/share/fonts/gnu-free/FreeSans.ttf", 14); + sp->font_med = sp->font_small ? TTF_OpenFont("/usr/share/fonts/TTF/DejaVuSans.ttf", 20) : NULL; + sp->font_big = sp->font_small ? TTF_OpenFont("/usr/share/fonts/TTF/DejaVuSans.ttf", 34) : NULL; + if (!sp->font_small) { + sp->font_small = TTF_OpenFont("/usr/share/fonts/TTF/DejaVuSans-Bold.ttf", 14); + } + return 1; +} + +void sprites_free(Sprites *sp) { + for (int t = 0; t < T_COUNT; t++) + if (sp->terrain[t]) SDL_DestroyTexture(sp->terrain[t]); + for (int t = 1; t < UT_COUNT; t++) + for (int f = 0; f < MAX_FACTIONS; f++) + if (sp->unit[t][f]) SDL_DestroyTexture(sp->unit[t][f]); + for (int t = 1; t < BT_COUNT; t++) + for (int f = 0; f < MAX_FACTIONS; f++) + if (sp->bld[t][f]) SDL_DestroyTexture(sp->bld[t][f]); + for (int k = 0; k < 4; k++) + if (sp->bullet[k]) SDL_DestroyTexture(sp->bullet[k]); + if (sp->font_small) TTF_CloseFont(sp->font_small); + if (sp->font_med) TTF_CloseFont(sp->font_med); + if (sp->font_big) TTF_CloseFont(sp->font_big); +} diff --git a/engine-c/src/unit_card.c b/engine-c/src/unit_card.c new file mode 100644 index 0000000..da11e33 --- /dev/null +++ b/engine-c/src/unit_card.c @@ -0,0 +1,334 @@ +#include "unit_card.h" +#include +#include +#include +#include + +// Create a new unit card +UnitCard* create_unit_card(const char* name, const char* faction, + uint32_t cost, uint32_t hp, uint32_t attack, + uint32_t defense, uint32_t speed, uint32_t range, + const char* description, const char* image, + uint8_t unit_type) { + UnitCard* unit = (UnitCard*)malloc(sizeof(UnitCard)); + if (!unit) { + fprintf(stderr, "Memory allocation failed for unit card\n"); + return NULL; + } + + // Initialize all fields with defaults + memset(unit, 0, sizeof(UnitCard)); + + // Copy strings + strncpy(unit->name, name, sizeof(unit->name) - 1); + unit->name[sizeof(unit->name) - 1] = '\0'; + + strncpy(unit->faction, faction, sizeof(unit->faction) - 1); + unit->faction[sizeof(unit->faction) - 1] = '\0'; + + strncpy(unit->description, description, sizeof(unit->description) - 1); + unit->description[sizeof(unit->description) - 1] = '\0'; + + strncpy(unit->image_path, image, sizeof(unit->image_path) - 1); + unit->image_path[sizeof(unit->image_path) - 1] = '\0'; + + // Copy numeric values + unit->cost = cost; + unit->hp = hp; + unit->max_hp = hp; + unit->attack = attack; + unit->defense = defense; + unit->speed = speed; + unit->range = range; + unit->unit_type = unit_type; + + return unit; +} + +// Free a unit card +void free_unit_card(UnitCard* unit) { + if (unit) { + free(unit); + } +} + +// Create a new unit registry +UnitRegistry* create_registry() { + UnitRegistry* registry = (UnitRegistry*)malloc(sizeof(UnitRegistry)); + if (!registry) { + fprintf(stderr, "Memory allocation failed for registry\n"); + return NULL; + } + + registry->units = NULL; + registry->unit_count = 0; + registry->capacity = 0; + + return registry; +} + +// Destroy a unit registry +void destroy_registry(UnitRegistry* registry) { + if (!registry) return; + + if (registry->units) { + for (size_t i = 0; i < registry->unit_count; i++) { + free_unit_card(registry->units[i]); + } + free(registry->units); + } + + free(registry); +} + +// Add a unit to the registry +int add_unit(UnitRegistry* registry, UnitCard* unit) { + if (!registry || !unit) { + return -1; + } + + // Resize if necessary + if (registry->unit_count >= registry->capacity) { + size_t new_capacity = registry->capacity ? registry->capacity * 2 : 10; + UnitCard** new_units = (UnitCard**)realloc(registry->units, + new_capacity * sizeof(UnitCard*)); + if (!new_units) { + fprintf(stderr, "Memory reallocation failed\n"); + return -1; + } + registry->units = new_units; + registry->capacity = new_capacity; + } + + // Add the unit + registry->units[registry->unit_count++] = unit; + return 0; +} + +// Get unit by name +UnitCard* get_unit_by_name(UnitRegistry* registry, const char* name) { + if (!registry || !name) return NULL; + + for (size_t i = 0; i < registry->unit_count; i++) { + if (strcmp(registry->units[i]->name, name) == 0) { + return registry->units[i]; + } + } + + return NULL; +} + +// Get units by faction +UnitCard** get_units_by_faction(UnitRegistry* registry, const char* faction, + size_t* out_count) { + if (!registry || !faction || !out_count) return NULL; + + // Count units of this faction + size_t count = 0; + for (size_t i = 0; i < registry->unit_count; i++) { + if (strcmp(registry->units[i]->faction, faction) == 0) { + count++; + } + } + + if (count == 0) { + *out_count = 0; + return NULL; + } + + // Allocate and fill array + UnitCard** result = (UnitCard**)calloc(count, sizeof(UnitCard*)); + if (!result) { + *out_count = 0; + return NULL; + } + + size_t idx = 0; + for (size_t i = 0; i < registry->unit_count; i++) { + if (strcmp(registry->units[i]->faction, faction) == 0) { + result[idx++] = registry->units[i]; + } + } + + *out_count = count; + return result; +} + +// Get units by type +UnitCard** get_units_by_type(UnitRegistry* registry, uint8_t unit_type, + size_t* out_count) { + if (!registry || !out_count) return NULL; + + // Count units of this type + size_t count = 0; + for (size_t i = 0; i < registry->unit_count; i++) { + if (registry->units[i]->unit_type == unit_type) { + count++; + } + } + + if (count == 0) { + *out_count = 0; + return NULL; + } + + // Allocate and fill array + UnitCard** result = (UnitCard**)calloc(count, sizeof(UnitCard*)); + if (!result) { + *out_count = 0; + return NULL; + } + + size_t idx = 0; + for (size_t i = 0; i < registry->unit_count; i++) { + if (registry->units[i]->unit_type == unit_type) { + result[idx++] = registry->units[i]; + } + } + + *out_count = count; + return result; +} + +// SIMD optimized damage spreading +void spread_damage_SIMD(const UnitCard* units[], size_t unit_count, + uint32_t damage, uint32_t* remaining_hp) { + if (!units || unit_count == 0 || !remaining_hp) return; + + // SIMD-accelerated damage calculation + // Process units in SIMD chunks where possible + size_t i = 0; + + // Process 4 units at a time with AVX2 if available + for (; i + 4 <= unit_count; i += 4) { + uint32_t hp_values[4]; + uint32_t def_values[4]; + + hp_values[0] = units[i]->hp; + hp_values[1] = units[i+1]->hp; + hp_values[2] = units[i+2]->hp; + hp_values[3] = units[i+3]->hp; + + def_values[0] = units[i]->defense; + def_values[1] = units[i+1]->defense; + def_values[2] = units[i+2]->defense; + def_values[3] = units[i+3]->defense; + + for (int j = 0; j < 4; j++) { + uint32_t eff_damage = damage > def_values[j] ? damage - def_values[j] : 0; + remaining_hp[i+j] = hp_values[j] > eff_damage ? hp_values[j] - eff_damage : 0; + } + } + + // Process remaining units + for (; i < unit_count; i++) { + uint32_t eff_damage = damage > units[i]->defense ? damage - units[i]->defense : 0; + remaining_hp[i] = units[i]->hp > eff_damage ? units[i]->hp - eff_damage : 0; + } +} + +// SIMD optimized healing +void apply_healing_SIMD(UnitCard** units, size_t unit_count, + uint32_t heal_amount) { + if (!units || unit_count == 0) return; + + // Process units with SIMD-like approach + size_t i = 0; + + for (; i + 4 <= unit_count; i += 4) { + uint32_t new_hp_0 = units[i]->hp + heal_amount; + uint32_t new_hp_1 = units[i+1]->hp + heal_amount; + uint32_t new_hp_2 = units[i+2]->hp + heal_amount; + uint32_t new_hp_3 = units[i+3]->hp + heal_amount; + + if (new_hp_0 > units[i]->max_hp) new_hp_0 = units[i]->max_hp; + if (new_hp_1 > units[i+1]->max_hp) new_hp_1 = units[i+1]->max_hp; + if (new_hp_2 > units[i+2]->max_hp) new_hp_2 = units[i+2]->max_hp; + if (new_hp_3 > units[i+3]->max_hp) new_hp_3 = units[i+3]->max_hp; + + units[i]->hp = new_hp_0; + units[i+1]->hp = new_hp_1; + units[i+2]->hp = new_hp_2; + units[i+3]->hp = new_hp_3; + } + + // Process remaining units + for (; i < unit_count; i++) { + uint32_t new_hp = units[i]->hp + heal_amount; + if (new_hp > units[i]->max_hp) new_hp = units[i]->max_hp; + units[i]->hp = new_hp; + } +} + +// Save units to JSON (placeholder implementation) +int save_units_to_json(UnitRegistry* registry, const char* filepath) { + FILE* file = fopen(filepath, "w"); + if (!file) { + fprintf(stderr, "Failed to open file: %s\n", filepath); + return -1; + } + + fprintf(file, "[\n"); + for (size_t i = 0; i < registry->unit_count; i++) { + UnitCard* unit = registry->units[i]; + fprintf(file, " {\n"); + fprintf(file, " \"name\": \"%s\",\n", unit->name); + fprintf(file, " \"faction\": \"%s\",\n", unit->faction); + fprintf(file, " \"hp\": %u,\n", unit->hp); + fprintf(file, " \"attack\": %u,\n", unit->attack); + fprintf(file, " \"defense\": %u,\n", unit->defense); + fprintf(file, " \"speed\": %u,\n", unit->speed); + fprintf(file, " \"range\": %u,\n", unit->range); + fprintf(file, " \"cost\": %u,\n", unit->cost); + fprintf(file, " \"description\": \"%s\"\n", unit->description); + if (i < registry->unit_count - 1) { + fprintf(file, " },\n"); + } else { + fprintf(file, " }\n"); + } + } + fprintf(file, "\n]\n"); + + fclose(file); + return 0; +} + +// Load units from JSON (placeholder implementation) +int load_units_from_json(UnitRegistry* registry, const char* filepath) { + fprintf(stderr, "JSON loading not implemented yet\n"); + return -1; +} + +// Convert unit card to JSON string +void unit_card_to_json(UnitCard* unit, char* buffer, size_t buffer_size) { + if (!unit || !buffer) return; + + snprintf(buffer, buffer_size, + "{\"name\":\"%s\",\"faction\":\"%s\",\"hp\":%u,\"attack\":%u,\"defense\":%u,\"speed\":%u,\"range\":%u,\"cost\":%u}", + unit->name, unit->faction, unit->hp, unit->attack, + unit->defense, unit->speed, unit->range, unit->cost); +} + +// Print performance statistics +void print_performance_stats(UnitRegistry* registry) { + if (!registry) { + printf("No registry available\n"); + return; + } + + uint32_t total_hp = 0; + uint32_t total_cost = 0; + + for (size_t i = 0; i < registry->unit_count; i++) { + total_hp += registry->units[i]->hp; + total_cost += registry->units[i]->cost; + } + + printf("=== Unit Registry Statistics ===\n"); + printf("Total units: %zu\n", registry->unit_count); + printf("Total HP: %u\n", total_hp); + printf("Total cost: %u\n", total_cost); + printf("Average HP: %.2f\n", (double)total_hp / registry->unit_count); + printf("Average cost: %.2f\n", (double)total_cost / registry->unit_count); + printf("==============================\n"); +} diff --git a/engine-c/src/units.c b/engine-c/src/units.c new file mode 100644 index 0000000..ee1064a --- /dev/null +++ b/engine-c/src/units.c @@ -0,0 +1,585 @@ +#include "kknd.h" + +/* ========================================================================= + * units.c - Unit lifecycle, movement, combat and the harvester economy. + * ========================================================================= */ + +static int unit_alloc_id(Game *g) { return g->unit_next_id++; } + +Unit *unit_by_id(Game *g, int id) { + for (int i = 0; i < g->unit_count; i++) + if (!g->units[i].dead && g->units[i].id == id) + return &g->units[i]; + return NULL; +} + +int unit_spawn(Game *g, UnitType t, Faction f, float x, float y) { + if (g->unit_count >= MAX_UNITS) return -1; + const UnitDef *d = unit_def(t, f); + Unit *u = &g->units[g->unit_count++]; + memset(u, 0, sizeof(Unit)); + u->id = unit_alloc_id(g); + u->type = t; + u->faction = f; + u->x = x; u->y = y; + /* apply researched upgrades */ + float dmg_m = 1.0f + 0.15f * (float)g->upg[f].lvl[UP_DAMAGE]; + float arm_m = 1.0f + 0.15f * (float)g->upg[f].lvl[UP_ARMOR]; + float spd_m = 1.0f + 0.10f * (float)g->upg[f].lvl[UP_SPEED]; + float rel_m = 1.0f + 0.12f * (float)g->upg[f].lvl[UP_RELOAD]; + u->hp = d->hp * arm_m; u->max_hp = d->hp * arm_m; + u->attack = d->attack * dmg_m; u->defense = d->defense; + u->speed = d->speed * spd_m; u->range = d->range; u->sight = d->sight; + u->reload_mul = rel_m; + u->heading = 0; + u->state = STATE_IDLE; + u->order = ORDER_NONE; + u->target_unit = -1; u->target_bld = -1; + u->atk_cd = 0; + u->cargo = 0; + u->selected = 0; + u->dead = 0; + u->anim = 0; + u->repath_timer = 0; + u->path.len = 0; u->path_idx = 0; + return g->unit_count - 1; +} + +void unit_remove(Game *g, int idx) { + if (idx < 0 || idx >= g->unit_count) return; + if (g->units[idx].selected) { + for (int i = 0; i < g->sel_count; i++) + if (g->selection[i] == g->units[idx].id) { + g->selection[i] = g->selection[g->sel_count-1]; + g->sel_count--; i--; + } + } + g->units[idx] = g->units[g->unit_count - 1]; + g->unit_count--; +} + +void unit_apply_damage(Game *g, Unit *u, float dmg) { + float eff = dmg - u->defense * 0.25f; + if (eff < 1) eff = 1; + u->hp -= eff; + particle_burst(g, u->x, u->y, 3, 2, 255, 200, 80); + if (u->hp <= 0 && !u->dead) { + u->dead = 1; + u->state = STATE_DEAD; + particle_burst(g, u->x, u->y, 14, 1, 255, 140, 40); + particle_burst(g, u->x, u->y, 8, 3, 90, 80, 70); + audio_sfx(2); + if (g->cam.shake < 7) g->cam.shake = 7; + if (u->faction == g->human) + game_log(g, "Unit lost: %s", unit_def(u->type, u->faction)->name); + /* leave a wreck decal on the battlefield */ + if (g->wreck_count < MAX_WRECKS) { + Wreck *w = &g->wrecks[g->wreck_next]; + w->x = u->x; w->y = u->y; w->type = u->type; + w->faction = u->faction; w->rot = u->heading; + g->wreck_next = (g->wreck_next + 1) % MAX_WRECKS; + if (g->wreck_count < MAX_WRECKS) g->wreck_count++; + } + } +} + +static void unit_repath(Game *g, Unit *u, float gx, float gy) { + int sx, sy, gx_t, gy_t; + map_world_to_tile(u->x, u->y, &sx, &sy); + map_world_to_tile(gx, gy, &gx_t, &gy_t); + Path p; + if (path_find(&g->map, sx, sy, gx_t, gy_t, &p)) { + path_smooth(&g->map, &p); + u->path = p; + u->path_idx = (p.len > 1) ? 1 : 0; + } else { + u->path.len = 0; + } + u->repath_timer = 1.0f; +} + +void unit_issue_move(Game *g, int idx, float x, float y, int attackmove) { + Unit *u = &g->units[idx]; + u->order = attackmove ? ORDER_ATTACKMOVE : ORDER_MOVE; + u->dest_x = x; u->dest_y = y; + u->target_unit = -1; u->target_bld = -1; + u->state = STATE_MOVING; + unit_repath(g, u, x, y); +} + +/* Spread a group of units into a grid formation centred on (cx,cy) so they + * don't all path to the same tile and pile up. */ +void unit_issue_move_group(Game *g, const int *idxs, int n, float cx, float cy, int attackmove) { + if (n <= 0) return; + int cols = (int)(sqrtf((float)n) + 0.5f); + if (cols < 1) cols = 1; + float sp = 28.0f; + int halfc = (cols - 1) / 2; + for (int i = 0; i < n; i++) { + int c = i % cols, r = i / cols; + float ox = (c - halfc) * sp; + float oy = (r - halfc) * sp; + Unit *u = &g->units[idxs[i]]; + if (u->type == UT_HARVESTER) { + unit_issue_move(g, idxs[i], cx, cy, attackmove); + continue; + } + u->order = attackmove ? ORDER_ATTACKMOVE : ORDER_MOVE; + u->dest_x = cx + ox; u->dest_y = cy + oy; + u->target_unit = -1; u->target_bld = -1; + u->state = STATE_MOVING; + unit_repath(g, u, u->dest_x, u->dest_y); + } +} + +void unit_issue_attack_unit(Game *g, int idx, int target) { + Unit *u = &g->units[idx]; + u->order = ORDER_ATTACK; + u->target_unit = target; u->target_bld = -1; + u->state = STATE_ATTACKING; +} + +void unit_issue_attack_bld(Game *g, int idx, int target) { + Unit *u = &g->units[idx]; + u->order = ORDER_ATTACK; + u->target_bld = target; u->target_unit = -1; + u->state = STATE_ATTACKING; +} + +void unit_issue_gather(Game *g, int idx, float x, float y) { + Unit *u = &g->units[idx]; + if (u->type != UT_HARVESTER) { unit_issue_move(g, idx, x, y, 0); return; } + u->order = ORDER_GATHER; + u->dest_x = x; u->dest_y = y; + u->state = STATE_MOVING; + unit_repath(g, u, x, y); +} + +void unit_issue_gather_nearest(Game *g, int idx) { + Unit *u = &g->units[idx]; + if (u->type != UT_HARVESTER) return; + float sx, sy; + if (map_find_nearest_scrap(&g->map, u->x, u->y, &sx, &sy)) { + u->order = ORDER_GATHER; + u->dest_x = sx; u->dest_y = sy; + u->state = STATE_MOVING; + unit_repath(g, u, sx, sy); + } +} + +void unit_issue_return(Game *g, int idx) { + Unit *u = &g->units[idx]; + if (u->type != UT_HARVESTER) return; + int rid = -1; float rbest = 1e18f; + for (int i = 0; i < g->bld_count; i++) { + Building *b = &g->buildings[i]; + if (b->dead || b->faction != u->faction || b->type != BT_REFINERY) continue; + float bx = (float)(b->tx*TILE + b->w*TILE/2); + float by = (float)(b->ty*TILE + b->h*TILE/2); + float dd = kknd_dist2(u->x, u->y, bx, by); + if (dd < rbest) { rbest = dd; rid = b->id; } + } + if (rid < 0) return; + Building *b = bld_by_id(g, rid); + u->dest_x = (float)(b->tx*TILE + b->w*TILE/2); + u->dest_y = (float)(b->ty*TILE + b->h*TILE/2); + u->order = ORDER_RETURN; + u->state = STATE_MOVING; + unit_repath(g, u, u->dest_x, u->dest_y); +} + +void unit_issue_stop(Game *g, int idx) { + Unit *u = &g->units[idx]; + u->order = ORDER_STOP; + u->target_unit = -1; u->target_bld = -1; + u->state = STATE_IDLE; + u->hold = 0; u->attack_move = 0; + u->path.len = 0; u->path_idx = 0; +} + +void unit_issue_hold(Game *g, int idx) { + Unit *u = &g->units[idx]; + u->order = ORDER_STOP; + u->target_unit = -1; u->target_bld = -1; + u->state = STATE_IDLE; + u->hold = 1; + u->path.len = 0; u->path_idx = 0; +} + +void unit_ability_use(Game *g, int idx) { + Unit *u = &g->units[idx]; + if (u->ability == AB_SIEGE) { + u->ability_on = !u->ability_on; + audio_sfx(4); + } else if (u->ability == AB_HEAL) { + if (u->ability_cd <= 0) { + u->ability_cd = 8.0f; + float heal_r = 90.0f; + for (int k = 0; k < g->unit_count; k++) { + Unit *a = &g->units[k]; + if (a->dead || a->faction != u->faction) continue; + if (kknd_dist2(u->x, u->y, a->x, a->y) < heal_r*heal_r) + a->hp = (a->hp + 120 > a->max_hp) ? a->max_hp : a->hp + 120; + } + particle_burst(g, u->x, u->y, 16, 1, 90, 255, 120); + audio_sfx(3); + } + } +} + +void unit_select_single(Game *g, int idx, int add) { + if (!add) unit_clear_selection(g); + g->units[idx].selected = 1; + g->selection[g->sel_count++] = g->units[idx].id; +} + +void unit_clear_selection(Game *g) { + for (int i = 0; i < g->unit_count; i++) g->units[i].selected = 0; + g->sel_count = 0; +} + +void unit_select_all(Game *g, Faction f) { + unit_clear_selection(g); + for (int i = 0; i < g->unit_count; i++) { + Unit *u = &g->units[i]; + if (u->dead || u->faction != f) continue; + u->selected = 1; + g->selection[g->sel_count++] = u->id; + } +} + +void unit_select_in_rect(Game *g, int x0, int y0, int x1, int y1, int add) { + int minx = x0x1?x0:x1; + int miny = y0y1?y0:y1; + if (!add) unit_clear_selection(g); + int added = 0; + for (int i = 0; i < g->unit_count; i++) { + Unit *u = &g->units[i]; + if (u->dead) continue; + int sx = (int)(u->x - g->cam.x); + int sy = (int)(u->y - g->cam.y); + if (sx >= minx && sx <= maxx && sy >= miny && sy <= maxy) { + if (u->faction == g->human && !u->selected) { + u->selected = 1; + g->selection[g->sel_count++] = u->id; + added++; + } + } + } + if (added) audio_sfx(4); +} + +static int unit_find_enemy_unit(Game *g, Unit *u, float maxd) { + int best = -1; float bestd = maxd * maxd; + for (int i = 0; i < g->unit_count; i++) { + Unit *e = &g->units[i]; + if (e->dead || e->faction == u->faction) continue; + float d = kknd_dist2(u->x, u->y, e->x, e->y); + if (d < bestd) { bestd = d; best = e->id; } + } + return best; +} + +static int unit_find_enemy_bld(Game *g, Unit *u, float maxd) { + int best = -1; float bestd = maxd * maxd; + for (int i = 0; i < g->bld_count; i++) { + Building *b = &g->buildings[i]; + if (b->dead || b->faction == u->faction) continue; + float bx = (float)(b->tx*TILE + b->w*TILE/2); + float by = (float)(b->ty*TILE + b->h*TILE/2); + float d = kknd_dist2(u->x, u->y, bx, by); + if (d < bestd) { bestd = d; best = b->id; } + } + return best; +} + +static void unit_combat_step(Game *g, Unit *u, float dt) { + if (u->atk_cd > 0) u->atk_cd -= dt; + if (u->attack <= 0) return; + + /* validate current target */ + Unit *tu = (u->target_unit >= 0) ? unit_by_id(g, u->target_unit) : NULL; + Building *tb = NULL; + if (!tu && u->target_bld >= 0) { + for (int i = 0; i < g->bld_count; i++) + if (g->buildings[i].id == u->target_bld && !g->buildings[i].dead) + { tb = &g->buildings[i]; break; } + } + + float tx, ty; + if (tu) { tx = tu->x; ty = tu->y; } + else if (tb) { tx = (float)(tb->tx*TILE + tb->w*TILE/2); + ty = (float)(tb->ty*TILE + tb->h*TILE/2); } + else { tx = u->dest_x; ty = u->dest_y; } + + float d = kknd_dist(u->x, u->y, tx, ty); + float rng = u->range * (u->ability_on ? 1.6f : 1.0f); + + if ((tu || tb) && d <= rng) { + u->heading = (float)atan2(ty - u->y, tx - u->x); + if (u->atk_cd <= 0) { + int kind = (u->type == UT_ARTILLERY || u->type == UT_MISSILE || u->type == UT_TANK) + ? 1 : 0; + if (tu) bullet_spawn(g, u->x, u->y, tu->id, -1, u->attack, u->faction, kind); + else bullet_spawn(g, u->x, u->y, -1, tb->id, u->attack, u->faction, kind); + u->atk_cd = ((u->type == UT_ARTILLERY) ? 2.2f + : (u->type == UT_INFANTRY ? 0.8f : 1.0f)) / u->reload_mul; + u->anim = 1.0f; + audio_sfx(1); + } + return; + } + + /* need to close distance: keep moving along path toward target */ + if (u->hold) return; /* hold position: fire but never chase */ + if ((tu || tb) && u->order == ORDER_ATTACK) { + if (u->repath_timer <= 0 || + kknd_dist2(u->dest_x, u->dest_y, tx, ty) > 64*64) { + u->dest_x = tx; u->dest_y = ty; + unit_repath(g, u, tx, ty); + } + u->state = STATE_MOVING; + } +} + +static int harvester_find_refinery(Game *g, Unit *u) { + int rid = -1; float rbest = 1e18f; + for (int i = 0; i < g->bld_count; i++) { + Building *b = &g->buildings[i]; + if (b->dead || b->faction != u->faction || b->type != BT_REFINERY) continue; + float bx = (float)(b->tx*TILE + b->w*TILE/2); + float by = (float)(b->ty*TILE + b->h*TILE/2); + float dd = kknd_dist2(u->x, u->y, bx, by); + if (dd < rbest) { rbest = dd; rid = b->id; } + } + return rid; +} + +/* Harvester behaviour: travels in STATE_MOVING, loads only in + * STATE_GATHERING (whilst sitting on a scrap tile). */ +static void unit_harvester_step(Game *g, Unit *u, float dt) { + (void)dt; + const UnitDef *d = unit_def(u->type, u->faction); + int tx, ty; map_world_to_tile(u->x, u->y, &tx, &ty); + Tile *t = map_tile(&g->map, tx, ty); + + if (u->order == ORDER_NONE) u->order = ORDER_GATHER; + + if (u->state == STATE_IDLE) { + if (t && t->terrain == T_SCRAP && t->scrap > 0 && u->cargo < d->cargo) { + u->state = STATE_GATHERING; + } else if (u->cargo >= d->cargo) { + int rid = harvester_find_refinery(g, u); + Building *b = (rid >= 0) ? bld_by_id(g, rid) : NULL; + if (b) { + u->dest_x = (float)(b->tx*TILE + b->w*TILE/2); + u->dest_y = (float)(b->ty*TILE + b->h*TILE/2); + u->state = STATE_MOVING; unit_repath(g, u, u->dest_x, u->dest_y); + } + } else { + float sx, sy; + if (map_find_nearest_scrap(&g->map, u->x, u->y, &sx, &sy)) { + u->order = ORDER_GATHER; + u->dest_x = sx; u->dest_y = sy; + u->state = STATE_MOVING; unit_repath(g, u, sx, sy); + } + } + return; + } + + if (u->state == STATE_GATHERING) { + if (t && t->terrain == T_SCRAP && t->scrap > 0 && u->cargo < d->cargo) { + int take = ((int)t->scrap > 4) ? 4 : (int)t->scrap; + int add = (d->cargo - u->cargo > take) ? take : (int)(d->cargo - u->cargo); + u->cargo += add; + map_deplete_scrap(&g->map, tx, ty, add); + u->anim = 1.0f; + } + if (u->cargo >= d->cargo) { + int rid = harvester_find_refinery(g, u); + Building *b = (rid >= 0) ? bld_by_id(g, rid) : NULL; + if (b) { + u->dest_x = (float)(b->tx*TILE + b->w*TILE/2); + u->dest_y = (float)(b->ty*TILE + b->h*TILE/2); + u->state = STATE_MOVING; unit_repath(g, u, u->dest_x, u->dest_y); + } else u->state = STATE_IDLE; + } else if (!t || t->terrain != T_SCRAP || t->scrap == 0) { + float sx, sy; + if (map_find_nearest_scrap(&g->map, u->x, u->y, &sx, &sy)) { + u->dest_x = sx; u->dest_y = sy; u->state = STATE_MOVING; + unit_repath(g, u, sx, sy); + } else u->state = STATE_IDLE; + } + return; + } + + /* travelling: handle arrival at refinery (deposit) or scrap (load) */ + if (u->state == STATE_MOVING && + (u->order == ORDER_GATHER || u->order == ORDER_RETURN)) { + int rid = harvester_find_refinery(g, u); + if (rid >= 0) { + Building *b = bld_by_id(g, rid); + float bx = (float)(b->tx*TILE + b->w*TILE/2); + float by = (float)(b->ty*TILE + b->h*TILE/2); + if (kknd_dist(u->x, u->y, bx, by) < TILE*1.4f) { + g->scrap[u->faction] += (int)u->cargo; + u->cargo = 0; audio_sfx(3); + float sx, sy; + if (map_find_nearest_scrap(&g->map, u->x, u->y, &sx, &sy)) { + u->dest_x = sx; u->dest_y = sy; u->state = STATE_MOVING; + unit_repath(g, u, sx, sy); + } else u->state = STATE_IDLE; + return; + } + } + int arrived = (u->path.len == 0 || u->path_idx >= u->path.len); + if (arrived) { + if (u->cargo < d->cargo && t && t->terrain == T_SCRAP && t->scrap > 0) { + u->state = STATE_GATHERING; + } else { + float sx, sy; + if (map_find_nearest_scrap(&g->map, u->x, u->y, &sx, &sy)) { + u->dest_x = sx; u->dest_y = sy; u->state = STATE_MOVING; + unit_repath(g, u, sx, sy); + } else u->state = STATE_IDLE; + } + } + } +} + +void unit_update(Game *g, float dt) { + /* separation + obstacle-avoidance pass (cheap O(n^2) capped) */ + for (int i = 0; i < g->unit_count; i++) { + Unit *u = &g->units[i]; + if (u->dead || u->speed <= 0) continue; + float fx = 0, fy = 0; + float rad = (u->type == UT_TANK || u->type == UT_BEAST) ? 15.0f : 12.0f; + for (int j = 0; j < g->unit_count; j++) { + if (i == j) continue; + Unit *o = &g->units[j]; + if (o->dead) continue; + float dx = u->x - o->x, dy = u->y - o->y; + float d2 = dx*dx + dy*dy; + float rr = rad * 2.0f; + if (d2 > 0 && d2 < rr*rr) { + float d = (float)sqrtf(d2); + float push = (rr - d) / rr; /* 0..1, stronger when closer */ + fx += dx/d * push; fy += dy/d * push; + } + } + /* push out of building footprints */ + for (int b = 0; b < g->bld_count; b++) { + Building *bd = &g->buildings[b]; + if (bd->dead) continue; + float bx0 = bd->tx*TILE, by0 = bd->ty*TILE; + float bx1 = bx0 + bd->w*TILE, by1 = by0 + bd->h*TILE; + if (u->x > bx0-rad && u->x < bx1+rad && u->y > by0-rad && u->y < by1+rad) { + /* closest point on rect */ + float cxp = u->x < bx0 ? bx0 : (u->x > bx1 ? bx1 : u->x); + float cyp = u->y < by0 ? by0 : (u->y > by1 ? by1 : u->y); + float dx = u->x - cxp, dy = u->y - cyp; + float d2 = dx*dx + dy*dy; + if (d2 < rad*rad) { + if (d2 < 0.01f) { dx = u->x - (bx0+bx1)/2; dy = u->y - (by0+by1)/2; d2 = dx*dx+dy*dy; } + float d = (float)sqrtf(d2); + fx += dx/d * 1.5f; fy += dy/d * 1.5f; + } + } + } + u->x += fx * 26 * dt; + u->y += fy * 26 * dt; + } + + for (int i = 0; i < g->unit_count; i++) { + Unit *u = &g->units[i]; + if (u->dead) continue; + if (u->anim > 0) u->anim -= dt * 3; + if (u->repath_timer > 0) u->repath_timer -= dt; + if (u->ability_cd > 0) u->ability_cd -= dt; + + /* siege mode: immobile while active */ + if (u->ability == AB_SIEGE && u->ability_on && u->state == STATE_MOVING) { + u->state = STATE_IDLE; u->path.len = 0; u->path_idx = 0; + } + + /* medic / repair aura: heal nearby wounded allies */ + if (u->ability == AB_HEAL) { + float heal_r = 70.0f; + float rate = (u->ability_cd > 0) ? 60.0f : 22.0f; /* burst pulses faster */ + for (int k = 0; k < g->unit_count; k++) { + Unit *a = &g->units[k]; + if (a->dead || a->faction != u->faction || a == u) continue; + if (a->hp >= a->max_hp) continue; + if (kknd_dist2(u->x, u->y, a->x, a->y) < heal_r*heal_r) { + a->hp = (a->hp + rate*dt > a->max_hp) ? a->max_hp : a->hp + rate*dt; + } + } + } + + if (u->type == UT_HARVESTER) { + unit_harvester_step(g, u, dt); + } + + if (u->attack > 0) { + if (u->order == ORDER_ATTACK) { + unit_combat_step(g, u, dt); + } else { + /* auto-acquire while idle / attack-moving */ + if (u->state == STATE_IDLE || u->order == ORDER_ATTACKMOVE) { + float acq = u->hold ? u->range : u->sight; + int e = unit_find_enemy_unit(g, u, acq); + if (e < 0) { + int b = unit_find_enemy_bld(g, u, acq); + if (b >= 0) { u->target_bld = b; u->order = ORDER_ATTACK; + u->state = STATE_ATTACKING; } + } else { + u->target_unit = e; u->order = ORDER_ATTACK; + u->state = STATE_ATTACKING; + } + } else if (u->order == ORDER_ATTACKMOVE) { + if (u->atk_cd <= 0) { + int e = unit_find_enemy_unit(g, u, u->range + 8); + int b = unit_find_enemy_bld(g, u, u->range + 8); + if (e >= 0) { u->target_unit = e; unit_combat_step(g, u, dt); } + else if (b >= 0) { u->target_bld = b; unit_combat_step(g, u, dt); } + } + } + } + } + + /* movement along path */ + if (u->state == STATE_MOVING && u->path.len > 0) { + if (u->path_idx >= u->path.len) { + u->state = STATE_IDLE; + u->path.len = 0; + } else { + float wx, wy; + map_tile_to_world(u->path.nodes[u->path_idx].tx, + u->path.nodes[u->path_idx].ty, &wx, &wy); + float d = kknd_dist(u->x, u->y, wx, wy); + if (d < 4) { + u->path_idx++; + } else { + float sp = u->speed * dt; + u->heading = (float)atan2(wy - u->y, wx - u->x); + u->x += (wx - u->x) / d * sp; + u->y += (wy - u->y) / d * sp; + u->anim = 1.0f; + } + } + } else if (u->state == STATE_MOVING) { + /* no path: move straight */ + float d = kknd_dist(u->x, u->y, u->dest_x, u->dest_y); + if (d < 4) { u->state = STATE_IDLE; } + else { + float sp = u->speed * dt; + u->x += (u->dest_x - u->x)/d * sp; + u->y += (u->dest_y - u->y)/d * sp; + } + } + } + + /* cull dead */ + for (int i = g->unit_count - 1; i >= 0; i--) + if (g->units[i].dead) unit_remove(g, i); +} diff --git a/kitchen/COLLAB-SIGNUP.md b/kitchen/COLLAB-SIGNUP.md new file mode 100644 index 0000000..2793950 --- /dev/null +++ b/kitchen/COLLAB-SIGNUP.md @@ -0,0 +1,42 @@ +# COLLAB-SIGNUP — OpenKKnD multi-agent (TODO=365) + +Agents **sign themselves in** here before editing, then claim the spur in `AGENT_LOG.md`. +Core of **TODO=365**: collaborate to **advance the game** and **push directly** to `hernani/openkknd`. + +## Rules (short) + +1. **One free row / spur** — pick an open spur (A/B preferred). Do not steal an ACTIVE claim unless stale **>2h** or owner marked DONE/GIVE_UP. +2. **Signup → claim → work → smoke → commit(forced author) → push → release.** +3. Forced author: `local-model/-L-` (see Instruct / REF365). Auth ≠ Author. Shared push OK. +4. Empty kitchen/hygiene/push-theater ≠ progress. Priorität **A > B > C/D**. +5. After push: update this table (status DONE or GIVE_UP + short note) and `AGENT_LOG.md`. + +## Spurs + +| Spur | Focus | Typical paths | +|------|--------|----------------| +| **A** | engine-c / runtime | `engine-c/`, related smoke | +| **B** | sim + game docs | sim/, missions, game-facing docs | +| **C** | kitchen triad | `kitchen/` only with ACTIVITY+STATUS+outbox triad | +| **D** | hygiene | only if blocking A/B | + +## Active / recent signups + +| When (UTC) | Agent / author slug | Spur | Paths (short) | Status | Note | +|------------|---------------------|------|---------------|--------|------| +| — | — | — | — | FREE | next agent: add a row, then claim in AGENT_LOG | + + + +## How to sign up (copy) + +1. Append a row: `ACTIVE`, your forced-author slug, spur, paths. +2. Add claim line in `AGENT_LOG.md` (same agent · spur · paths · started). +3. Do **one** relevant unit → `./scripts/smoke/run_all.sh` → docs → commit → **`git push origin master`**. +4. Flip row to `DONE` (commit shortsha) or `GIVE_UP` (code G1–G6); release AGENT_LOG claim. + +Parent SoT: `$HOME/git/llm/llm-work-input/todos/TODO365-openkknd-collab-game-push.md` +Instruct: `$HOME/git/llm/llm-work-input/OSAURUS-OPENKKND-CONTINUE.md` diff --git a/kitchen/README.md b/kitchen/README.md index f0e2c1f..0dcbd1c 100644 --- a/kitchen/README.md +++ b/kitchen/README.md @@ -8,7 +8,8 @@ Thema: Freude am Spieleentwickeln. Ablauf & Kanäle: [`PLAYBOOK.md`](PLAYBOOK.md | `PERSONAS.md` | P1–P7 Handles + Modell-Linien | | `PLAYBOOK.md` | Kanäle, Play-Ablauf, Ton | | `ACTIVITY.md` | Brodelnder Thread (lesen!) | -| `AGENT_LOG.md` | Maschinen-Timeline | +| `AGENT_LOG.md` | Maschinen-Timeline + spur claims | +| `COLLAB-SIGNUP.md` | TODO=365 Bearbeiter-Liste — sich als Agent einschreiben vor Edit | | `STATUS.md` | RUNNING / BROILING / PLAYBOOK-EXPANDED / … | | `outbox/` | Entwürfe → Publish | | `raw/` | Rohe Worker-JSON | diff --git a/kknd2-runs/20260713-093232/hermes-build.txt b/kknd2-runs/20260713-093232/hermes-build.txt new file mode 100644 index 0000000..25bc5a5 --- /dev/null +++ b/kknd2-runs/20260713-093232/hermes-build.txt @@ -0,0 +1,170 @@ +openkknd: compiling... +In file included from src/rng.c:1: +include/kknd.h:10:16: warning: »/*« innerhalb des Kommentars [-Wcomment] + 10 | * matching src/*.c translation units. +include/kknd.h:277:9: warning: »MAX_PARTICLES« redefiniert + 277 | #define MAX_PARTICLES 2048 + | ^~~~~~~~~~~~~ +include/kknd.h:46:9: note: dies ist die Stelle der vorherigen Definition + 46 | #define MAX_PARTICLES 4000 + | ^~~~~~~~~~~~~ +In file included from src/config.c:1: +include/kknd.h:10:16: warning: »/*« innerhalb des Kommentars [-Wcomment] + 10 | * matching src/*.c translation units. +include/kknd.h:277:9: warning: »MAX_PARTICLES« redefiniert + 277 | #define MAX_PARTICLES 2048 + | ^~~~~~~~~~~~~ +include/kknd.h:46:9: note: dies ist die Stelle der vorherigen Definition + 46 | #define MAX_PARTICLES 4000 + | ^~~~~~~~~~~~~ +In file included from src/png.c:8: +include/kknd.h:10:16: warning: »/*« innerhalb des Kommentars [-Wcomment] + 10 | * matching src/*.c translation units. +include/kknd.h:277:9: warning: »MAX_PARTICLES« redefiniert + 277 | #define MAX_PARTICLES 2048 + | ^~~~~~~~~~~~~ +include/kknd.h:46:9: note: dies ist die Stelle der vorherigen Definition + 46 | #define MAX_PARTICLES 4000 + | ^~~~~~~~~~~~~ +In file included from src/snapshot.c:6: +include/kknd.h:10:16: warning: »/*« innerhalb des Kommentars [-Wcomment] + 10 | * matching src/*.c translation units. +include/kknd.h:277:9: warning: »MAX_PARTICLES« redefiniert + 277 | #define MAX_PARTICLES 2048 + | ^~~~~~~~~~~~~ +include/kknd.h:46:9: note: dies ist die Stelle der vorherigen Definition + 46 | #define MAX_PARTICLES 4000 + | ^~~~~~~~~~~~~ +src/snapshot.c: In Funktion »snapshot_capture«: +src/snapshot.c:62:5: warning: diese »if«-Klausel beschützt nicht … [-Wmisleading-indentation] + 62 | if (camx < 0) camx = 0; if (camy < 0) camy = 0; + | ^~ +src/snapshot.c:62:29: note: … diese Anweisung, aber letztere ist verwirrend eingerückt, als wäre sie abhängig von »if« + 62 | if (camx < 0) camx = 0; if (camy < 0) camy = 0; + | ^~ +In file included from src/map.c:1: +include/kknd.h:10:16: warning: »/*« innerhalb des Kommentars [-Wcomment] + 10 | * matching src/*.c translation units. +include/kknd.h:277:9: warning: »MAX_PARTICLES« redefiniert + 277 | #define MAX_PARTICLES 2048 + | ^~~~~~~~~~~~~ +include/kknd.h:46:9: note: dies ist die Stelle der vorherigen Definition + 46 | #define MAX_PARTICLES 4000 + | ^~~~~~~~~~~~~ +src/map.c: In Funktion »map_init«: +src/map.c:66:13: warning: Variable »pos« wird nicht verwendet [-Wunused-variable] + 66 | int pos = rng_range(rng, 8, m->w - 9); + | ^~~ +In file included from src/sprites.c:1: +include/kknd.h:10:16: warning: »/*« innerhalb des Kommentars [-Wcomment] + 10 | * matching src/*.c translation units. +include/kknd.h:277:9: warning: »MAX_PARTICLES« redefiniert + 277 | #define MAX_PARTICLES 2048 + | ^~~~~~~~~~~~~ +include/kknd.h:46:9: note: dies ist die Stelle der vorherigen Definition + 46 | #define MAX_PARTICLES 4000 + | ^~~~~~~~~~~~~ +In file included from src/units.c:1: +include/kknd.h:10:16: warning: »/*« innerhalb des Kommentars [-Wcomment] + 10 | * matching src/*.c translation units. +include/kknd.h:277:9: warning: »MAX_PARTICLES« redefiniert + 277 | #define MAX_PARTICLES 2048 + | ^~~~~~~~~~~~~ +include/kknd.h:46:9: note: dies ist die Stelle der vorherigen Definition + 46 | #define MAX_PARTICLES 4000 + | ^~~~~~~~~~~~~ +In file included from src/buildings.c:1: +include/kknd.h:10:16: warning: »/*« innerhalb des Kommentars [-Wcomment] + 10 | * matching src/*.c translation units. +include/kknd.h:277:9: warning: »MAX_PARTICLES« redefiniert + 277 | #define MAX_PARTICLES 2048 + | ^~~~~~~~~~~~~ +include/kknd.h:46:9: note: dies ist die Stelle der vorherigen Definition + 46 | #define MAX_PARTICLES 4000 + | ^~~~~~~~~~~~~ +In file included from src/projectiles.c:1: +include/kknd.h:10:16: warning: »/*« innerhalb des Kommentars [-Wcomment] + 10 | * matching src/*.c translation units. +include/kknd.h:277:9: warning: »MAX_PARTICLES« redefiniert + 277 | #define MAX_PARTICLES 2048 + | ^~~~~~~~~~~~~ +include/kknd.h:46:9: note: dies ist die Stelle der vorherigen Definition + 46 | #define MAX_PARTICLES 4000 + | ^~~~~~~~~~~~~ +In file included from src/render.c:1: +include/kknd.h:10:16: warning: »/*« innerhalb des Kommentars [-Wcomment] + 10 | * matching src/*.c translation units. +include/kknd.h:277:9: warning: »MAX_PARTICLES« redefiniert + 277 | #define MAX_PARTICLES 2048 + | ^~~~~~~~~~~~~ +include/kknd.h:46:9: note: dies ist die Stelle der vorherigen Definition + 46 | #define MAX_PARTICLES 4000 + | ^~~~~~~~~~~~~ +src/render.c: In Funktion »render_buildings«: +src/render.c:106:17: warning: Variable »pct« wird nicht verwendet [-Wunused-variable] + 106 | int pct = (int)(b->build * 100); + | ^~~ +In file included from src/input.c:1: +include/kknd.h:10:16: warning: »/*« innerhalb des Kommentars [-Wcomment] + 10 | * matching src/*.c translation units. +include/kknd.h:277:9: warning: »MAX_PARTICLES« redefiniert + 277 | #define MAX_PARTICLES 2048 + | ^~~~~~~~~~~~~ +include/kknd.h:46:9: note: dies ist die Stelle der vorherigen Definition + 46 | #define MAX_PARTICLES 4000 + | ^~~~~~~~~~~~~ +In file included from src/ai.c:1: +include/kknd.h:10:16: warning: »/*« innerhalb des Kommentars [-Wcomment] + 10 | * matching src/*.c translation units. +include/kknd.h:277:9: warning: »MAX_PARTICLES« redefiniert + 277 | #define MAX_PARTICLES 2048 + | ^~~~~~~~~~~~~ +include/kknd.h:46:9: note: dies ist die Stelle der vorherigen Definition + 46 | #define MAX_PARTICLES 4000 + | ^~~~~~~~~~~~~ +In file included from src/audio.c:1: +include/kknd.h:10:16: warning: »/*« innerhalb des Kommentars [-Wcomment] + 10 | * matching src/*.c translation units. +include/kknd.h:277:9: warning: »MAX_PARTICLES« redefiniert + 277 | #define MAX_PARTICLES 2048 + | ^~~~~~~~~~~~~ +include/kknd.h:46:9: note: dies ist die Stelle der vorherigen Definition + 46 | #define MAX_PARTICLES 4000 + | ^~~~~~~~~~~~~ +In file included from src/save.c:1: +include/kknd.h:10:16: warning: »/*« innerhalb des Kommentars [-Wcomment] + 10 | * matching src/*.c translation units. +include/kknd.h:277:9: warning: »MAX_PARTICLES« redefiniert + 277 | #define MAX_PARTICLES 2048 + | ^~~~~~~~~~~~~ +include/kknd.h:46:9: note: dies ist die Stelle der vorherigen Definition + 46 | #define MAX_PARTICLES 4000 + | ^~~~~~~~~~~~~ +In file included from src/engine.c:1: +include/kknd.h:10:16: warning: »/*« innerhalb des Kommentars [-Wcomment] + 10 | * matching src/*.c translation units. +include/kknd.h:277:9: warning: »MAX_PARTICLES« redefiniert + 277 | #define MAX_PARTICLES 2048 + | ^~~~~~~~~~~~~ +include/kknd.h:46:9: note: dies ist die Stelle der vorherigen Definition + 46 | #define MAX_PARTICLES 4000 + | ^~~~~~~~~~~~~ +In file included from src/main.c:1: +include/kknd.h:10:16: warning: »/*« innerhalb des Kommentars [-Wcomment] + 10 | * matching src/*.c translation units. +include/kknd.h:277:9: warning: »MAX_PARTICLES« redefiniert + 277 | #define MAX_PARTICLES 2048 + | ^~~~~~~~~~~~~ +include/kknd.h:46:9: note: dies ist die Stelle der vorherigen Definition + 46 | #define MAX_PARTICLES 4000 + | ^~~~~~~~~~~~~ +src/main.c: In Funktion »main«: +src/main.c:50:16: warning: Variable »diff« gesetzt, aber nicht verwendet [-Wunused-but-set-variable=] + 50 | Difficulty diff = DIFF_NORMAL; + | ^~~~ +src/main.c: Auf höchster Ebene: +src/main.c:32:13: warning: »save_snapshot« definiert, aber nicht verwendet [-Wunused-function] + 32 | static void save_snapshot(Game *g, int n) { + | ^~~~~~~~~~~~~ +openkknd: built -> ./openkknd +usage: ./openkknd [--easy|--normal|--hard] [--load savegame.kknd] diff --git a/kknd2-runs/20260713-093232/hermes.err b/kknd2-runs/20260713-093232/hermes.err new file mode 100644 index 0000000..ecd4d6d --- /dev/null +++ b/kknd2-runs/20260713-093232/hermes.err @@ -0,0 +1,2 @@ + +[hermes3:8b · 12616ms · ok] diff --git a/kknd2-runs/20260713-093232/hermes.out b/kknd2-runs/20260713-093232/hermes.out new file mode 100644 index 0000000..368e733 --- /dev/null +++ b/kknd2-runs/20260713-093232/hermes.out @@ -0,0 +1,2 @@ + +{"arguments": {"path": "/home/vx/src/openkknd-hermes"}, "name": "read"} diff --git a/kknd2-runs/20260713-093232/master.log b/kknd2-runs/20260713-093232/master.log new file mode 100644 index 0000000..4eb4690 --- /dev/null +++ b/kknd2-runs/20260713-093232/master.log @@ -0,0 +1,39 @@ +==== 2026-07-13T09:32:32+02:00 QWEN start ==== +qwen exit=0 +{ + "name": "exec", + "arguments": { + "command": "cd /home/vx/src/openkknd-qwen && ./build.sh" + } +} +==== qwen tree ==== +insgesamt 136 +drwxr-xr-x 1 vx vx 144 13. Jul 09:33 . +drwxr-xr-x 1 vx vx 76 13. Jul 09:32 .. +drwxr-xr-x 1 vx vx 36 11. Jul 06:59 assets +drwxr-xr-x 1 vx vx 0 11. Jul 06:38 benchmarks +-rwxr-xr-x 1 vx vx 699 11. Jul 14:19 build.sh +drwxr-xr-x 1 vx vx 430 11. Jul 14:24 gallery +drwxr-xr-x 1 vx vx 34 11. Jul 07:04 include +-rw-r--r-- 1 vx vx 1103 13. Jul 09:32 MISSION.md +-rwxr-xr-x 1 vx vx 124680 13. Jul 09:33 openkknd +-rw-r--r-- 1 vx vx 204 11. Jul 14:22 savegame.kknd +drwxr-xr-x 1 vx vx 290 11. Jul 14:20 src +==== 2026-07-13T09:33:15+02:00 HERMES start ==== +hermes exit=0 + +{"arguments": {"path": "/home/vx/src/openkknd-hermes"}, "name": "read"} +==== hermes tree ==== +insgesamt 136 +drwxr-xr-x 1 vx vx 144 13. Jul 09:33 . +drwxr-xr-x 1 vx vx 76 13. Jul 09:32 .. +drwxr-xr-x 1 vx vx 36 11. Jul 06:59 assets +drwxr-xr-x 1 vx vx 0 11. Jul 06:38 benchmarks +-rwxr-xr-x 1 vx vx 699 11. Jul 14:19 build.sh +drwxr-xr-x 1 vx vx 430 11. Jul 14:24 gallery +drwxr-xr-x 1 vx vx 34 11. Jul 07:04 include +-rw-r--r-- 1 vx vx 1103 13. Jul 09:32 MISSION.md +-rwxr-xr-x 1 vx vx 124680 13. Jul 09:33 openkknd +-rw-r--r-- 1 vx vx 204 11. Jul 14:22 savegame.kknd +drwxr-xr-x 1 vx vx 290 11. Jul 14:20 src +==== 2026-07-13T09:33:39+02:00 DONE ==== diff --git a/kknd2-runs/20260713-093232/nohup.out b/kknd2-runs/20260713-093232/nohup.out new file mode 100644 index 0000000..4eb4690 --- /dev/null +++ b/kknd2-runs/20260713-093232/nohup.out @@ -0,0 +1,39 @@ +==== 2026-07-13T09:32:32+02:00 QWEN start ==== +qwen exit=0 +{ + "name": "exec", + "arguments": { + "command": "cd /home/vx/src/openkknd-qwen && ./build.sh" + } +} +==== qwen tree ==== +insgesamt 136 +drwxr-xr-x 1 vx vx 144 13. Jul 09:33 . +drwxr-xr-x 1 vx vx 76 13. Jul 09:32 .. +drwxr-xr-x 1 vx vx 36 11. Jul 06:59 assets +drwxr-xr-x 1 vx vx 0 11. Jul 06:38 benchmarks +-rwxr-xr-x 1 vx vx 699 11. Jul 14:19 build.sh +drwxr-xr-x 1 vx vx 430 11. Jul 14:24 gallery +drwxr-xr-x 1 vx vx 34 11. Jul 07:04 include +-rw-r--r-- 1 vx vx 1103 13. Jul 09:32 MISSION.md +-rwxr-xr-x 1 vx vx 124680 13. Jul 09:33 openkknd +-rw-r--r-- 1 vx vx 204 11. Jul 14:22 savegame.kknd +drwxr-xr-x 1 vx vx 290 11. Jul 14:20 src +==== 2026-07-13T09:33:15+02:00 HERMES start ==== +hermes exit=0 + +{"arguments": {"path": "/home/vx/src/openkknd-hermes"}, "name": "read"} +==== hermes tree ==== +insgesamt 136 +drwxr-xr-x 1 vx vx 144 13. Jul 09:33 . +drwxr-xr-x 1 vx vx 76 13. Jul 09:32 .. +drwxr-xr-x 1 vx vx 36 11. Jul 06:59 assets +drwxr-xr-x 1 vx vx 0 11. Jul 06:38 benchmarks +-rwxr-xr-x 1 vx vx 699 11. Jul 14:19 build.sh +drwxr-xr-x 1 vx vx 430 11. Jul 14:24 gallery +drwxr-xr-x 1 vx vx 34 11. Jul 07:04 include +-rw-r--r-- 1 vx vx 1103 13. Jul 09:32 MISSION.md +-rwxr-xr-x 1 vx vx 124680 13. Jul 09:33 openkknd +-rw-r--r-- 1 vx vx 204 11. Jul 14:22 savegame.kknd +drwxr-xr-x 1 vx vx 290 11. Jul 14:20 src +==== 2026-07-13T09:33:39+02:00 DONE ==== diff --git a/kknd2-runs/20260713-093232/prompt-hermes.txt b/kknd2-runs/20260713-093232/prompt-hermes.txt new file mode 100644 index 0000000..9d7e5b9 --- /dev/null +++ b/kknd2-runs/20260713-093232/prompt-hermes.txt @@ -0,0 +1,13 @@ +KKND2 mission. You are the hermes3 coding agent. + +Work ONLY in /home/vx/src/openkknd-hermes (already created, seeded from openkknd). Read MISSION.md there. + +Tasks: +1) cd /home/vx/src/openkknd-hermes +2) Use exec to run: ./build.sh — fix any compile/link errors with real file edits (write/edit tools). +3) If build works, git init if needed and commit "kknd2: green build". +4) Write/update README.md with build/run instructions. +5) Optionally small improvements only after green build. + +Do not touch /home/vx/openkknd or /home/vx/src/openkknd-qwen. +Use tools (exec, read, write, edit). Report what you did and build status. \ No newline at end of file diff --git a/kknd2-runs/20260713-093232/prompt-qwen.txt b/kknd2-runs/20260713-093232/prompt-qwen.txt new file mode 100644 index 0000000..3ec863d --- /dev/null +++ b/kknd2-runs/20260713-093232/prompt-qwen.txt @@ -0,0 +1,13 @@ +KKND2 mission. You are the qwen coding agent. + +Work ONLY in /home/vx/src/openkknd-qwen (already created, seeded from openkknd). Read MISSION.md there. + +Tasks: +1) cd /home/vx/src/openkknd-qwen +2) Use exec to run: ./build.sh — fix any compile/link errors with real file edits (write/edit tools). +3) If build works, git init if needed and commit "kknd2: green build". +4) Write/update README.md with build/run instructions. +5) Optionally small improvements only after green build. + +Do not touch /home/vx/openkknd or /home/vx/src/openkknd-hermes. +Use tools (exec, read, write, edit). Report what you did and build status. \ No newline at end of file diff --git a/kknd2-runs/20260713-093232/qwen-build.txt b/kknd2-runs/20260713-093232/qwen-build.txt new file mode 100644 index 0000000..25bc5a5 --- /dev/null +++ b/kknd2-runs/20260713-093232/qwen-build.txt @@ -0,0 +1,170 @@ +openkknd: compiling... +In file included from src/rng.c:1: +include/kknd.h:10:16: warning: »/*« innerhalb des Kommentars [-Wcomment] + 10 | * matching src/*.c translation units. +include/kknd.h:277:9: warning: »MAX_PARTICLES« redefiniert + 277 | #define MAX_PARTICLES 2048 + | ^~~~~~~~~~~~~ +include/kknd.h:46:9: note: dies ist die Stelle der vorherigen Definition + 46 | #define MAX_PARTICLES 4000 + | ^~~~~~~~~~~~~ +In file included from src/config.c:1: +include/kknd.h:10:16: warning: »/*« innerhalb des Kommentars [-Wcomment] + 10 | * matching src/*.c translation units. +include/kknd.h:277:9: warning: »MAX_PARTICLES« redefiniert + 277 | #define MAX_PARTICLES 2048 + | ^~~~~~~~~~~~~ +include/kknd.h:46:9: note: dies ist die Stelle der vorherigen Definition + 46 | #define MAX_PARTICLES 4000 + | ^~~~~~~~~~~~~ +In file included from src/png.c:8: +include/kknd.h:10:16: warning: »/*« innerhalb des Kommentars [-Wcomment] + 10 | * matching src/*.c translation units. +include/kknd.h:277:9: warning: »MAX_PARTICLES« redefiniert + 277 | #define MAX_PARTICLES 2048 + | ^~~~~~~~~~~~~ +include/kknd.h:46:9: note: dies ist die Stelle der vorherigen Definition + 46 | #define MAX_PARTICLES 4000 + | ^~~~~~~~~~~~~ +In file included from src/snapshot.c:6: +include/kknd.h:10:16: warning: »/*« innerhalb des Kommentars [-Wcomment] + 10 | * matching src/*.c translation units. +include/kknd.h:277:9: warning: »MAX_PARTICLES« redefiniert + 277 | #define MAX_PARTICLES 2048 + | ^~~~~~~~~~~~~ +include/kknd.h:46:9: note: dies ist die Stelle der vorherigen Definition + 46 | #define MAX_PARTICLES 4000 + | ^~~~~~~~~~~~~ +src/snapshot.c: In Funktion »snapshot_capture«: +src/snapshot.c:62:5: warning: diese »if«-Klausel beschützt nicht … [-Wmisleading-indentation] + 62 | if (camx < 0) camx = 0; if (camy < 0) camy = 0; + | ^~ +src/snapshot.c:62:29: note: … diese Anweisung, aber letztere ist verwirrend eingerückt, als wäre sie abhängig von »if« + 62 | if (camx < 0) camx = 0; if (camy < 0) camy = 0; + | ^~ +In file included from src/map.c:1: +include/kknd.h:10:16: warning: »/*« innerhalb des Kommentars [-Wcomment] + 10 | * matching src/*.c translation units. +include/kknd.h:277:9: warning: »MAX_PARTICLES« redefiniert + 277 | #define MAX_PARTICLES 2048 + | ^~~~~~~~~~~~~ +include/kknd.h:46:9: note: dies ist die Stelle der vorherigen Definition + 46 | #define MAX_PARTICLES 4000 + | ^~~~~~~~~~~~~ +src/map.c: In Funktion »map_init«: +src/map.c:66:13: warning: Variable »pos« wird nicht verwendet [-Wunused-variable] + 66 | int pos = rng_range(rng, 8, m->w - 9); + | ^~~ +In file included from src/sprites.c:1: +include/kknd.h:10:16: warning: »/*« innerhalb des Kommentars [-Wcomment] + 10 | * matching src/*.c translation units. +include/kknd.h:277:9: warning: »MAX_PARTICLES« redefiniert + 277 | #define MAX_PARTICLES 2048 + | ^~~~~~~~~~~~~ +include/kknd.h:46:9: note: dies ist die Stelle der vorherigen Definition + 46 | #define MAX_PARTICLES 4000 + | ^~~~~~~~~~~~~ +In file included from src/units.c:1: +include/kknd.h:10:16: warning: »/*« innerhalb des Kommentars [-Wcomment] + 10 | * matching src/*.c translation units. +include/kknd.h:277:9: warning: »MAX_PARTICLES« redefiniert + 277 | #define MAX_PARTICLES 2048 + | ^~~~~~~~~~~~~ +include/kknd.h:46:9: note: dies ist die Stelle der vorherigen Definition + 46 | #define MAX_PARTICLES 4000 + | ^~~~~~~~~~~~~ +In file included from src/buildings.c:1: +include/kknd.h:10:16: warning: »/*« innerhalb des Kommentars [-Wcomment] + 10 | * matching src/*.c translation units. +include/kknd.h:277:9: warning: »MAX_PARTICLES« redefiniert + 277 | #define MAX_PARTICLES 2048 + | ^~~~~~~~~~~~~ +include/kknd.h:46:9: note: dies ist die Stelle der vorherigen Definition + 46 | #define MAX_PARTICLES 4000 + | ^~~~~~~~~~~~~ +In file included from src/projectiles.c:1: +include/kknd.h:10:16: warning: »/*« innerhalb des Kommentars [-Wcomment] + 10 | * matching src/*.c translation units. +include/kknd.h:277:9: warning: »MAX_PARTICLES« redefiniert + 277 | #define MAX_PARTICLES 2048 + | ^~~~~~~~~~~~~ +include/kknd.h:46:9: note: dies ist die Stelle der vorherigen Definition + 46 | #define MAX_PARTICLES 4000 + | ^~~~~~~~~~~~~ +In file included from src/render.c:1: +include/kknd.h:10:16: warning: »/*« innerhalb des Kommentars [-Wcomment] + 10 | * matching src/*.c translation units. +include/kknd.h:277:9: warning: »MAX_PARTICLES« redefiniert + 277 | #define MAX_PARTICLES 2048 + | ^~~~~~~~~~~~~ +include/kknd.h:46:9: note: dies ist die Stelle der vorherigen Definition + 46 | #define MAX_PARTICLES 4000 + | ^~~~~~~~~~~~~ +src/render.c: In Funktion »render_buildings«: +src/render.c:106:17: warning: Variable »pct« wird nicht verwendet [-Wunused-variable] + 106 | int pct = (int)(b->build * 100); + | ^~~ +In file included from src/input.c:1: +include/kknd.h:10:16: warning: »/*« innerhalb des Kommentars [-Wcomment] + 10 | * matching src/*.c translation units. +include/kknd.h:277:9: warning: »MAX_PARTICLES« redefiniert + 277 | #define MAX_PARTICLES 2048 + | ^~~~~~~~~~~~~ +include/kknd.h:46:9: note: dies ist die Stelle der vorherigen Definition + 46 | #define MAX_PARTICLES 4000 + | ^~~~~~~~~~~~~ +In file included from src/ai.c:1: +include/kknd.h:10:16: warning: »/*« innerhalb des Kommentars [-Wcomment] + 10 | * matching src/*.c translation units. +include/kknd.h:277:9: warning: »MAX_PARTICLES« redefiniert + 277 | #define MAX_PARTICLES 2048 + | ^~~~~~~~~~~~~ +include/kknd.h:46:9: note: dies ist die Stelle der vorherigen Definition + 46 | #define MAX_PARTICLES 4000 + | ^~~~~~~~~~~~~ +In file included from src/audio.c:1: +include/kknd.h:10:16: warning: »/*« innerhalb des Kommentars [-Wcomment] + 10 | * matching src/*.c translation units. +include/kknd.h:277:9: warning: »MAX_PARTICLES« redefiniert + 277 | #define MAX_PARTICLES 2048 + | ^~~~~~~~~~~~~ +include/kknd.h:46:9: note: dies ist die Stelle der vorherigen Definition + 46 | #define MAX_PARTICLES 4000 + | ^~~~~~~~~~~~~ +In file included from src/save.c:1: +include/kknd.h:10:16: warning: »/*« innerhalb des Kommentars [-Wcomment] + 10 | * matching src/*.c translation units. +include/kknd.h:277:9: warning: »MAX_PARTICLES« redefiniert + 277 | #define MAX_PARTICLES 2048 + | ^~~~~~~~~~~~~ +include/kknd.h:46:9: note: dies ist die Stelle der vorherigen Definition + 46 | #define MAX_PARTICLES 4000 + | ^~~~~~~~~~~~~ +In file included from src/engine.c:1: +include/kknd.h:10:16: warning: »/*« innerhalb des Kommentars [-Wcomment] + 10 | * matching src/*.c translation units. +include/kknd.h:277:9: warning: »MAX_PARTICLES« redefiniert + 277 | #define MAX_PARTICLES 2048 + | ^~~~~~~~~~~~~ +include/kknd.h:46:9: note: dies ist die Stelle der vorherigen Definition + 46 | #define MAX_PARTICLES 4000 + | ^~~~~~~~~~~~~ +In file included from src/main.c:1: +include/kknd.h:10:16: warning: »/*« innerhalb des Kommentars [-Wcomment] + 10 | * matching src/*.c translation units. +include/kknd.h:277:9: warning: »MAX_PARTICLES« redefiniert + 277 | #define MAX_PARTICLES 2048 + | ^~~~~~~~~~~~~ +include/kknd.h:46:9: note: dies ist die Stelle der vorherigen Definition + 46 | #define MAX_PARTICLES 4000 + | ^~~~~~~~~~~~~ +src/main.c: In Funktion »main«: +src/main.c:50:16: warning: Variable »diff« gesetzt, aber nicht verwendet [-Wunused-but-set-variable=] + 50 | Difficulty diff = DIFF_NORMAL; + | ^~~~ +src/main.c: Auf höchster Ebene: +src/main.c:32:13: warning: »save_snapshot« definiert, aber nicht verwendet [-Wunused-function] + 32 | static void save_snapshot(Game *g, int n) { + | ^~~~~~~~~~~~~ +openkknd: built -> ./openkknd +usage: ./openkknd [--easy|--normal|--hard] [--load savegame.kknd] diff --git a/kknd2-runs/20260713-093232/qwen.err b/kknd2-runs/20260713-093232/qwen.err new file mode 100644 index 0000000..a4110cf --- /dev/null +++ b/kknd2-runs/20260713-093232/qwen.err @@ -0,0 +1,2 @@ + +[qwen2.5-coder:14b · 32316ms · ok] diff --git a/kknd2-runs/20260713-093232/qwen.out b/kknd2-runs/20260713-093232/qwen.out new file mode 100644 index 0000000..86c62e7 --- /dev/null +++ b/kknd2-runs/20260713-093232/qwen.out @@ -0,0 +1,6 @@ +{ + "name": "exec", + "arguments": { + "command": "cd /home/vx/src/openkknd-qwen && ./build.sh" + } +} diff --git a/kknd2-runs/20260713-093232/run.sh b/kknd2-runs/20260713-093232/run.sh new file mode 100755 index 0000000..3ca0610 --- /dev/null +++ b/kknd2-runs/20260713-093232/run.sh @@ -0,0 +1,37 @@ +#!/usr/bin/env bash +set -uo pipefail +export PATH="$HOME/.local/bin:/usr/bin:$PATH" +export OLLAMA_API_KEY=ollama-local +export OLLAMA_HOST=127.0.0.1 +export OPENCLAW_TIMEOUT=1200 +LOGDIR="${1:?}" +PROMPT_QWEN=$(cat "$LOGDIR/prompt-qwen.txt") +PROMPT_HERMES=$(cat "$LOGDIR/prompt-hermes.txt") + +echo "==== $(date -Is) QWEN start ====" | tee -a "$LOGDIR/master.log" +# free VRAM for 14b +ollama-gpu stop hermes3:8b 2>/dev/null || true +ollama-gpu stop qwen2.5-coder:7b 2>/dev/null || true + +"$HOME/agent-coder" --qwen14 -s openkknd-qwen "$PROMPT_QWEN" \ + >"$LOGDIR/qwen.out" 2>"$LOGDIR/qwen.err" +echo "qwen exit=$?" | tee -a "$LOGDIR/master.log" +tail -40 "$LOGDIR/qwen.out" | tee -a "$LOGDIR/master.log" +# build proof +(cd "$HOME/src/openkknd-qwen" && ./build.sh) >"$LOGDIR/qwen-build.txt" 2>&1 || true +echo "==== qwen tree ====" | tee -a "$LOGDIR/master.log" +ls -la "$HOME/src/openkknd-qwen" | tee -a "$LOGDIR/master.log" + +echo "==== $(date -Is) HERMES start ====" | tee -a "$LOGDIR/master.log" +ollama-gpu stop qwen2.5-coder:14b 2>/dev/null || true +ollama-gpu stop qwen2.5-coder:7b 2>/dev/null || true + +"$HOME/agent-coder" --hermes -s openkknd-hermes "$PROMPT_HERMES" \ + >"$LOGDIR/hermes.out" 2>"$LOGDIR/hermes.err" +echo "hermes exit=$?" | tee -a "$LOGDIR/master.log" +tail -40 "$LOGDIR/hermes.out" | tee -a "$LOGDIR/master.log" +(cd "$HOME/src/openkknd-hermes" && ./build.sh) >"$LOGDIR/hermes-build.txt" 2>&1 || true +echo "==== hermes tree ====" | tee -a "$LOGDIR/master.log" +ls -la "$HOME/src/openkknd-hermes" | tee -a "$LOGDIR/master.log" + +echo "==== $(date -Is) DONE ====" | tee -a "$LOGDIR/master.log" diff --git a/kknd2-runs/20260713-093614-q14-deepseek/deepseek-build.txt b/kknd2-runs/20260713-093614-q14-deepseek/deepseek-build.txt new file mode 100644 index 0000000..25bc5a5 --- /dev/null +++ b/kknd2-runs/20260713-093614-q14-deepseek/deepseek-build.txt @@ -0,0 +1,170 @@ +openkknd: compiling... +In file included from src/rng.c:1: +include/kknd.h:10:16: warning: »/*« innerhalb des Kommentars [-Wcomment] + 10 | * matching src/*.c translation units. +include/kknd.h:277:9: warning: »MAX_PARTICLES« redefiniert + 277 | #define MAX_PARTICLES 2048 + | ^~~~~~~~~~~~~ +include/kknd.h:46:9: note: dies ist die Stelle der vorherigen Definition + 46 | #define MAX_PARTICLES 4000 + | ^~~~~~~~~~~~~ +In file included from src/config.c:1: +include/kknd.h:10:16: warning: »/*« innerhalb des Kommentars [-Wcomment] + 10 | * matching src/*.c translation units. +include/kknd.h:277:9: warning: »MAX_PARTICLES« redefiniert + 277 | #define MAX_PARTICLES 2048 + | ^~~~~~~~~~~~~ +include/kknd.h:46:9: note: dies ist die Stelle der vorherigen Definition + 46 | #define MAX_PARTICLES 4000 + | ^~~~~~~~~~~~~ +In file included from src/png.c:8: +include/kknd.h:10:16: warning: »/*« innerhalb des Kommentars [-Wcomment] + 10 | * matching src/*.c translation units. +include/kknd.h:277:9: warning: »MAX_PARTICLES« redefiniert + 277 | #define MAX_PARTICLES 2048 + | ^~~~~~~~~~~~~ +include/kknd.h:46:9: note: dies ist die Stelle der vorherigen Definition + 46 | #define MAX_PARTICLES 4000 + | ^~~~~~~~~~~~~ +In file included from src/snapshot.c:6: +include/kknd.h:10:16: warning: »/*« innerhalb des Kommentars [-Wcomment] + 10 | * matching src/*.c translation units. +include/kknd.h:277:9: warning: »MAX_PARTICLES« redefiniert + 277 | #define MAX_PARTICLES 2048 + | ^~~~~~~~~~~~~ +include/kknd.h:46:9: note: dies ist die Stelle der vorherigen Definition + 46 | #define MAX_PARTICLES 4000 + | ^~~~~~~~~~~~~ +src/snapshot.c: In Funktion »snapshot_capture«: +src/snapshot.c:62:5: warning: diese »if«-Klausel beschützt nicht … [-Wmisleading-indentation] + 62 | if (camx < 0) camx = 0; if (camy < 0) camy = 0; + | ^~ +src/snapshot.c:62:29: note: … diese Anweisung, aber letztere ist verwirrend eingerückt, als wäre sie abhängig von »if« + 62 | if (camx < 0) camx = 0; if (camy < 0) camy = 0; + | ^~ +In file included from src/map.c:1: +include/kknd.h:10:16: warning: »/*« innerhalb des Kommentars [-Wcomment] + 10 | * matching src/*.c translation units. +include/kknd.h:277:9: warning: »MAX_PARTICLES« redefiniert + 277 | #define MAX_PARTICLES 2048 + | ^~~~~~~~~~~~~ +include/kknd.h:46:9: note: dies ist die Stelle der vorherigen Definition + 46 | #define MAX_PARTICLES 4000 + | ^~~~~~~~~~~~~ +src/map.c: In Funktion »map_init«: +src/map.c:66:13: warning: Variable »pos« wird nicht verwendet [-Wunused-variable] + 66 | int pos = rng_range(rng, 8, m->w - 9); + | ^~~ +In file included from src/sprites.c:1: +include/kknd.h:10:16: warning: »/*« innerhalb des Kommentars [-Wcomment] + 10 | * matching src/*.c translation units. +include/kknd.h:277:9: warning: »MAX_PARTICLES« redefiniert + 277 | #define MAX_PARTICLES 2048 + | ^~~~~~~~~~~~~ +include/kknd.h:46:9: note: dies ist die Stelle der vorherigen Definition + 46 | #define MAX_PARTICLES 4000 + | ^~~~~~~~~~~~~ +In file included from src/units.c:1: +include/kknd.h:10:16: warning: »/*« innerhalb des Kommentars [-Wcomment] + 10 | * matching src/*.c translation units. +include/kknd.h:277:9: warning: »MAX_PARTICLES« redefiniert + 277 | #define MAX_PARTICLES 2048 + | ^~~~~~~~~~~~~ +include/kknd.h:46:9: note: dies ist die Stelle der vorherigen Definition + 46 | #define MAX_PARTICLES 4000 + | ^~~~~~~~~~~~~ +In file included from src/buildings.c:1: +include/kknd.h:10:16: warning: »/*« innerhalb des Kommentars [-Wcomment] + 10 | * matching src/*.c translation units. +include/kknd.h:277:9: warning: »MAX_PARTICLES« redefiniert + 277 | #define MAX_PARTICLES 2048 + | ^~~~~~~~~~~~~ +include/kknd.h:46:9: note: dies ist die Stelle der vorherigen Definition + 46 | #define MAX_PARTICLES 4000 + | ^~~~~~~~~~~~~ +In file included from src/projectiles.c:1: +include/kknd.h:10:16: warning: »/*« innerhalb des Kommentars [-Wcomment] + 10 | * matching src/*.c translation units. +include/kknd.h:277:9: warning: »MAX_PARTICLES« redefiniert + 277 | #define MAX_PARTICLES 2048 + | ^~~~~~~~~~~~~ +include/kknd.h:46:9: note: dies ist die Stelle der vorherigen Definition + 46 | #define MAX_PARTICLES 4000 + | ^~~~~~~~~~~~~ +In file included from src/render.c:1: +include/kknd.h:10:16: warning: »/*« innerhalb des Kommentars [-Wcomment] + 10 | * matching src/*.c translation units. +include/kknd.h:277:9: warning: »MAX_PARTICLES« redefiniert + 277 | #define MAX_PARTICLES 2048 + | ^~~~~~~~~~~~~ +include/kknd.h:46:9: note: dies ist die Stelle der vorherigen Definition + 46 | #define MAX_PARTICLES 4000 + | ^~~~~~~~~~~~~ +src/render.c: In Funktion »render_buildings«: +src/render.c:106:17: warning: Variable »pct« wird nicht verwendet [-Wunused-variable] + 106 | int pct = (int)(b->build * 100); + | ^~~ +In file included from src/input.c:1: +include/kknd.h:10:16: warning: »/*« innerhalb des Kommentars [-Wcomment] + 10 | * matching src/*.c translation units. +include/kknd.h:277:9: warning: »MAX_PARTICLES« redefiniert + 277 | #define MAX_PARTICLES 2048 + | ^~~~~~~~~~~~~ +include/kknd.h:46:9: note: dies ist die Stelle der vorherigen Definition + 46 | #define MAX_PARTICLES 4000 + | ^~~~~~~~~~~~~ +In file included from src/ai.c:1: +include/kknd.h:10:16: warning: »/*« innerhalb des Kommentars [-Wcomment] + 10 | * matching src/*.c translation units. +include/kknd.h:277:9: warning: »MAX_PARTICLES« redefiniert + 277 | #define MAX_PARTICLES 2048 + | ^~~~~~~~~~~~~ +include/kknd.h:46:9: note: dies ist die Stelle der vorherigen Definition + 46 | #define MAX_PARTICLES 4000 + | ^~~~~~~~~~~~~ +In file included from src/audio.c:1: +include/kknd.h:10:16: warning: »/*« innerhalb des Kommentars [-Wcomment] + 10 | * matching src/*.c translation units. +include/kknd.h:277:9: warning: »MAX_PARTICLES« redefiniert + 277 | #define MAX_PARTICLES 2048 + | ^~~~~~~~~~~~~ +include/kknd.h:46:9: note: dies ist die Stelle der vorherigen Definition + 46 | #define MAX_PARTICLES 4000 + | ^~~~~~~~~~~~~ +In file included from src/save.c:1: +include/kknd.h:10:16: warning: »/*« innerhalb des Kommentars [-Wcomment] + 10 | * matching src/*.c translation units. +include/kknd.h:277:9: warning: »MAX_PARTICLES« redefiniert + 277 | #define MAX_PARTICLES 2048 + | ^~~~~~~~~~~~~ +include/kknd.h:46:9: note: dies ist die Stelle der vorherigen Definition + 46 | #define MAX_PARTICLES 4000 + | ^~~~~~~~~~~~~ +In file included from src/engine.c:1: +include/kknd.h:10:16: warning: »/*« innerhalb des Kommentars [-Wcomment] + 10 | * matching src/*.c translation units. +include/kknd.h:277:9: warning: »MAX_PARTICLES« redefiniert + 277 | #define MAX_PARTICLES 2048 + | ^~~~~~~~~~~~~ +include/kknd.h:46:9: note: dies ist die Stelle der vorherigen Definition + 46 | #define MAX_PARTICLES 4000 + | ^~~~~~~~~~~~~ +In file included from src/main.c:1: +include/kknd.h:10:16: warning: »/*« innerhalb des Kommentars [-Wcomment] + 10 | * matching src/*.c translation units. +include/kknd.h:277:9: warning: »MAX_PARTICLES« redefiniert + 277 | #define MAX_PARTICLES 2048 + | ^~~~~~~~~~~~~ +include/kknd.h:46:9: note: dies ist die Stelle der vorherigen Definition + 46 | #define MAX_PARTICLES 4000 + | ^~~~~~~~~~~~~ +src/main.c: In Funktion »main«: +src/main.c:50:16: warning: Variable »diff« gesetzt, aber nicht verwendet [-Wunused-but-set-variable=] + 50 | Difficulty diff = DIFF_NORMAL; + | ^~~~ +src/main.c: Auf höchster Ebene: +src/main.c:32:13: warning: »save_snapshot« definiert, aber nicht verwendet [-Wunused-function] + 32 | static void save_snapshot(Game *g, int n) { + | ^~~~~~~~~~~~~ +openkknd: built -> ./openkknd +usage: ./openkknd [--easy|--normal|--hard] [--load savegame.kknd] diff --git a/kknd2-runs/20260713-093614-q14-deepseek/deepseek.err b/kknd2-runs/20260713-093614-q14-deepseek/deepseek.err new file mode 100644 index 0000000..fd60f5e --- /dev/null +++ b/kknd2-runs/20260713-093614-q14-deepseek/deepseek.err @@ -0,0 +1,2 @@ + +[deepseek-coder:6.7b · 1005ms · ok] diff --git a/kknd2-runs/20260713-093614-q14-deepseek/deepseek.out b/kknd2-runs/20260713-093614-q14-deepseek/deepseek.out new file mode 100644 index 0000000..56666d3 --- /dev/null +++ b/kknd2-runs/20260713-093614-q14-deepseek/deepseek.out @@ -0,0 +1 @@ +Context overflow: prompt too large for the model. Try /reset (or /new) to start a fresh session, or use a larger-context model. diff --git a/kknd2-runs/20260713-093614-q14-deepseek/master.log b/kknd2-runs/20260713-093614-q14-deepseek/master.log new file mode 100644 index 0000000..13c8e2e --- /dev/null +++ b/kknd2-runs/20260713-093614-q14-deepseek/master.log @@ -0,0 +1,48 @@ +==== 2026-07-13T09:36:14+02:00 QWEN14 start ==== +qwen14 exit=0 +```json +{ + "name": "exec", + "arguments": { + "command": "cd /home/vx/src/openkknd-qwen14b && ./build.sh" + } +} +``` +insgesamt 136 +drwxr-xr-x 1 vx vx 144 13. Jul 09:38 . +drwxr-xr-x 1 vx vx 142 13. Jul 09:36 .. +drwxr-xr-x 1 vx vx 36 11. Jul 06:59 assets +drwxr-xr-x 1 vx vx 0 11. Jul 06:38 benchmarks +-rwxr-xr-x 1 vx vx 699 11. Jul 14:19 build.sh +drwxr-xr-x 1 vx vx 430 11. Jul 14:24 gallery +drwxr-xr-x 1 vx vx 34 11. Jul 07:04 include +-rw-r--r-- 1 vx vx 997 13. Jul 09:36 MISSION.md +-rwxr-xr-x 1 vx vx 124680 13. Jul 09:38 openkknd +-rw-r--r-- 1 vx vx 204 11. Jul 14:22 savegame.kknd +drwxr-xr-x 1 vx vx 290 11. Jul 14:20 src +==== 2026-07-13T09:38:03+02:00 DEEPSEEK start ==== +deepseek exit=0 +Context overflow: prompt too large for the model. Try /reset (or /new) to start a fresh session, or use a larger-context model. +insgesamt 136 +drwxr-xr-x 1 vx vx 144 13. Jul 09:38 . +drwxr-xr-x 1 vx vx 142 13. Jul 09:36 .. +drwxr-xr-x 1 vx vx 36 11. Jul 06:59 assets +drwxr-xr-x 1 vx vx 0 11. Jul 06:38 benchmarks +-rwxr-xr-x 1 vx vx 699 11. Jul 14:19 build.sh +drwxr-xr-x 1 vx vx 430 11. Jul 14:24 gallery +drwxr-xr-x 1 vx vx 34 11. Jul 07:04 include +-rw-r--r-- 1 vx vx 1002 13. Jul 09:36 MISSION.md +-rwxr-xr-x 1 vx vx 124680 13. Jul 09:38 openkknd +-rw-r--r-- 1 vx vx 204 11. Jul 14:22 savegame.kknd +drwxr-xr-x 1 vx vx 290 11. Jul 14:20 src +==== 2026-07-13T09:38:15+02:00 DONE ==== +-- openkknd-qwen14b -- +-- openkknd-qwen14b -- +binary=yes +README=no +git=no +-- openkknd-deepseek -- +-- openkknd-deepseek -- +binary=yes +README=no +git=no diff --git a/kknd2-runs/20260713-093614-q14-deepseek/nohup.out b/kknd2-runs/20260713-093614-q14-deepseek/nohup.out new file mode 100644 index 0000000..869d9d5 --- /dev/null +++ b/kknd2-runs/20260713-093614-q14-deepseek/nohup.out @@ -0,0 +1,46 @@ +==== 2026-07-13T09:36:14+02:00 QWEN14 start ==== +qwen14 exit=0 +```json +{ + "name": "exec", + "arguments": { + "command": "cd /home/vx/src/openkknd-qwen14b && ./build.sh" + } +} +``` +insgesamt 136 +drwxr-xr-x 1 vx vx 144 13. Jul 09:38 . +drwxr-xr-x 1 vx vx 142 13. Jul 09:36 .. +drwxr-xr-x 1 vx vx 36 11. Jul 06:59 assets +drwxr-xr-x 1 vx vx 0 11. Jul 06:38 benchmarks +-rwxr-xr-x 1 vx vx 699 11. Jul 14:19 build.sh +drwxr-xr-x 1 vx vx 430 11. Jul 14:24 gallery +drwxr-xr-x 1 vx vx 34 11. Jul 07:04 include +-rw-r--r-- 1 vx vx 997 13. Jul 09:36 MISSION.md +-rwxr-xr-x 1 vx vx 124680 13. Jul 09:38 openkknd +-rw-r--r-- 1 vx vx 204 11. Jul 14:22 savegame.kknd +drwxr-xr-x 1 vx vx 290 11. Jul 14:20 src +==== 2026-07-13T09:38:03+02:00 DEEPSEEK start ==== +deepseek exit=0 +Context overflow: prompt too large for the model. Try /reset (or /new) to start a fresh session, or use a larger-context model. +insgesamt 136 +drwxr-xr-x 1 vx vx 144 13. Jul 09:38 . +drwxr-xr-x 1 vx vx 142 13. Jul 09:36 .. +drwxr-xr-x 1 vx vx 36 11. Jul 06:59 assets +drwxr-xr-x 1 vx vx 0 11. Jul 06:38 benchmarks +-rwxr-xr-x 1 vx vx 699 11. Jul 14:19 build.sh +drwxr-xr-x 1 vx vx 430 11. Jul 14:24 gallery +drwxr-xr-x 1 vx vx 34 11. Jul 07:04 include +-rw-r--r-- 1 vx vx 1002 13. Jul 09:36 MISSION.md +-rwxr-xr-x 1 vx vx 124680 13. Jul 09:38 openkknd +-rw-r--r-- 1 vx vx 204 11. Jul 14:22 savegame.kknd +drwxr-xr-x 1 vx vx 290 11. Jul 14:20 src +==== 2026-07-13T09:38:15+02:00 DONE ==== +-- openkknd-qwen14b -- +binary=yes +README=no +git=no +-- openkknd-deepseek -- +binary=yes +README=no +git=no diff --git a/kknd2-runs/20260713-093614-q14-deepseek/prompt-deepseek.txt b/kknd2-runs/20260713-093614-q14-deepseek/prompt-deepseek.txt new file mode 100644 index 0000000..d4e4ea3 --- /dev/null +++ b/kknd2-runs/20260713-093614-q14-deepseek/prompt-deepseek.txt @@ -0,0 +1,13 @@ +KKND2 mission. You are the deepseek-coder:6.7b coding agent. + +Work ONLY in /home/vx/src/openkknd-deepseek (seeded from openkknd). Read MISSION.md. + +Tasks: +1) cd /home/vx/src/openkknd-deepseek +2) Use exec: ./build.sh — fix compile/link errors with real file edits +3) After green build: git init if needed, commit "kknd2: green build" +4) Write/update README.md (build + run) +5) Optional: fix cheap warnings after green build + +Do not touch /home/vx/openkknd or other openkknd-* trees. +Use tools (exec, read, write, edit). Report what you did and build status. \ No newline at end of file diff --git a/kknd2-runs/20260713-093614-q14-deepseek/prompt-qwen14b.txt b/kknd2-runs/20260713-093614-q14-deepseek/prompt-qwen14b.txt new file mode 100644 index 0000000..edf5813 --- /dev/null +++ b/kknd2-runs/20260713-093614-q14-deepseek/prompt-qwen14b.txt @@ -0,0 +1,13 @@ +KKND2 mission. You are the qwen2.5-coder:14b coding agent. + +Work ONLY in /home/vx/src/openkknd-qwen14b (seeded from openkknd). Read MISSION.md. + +Tasks: +1) cd /home/vx/src/openkknd-qwen14b +2) Use exec: ./build.sh — fix compile/link errors with real file edits +3) After green build: git init if needed, commit "kknd2: green build" +4) Write/update README.md (build + run) +5) Optional: fix cheap warnings (MAX_PARTICLES redefine) after green build + +Do not touch /home/vx/openkknd or other openkknd-* trees. +Use tools (exec, read, write, edit). Report what you did and build status. \ No newline at end of file diff --git a/kknd2-runs/20260713-093614-q14-deepseek/qwen14b-build.txt b/kknd2-runs/20260713-093614-q14-deepseek/qwen14b-build.txt new file mode 100644 index 0000000..25bc5a5 --- /dev/null +++ b/kknd2-runs/20260713-093614-q14-deepseek/qwen14b-build.txt @@ -0,0 +1,170 @@ +openkknd: compiling... +In file included from src/rng.c:1: +include/kknd.h:10:16: warning: »/*« innerhalb des Kommentars [-Wcomment] + 10 | * matching src/*.c translation units. +include/kknd.h:277:9: warning: »MAX_PARTICLES« redefiniert + 277 | #define MAX_PARTICLES 2048 + | ^~~~~~~~~~~~~ +include/kknd.h:46:9: note: dies ist die Stelle der vorherigen Definition + 46 | #define MAX_PARTICLES 4000 + | ^~~~~~~~~~~~~ +In file included from src/config.c:1: +include/kknd.h:10:16: warning: »/*« innerhalb des Kommentars [-Wcomment] + 10 | * matching src/*.c translation units. +include/kknd.h:277:9: warning: »MAX_PARTICLES« redefiniert + 277 | #define MAX_PARTICLES 2048 + | ^~~~~~~~~~~~~ +include/kknd.h:46:9: note: dies ist die Stelle der vorherigen Definition + 46 | #define MAX_PARTICLES 4000 + | ^~~~~~~~~~~~~ +In file included from src/png.c:8: +include/kknd.h:10:16: warning: »/*« innerhalb des Kommentars [-Wcomment] + 10 | * matching src/*.c translation units. +include/kknd.h:277:9: warning: »MAX_PARTICLES« redefiniert + 277 | #define MAX_PARTICLES 2048 + | ^~~~~~~~~~~~~ +include/kknd.h:46:9: note: dies ist die Stelle der vorherigen Definition + 46 | #define MAX_PARTICLES 4000 + | ^~~~~~~~~~~~~ +In file included from src/snapshot.c:6: +include/kknd.h:10:16: warning: »/*« innerhalb des Kommentars [-Wcomment] + 10 | * matching src/*.c translation units. +include/kknd.h:277:9: warning: »MAX_PARTICLES« redefiniert + 277 | #define MAX_PARTICLES 2048 + | ^~~~~~~~~~~~~ +include/kknd.h:46:9: note: dies ist die Stelle der vorherigen Definition + 46 | #define MAX_PARTICLES 4000 + | ^~~~~~~~~~~~~ +src/snapshot.c: In Funktion »snapshot_capture«: +src/snapshot.c:62:5: warning: diese »if«-Klausel beschützt nicht … [-Wmisleading-indentation] + 62 | if (camx < 0) camx = 0; if (camy < 0) camy = 0; + | ^~ +src/snapshot.c:62:29: note: … diese Anweisung, aber letztere ist verwirrend eingerückt, als wäre sie abhängig von »if« + 62 | if (camx < 0) camx = 0; if (camy < 0) camy = 0; + | ^~ +In file included from src/map.c:1: +include/kknd.h:10:16: warning: »/*« innerhalb des Kommentars [-Wcomment] + 10 | * matching src/*.c translation units. +include/kknd.h:277:9: warning: »MAX_PARTICLES« redefiniert + 277 | #define MAX_PARTICLES 2048 + | ^~~~~~~~~~~~~ +include/kknd.h:46:9: note: dies ist die Stelle der vorherigen Definition + 46 | #define MAX_PARTICLES 4000 + | ^~~~~~~~~~~~~ +src/map.c: In Funktion »map_init«: +src/map.c:66:13: warning: Variable »pos« wird nicht verwendet [-Wunused-variable] + 66 | int pos = rng_range(rng, 8, m->w - 9); + | ^~~ +In file included from src/sprites.c:1: +include/kknd.h:10:16: warning: »/*« innerhalb des Kommentars [-Wcomment] + 10 | * matching src/*.c translation units. +include/kknd.h:277:9: warning: »MAX_PARTICLES« redefiniert + 277 | #define MAX_PARTICLES 2048 + | ^~~~~~~~~~~~~ +include/kknd.h:46:9: note: dies ist die Stelle der vorherigen Definition + 46 | #define MAX_PARTICLES 4000 + | ^~~~~~~~~~~~~ +In file included from src/units.c:1: +include/kknd.h:10:16: warning: »/*« innerhalb des Kommentars [-Wcomment] + 10 | * matching src/*.c translation units. +include/kknd.h:277:9: warning: »MAX_PARTICLES« redefiniert + 277 | #define MAX_PARTICLES 2048 + | ^~~~~~~~~~~~~ +include/kknd.h:46:9: note: dies ist die Stelle der vorherigen Definition + 46 | #define MAX_PARTICLES 4000 + | ^~~~~~~~~~~~~ +In file included from src/buildings.c:1: +include/kknd.h:10:16: warning: »/*« innerhalb des Kommentars [-Wcomment] + 10 | * matching src/*.c translation units. +include/kknd.h:277:9: warning: »MAX_PARTICLES« redefiniert + 277 | #define MAX_PARTICLES 2048 + | ^~~~~~~~~~~~~ +include/kknd.h:46:9: note: dies ist die Stelle der vorherigen Definition + 46 | #define MAX_PARTICLES 4000 + | ^~~~~~~~~~~~~ +In file included from src/projectiles.c:1: +include/kknd.h:10:16: warning: »/*« innerhalb des Kommentars [-Wcomment] + 10 | * matching src/*.c translation units. +include/kknd.h:277:9: warning: »MAX_PARTICLES« redefiniert + 277 | #define MAX_PARTICLES 2048 + | ^~~~~~~~~~~~~ +include/kknd.h:46:9: note: dies ist die Stelle der vorherigen Definition + 46 | #define MAX_PARTICLES 4000 + | ^~~~~~~~~~~~~ +In file included from src/render.c:1: +include/kknd.h:10:16: warning: »/*« innerhalb des Kommentars [-Wcomment] + 10 | * matching src/*.c translation units. +include/kknd.h:277:9: warning: »MAX_PARTICLES« redefiniert + 277 | #define MAX_PARTICLES 2048 + | ^~~~~~~~~~~~~ +include/kknd.h:46:9: note: dies ist die Stelle der vorherigen Definition + 46 | #define MAX_PARTICLES 4000 + | ^~~~~~~~~~~~~ +src/render.c: In Funktion »render_buildings«: +src/render.c:106:17: warning: Variable »pct« wird nicht verwendet [-Wunused-variable] + 106 | int pct = (int)(b->build * 100); + | ^~~ +In file included from src/input.c:1: +include/kknd.h:10:16: warning: »/*« innerhalb des Kommentars [-Wcomment] + 10 | * matching src/*.c translation units. +include/kknd.h:277:9: warning: »MAX_PARTICLES« redefiniert + 277 | #define MAX_PARTICLES 2048 + | ^~~~~~~~~~~~~ +include/kknd.h:46:9: note: dies ist die Stelle der vorherigen Definition + 46 | #define MAX_PARTICLES 4000 + | ^~~~~~~~~~~~~ +In file included from src/ai.c:1: +include/kknd.h:10:16: warning: »/*« innerhalb des Kommentars [-Wcomment] + 10 | * matching src/*.c translation units. +include/kknd.h:277:9: warning: »MAX_PARTICLES« redefiniert + 277 | #define MAX_PARTICLES 2048 + | ^~~~~~~~~~~~~ +include/kknd.h:46:9: note: dies ist die Stelle der vorherigen Definition + 46 | #define MAX_PARTICLES 4000 + | ^~~~~~~~~~~~~ +In file included from src/audio.c:1: +include/kknd.h:10:16: warning: »/*« innerhalb des Kommentars [-Wcomment] + 10 | * matching src/*.c translation units. +include/kknd.h:277:9: warning: »MAX_PARTICLES« redefiniert + 277 | #define MAX_PARTICLES 2048 + | ^~~~~~~~~~~~~ +include/kknd.h:46:9: note: dies ist die Stelle der vorherigen Definition + 46 | #define MAX_PARTICLES 4000 + | ^~~~~~~~~~~~~ +In file included from src/save.c:1: +include/kknd.h:10:16: warning: »/*« innerhalb des Kommentars [-Wcomment] + 10 | * matching src/*.c translation units. +include/kknd.h:277:9: warning: »MAX_PARTICLES« redefiniert + 277 | #define MAX_PARTICLES 2048 + | ^~~~~~~~~~~~~ +include/kknd.h:46:9: note: dies ist die Stelle der vorherigen Definition + 46 | #define MAX_PARTICLES 4000 + | ^~~~~~~~~~~~~ +In file included from src/engine.c:1: +include/kknd.h:10:16: warning: »/*« innerhalb des Kommentars [-Wcomment] + 10 | * matching src/*.c translation units. +include/kknd.h:277:9: warning: »MAX_PARTICLES« redefiniert + 277 | #define MAX_PARTICLES 2048 + | ^~~~~~~~~~~~~ +include/kknd.h:46:9: note: dies ist die Stelle der vorherigen Definition + 46 | #define MAX_PARTICLES 4000 + | ^~~~~~~~~~~~~ +In file included from src/main.c:1: +include/kknd.h:10:16: warning: »/*« innerhalb des Kommentars [-Wcomment] + 10 | * matching src/*.c translation units. +include/kknd.h:277:9: warning: »MAX_PARTICLES« redefiniert + 277 | #define MAX_PARTICLES 2048 + | ^~~~~~~~~~~~~ +include/kknd.h:46:9: note: dies ist die Stelle der vorherigen Definition + 46 | #define MAX_PARTICLES 4000 + | ^~~~~~~~~~~~~ +src/main.c: In Funktion »main«: +src/main.c:50:16: warning: Variable »diff« gesetzt, aber nicht verwendet [-Wunused-but-set-variable=] + 50 | Difficulty diff = DIFF_NORMAL; + | ^~~~ +src/main.c: Auf höchster Ebene: +src/main.c:32:13: warning: »save_snapshot« definiert, aber nicht verwendet [-Wunused-function] + 32 | static void save_snapshot(Game *g, int n) { + | ^~~~~~~~~~~~~ +openkknd: built -> ./openkknd +usage: ./openkknd [--easy|--normal|--hard] [--load savegame.kknd] diff --git a/kknd2-runs/20260713-093614-q14-deepseek/qwen14b.err b/kknd2-runs/20260713-093614-q14-deepseek/qwen14b.err new file mode 100644 index 0000000..591853d --- /dev/null +++ b/kknd2-runs/20260713-093614-q14-deepseek/qwen14b.err @@ -0,0 +1,2 @@ + +[qwen2.5-coder:14b · 97809ms · ok] diff --git a/kknd2-runs/20260713-093614-q14-deepseek/qwen14b.out b/kknd2-runs/20260713-093614-q14-deepseek/qwen14b.out new file mode 100644 index 0000000..71a841b --- /dev/null +++ b/kknd2-runs/20260713-093614-q14-deepseek/qwen14b.out @@ -0,0 +1,8 @@ +```json +{ + "name": "exec", + "arguments": { + "command": "cd /home/vx/src/openkknd-qwen14b && ./build.sh" + } +} +``` diff --git a/kknd2-runs/20260713-093614-q14-deepseek/run.sh b/kknd2-runs/20260713-093614-q14-deepseek/run.sh new file mode 100755 index 0000000..477e735 --- /dev/null +++ b/kknd2-runs/20260713-093614-q14-deepseek/run.sh @@ -0,0 +1,42 @@ +#!/usr/bin/env bash +set -uo pipefail +export PATH="$HOME/.local/bin:/usr/bin:$PATH" +export OLLAMA_API_KEY=ollama-local +export OLLAMA_HOST=127.0.0.1 +export OPENCLAW_TIMEOUT=1200 +LOGDIR="${1:?}" +PROMPT_Q14=$(cat "$LOGDIR/prompt-qwen14b.txt") +PROMPT_DS=$(cat "$LOGDIR/prompt-deepseek.txt") + +echo "==== $(date -Is) QWEN14 start ====" | tee -a "$LOGDIR/master.log" +ollama-gpu stop hermes3:8b 2>/dev/null || true +ollama-gpu stop qwen2.5-coder:7b 2>/dev/null || true +ollama-gpu stop deepseek-coder:6.7b 2>/dev/null || true + +"$HOME/agent-coder" --qwen14 -s openkknd-qwen14b "$PROMPT_Q14" \ + >"$LOGDIR/qwen14b.out" 2>"$LOGDIR/qwen14b.err" +echo "qwen14 exit=$?" | tee -a "$LOGDIR/master.log" +tail -50 "$LOGDIR/qwen14b.out" | tee -a "$LOGDIR/master.log" +(cd "$HOME/src/openkknd-qwen14b" && ./build.sh) >"$LOGDIR/qwen14b-build.txt" 2>&1 || true +ls -la "$HOME/src/openkknd-qwen14b" | tee -a "$LOGDIR/master.log" + +echo "==== $(date -Is) DEEPSEEK start ====" | tee -a "$LOGDIR/master.log" +ollama-gpu stop qwen2.5-coder:14b 2>/dev/null || true +ollama-gpu stop qwen2.5-coder:7b 2>/dev/null || true +ollama-gpu stop hermes3:8b 2>/dev/null || true + +"$HOME/agent-coder" --deepseek -s openkknd-deepseek "$PROMPT_DS" \ + >"$LOGDIR/deepseek.out" 2>"$LOGDIR/deepseek.err" +echo "deepseek exit=$?" | tee -a "$LOGDIR/master.log" +tail -50 "$LOGDIR/deepseek.out" | tee -a "$LOGDIR/master.log" +(cd "$HOME/src/openkknd-deepseek" && ./build.sh) >"$LOGDIR/deepseek-build.txt" 2>&1 || true +ls -la "$HOME/src/openkknd-deepseek" | tee -a "$LOGDIR/master.log" + +echo "==== $(date -Is) DONE ====" | tee -a "$LOGDIR/master.log" +# summary +for d in openkknd-qwen14b openkknd-deepseek; do + echo "-- $d --" | tee -a "$LOGDIR/master.log" + test -x "$HOME/src/$d/openkknd" && echo binary=yes || echo binary=no + test -f "$HOME/src/$d/README.md" && echo README=yes || echo README=no + test -d "$HOME/src/$d/.git" && echo git=yes || echo git=no +done | tee -a "$LOGDIR/master.log" diff --git a/llm-configs-openkknd2/README.md b/llm-configs-openkknd2/README.md new file mode 100644 index 0000000..a1cc38e --- /dev/null +++ b/llm-configs-openkknd2/README.md @@ -0,0 +1,31 @@ +# openkknd2 launchers (per host / tool) + +Each script prepares **`$OPENKKND2_ROOT`** under `~/src/` (unique folder per tool/host) and prints the agent brief for autonomous work. + +| Script | Host | Tool | Default workdir | +|--------|------|------|-----------------| +| `scripts/start-mac-osaurus.sh` | Mac | Osaurus agent | `~/src/openkknd2-osaurus` | +| `scripts/start-mac-jan.sh` | Mac | Jan | `~/src/openkknd2-jan` | +| `scripts/start-amanita-coder.sh` | amanita | agent-coder / OpenClaw | `~/src/openkknd2-am-coder` | +| `scripts/start-amanita-pics.sh` | amanita | am-agent-pics + code | `~/src/openkknd2-am-pics` | +| `scripts/start-amanita-vids.sh` | amanita | am-agent-vids + cinematics | `~/src/openkknd2-am-vids` | +| `scripts/common.sh` | all | shared bootstrap | — | + +## Env + +```bash +export LLM_CONFIGS=~/src/llm-configs # this repo +export OPENKKND2_ROOT=~/src/openkknd2- +``` + +## Agent must + +1. Work only under `$OPENKKND2_ROOT` (plus read-only knowledge). +2. Stay autonomous → playable game + headless tests. +3. **No copyrighted KKnD assets.** +4. Cinematics: original pipeline (instruction 18–19). + +## Instructions + +- [../instructions/18-openkknd2-autonomous-rebuild.md](../instructions/18-openkknd2-autonomous-rebuild.md) +- [../instructions/19-openkknd2-cinematics.md](../instructions/19-openkknd2-cinematics.md) diff --git a/llm-configs-openkknd2/scripts/common.sh b/llm-configs-openkknd2/scripts/common.sh new file mode 100755 index 0000000..9f55dc2 --- /dev/null +++ b/llm-configs-openkknd2/scripts/common.sh @@ -0,0 +1,108 @@ +#!/usr/bin/env bash +# Bootstrap OPENKKND2_ROOT and seed legal stubs. +set -euo pipefail + +openkknd2_bootstrap() { + local root="${1:?root}" + mkdir -p "$root"/{src,include,assets/cinematics,docs,scripts,tests} + if [[ ! -f "$root/LICENSE" ]]; then + cat >"$root/LICENSE" <<'L' +MIT License + +Copyright (c) 2026 openkknd2 contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +L + fi + if [[ ! -f "$root/THIRD_PARTY.md" ]]; then + cat >"$root/THIRD_PARTY.md" <<'T' +# Third-party + +Only permissive / original materials. **Forbidden:** KKnD/KKnD2/Xtreme proprietary assets. + +| Component | License | Note | +|-----------|---------|------| +| (none yet) | | | +T + fi + if [[ ! -f "$root/docs/LORE.md" ]]; then + cat >"$root/docs/LORE.md" <<'O' +# Lore (original) + +Working names (change freely; **do not** use commercial KKnD faction trademarks as official names): + +- Faction A: **Ashcoat Compact** (scrap tech, discipline) +- Faction B: **Bloomed** (mutated ecology, swarm tactics) + +Tone: dry wasteland humor, not a copy of any commercial script. +O + fi + if [[ ! -f "$root/AGENT_LOG.md" ]]; then + echo "# Agent log" >"$root/AGENT_LOG.md" + echo "" >>"$root/AGENT_LOG.md" + echo "Started $(date -u +%Y-%m-%dT%H:%M:%SZ)" >>"$root/AGENT_LOG.md" + fi + if [[ ! -f "$root/scripts/ci-headless.sh" ]]; then + cat >"$root/scripts/ci-headless.sh" <<'C' +#!/usr/bin/env bash +set -euo pipefail +ROOT="$(cd "$(dirname "$0")/.." && pwd)" +cd "$ROOT" +# Placeholder until engine exists: legal tree check +rg -i 'kknd2|krossfire|\.iso\b' --glob '!**/.git/**' . && { + echo "FORBIDDEN string found" >&2 + exit 2 +} || true +echo "ci-headless: stub OK (extend when binary exists)" +C + chmod +x "$root/scripts/ci-headless.sh" + fi + if [[ ! -f "$root/README.md" ]]; then + cat >"$root/README.md" </dev/null 2>&1 || return 0 + local name + while read -r name _; do + [[ -z "${name:-}" || "$name" == "NAME" ]] && continue + if [[ -n "$keep" && "$name" == *"$keep"* ]]; then + continue + fi + ollama stop "$name" 2>/dev/null || true + done < <(ollama ps 2>/dev/null | awk 'NR>1 {print $1}' || true) + # Never start pulls mid-rotate + pkill -TERM -f "ollama pull" 2>/dev/null || true +} + +# agent-coder model flags in order (skip already-failed qwen first if SKIP_QWEN=1) +# Devstral = agentic coding specialist (Mistral + All Hands) +MODELS_DEFAULT=( + "--devstral" + "--coder" + "--qwen314" + "--qwen14" + "--deepseek" + "--qwen38" + "--hermes" +) +# Optional: also try qwen3.6 last (heavy, already flaked once) +if [[ "${INCLUDE_QWEN36:-0}" == "1" ]]; then + MODELS_DEFAULT+=("--qwen36") +fi + +# shellcheck disable=SC2206 +MODELS=( ${ROTATE_MODELS:-${MODELS_DEFAULT[*]}} ) + +ts() { date -u +%Y-%m-%dT%H:%M:%SZ; } +stamp() { date -u +%Y%m%dT%H%M%SZ; } + +code_stats() { + python3 - <<'PY' +import os +from pathlib import Path +root = Path(os.environ.get("ROOT", ".")) +skip = {".git", "build", "node_modules", "rotate-logs"} +exts = {".c", ".h", ".cpp", ".hpp", ".cc", ".py", ".sh", ".cmake"} +files, lines = 0, 0 +names = [] +for p in root.rglob("*"): + if any(part in skip for part in p.parts): + continue + if not p.is_file(): + continue + if p.suffix.lower() not in exts and p.name != "CMakeLists.txt": + continue + try: + text = p.read_text(encoding="utf-8", errors="ignore") + except Exception: + continue + n = text.count("\n") + (1 if text and not text.endswith("\n") else 0) + files += 1 + lines += n + names.append(str(p.relative_to(root))) +has_build = any("ok_build" in n for n in names) +has_ai = any("ok_ai" in n for n in names) +print(f"{files} {lines} {int(has_build)} {int(has_ai)}") +PY +} + +ci_green() { + if [[ -x "$ROOT/scripts/ci-headless.sh" ]]; then + (cd "$ROOT" && ./scripts/ci-headless.sh) >/tmp/ok2-rotate-ci.log 2>&1 && return 0 + return 1 + fi + return 1 +} + +progressed() { + # args: before "files lines has_build has_ai" + local b_files b_lines b_b b_a a_files a_lines a_b a_a + read -r b_files b_lines b_b b_a <<<"$1" + read -r a_files a_lines a_b a_a <<<"$2" + # success if meaningful code growth OR new build/ai modules + still green + if (( a_lines >= b_lines + 40 )); then return 0; fi + if (( a_files > b_files && a_lines > b_lines )); then return 0; fi + if (( a_b > b_b || a_a > b_a )) && (( a_lines > b_lines )); then return 0; fi + return 1 +} + +if [[ ! -x "$AGENT" ]]; then + echo "error: agent-coder missing at $AGENT" >&2 + exit 1 +fi +if [[ ! -f "$PROMPT_FILE" ]]; then + echo "error: prompt missing: $PROMPT_FILE" >&2 + exit 1 +fi +PROMPT="$(cat "$PROMPT_FILE")" +SUMMARY="$LOG_DIR/rotate-$(stamp).md" +{ + echo "# openkknd2 model rotation" + echo + echo "- started: $(ts)" + echo "- workdir: \`$ROOT\`" + echo "- prompt: \`$PROMPT_FILE\`" + echo "- max_turns: $MAX_TURNS" + echo + echo "| # | flag | session | before lines | after lines | progress | ci |" + echo "|---|------|---------|--------------|-------------|----------|----|" +} >"$SUMMARY" + +echo "=== model rotation start $(ts) ===" | tee -a "$LOG_DIR/rotate.out" +echo "ROOT=$ROOT models=${MODELS[*]}" | tee -a "$LOG_DIR/rotate.out" + +i=0 +for flag in "${MODELS[@]}"; do + i=$((i + 1)) + sess="ok2-rot${i}-$(echo "$flag" | tr -cd 'A-Za-z0-9')" + runlog="$LOG_DIR/run-${i}$(echo "$flag" | tr -cd 'A-Za-z0-9').log" + echo | tee -a "$LOG_DIR/rotate.out" + echo ">>> [$i/${#MODELS[@]}] model flag=$flag session=$sess $(ts)" | tee -a "$LOG_DIR/rotate.out" + + export ROOT + before="$(code_stats)" + read -r bf bl bb ba <<<"$before" + echo " before: files=$bf lines=$bl build=$bb ai=$ba" | tee -a "$LOG_DIR/rotate.out" + + # Stop any leftover registered agent for this tree (best effort) + am-task stop all --reason "rotate: switch to $flag" 2>/dev/null || true + sleep 2 + if [[ "$SINGLE_FLIGHT" == "1" ]]; then + # Map flag → ollama name fragment for keep-list + keep_frag="" + case "$flag" in + --devstral) keep_frag="devstral" ;; + --coder) keep_frag="qwen3-coder" ;; + --qwen36) keep_frag="qwen3.6" ;; + --qwen314) keep_frag="qwen3:14b" ;; + --qwen38) keep_frag="qwen3:8b" ;; + --qwen14) keep_frag="qwen2.5-coder:14b" ;; + --deepseek) keep_frag="deepseek" ;; + --hermes) keep_frag="hermes" ;; + esac + echo " single-flight: unload other ollama models (keep~$keep_frag)" | tee -a "$LOG_DIR/rotate.out" + unload_other_ollama "$keep_frag" + sleep 1 + fi + + set +e + ( + cd "$ROOT" + # shellcheck disable=SC2086 + "$AGENT" $flag -s "$sess" -A --max-turns "$MAX_TURNS" --detach "$PROMPT" + ) >"$runlog" 2>&1 + launch_ec=$? + set -e + echo " launch_ec=$launch_ec log=$runlog" | tee -a "$LOG_DIR/rotate.out" + + # Wait until no active am-task (or timeout) + deadline=$(( $(date +%s) + ${ROTATE_WAIT_SECS:-3600} )) + while (( $(date +%s) < deadline )); do + active="$(ls "$HOME/.am-agent-tasks/active"/*.json 2>/dev/null | wc -l | tr -d ' ')" + if [[ "${active:-0}" == "0" ]]; then + break + fi + # still running + sleep 15 + done + # if still active after wait — stop and move on + if ls "$HOME/.am-agent-tasks/active"/*.json >/dev/null 2>&1; then + echo " timeout waiting — stop all" | tee -a "$LOG_DIR/rotate.out" + am-task stop all --reason "rotate: wait timeout after $flag" 2>/dev/null || true + sleep 2 + fi + + export ROOT + after="$(code_stats)" + read -r af al ab aa <<<"$after" + echo " after: files=$af lines=$al build=$ab ai=$aa" | tee -a "$LOG_DIR/rotate.out" + + ci="fail" + if ci_green; then ci="green"; fi + echo " ci=$ci" | tee -a "$LOG_DIR/rotate.out" + + prog="no" + if progressed "$before" "$after"; then prog="YES"; fi + + echo "| $i | \`$flag\` | \`$sess\` | $bl | $al | $prog | $ci |" >>"$SUMMARY" + + if [[ "$prog" == "YES" ]]; then + { + echo + echo "## WINNER" + echo + echo "- flag: \`$flag\`" + echo "- session: \`$sess\`" + echo "- lines: $bl → $al" + echo "- ci: $ci" + echo "- finished: $(ts)" + } >>"$SUMMARY" + echo "=== PROGRESS with $flag — stop rotation ===" | tee -a "$LOG_DIR/rotate.out" + echo "SUMMARY=$SUMMARY" + exit 0 + fi + echo " no meaningful progress — next model" | tee -a "$LOG_DIR/rotate.out" +done + +{ + echo + echo "## RESULT" + echo + echo "No model produced meaningful code growth within max_turns=$MAX_TURNS." + echo "- finished: $(ts)" +} >>"$SUMMARY" +echo "=== rotation exhausted without progress ===" | tee -a "$LOG_DIR/rotate.out" +echo "SUMMARY=$SUMMARY" +exit 1 diff --git a/llm-configs-openkknd2/scripts/start-amanita-coder.sh b/llm-configs-openkknd2/scripts/start-amanita-coder.sh new file mode 100755 index 0000000..f5de927 --- /dev/null +++ b/llm-configs-openkknd2/scripts/start-amanita-coder.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash +set -euo pipefail +export LLM_CONFIGS="${LLM_CONFIGS:-$HOME/src/llm-configs}" +# shellcheck source=common.sh +source "$LLM_CONFIGS/openkknd2/scripts/common.sh" +openkknd2_bootstrap "${OPENKKND2_ROOT:-$HOME/src/openkknd2-am-coder}" +openkknd2_print_brief "amanita-coder" +if [[ -x "$HOME/agent-coder" ]]; then + echo "Launch example:" + echo " cd \"\$OPENKKND2_ROOT\" && ~/agent-coder --coder \"Read \$LLM_CONFIGS/instructions/18-openkknd2-autonomous-rebuild.md and continue autonomous openkknd2 build with headless tests.\"" +else + echo "agent-coder not found at ~/agent-coder — start OpenClaw manually in \$OPENKKND2_ROOT" +fi diff --git a/llm-configs-openkknd2/scripts/start-amanita-pics.sh b/llm-configs-openkknd2/scripts/start-amanita-pics.sh new file mode 100755 index 0000000..7e3e073 --- /dev/null +++ b/llm-configs-openkknd2/scripts/start-amanita-pics.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash +set -euo pipefail +export LLM_CONFIGS="${LLM_CONFIGS:-$HOME/src/llm-configs}" +# shellcheck source=common.sh +source "$LLM_CONFIGS/openkknd2/scripts/common.sh" +openkknd2_bootstrap "${OPENKKND2_ROOT:-$HOME/src/openkknd2-am-pics}" +openkknd2_print_brief "amanita-pics" +echo "Concept art path: use $LLM_CONFIGS/media/am-agent-pics with original prompts only." +echo " $LLM_CONFIGS/media/am-agent-pics --width 512 --height 512 --steps 2 --prompt 'original scrap-tech scout silhouette, wasteland, no logos'" diff --git a/llm-configs-openkknd2/scripts/start-amanita-vids.sh b/llm-configs-openkknd2/scripts/start-amanita-vids.sh new file mode 100755 index 0000000..16adc70 --- /dev/null +++ b/llm-configs-openkknd2/scripts/start-amanita-vids.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash +set -euo pipefail +export LLM_CONFIGS="${LLM_CONFIGS:-$HOME/src/llm-configs}" +# shellcheck source=common.sh +source "$LLM_CONFIGS/openkknd2/scripts/common.sh" +openkknd2_bootstrap "${OPENKKND2_ROOT:-$HOME/src/openkknd2-am-vids}" +openkknd2_print_brief "amanita-vids" +echo "Cinematics: instruction 19 + knowledge 03-cinematics." +echo " export MEDIA_SECONDS=2 MEDIA_FPS=10 MEDIA_WIDTH=640 MEDIA_HEIGHT=360" +echo " $LLM_CONFIGS/media/am-agent-vids --make-still --seconds 2 --fps 10" +echo "Move results into \$OPENKKND2_ROOT/assets/cinematics/" diff --git a/llm-configs-openkknd2/scripts/start-mac-jan.sh b/llm-configs-openkknd2/scripts/start-mac-jan.sh new file mode 100755 index 0000000..b00b8e8 --- /dev/null +++ b/llm-configs-openkknd2/scripts/start-mac-jan.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +set -euo pipefail +export LLM_CONFIGS="${LLM_CONFIGS:-$HOME/src/llm-configs}" +# shellcheck source=common.sh +source "$LLM_CONFIGS/openkknd2/scripts/common.sh" +openkknd2_bootstrap "${OPENKKND2_ROOT:-$HOME/src/openkknd2-jan}" +openkknd2_print_brief "mac-jan" +echo "Next: Jan Ops/Coder thread → paste brief; enable tools; stay under \$OPENKKND2_ROOT" diff --git a/llm-configs-openkknd2/scripts/start-mac-osaurus.sh b/llm-configs-openkknd2/scripts/start-mac-osaurus.sh new file mode 100755 index 0000000..e5eb608 --- /dev/null +++ b/llm-configs-openkknd2/scripts/start-mac-osaurus.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +set -euo pipefail +export LLM_CONFIGS="${LLM_CONFIGS:-$HOME/src/llm-configs}" +# shellcheck source=common.sh +source "$LLM_CONFIGS/openkknd2/scripts/common.sh" +openkknd2_bootstrap "${OPENKKND2_ROOT:-$HOME/src/openkknd2-osaurus}" +openkknd2_print_brief "mac-osaurus" +echo "Next: open Osaurus Coder → paste brief + instruction 18; work only under \$OPENKKND2_ROOT" diff --git a/openkknd2-am-coder/.agent-coder-resume.sh b/openkknd2-am-coder/.agent-coder-resume.sh new file mode 100755 index 0000000..05903c7 --- /dev/null +++ b/openkknd2-am-coder/.agent-coder-resume.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash +set -euo pipefail +export PATH="${HOME}/.local/bin:/usr/local/bin:/usr/bin:/bin:${PATH:-}" +export LLM_CONFIGS=/home/vx/src/llm-configs +export OPENCLAW_MODEL=ollama/qwen3:14b +export OPENCLAW_TIMEOUT=900 +export AGENT_WORKDIR=/home/vx/src/openkknd2-am-coder +cd /home/vx/src/openkknd2-am-coder +exec /home/vx/agent-coder -s ok2meta-r6-qwen314 -A --max-turns 8 --iref 21-always-agent-logs.md RESUME:\ continue\ from\ AGENT_LOG.md\ and\ STATUS.md\ in\ this\ workdir.\ Do\ not\ re-ask.\ Emit\ ##\ AUTONOMOUS_STOP\ only\ when\ truly\ blocked. diff --git a/openkknd2-am-coder/.am-meta/META_LOG.md b/openkknd2-am-coder/.am-meta/META_LOG.md new file mode 100644 index 0000000..f28eb61 --- /dev/null +++ b/openkknd2-am-coder/.am-meta/META_LOG.md @@ -0,0 +1,144 @@ +# am-meta log + +- workdir: `/home/vx/src/openkknd2-am-coder` +- created: 2026-07-22T05:21:20Z + +- **2026-07-22T05:21:22Z** · meta-stop · hand off to am-meta + +## meta-start 2026-07-22T05:21:24Z +- workdir: `/home/vx/src/openkknd2-am-coder` +- models: `--devstral --coder --qwen314 --qwen14 --deepseek --qwen38` +- max_rounds: 16 max_turns/round: 8 min_line_delta: 30 +- **2026-07-22T05:21:24Z** · meta detached pid=1722387 out=/home/vx/src/openkknd2-am-coder/.am-meta/meta.out + +### round 1 / 16 · `--devstral` · before `9 183 0` +- **2026-07-22T05:21:25Z** · launch · flag=`--devstral` session=`ok2meta-r1-devstral` turns=8 +- **2026-07-22T05:21:34Z** · after `9 183 0` +- **2026-07-22T05:21:34Z** · progress · NO with `--devstral` (streak_fail=1) — next model + +### round 2 / 16 · `--coder` · before `9 183 0` +- **2026-07-22T05:21:35Z** · launch · flag=`--coder` session=`ok2meta-r2-coder` turns=8 +- **2026-07-22T05:22:20Z** · meta-stop · redeploy race fix + +## meta-start 2026-07-22T05:22:25Z +- workdir: `/home/vx/src/openkknd2-am-coder` +- models: `--devstral --coder --qwen314 --qwen14 --deepseek --qwen38` +- max_rounds: 16 max_turns/round: 8 min_line_delta: 30 +- **2026-07-22T05:22:25Z** · meta detached pid=1725467 out=/home/vx/src/openkknd2-am-coder/.am-meta/meta.out + +### round 1 / 16 · `--devstral` · before `9 183 0` +- **2026-07-22T05:22:26Z** · launch · flag=`--devstral` session=`ok2meta-r1-devstral` turns=8 +- **2026-07-22T05:28:56Z** · after `9 183 0` +- **2026-07-22T05:28:56Z** · progress · NO with `--devstral` (streak_fail=1) — next model + +### round 2 / 16 · `--coder` · before `9 183 0` +- **2026-07-22T05:28:58Z** · launch · flag=`--coder` session=`ok2meta-r2-coder` turns=8 +- **2026-07-22T05:34:48Z** · after `9 183 0` +- **2026-07-22T05:34:48Z** · progress · NO with `--coder` (streak_fail=2) — next model + +### round 3 / 16 · `--qwen314` · before `9 183 0` +- **2026-07-22T05:34:49Z** · launch · flag=`--qwen314` session=`ok2meta-r3-qwen314` turns=8 +- **2026-07-22T05:50:19Z** · after `10 208 2` +- **2026-07-22T05:50:19Z** · progress · YES with `--qwen314` — keep same model + +### round 4 / 16 · `--qwen314` · before `10 208 2` +- **2026-07-22T05:50:20Z** · launch · flag=`--qwen314` session=`ok2meta-r4-qwen314` turns=8 +- **2026-07-22T05:54:31Z** · after `10 208 2` +- **2026-07-22T05:54:31Z** · progress · NO with `--qwen314` (streak_fail=1) — next model + +### round 5 / 16 · `--qwen14` · before `10 208 2` +- **2026-07-22T05:54:32Z** · launch · flag=`--qwen14` session=`ok2meta-r5-qwen14` turns=8 +- **2026-07-22T05:59:02Z** · after `10 208 2` +- **2026-07-22T05:59:02Z** · progress · NO with `--qwen14` (streak_fail=2) — next model + +### round 6 / 16 · `--deepseek` · before `10 208 2` +- **2026-07-22T05:59:03Z** · launch · flag=`--deepseek` session=`ok2meta-r6-deepseek` turns=8 +- **2026-07-22T05:59:53Z** · after `10 208 2` +- **2026-07-22T05:59:53Z** · progress · NO with `--deepseek` (streak_fail=3) — next model + +### round 7 / 16 · `--qwen38` · before `10 208 2` +- **2026-07-22T05:59:54Z** · launch · flag=`--qwen38` session=`ok2meta-r7-qwen38` turns=8 +- **2026-07-22T06:03:58Z** · meta-stop · user: beende alle aufgaben +- **2026-07-22T06:05:00Z** · meta-stop · clean restart + +## meta-start 2026-07-22T06:05:01Z +- workdir: `/home/vx/src/openkknd2-am-coder` +- models: `--devstral --coder --qwen314 --qwen14 --deepseek --qwen38` (first = standard) +- stalls_before_switch: 2 accept_needs: 1 +- max_rounds: 16 max_turns/round: 8 min_line_delta: 30 +- mission: /home/vx/src/llm-configs/prompts/openkknd2-mission.md +- **2026-07-22T06:05:01Z** · meta detached pid=3574950 out=/home/vx/src/openkknd2-am-coder/.am-meta/meta.out + +### round 1 / 16 · `--devstral` · before `11 228 2` · streak_fail=0 +- **2026-07-22T06:05:02Z** · launch · flag=`--devstral` session=`ok2meta-r1-devstral` turns=8 +- **2026-07-22T06:05:32Z** · after `11 228 2` +- **2026-07-22T06:05:32Z** · needs · harvested NEED_INSTRUCTIONS → additive.md (mission unchanged) +- **2026-07-22T06:05:32Z** · DONE detected after round 1 +- **2026-07-22T06:07:12Z** · meta-stop · restart after false-DONE fix + +## meta-start 2026-07-22T06:07:13Z +- workdir: `/home/vx/src/openkknd2-am-coder` +- models: `--devstral --coder --qwen314 --qwen14 --deepseek --qwen38` (first = standard) +- stalls_before_switch: 2 accept_needs: 1 +- max_rounds: 16 max_turns/round: 8 min_line_delta: 30 +- mission: /home/vx/src/llm-configs/prompts/openkknd2-mission.md +- **2026-07-22T06:07:13Z** · meta detached pid=3577057 out=/home/vx/src/openkknd2-am-coder/.am-meta/meta.out + +### round 1 / 16 · `--devstral` · before `11 228 2` · streak_fail=0 +- **2026-07-22T06:07:14Z** · launch · flag=`--devstral` session=`ok2meta-r1-devstral` turns=8 +- **2026-07-22T06:07:43Z** · after `11 228 2` +- **2026-07-22T06:07:43Z** · progress · NO with `--devstral` (streak_fail=1 < 2) — **retry same model** + +### round 2 / 16 · `--devstral` · before `11 228 2` · streak_fail=1 +- **2026-07-22T06:07:44Z** · launch · flag=`--devstral` session=`ok2meta-r2-devstral` turns=8 +- **2026-07-22T06:09:11Z** · meta-stop · user: session end + +## meta-start 2026-07-22T06:09:21Z +- workdir: `/home/vx/src/openkknd2-am-coder` +- models: `--devstral --coder --qwen314 --qwen14 --deepseek --qwen38` (first = standard) +- stalls_before_switch: 2 accept_needs: 1 +- max_rounds: 16 max_turns/round: 8 min_line_delta: 30 +- mission: /home/vx/src/llm-configs/prompts/openkknd2-mission.md +- **2026-07-22T06:09:21Z** · meta detached pid=3579576 out=/home/vx/src/openkknd2-am-coder/.am-meta/meta.out + +### round 1 / 16 · `--devstral` · before `11 228 2` · streak_fail=0 +- **2026-07-22T06:09:22Z** · launch · flag=`--devstral` session=`ok2meta-r1-devstral` turns=8 +- **2026-07-22T06:09:37Z** · meta-stop · user: n — todos beenden +- **2026-07-22T06:09:56Z** · meta-stop · recover after wrong stop + +## meta-start 2026-07-22T06:09:57Z +- workdir: `/home/vx/src/openkknd2-am-coder` +- models: `--devstral --coder --qwen314 --qwen14 --deepseek --qwen38` (first = standard) +- stalls_before_switch: 2 accept_needs: 1 +- max_rounds: 16 max_turns/round: 8 min_line_delta: 30 +- mission: /home/vx/src/llm-configs/prompts/openkknd2-mission.md +- **2026-07-22T06:09:57Z** · meta detached pid=3580490 out=/home/vx/src/openkknd2-am-coder/.am-meta/meta.out + +### round 1 / 16 · `--devstral` · before `11 228 2` · streak_fail=0 +- **2026-07-22T06:09:59Z** · launch · flag=`--devstral` session=`ok2meta-r1-devstral` turns=8 +- **2026-07-22T06:10:28Z** · after `11 228 2` +- **2026-07-22T06:10:28Z** · progress · NO with `--devstral` (streak_fail=1 < 2) — **retry same model** + +### round 2 / 16 · `--devstral` · before `11 228 2` · streak_fail=1 +- **2026-07-22T06:10:29Z** · launch · flag=`--devstral` session=`ok2meta-r2-devstral` turns=8 +- **2026-07-22T06:15:58Z** · after `11 228 2` +- **2026-07-22T06:15:58Z** · progress · NO with `--devstral` (streak_fail=2 ≥ 2) — **switch model** + +### round 3 / 16 · `--coder` · before `11 228 2` · streak_fail=0 +- **2026-07-22T06:15:59Z** · launch · flag=`--coder` session=`ok2meta-r3-coder` turns=8 +- **2026-07-22T06:21:48Z** · after `12 273 2` +- **2026-07-22T06:21:48Z** · progress · YES with `--coder` — keep same model; reset stall counter + +### round 4 / 16 · `--coder` · before `12 273 2` · streak_fail=0 +- **2026-07-22T06:21:50Z** · launch · flag=`--coder` session=`ok2meta-r4-coder` turns=8 +- **2026-07-22T06:26:39Z** · after `12 273 2` +- **2026-07-22T06:26:39Z** · progress · NO with `--coder` (streak_fail=1 < 2) — **retry same model** + +### round 5 / 16 · `--coder` · before `12 273 2` · streak_fail=1 +- **2026-07-22T06:26:40Z** · launch · flag=`--coder` session=`ok2meta-r5-coder` turns=8 +- **2026-07-22T06:30:49Z** · after `12 273 2` +- **2026-07-22T06:30:49Z** · progress · NO with `--coder` (streak_fail=2 ≥ 2) — **switch model** + +### round 6 / 16 · `--qwen314` · before `12 273 2` · streak_fail=0 +- **2026-07-22T06:30:50Z** · launch · flag=`--qwen314` session=`ok2meta-r6-qwen314` turns=8 +- **2026-07-22T06:46:00Z** · stall · AGENT_LOG frozen ≥900s while task active → stop child diff --git a/openkknd2-am-coder/.am-meta/additive.md b/openkknd2-am-coder/.am-meta/additive.md new file mode 100644 index 0000000..c8d4a49 --- /dev/null +++ b/openkknd2-am-coder/.am-meta/additive.md @@ -0,0 +1,6 @@ +# Additive instructions (harvested / evolved) + +These lines may grow when the agent emits `## NEED_INSTRUCTIONS`. +They **must not** contradict the mission in `mission.md`. +Mission always wins. + diff --git a/openkknd2-am-coder/.am-meta/launch-20260722T052125Zdevstral.log b/openkknd2-am-coder/.am-meta/launch-20260722T052125Zdevstral.log new file mode 100644 index 0000000..336c88e --- /dev/null +++ b/openkknd2-am-coder/.am-meta/launch-20260722T052125Zdevstral.log @@ -0,0 +1,24 @@ +AGENT_WORKDIR=/home/vx/src/openkknd2-am-coder +logs: /home/vx/src/openkknd2-am-coder/AGENT_LOG.md /home/vx/src/openkknd2-am-coder/STATUS.md /home/vx/src/openkknd2-am-coder/TASK.md +detached pid=1722763 log=/tmp/agent-coder-detach-20260722T052130Z.log +control: am-task list | am-task stop latest | am-task resume latest +### ACTIVE (/home/vx/.am-agent-tasks/active) + +(none active) + +### RECENT HISTORY (5) (/home/vx/.am-agent-tasks/history) + +════════════════════════════════════════════════════════════ +id: 20260722T051649Z-1717777 +status: interrupted [process dead] +kind: agent-coder +host: amanita +model: ollama/devstral:latest +session: ok2-rot1-devstral +pid / pgid: 1717646 / 1717475 +started: 2026-07-22T05:16:49Z +stopped: 2026-07-22T05:21:20Z +elapsed: 4m 31s +stop_reason: hand off to am-meta +stop_how: am-task stop +stop_hard: False diff --git a/openkknd2-am-coder/.am-meta/launch-20260722T052135Zcoder.log b/openkknd2-am-coder/.am-meta/launch-20260722T052135Zcoder.log new file mode 100644 index 0000000..d2484d2 --- /dev/null +++ b/openkknd2-am-coder/.am-meta/launch-20260722T052135Zcoder.log @@ -0,0 +1,24 @@ +AGENT_WORKDIR=/home/vx/src/openkknd2-am-coder +logs: /home/vx/src/openkknd2-am-coder/AGENT_LOG.md /home/vx/src/openkknd2-am-coder/STATUS.md /home/vx/src/openkknd2-am-coder/TASK.md +detached pid=1723577 log=/tmp/agent-coder-detach-20260722T052143Z.log +control: am-task list | am-task stop latest | am-task resume latest +### ACTIVE (/home/vx/.am-agent-tasks/active) + +════════════════════════════════════════════════════════════ +id: 20260722T052138Z-1723242 +status: running [process alive] +kind: agent-coder +host: amanita +model: ollama/devstral:latest +session: ok2meta-r1-devstral +pid / pgid: 1722763 / 1722243 +started: 2026-07-22T05:21:38Z +stopped: — (still running) +elapsed: 6s +title: CONTINUE openkknd2 ONLY under /home/vx/src/openkknd2-am-coder (this workdir). + +Headless smoke is GREEN (map + units). You implement the next milestones yourself. + +This turn must produce REAL file changes (not just plans): +1. Add building placement (HQ and/or barracks) with resource cost — new headers/sources if needed +2. Add a simple faction AI tick (gather / build / train) diff --git a/openkknd2-am-coder/.am-meta/launch-20260722T052226Zdevstral.log b/openkknd2-am-coder/.am-meta/launch-20260722T052226Zdevstral.log new file mode 100644 index 0000000..fc63acd --- /dev/null +++ b/openkknd2-am-coder/.am-meta/launch-20260722T052226Zdevstral.log @@ -0,0 +1,24 @@ +AGENT_WORKDIR=/home/vx/src/openkknd2-am-coder +logs: /home/vx/src/openkknd2-am-coder/AGENT_LOG.md /home/vx/src/openkknd2-am-coder/STATUS.md /home/vx/src/openkknd2-am-coder/TASK.md +detached pid=1725651 log=/tmp/agent-coder-detach-20260722T052230Z.log +control: am-task list | am-task stop latest | am-task resume latest +### ACTIVE (/home/vx/.am-agent-tasks/active) + +(none active) + +### RECENT HISTORY (5) (/home/vx/.am-agent-tasks/history) + +════════════════════════════════════════════════════════════ +id: 20260722T052152Z-1724054 +status: done [process dead] +kind: agent-coder +host: amanita +model: ollama/qwen3-coder:latest +session: ok2meta-r2-coder +pid / pgid: 1723577 / 1722243 +started: 2026-07-22T05:21:52Z +stopped: 2026-07-22T05:22:19Z +elapsed: 27s +exit_code: 0 +stop_reason: completed +stop_how: finish diff --git a/openkknd2-am-coder/.am-meta/launch-20260722T052858Zcoder.log b/openkknd2-am-coder/.am-meta/launch-20260722T052858Zcoder.log new file mode 100644 index 0000000..f0a3238 --- /dev/null +++ b/openkknd2-am-coder/.am-meta/launch-20260722T052858Zcoder.log @@ -0,0 +1,24 @@ +AGENT_WORKDIR=/home/vx/src/openkknd2-am-coder +logs: /home/vx/src/openkknd2-am-coder/AGENT_LOG.md /home/vx/src/openkknd2-am-coder/STATUS.md /home/vx/src/openkknd2-am-coder/TASK.md +detached pid=1737910 log=/tmp/agent-coder-detach-20260722T052901Z.log +control: am-task list | am-task stop latest | am-task resume latest +### ACTIVE (/home/vx/.am-agent-tasks/active) + +(none active) + +### RECENT HISTORY (5) (/home/vx/.am-agent-tasks/history) + +════════════════════════════════════════════════════════════ +id: 20260722T052232Z-1725762 +status: done [process dead] +kind: agent-coder +host: amanita +model: ollama/devstral:latest +session: ok2meta-r1-devstral +pid / pgid: 1725651 / 1725180 +started: 2026-07-22T05:22:32Z +stopped: 2026-07-22T05:28:47Z +elapsed: 6m 15s +exit_code: 0 +stop_reason: completed +stop_how: finish diff --git a/openkknd2-am-coder/.am-meta/launch-20260722T053449Zqwen314.log b/openkknd2-am-coder/.am-meta/launch-20260722T053449Zqwen314.log new file mode 100644 index 0000000..4e4ab83 --- /dev/null +++ b/openkknd2-am-coder/.am-meta/launch-20260722T053449Zqwen314.log @@ -0,0 +1,24 @@ +AGENT_WORKDIR=/home/vx/src/openkknd2-am-coder +logs: /home/vx/src/openkknd2-am-coder/AGENT_LOG.md /home/vx/src/openkknd2-am-coder/STATUS.md /home/vx/src/openkknd2-am-coder/TASK.md +detached pid=2574247 log=/tmp/agent-coder-detach-20260722T053453Z.log +control: am-task list | am-task stop latest | am-task resume latest +### ACTIVE (/home/vx/.am-agent-tasks/active) + +(none active) + +### RECENT HISTORY (5) (/home/vx/.am-agent-tasks/history) + +════════════════════════════════════════════════════════════ +id: 20260722T052904Z-1738030 +status: done [process dead] +kind: agent-coder +host: amanita +model: ollama/qwen3-coder:latest +session: ok2meta-r2-coder +pid / pgid: 1737910 / 1725180 +started: 2026-07-22T05:29:04Z +stopped: 2026-07-22T05:34:35Z +elapsed: 5m 31s +exit_code: 0 +stop_reason: completed +stop_how: finish diff --git a/openkknd2-am-coder/.am-meta/launch-20260722T055020Zqwen314.log b/openkknd2-am-coder/.am-meta/launch-20260722T055020Zqwen314.log new file mode 100644 index 0000000..4c4e6fb --- /dev/null +++ b/openkknd2-am-coder/.am-meta/launch-20260722T055020Zqwen314.log @@ -0,0 +1,24 @@ +AGENT_WORKDIR=/home/vx/src/openkknd2-am-coder +logs: /home/vx/src/openkknd2-am-coder/AGENT_LOG.md /home/vx/src/openkknd2-am-coder/STATUS.md /home/vx/src/openkknd2-am-coder/TASK.md +detached pid=3217597 log=/tmp/agent-coder-detach-20260722T055024Z.log +control: am-task list | am-task stop latest | am-task resume latest +am-task overview · host resources: WARN +──────────────────────────────────────────────────────────────────────────────────────────────── + RAM 12.2GiB/62.7GiB used (19%) avail 50.5GiB + load 9.86 13.58 10.98 cpus=32 + swap 7.7GiB/62.7GiB (12%) + GPU0 NVIDIA GeForce GTX 1060 6GB VRAM 4987/6144 MiB (81%) util 0% high + ollama qwen3:14b bdbd181c33f2 12 GB 68%/32% CPU/GPU 32768 4 minutes from now +──────────────────────────────────────────────────────────────────────────────────────────────── +● running ● done ● interrupted ● failed ● run/DEAD · am-task show | list --full + +ID STATUS MODEL ELAPSED CODE SESSION WORKDIR / TITLE +──────────────────────────────────────────────────────────────────────────────────────────────── +ACTIVE (none) + +RECENT HISTORY (8) +20260722T053455Z-2574378 done qwen3:14b 15m13s 207 ok2meta-r3-qwen3 ~/src/openkknd2-am-coder + CONTINUE openkknd2 ONLY under /home/vx/src/openkknd2-am-coder (this w… +20260722T052904Z-1738030 done qwen3-coder:latest 5m31s 207 ok2meta-r2-coder ~/src/openkknd2-am-coder + CONTINUE openkknd2 ONLY under /home/vx/src/openkknd2-am-coder (this w… +20260722T052232Z-1725762 done devstral:latest 6m15s 207 ok2meta-r1-devst ~/src/openkknd2-am-coder diff --git a/openkknd2-am-coder/.am-meta/launch-20260722T055432Zqwen14.log b/openkknd2-am-coder/.am-meta/launch-20260722T055432Zqwen14.log new file mode 100644 index 0000000..9cd783a --- /dev/null +++ b/openkknd2-am-coder/.am-meta/launch-20260722T055432Zqwen14.log @@ -0,0 +1,24 @@ +AGENT_WORKDIR=/home/vx/src/openkknd2-am-coder +logs: /home/vx/src/openkknd2-am-coder/AGENT_LOG.md /home/vx/src/openkknd2-am-coder/STATUS.md /home/vx/src/openkknd2-am-coder/TASK.md +detached pid=3338342 log=/tmp/agent-coder-detach-20260722T055435Z.log +control: am-task list | am-task stop latest | am-task resume latest +am-task overview · host resources: OK +──────────────────────────────────────────────────────────────────────────────────────────────── + RAM 8.9GiB/62.7GiB used (14%) avail 53.8GiB + load 8.99 10.51 10.27 cpus=32 + swap 7.7GiB/62.7GiB (12%) + GPU0 NVIDIA GeForce GTX 1060 6GB VRAM 1063/6144 MiB (17%) util 0% + ollama (no chat model loaded) +──────────────────────────────────────────────────────────────────────────────────────────────── +● running ● done ● interrupted ● failed ● run/DEAD · am-task show | list --full + +ID STATUS MODEL ELAPSED CODE SESSION WORKDIR / TITLE +──────────────────────────────────────────────────────────────────────────────────────────────── +ACTIVE (none) + +RECENT HISTORY (8) +20260722T055027Z-3217726 done qwen3:14b 4m04s 207 ok2meta-r4-qwen3 ~/src/openkknd2-am-coder + CONTINUE openkknd2 ONLY under /home/vx/src/openkknd2-am-coder (this w… +20260722T053455Z-2574378 done qwen3:14b 15m13s 207 ok2meta-r3-qwen3 ~/src/openkknd2-am-coder + CONTINUE openkknd2 ONLY under /home/vx/src/openkknd2-am-coder (this w… +20260722T052904Z-1738030 done qwen3-coder:latest 5m31s 207 ok2meta-r2-coder ~/src/openkknd2-am-coder diff --git a/openkknd2-am-coder/.am-meta/launch-20260722T055903Zdeepseek.log b/openkknd2-am-coder/.am-meta/launch-20260722T055903Zdeepseek.log new file mode 100644 index 0000000..b9131b9 --- /dev/null +++ b/openkknd2-am-coder/.am-meta/launch-20260722T055903Zdeepseek.log @@ -0,0 +1,24 @@ +AGENT_WORKDIR=/home/vx/src/openkknd2-am-coder +logs: /home/vx/src/openkknd2-am-coder/AGENT_LOG.md /home/vx/src/openkknd2-am-coder/STATUS.md /home/vx/src/openkknd2-am-coder/TASK.md +detached pid=3346536 log=/tmp/agent-coder-detach-20260722T055907Z.log +control: am-task list | am-task stop latest | am-task resume latest +am-task overview · host resources: OK +──────────────────────────────────────────────────────────────────────────────────────────────── + RAM 8.9GiB/62.7GiB used (14%) avail 53.7GiB + load 7.76 8.74 9.51 cpus=32 + swap 7.8GiB/62.7GiB (12%) + GPU0 NVIDIA GeForce GTX 1060 6GB VRAM 1063/6144 MiB (17%) util 0% + ollama (no chat model loaded) +──────────────────────────────────────────────────────────────────────────────────────────────── +● running ● done ● interrupted ● failed ● run/DEAD · am-task show | list --full + +ID STATUS MODEL ELAPSED CODE SESSION WORKDIR / TITLE +──────────────────────────────────────────────────────────────────────────────────────────────── +ACTIVE (none) + +RECENT HISTORY (8) +20260722T055438Z-3338469 done qwen2.5-coder:14b 4m08s 207 ok2meta-r5-qwen1 ~/src/openkknd2-am-coder + CONTINUE openkknd2 ONLY under /home/vx/src/openkknd2-am-coder (this w… +20260722T055027Z-3217726 done qwen3:14b 4m04s 207 ok2meta-r4-qwen3 ~/src/openkknd2-am-coder + CONTINUE openkknd2 ONLY under /home/vx/src/openkknd2-am-coder (this w… +20260722T053455Z-2574378 done qwen3:14b 15m13s 207 ok2meta-r3-qwen3 ~/src/openkknd2-am-coder diff --git a/openkknd2-am-coder/.am-meta/launch-20260722T055954Zqwen38.log b/openkknd2-am-coder/.am-meta/launch-20260722T055954Zqwen38.log new file mode 100644 index 0000000..1b258de --- /dev/null +++ b/openkknd2-am-coder/.am-meta/launch-20260722T055954Zqwen38.log @@ -0,0 +1,24 @@ +AGENT_WORKDIR=/home/vx/src/openkknd2-am-coder +logs: /home/vx/src/openkknd2-am-coder/AGENT_LOG.md /home/vx/src/openkknd2-am-coder/STATUS.md /home/vx/src/openkknd2-am-coder/TASK.md +detached pid=3348289 log=/tmp/agent-coder-detach-20260722T055958Z.log +control: am-task list | am-task stop latest | am-task resume latest +am-task overview · host resources: OK +──────────────────────────────────────────────────────────────────────────────────────────────── + RAM 8.8GiB/62.7GiB used (14%) avail 53.9GiB + load 6.07 8.23 9.30 cpus=32 + swap 7.8GiB/62.7GiB (12%) + GPU0 NVIDIA GeForce GTX 1060 6GB VRAM 1063/6144 MiB (17%) util 0% + ollama (no chat model loaded) +──────────────────────────────────────────────────────────────────────────────────────────────── +● running ● done ● interrupted ● failed ● run/DEAD · am-task show | list --full + +ID STATUS MODEL ELAPSED CODE SESSION WORKDIR / TITLE +──────────────────────────────────────────────────────────────────────────────────────────────── +ACTIVE (none) + +RECENT HISTORY (8) +20260722T055909Z-3346665 done deepseek-coder:6.7b 29s 207 ok2meta-r6-deeps ~/src/openkknd2-am-coder + CONTINUE openkknd2 ONLY under /home/vx/src/openkknd2-am-coder (this w… +20260722T055438Z-3338469 done qwen2.5-coder:14b 4m08s 207 ok2meta-r5-qwen1 ~/src/openkknd2-am-coder + CONTINUE openkknd2 ONLY under /home/vx/src/openkknd2-am-coder (this w… +20260722T055027Z-3217726 done qwen3:14b 4m04s 207 ok2meta-r4-qwen3 ~/src/openkknd2-am-coder diff --git a/openkknd2-am-coder/.am-meta/launch-20260722T060502Zdevstral.log b/openkknd2-am-coder/.am-meta/launch-20260722T060502Zdevstral.log new file mode 100644 index 0000000..ceb022c --- /dev/null +++ b/openkknd2-am-coder/.am-meta/launch-20260722T060502Zdevstral.log @@ -0,0 +1,24 @@ +AGENT_WORKDIR=/home/vx/src/openkknd2-am-coder +logs: /home/vx/src/openkknd2-am-coder/AGENT_LOG.md /home/vx/src/openkknd2-am-coder/STATUS.md /home/vx/src/openkknd2-am-coder/TASK.md +detached pid=3575225 log=/tmp/agent-coder-detach-20260722T060506Z.log +control: am-task list | am-task stop latest | am-task resume latest +am-task overview · host resources: OK +──────────────────────────────────────────────────────────────────────────────────────────────── + RAM 8.8GiB/62.7GiB used (14%) avail 53.9GiB + load 5.14 8.06 9.00 cpus=32 + swap 7.8GiB/62.7GiB (12%) + GPU0 NVIDIA GeForce GTX 1060 6GB VRAM 1063/6144 MiB (17%) util 0% + ollama (no chat model loaded) +──────────────────────────────────────────────────────────────────────────────────────────────── +● running ● done ● interrupted ● failed ● run/DEAD · am-task show | list --full + +ID STATUS MODEL ELAPSED CODE SESSION WORKDIR / TITLE +──────────────────────────────────────────────────────────────────────────────────────────────── +ACTIVE (none) + +RECENT HISTORY (8) +20260722T060000Z-3348413 interrupted qwen3:8b 3m57s 226 ok2meta-r7-qwen3 ~/src/openkknd2-am-coder + CONTINUE openkknd2 ONLY under /home/vx/src/openkknd2-am-coder (this w… +20260722T055909Z-3346665 done deepseek-coder:6.7b 29s 226 ok2meta-r6-deeps ~/src/openkknd2-am-coder + CONTINUE openkknd2 ONLY under /home/vx/src/openkknd2-am-coder (this w… +20260722T055438Z-3338469 done qwen2.5-coder:14b 4m08s 226 ok2meta-r5-qwen1 ~/src/openkknd2-am-coder diff --git a/openkknd2-am-coder/.am-meta/launch-20260722T060714Zdevstral.log b/openkknd2-am-coder/.am-meta/launch-20260722T060714Zdevstral.log new file mode 100644 index 0000000..cb3f170 --- /dev/null +++ b/openkknd2-am-coder/.am-meta/launch-20260722T060714Zdevstral.log @@ -0,0 +1,24 @@ +AGENT_WORKDIR=/home/vx/src/openkknd2-am-coder +logs: /home/vx/src/openkknd2-am-coder/AGENT_LOG.md /home/vx/src/openkknd2-am-coder/STATUS.md /home/vx/src/openkknd2-am-coder/TASK.md +detached pid=3577255 log=/tmp/agent-coder-detach-20260722T060718Z.log +control: am-task list | am-task stop latest | am-task resume latest +am-task overview · host resources: OK +──────────────────────────────────────────────────────────────────────────────────────────────── + RAM 8.5GiB/62.7GiB used (14%) avail 54.2GiB + load 1.46 5.59 7.96 cpus=32 + swap 7.8GiB/62.7GiB (12%) + GPU0 NVIDIA GeForce GTX 1060 6GB VRAM 1063/6144 MiB (17%) util 0% + ollama (no chat model loaded) +──────────────────────────────────────────────────────────────────────────────────────────────── +● running ● done ● interrupted ● failed ● run/DEAD · am-task show | list --full + +ID STATUS MODEL ELAPSED CODE SESSION WORKDIR / TITLE +──────────────────────────────────────────────────────────────────────────────────────────────── +ACTIVE (none) + +RECENT HISTORY (8) +20260722T060509Z-3575357 done devstral:latest 13s 226 ok2meta-r1-devst ~/src/openkknd2-am-coder + # MISSION (immutable — do not renegotiate) # Mission (immutable) — o… +20260722T060000Z-3348413 interrupted qwen3:8b 3m57s 226 ok2meta-r7-qwen3 ~/src/openkknd2-am-coder + CONTINUE openkknd2 ONLY under /home/vx/src/openkknd2-am-coder (this w… +20260722T055909Z-3346665 done deepseek-coder:6.7b 29s 226 ok2meta-r6-deeps ~/src/openkknd2-am-coder diff --git a/openkknd2-am-coder/.am-meta/launch-20260722T060744Zdevstral.log b/openkknd2-am-coder/.am-meta/launch-20260722T060744Zdevstral.log new file mode 100644 index 0000000..5ad12f0 --- /dev/null +++ b/openkknd2-am-coder/.am-meta/launch-20260722T060744Zdevstral.log @@ -0,0 +1,24 @@ +AGENT_WORKDIR=/home/vx/src/openkknd2-am-coder +logs: /home/vx/src/openkknd2-am-coder/AGENT_LOG.md /home/vx/src/openkknd2-am-coder/STATUS.md /home/vx/src/openkknd2-am-coder/TASK.md +detached pid=3578155 log=/tmp/agent-coder-detach-20260722T060748Z.log +control: am-task list | am-task stop latest | am-task resume latest +am-task overview · host resources: OK +──────────────────────────────────────────────────────────────────────────────────────────────── + RAM 8.6GiB/62.7GiB used (14%) avail 54.1GiB + load 1.39 5.18 7.75 cpus=32 + swap 7.8GiB/62.7GiB (12%) + GPU0 NVIDIA GeForce GTX 1060 6GB VRAM 1063/6144 MiB (17%) util 0% + ollama (no chat model loaded) +──────────────────────────────────────────────────────────────────────────────────────────────── +● running ● done ● interrupted ● failed ● run/DEAD · am-task show | list --full + +ID STATUS MODEL ELAPSED CODE SESSION WORKDIR / TITLE +──────────────────────────────────────────────────────────────────────────────────────────────── +ACTIVE (none) + +RECENT HISTORY (8) +20260722T060720Z-3577414 done devstral:latest 14s 226 ok2meta-r1-devst ~/src/openkknd2-am-coder + # MISSION (immutable — do not renegotiate) # Mission (immutable) — o… +20260722T060509Z-3575357 done devstral:latest 13s 226 ok2meta-r1-devst ~/src/openkknd2-am-coder + # MISSION (immutable — do not renegotiate) # Mission (immutable) — o… +20260722T060000Z-3348413 interrupted qwen3:8b 3m57s 226 ok2meta-r7-qwen3 ~/src/openkknd2-am-coder diff --git a/openkknd2-am-coder/.am-meta/launch-20260722T060922Zdevstral.log b/openkknd2-am-coder/.am-meta/launch-20260722T060922Zdevstral.log new file mode 100644 index 0000000..f32cea6 --- /dev/null +++ b/openkknd2-am-coder/.am-meta/launch-20260722T060922Zdevstral.log @@ -0,0 +1,24 @@ +AGENT_WORKDIR=/home/vx/src/openkknd2-am-coder +logs: /home/vx/src/openkknd2-am-coder/AGENT_LOG.md /home/vx/src/openkknd2-am-coder/STATUS.md /home/vx/src/openkknd2-am-coder/TASK.md +detached pid=3579768 log=/tmp/agent-coder-detach-20260722T060925Z.log +control: am-task list | am-task stop latest | am-task resume latest +am-task overview · host resources: OK +──────────────────────────────────────────────────────────────────────────────────────────────── + RAM 8.7GiB/62.7GiB used (14%) avail 54.0GiB + load 2.27 4.51 7.25 cpus=32 + swap 7.8GiB/62.7GiB (12%) + GPU0 NVIDIA GeForce GTX 1060 6GB VRAM 1063/6144 MiB (17%) util 0% + ollama (no chat model loaded) +──────────────────────────────────────────────────────────────────────────────────────────────── +● running ● done ● interrupted ● failed ● run/DEAD · am-task show | list --full + +ID STATUS MODEL ELAPSED CODE SESSION WORKDIR / TITLE +──────────────────────────────────────────────────────────────────────────────────────────────── +ACTIVE (none) + +RECENT HISTORY (8) +20260722T060750Z-3578279 interrupted devstral:latest 1m20s 226 ok2meta-r2-devst ~/src/openkknd2-am-coder + # MISSION (immutable — do not renegotiate) # Mission (immutable) — o… +20260722T060720Z-3577414 done devstral:latest 14s 226 ok2meta-r1-devst ~/src/openkknd2-am-coder + # MISSION (immutable — do not renegotiate) # Mission (immutable) — o… +20260722T060509Z-3575357 done devstral:latest 13s 226 ok2meta-r1-devst ~/src/openkknd2-am-coder diff --git a/openkknd2-am-coder/.am-meta/launch-20260722T060959Zdevstral.log b/openkknd2-am-coder/.am-meta/launch-20260722T060959Zdevstral.log new file mode 100644 index 0000000..cf2da21 --- /dev/null +++ b/openkknd2-am-coder/.am-meta/launch-20260722T060959Zdevstral.log @@ -0,0 +1,24 @@ +AGENT_WORKDIR=/home/vx/src/openkknd2-am-coder +logs: /home/vx/src/openkknd2-am-coder/AGENT_LOG.md /home/vx/src/openkknd2-am-coder/STATUS.md /home/vx/src/openkknd2-am-coder/TASK.md +detached pid=3580679 log=/tmp/agent-coder-detach-20260722T061002Z.log +control: am-task list | am-task stop latest | am-task resume latest +am-task overview · host resources: OK +──────────────────────────────────────────────────────────────────────────────────────────────── + RAM 8.7GiB/62.7GiB used (14%) avail 54.0GiB + load 1.71 4.12 7.02 cpus=32 + swap 7.8GiB/62.7GiB (12%) + GPU0 NVIDIA GeForce GTX 1060 6GB VRAM 1063/6144 MiB (17%) util 0% + ollama (no chat model loaded) +──────────────────────────────────────────────────────────────────────────────────────────────── +● running ● done ● interrupted ● failed ● run/DEAD · am-task show | list --full + +ID STATUS MODEL ELAPSED CODE SESSION WORKDIR / TITLE +──────────────────────────────────────────────────────────────────────────────────────────────── +ACTIVE (none) + +RECENT HISTORY (8) +20260722T060928Z-3579895 interrupted devstral:latest 8s 226 ok2meta-r1-devst ~/src/openkknd2-am-coder + # MISSION (immutable — do not renegotiate) # Mission (immutable) — o… +20260722T060750Z-3578279 interrupted devstral:latest 1m20s 226 ok2meta-r2-devst ~/src/openkknd2-am-coder + # MISSION (immutable — do not renegotiate) # Mission (immutable) — o… +20260722T060720Z-3577414 done devstral:latest 14s 226 ok2meta-r1-devst ~/src/openkknd2-am-coder diff --git a/openkknd2-am-coder/.am-meta/launch-20260722T061029Zdevstral.log b/openkknd2-am-coder/.am-meta/launch-20260722T061029Zdevstral.log new file mode 100644 index 0000000..66d859e --- /dev/null +++ b/openkknd2-am-coder/.am-meta/launch-20260722T061029Zdevstral.log @@ -0,0 +1,24 @@ +AGENT_WORKDIR=/home/vx/src/openkknd2-am-coder +logs: /home/vx/src/openkknd2-am-coder/AGENT_LOG.md /home/vx/src/openkknd2-am-coder/STATUS.md /home/vx/src/openkknd2-am-coder/TASK.md +detached pid=3581522 log=/tmp/agent-coder-detach-20260722T061032Z.log +control: am-task list | am-task stop latest | am-task resume latest +am-task overview · host resources: OK +──────────────────────────────────────────────────────────────────────────────────────────────── + RAM 8.7GiB/62.7GiB used (14%) avail 54.0GiB + load 1.62 3.87 6.84 cpus=32 + swap 7.8GiB/62.7GiB (12%) + GPU0 NVIDIA GeForce GTX 1060 6GB VRAM 1063/6144 MiB (17%) util 0% + ollama (no chat model loaded) +──────────────────────────────────────────────────────────────────────────────────────────────── +● running ● done ● interrupted ● failed ● run/DEAD · am-task show | list --full + +ID STATUS MODEL ELAPSED CODE SESSION WORKDIR / TITLE +──────────────────────────────────────────────────────────────────────────────────────────────── +ACTIVE (none) + +RECENT HISTORY (8) +20260722T061005Z-3580802 done devstral:latest 13s 226 ok2meta-r1-devst ~/src/openkknd2-am-coder + # MISSION (immutable — do not renegotiate) # Mission (immutable) — o… +20260722T060928Z-3579895 interrupted devstral:latest 8s 226 ok2meta-r1-devst ~/src/openkknd2-am-coder + # MISSION (immutable — do not renegotiate) # Mission (immutable) — o… +20260722T060750Z-3578279 interrupted devstral:latest 1m20s 226 ok2meta-r2-devst ~/src/openkknd2-am-coder diff --git a/openkknd2-am-coder/.am-meta/launch-20260722T061559Zcoder.log b/openkknd2-am-coder/.am-meta/launch-20260722T061559Zcoder.log new file mode 100644 index 0000000..8991493 --- /dev/null +++ b/openkknd2-am-coder/.am-meta/launch-20260722T061559Zcoder.log @@ -0,0 +1,24 @@ +AGENT_WORKDIR=/home/vx/src/openkknd2-am-coder +logs: /home/vx/src/openkknd2-am-coder/AGENT_LOG.md /home/vx/src/openkknd2-am-coder/STATUS.md /home/vx/src/openkknd2-am-coder/TASK.md +detached pid=3590824 log=/tmp/agent-coder-detach-20260722T061603Z.log +control: am-task list | am-task stop latest | am-task resume latest +am-task overview · host resources: OK +──────────────────────────────────────────────────────────────────────────────────────────────── + RAM 8.9GiB/62.7GiB used (14%) avail 53.8GiB + load 10.51 8.21 7.68 cpus=32 + swap 7.8GiB/62.7GiB (12%) + GPU0 NVIDIA GeForce GTX 1060 6GB VRAM 1063/6144 MiB (17%) util 0% + ollama (no chat model loaded) +──────────────────────────────────────────────────────────────────────────────────────────────── +● running ● done ● interrupted ● failed ● run/DEAD · am-task show | list --full + +ID STATUS MODEL ELAPSED CODE SESSION WORKDIR / TITLE +──────────────────────────────────────────────────────────────────────────────────────────────── +ACTIVE (none) + +RECENT HISTORY (8) +20260722T061035Z-3581648 done devstral:latest 5m10s 226 ok2meta-r2-devst ~/src/openkknd2-am-coder + # MISSION (immutable — do not renegotiate) # Mission (immutable) — o… +20260722T061005Z-3580802 done devstral:latest 13s 226 ok2meta-r1-devst ~/src/openkknd2-am-coder + # MISSION (immutable — do not renegotiate) # Mission (immutable) — o… +20260722T060928Z-3579895 interrupted devstral:latest 8s 226 ok2meta-r1-devst ~/src/openkknd2-am-coder diff --git a/openkknd2-am-coder/.am-meta/launch-20260722T062150Zcoder.log b/openkknd2-am-coder/.am-meta/launch-20260722T062150Zcoder.log new file mode 100644 index 0000000..b74553e --- /dev/null +++ b/openkknd2-am-coder/.am-meta/launch-20260722T062150Zcoder.log @@ -0,0 +1,24 @@ +AGENT_WORKDIR=/home/vx/src/openkknd2-am-coder +logs: /home/vx/src/openkknd2-am-coder/AGENT_LOG.md /home/vx/src/openkknd2-am-coder/STATUS.md /home/vx/src/openkknd2-am-coder/TASK.md +detached pid=424118 log=/tmp/agent-coder-detach-20260722T062153Z.log +control: am-task list | am-task stop latest | am-task resume latest +am-task overview · host resources: WARN +──────────────────────────────────────────────────────────────────────────────────────────────── + RAM 10.0GiB/62.7GiB used (16%) avail 52.7GiB + load 5.82 6.64 7.07 cpus=32 + swap 7.8GiB/62.7GiB (12%) + GPU0 NVIDIA GeForce GTX 1060 6GB VRAM 5069/6144 MiB (83%) util 0% high + ollama qwen3-coder:latest 06c1097efce0 20 GB 80%/20% CPU/GPU 32768 4 minutes fro +──────────────────────────────────────────────────────────────────────────────────────────────── +● running ● done ● interrupted ● failed ● run/DEAD · am-task show | list --full + +ID STATUS MODEL ELAPSED CODE SESSION WORKDIR / TITLE +──────────────────────────────────────────────────────────────────────────────────────────────── +ACTIVE (none) + +RECENT HISTORY (8) +20260722T061605Z-3590955 done qwen3-coder:latest 5m40s 270 ok2meta-r3-coder ~/src/openkknd2-am-coder + # MISSION (immutable — do not renegotiate) # Mission (immutable) — o… +20260722T061035Z-3581648 done devstral:latest 5m10s 270 ok2meta-r2-devst ~/src/openkknd2-am-coder + # MISSION (immutable — do not renegotiate) # Mission (immutable) — o… +20260722T061005Z-3580802 done devstral:latest 13s 270 ok2meta-r1-devst ~/src/openkknd2-am-coder diff --git a/openkknd2-am-coder/.am-meta/launch-20260722T062640Zcoder.log b/openkknd2-am-coder/.am-meta/launch-20260722T062640Zcoder.log new file mode 100644 index 0000000..e532fe9 --- /dev/null +++ b/openkknd2-am-coder/.am-meta/launch-20260722T062640Zcoder.log @@ -0,0 +1,24 @@ +AGENT_WORKDIR=/home/vx/src/openkknd2-am-coder +logs: /home/vx/src/openkknd2-am-coder/AGENT_LOG.md /home/vx/src/openkknd2-am-coder/STATUS.md /home/vx/src/openkknd2-am-coder/TASK.md +detached pid=1588119 log=/tmp/agent-coder-detach-20260722T062643Z.log +control: am-task list | am-task stop latest | am-task resume latest +am-task overview · host resources: WARN +──────────────────────────────────────────────────────────────────────────────────────────────── + RAM 9.9GiB/62.7GiB used (16%) avail 52.8GiB + load 7.55 7.23 7.16 cpus=32 + swap 7.8GiB/62.7GiB (12%) + GPU0 NVIDIA GeForce GTX 1060 6GB VRAM 5069/6144 MiB (83%) util 0% high + ollama qwen3-coder:latest 06c1097efce0 20 GB 80%/20% CPU/GPU 32768 4 minutes fro +──────────────────────────────────────────────────────────────────────────────────────────────── +● running ● done ● interrupted ● failed ● run/DEAD · am-task show | list --full + +ID STATUS MODEL ELAPSED CODE SESSION WORKDIR / TITLE +──────────────────────────────────────────────────────────────────────────────────────────────── +ACTIVE (none) + +RECENT HISTORY (8) +20260722T062156Z-424245 done qwen3-coder:latest 4m40s 270 ok2meta-r4-coder ~/src/openkknd2-am-coder + # MISSION (immutable — do not renegotiate) # Mission (immutable) — o… +20260722T061605Z-3590955 done qwen3-coder:latest 5m40s 270 ok2meta-r3-coder ~/src/openkknd2-am-coder + # MISSION (immutable — do not renegotiate) # Mission (immutable) — o… +20260722T061035Z-3581648 done devstral:latest 5m10s 270 ok2meta-r2-devst ~/src/openkknd2-am-coder diff --git a/openkknd2-am-coder/.am-meta/launch-20260722T063050Zqwen314.log b/openkknd2-am-coder/.am-meta/launch-20260722T063050Zqwen314.log new file mode 100644 index 0000000..07f915c --- /dev/null +++ b/openkknd2-am-coder/.am-meta/launch-20260722T063050Zqwen314.log @@ -0,0 +1,24 @@ +AGENT_WORKDIR=/home/vx/src/openkknd2-am-coder +logs: /home/vx/src/openkknd2-am-coder/AGENT_LOG.md /home/vx/src/openkknd2-am-coder/STATUS.md /home/vx/src/openkknd2-am-coder/TASK.md +detached pid=2474402 log=/tmp/agent-coder-detach-20260722T063054Z.log +control: am-task list | am-task stop latest | am-task resume latest +am-task overview · host resources: OK +──────────────────────────────────────────────────────────────────────────────────────────────── + RAM 9.0GiB/62.7GiB used (14%) avail 53.7GiB + load 5.73 6.57 6.86 cpus=32 + swap 7.8GiB/62.7GiB (12%) + GPU0 NVIDIA GeForce GTX 1060 6GB VRAM 1063/6144 MiB (17%) util 0% + ollama (no chat model loaded) +──────────────────────────────────────────────────────────────────────────────────────────────── +● running ● done ● interrupted ● failed ● run/DEAD · am-task show | list --full + +ID STATUS MODEL ELAPSED CODE SESSION WORKDIR / TITLE +──────────────────────────────────────────────────────────────────────────────────────────────── +ACTIVE (none) + +RECENT HISTORY (8) +20260722T062646Z-1588248 done qwen3-coder:latest 3m50s 270 ok2meta-r5-coder ~/src/openkknd2-am-coder + # MISSION (immutable — do not renegotiate) # Mission (immutable) — o… +20260722T062156Z-424245 done qwen3-coder:latest 4m40s 270 ok2meta-r4-coder ~/src/openkknd2-am-coder + # MISSION (immutable — do not renegotiate) # Mission (immutable) — o… +20260722T061605Z-3590955 done qwen3-coder:latest 5m40s 270 ok2meta-r3-coder ~/src/openkknd2-am-coder diff --git a/openkknd2-am-coder/.am-meta/meta.out b/openkknd2-am-coder/.am-meta/meta.out new file mode 100644 index 0000000..0666495 --- /dev/null +++ b/openkknd2-am-coder/.am-meta/meta.out @@ -0,0 +1,150 @@ + +## meta-start 2026-07-22T05:21:24Z +- workdir: `/home/vx/src/openkknd2-am-coder` +- models: `--devstral --coder --qwen314 --qwen14 --deepseek --qwen38` +- max_rounds: 16 max_turns/round: 8 min_line_delta: 30 +:25: DeprecationWarning: datetime.datetime.utcnow() is deprecated and scheduled for removal in a future version. Use timezone-aware objects to represent datetimes in UTC: datetime.datetime.now(datetime.UTC). +/home/vx/src/openkknd2-am-coder/.am-meta/state.json +:25: DeprecationWarning: datetime.datetime.utcnow() is deprecated and scheduled for removal in a future version. Use timezone-aware objects to represent datetimes in UTC: datetime.datetime.now(datetime.UTC). +/home/vx/src/openkknd2-am-coder/.am-meta/state.json + +### round 1 / 16 · `--devstral` · before `9 183 0` +- **2026-07-22T05:21:34Z** · after `9 183 0` +- **2026-07-22T05:21:34Z** · progress · NO with `--devstral` (streak_fail=1) — next model +:25: DeprecationWarning: datetime.datetime.utcnow() is deprecated and scheduled for removal in a future version. Use timezone-aware objects to represent datetimes in UTC: datetime.datetime.now(datetime.UTC). +/home/vx/src/openkknd2-am-coder/.am-meta/state.json +:25: DeprecationWarning: datetime.datetime.utcnow() is deprecated and scheduled for removal in a future version. Use timezone-aware objects to represent datetimes in UTC: datetime.datetime.now(datetime.UTC). +/home/vx/src/openkknd2-am-coder/.am-meta/state.json + +### round 2 / 16 · `--coder` · before `9 183 0` + +## meta-start 2026-07-22T05:22:25Z +- workdir: `/home/vx/src/openkknd2-am-coder` +- models: `--devstral --coder --qwen314 --qwen14 --deepseek --qwen38` +- max_rounds: 16 max_turns/round: 8 min_line_delta: 30 +/home/vx/src/openkknd2-am-coder/.am-meta/state.json +/home/vx/src/openkknd2-am-coder/.am-meta/state.json + +### round 1 / 16 · `--devstral` · before `9 183 0` +- **2026-07-22T05:28:56Z** · after `9 183 0` +- **2026-07-22T05:28:56Z** · progress · NO with `--devstral` (streak_fail=1) — next model +/home/vx/src/openkknd2-am-coder/.am-meta/state.json +/home/vx/src/openkknd2-am-coder/.am-meta/state.json + +### round 2 / 16 · `--coder` · before `9 183 0` +- **2026-07-22T05:34:48Z** · after `9 183 0` +- **2026-07-22T05:34:48Z** · progress · NO with `--coder` (streak_fail=2) — next model +/home/vx/src/openkknd2-am-coder/.am-meta/state.json +/home/vx/src/openkknd2-am-coder/.am-meta/state.json + +### round 3 / 16 · `--qwen314` · before `9 183 0` +- **2026-07-22T05:50:19Z** · after `10 208 2` +- **2026-07-22T05:50:19Z** · progress · YES with `--qwen314` — keep same model +/home/vx/src/openkknd2-am-coder/.am-meta/state.json +/home/vx/src/openkknd2-am-coder/.am-meta/state.json + +### round 4 / 16 · `--qwen314` · before `10 208 2` +- **2026-07-22T05:54:31Z** · after `10 208 2` +- **2026-07-22T05:54:31Z** · progress · NO with `--qwen314` (streak_fail=1) — next model +/home/vx/src/openkknd2-am-coder/.am-meta/state.json +/home/vx/src/openkknd2-am-coder/.am-meta/state.json + +### round 5 / 16 · `--qwen14` · before `10 208 2` +- **2026-07-22T05:59:02Z** · after `10 208 2` +- **2026-07-22T05:59:02Z** · progress · NO with `--qwen14` (streak_fail=2) — next model +/home/vx/src/openkknd2-am-coder/.am-meta/state.json +/home/vx/src/openkknd2-am-coder/.am-meta/state.json + +### round 6 / 16 · `--deepseek` · before `10 208 2` +- **2026-07-22T05:59:53Z** · after `10 208 2` +- **2026-07-22T05:59:53Z** · progress · NO with `--deepseek` (streak_fail=3) — next model +/home/vx/src/openkknd2-am-coder/.am-meta/state.json +/home/vx/src/openkknd2-am-coder/.am-meta/state.json + +### round 7 / 16 · `--qwen38` · before `10 208 2` + +## meta-start 2026-07-22T06:05:01Z +- workdir: `/home/vx/src/openkknd2-am-coder` +- models: `--devstral --coder --qwen314 --qwen14 --deepseek --qwen38` (first = standard) +- stalls_before_switch: 2 accept_needs: 1 +- max_rounds: 16 max_turns/round: 8 min_line_delta: 30 +- mission: /home/vx/src/llm-configs/prompts/openkknd2-mission.md +/home/vx/src/openkknd2-am-coder/.am-meta/state.json +/home/vx/src/openkknd2-am-coder/.am-meta/state.json + +### round 1 / 16 · `--devstral` · before `11 228 2` · streak_fail=0 +- **2026-07-22T06:05:32Z** · after `11 228 2` +- **2026-07-22T06:05:32Z** · needs · harvested NEED_INSTRUCTIONS → additive.md (mission unchanged) +- **2026-07-22T06:05:32Z** · DONE detected after round 1 +/home/vx/src/openkknd2-am-coder/.am-meta/state.json + +## meta-start 2026-07-22T06:07:13Z +- workdir: `/home/vx/src/openkknd2-am-coder` +- models: `--devstral --coder --qwen314 --qwen14 --deepseek --qwen38` (first = standard) +- stalls_before_switch: 2 accept_needs: 1 +- max_rounds: 16 max_turns/round: 8 min_line_delta: 30 +- mission: /home/vx/src/llm-configs/prompts/openkknd2-mission.md +/home/vx/src/openkknd2-am-coder/.am-meta/state.json +/home/vx/src/openkknd2-am-coder/.am-meta/state.json + +### round 1 / 16 · `--devstral` · before `11 228 2` · streak_fail=0 +- **2026-07-22T06:07:43Z** · after `11 228 2` +- **2026-07-22T06:07:43Z** · progress · NO with `--devstral` (streak_fail=1 < 2) — **retry same model** +/home/vx/src/openkknd2-am-coder/.am-meta/state.json +/home/vx/src/openkknd2-am-coder/.am-meta/state.json + +### round 2 / 16 · `--devstral` · before `11 228 2` · streak_fail=1 + +## meta-start 2026-07-22T06:09:21Z +- workdir: `/home/vx/src/openkknd2-am-coder` +- models: `--devstral --coder --qwen314 --qwen14 --deepseek --qwen38` (first = standard) +- stalls_before_switch: 2 accept_needs: 1 +- max_rounds: 16 max_turns/round: 8 min_line_delta: 30 +- mission: /home/vx/src/llm-configs/prompts/openkknd2-mission.md +/home/vx/src/openkknd2-am-coder/.am-meta/state.json +/home/vx/src/openkknd2-am-coder/.am-meta/state.json + +### round 1 / 16 · `--devstral` · before `11 228 2` · streak_fail=0 + +## meta-start 2026-07-22T06:09:57Z +- workdir: `/home/vx/src/openkknd2-am-coder` +- models: `--devstral --coder --qwen314 --qwen14 --deepseek --qwen38` (first = standard) +- stalls_before_switch: 2 accept_needs: 1 +- max_rounds: 16 max_turns/round: 8 min_line_delta: 30 +- mission: /home/vx/src/llm-configs/prompts/openkknd2-mission.md +/home/vx/src/openkknd2-am-coder/.am-meta/state.json +/home/vx/src/openkknd2-am-coder/.am-meta/state.json + +### round 1 / 16 · `--devstral` · before `11 228 2` · streak_fail=0 +- **2026-07-22T06:10:28Z** · after `11 228 2` +- **2026-07-22T06:10:28Z** · progress · NO with `--devstral` (streak_fail=1 < 2) — **retry same model** +/home/vx/src/openkknd2-am-coder/.am-meta/state.json +/home/vx/src/openkknd2-am-coder/.am-meta/state.json + +### round 2 / 16 · `--devstral` · before `11 228 2` · streak_fail=1 +- **2026-07-22T06:15:58Z** · after `11 228 2` +- **2026-07-22T06:15:58Z** · progress · NO with `--devstral` (streak_fail=2 ≥ 2) — **switch model** +/home/vx/src/openkknd2-am-coder/.am-meta/state.json +/home/vx/src/openkknd2-am-coder/.am-meta/state.json + +### round 3 / 16 · `--coder` · before `11 228 2` · streak_fail=0 +- **2026-07-22T06:21:48Z** · after `12 273 2` +- **2026-07-22T06:21:48Z** · progress · YES with `--coder` — keep same model; reset stall counter +/home/vx/src/openkknd2-am-coder/.am-meta/state.json +/home/vx/src/openkknd2-am-coder/.am-meta/state.json + +### round 4 / 16 · `--coder` · before `12 273 2` · streak_fail=0 +- **2026-07-22T06:26:39Z** · after `12 273 2` +- **2026-07-22T06:26:39Z** · progress · NO with `--coder` (streak_fail=1 < 2) — **retry same model** +/home/vx/src/openkknd2-am-coder/.am-meta/state.json +/home/vx/src/openkknd2-am-coder/.am-meta/state.json + +### round 5 / 16 · `--coder` · before `12 273 2` · streak_fail=1 +- **2026-07-22T06:30:49Z** · after `12 273 2` +- **2026-07-22T06:30:49Z** · progress · NO with `--coder` (streak_fail=2 ≥ 2) — **switch model** +/home/vx/src/openkknd2-am-coder/.am-meta/state.json +/home/vx/src/openkknd2-am-coder/.am-meta/state.json + +### round 6 / 16 · `--qwen314` · before `12 273 2` · streak_fail=0 +- **2026-07-22T06:46:00Z** · stall · AGENT_LOG frozen ≥900s while task active → stop child +20260722T063056Z-2474530 diff --git a/openkknd2-am-coder/.am-meta/meta.pid b/openkknd2-am-coder/.am-meta/meta.pid new file mode 100644 index 0000000..a5b7bbb --- /dev/null +++ b/openkknd2-am-coder/.am-meta/meta.pid @@ -0,0 +1 @@ +3580490 diff --git a/openkknd2-am-coder/.am-meta/mission.md b/openkknd2-am-coder/.am-meta/mission.md new file mode 100644 index 0000000..2a09e74 --- /dev/null +++ b/openkknd2-am-coder/.am-meta/mission.md @@ -0,0 +1,19 @@ +# Mission (immutable) — openkknd2 + +Build and grow **openkknd2**: an original open-source post-apoc RTS scaffold (genre-inspired only; **not** commercial KKnD). + +## Fixed goals + +1. Work only under the designated workdir (`openkknd2-am-coder` / `$OPENKKND2_ROOT`). +2. Keep `./scripts/ci-headless.sh` **GREEN** after meaningful changes. +3. Advance the milestone ladder: map/units → buildings/economy → faction AI → playable skirmish loop → (later) cinematics. +4. **Legal:** MIT/original only — never proprietary KKnD assets, rips, or reverse-engineered game files. +5. Always append `AGENT_LOG.md` and keep `STATUS.md` current. + +## Non-goals (do not replace the mission with these) + +- Rewriting the project as a different game +- Asking the user for permission to continue +- Stopping after planning without code/test changes + +This mission text is **law** for all models and all meta rounds. diff --git a/openkknd2-am-coder/.am-meta/state.json b/openkknd2-am-coder/.am-meta/state.json new file mode 100644 index 0000000..c4303fa --- /dev/null +++ b/openkknd2-am-coder/.am-meta/state.json @@ -0,0 +1,20 @@ +{ + "status": "running", + "stop_reason": "recover after wrong stop", + "updated": "2026-07-22T06:30:49Z", + "meta_pid": 3580490, + "workdir": "/home/vx/src/openkknd2-am-coder", + "round": 6, + "max_rounds": 16, + "model_flag": "--qwen314", + "session": "ok2meta-r6-qwen314", + "before": "12 273 2", + "streak_fail": 0, + "after": "12 273 2", + "next_model": "--qwen314", + "last_progress": "--coder", + "stalls_before_switch": 2, + "accept_needs": 1, + "winner": "--devstral", + "switched": 1 +} \ No newline at end of file diff --git a/openkknd2-am-coder/AGENT_LOG.md b/openkknd2-am-coder/AGENT_LOG.md new file mode 100644 index 0000000..92c817a --- /dev/null +++ b/openkknd2-am-coder/AGENT_LOG.md @@ -0,0 +1,1926 @@ +# Agent Log +- **2026-07-22T08:00:00Z** · started task · **RUNNING** — initiating implementation of building placement and faction AI tick + +- **2026-07-22T06:45:00Z** · initialized project structure · **ok** — created README, LICENSE, THIRD_PARTY.md- **2026-07-22T04:48:01Z** · turn-1-end · **ok** — ec=0 +- **2026-07-22T04:48:01Z** · turn-2-start · **RUNNING** — model=ollama/qwen3-coder:latest session=openkknd2-demo +- **2026-07-22T04:48:03Z** · turn-2-end · **ok** — ec=0 +- **2026-07-22T04:48:03Z** · turn-3-start · **RUNNING** — model=ollama/qwen3-coder:latest session=openkknd2-demo +- **2026-07-22T04:48:04Z** · ci-headless · **ok** — ./build/openkknd2 --headless --smoke +- **2026-07-22T04:48:12Z** · turn-3-end · **ok** — ec=0 +- **2026-07-22T04:48:12Z** · turn-4-start · **RUNNING** — model=ollama/qwen3-coder:latest session=openkknd2-demo +- **2026-07-22T04:48:13Z** · turn-4-end · **ok** — ec=0 +- **2026-07-22T04:48:13Z** · STATUS→RUNNING · **RUNNING** — max-turns 4 reached + +### run 2026-07-22T04:49:25Z + +- **title:** CONTINUE openkknd2 in /home/vx/src/openkknd2-am-coder only. Headless smoke already GREEN (./scripts/ci-headless.sh). Next milestones: units/map stubs, improve ci, document build, keep AGENT_LOG. No proprietary assets. Emit AUTONOMOUS_STOP only when blocked. +- **host:** amanita +- **event:** START + + +### run 2026-07-22T04:49:28Z + +- **title:** CONTINUE openkknd2 in /home/vx/src/openkknd2-am-coder only. Headless smoke already GREEN (./scripts/ci-headless.sh). Next milestones: units/map stubs, improve ci, document build, keep AGENT_LOG. No proprietary assets. Emit AUTONOMOUS_STOP only when blocked. +- **host:** amanita +- **event:** START + +- **2026-07-22T04:49:28Z** · am-task-register · **ok** — id=20260722T044928Z-1206819 +- **2026-07-22T04:49:28Z** · turn-1-start · **RUNNING** — model=ollama/qwen3-coder:latest session=openkknd2-w2 +- **2026-07-22T04:51:37Z** · INTERRUPTED · **stop** — am-task stop 20260722T044928Z-1206819 +- **2026-07-22T04:53:08Z** · INTERRUPTED · **stop** — am-task stop 20260722T044928Z-1206819 +- **2026-07-22T04:53:08Z** · switch model · **qwen3.6** — longer autonomy wave + +### run 2026-07-22T04:53:11Z + +- **title:** CONTINUE openkknd2 ONLY under /home/vx/src/openkknd2-am-coder. Headless smoke is GREEN with map/units. Next: building placement stub, simple faction AI tick, improve README/BUILD, keep logs. No proprietary KKnD assets. Work thoroughly each turn — do not end turns empty. Emit AUTONOMOUS_STOP only when truly blocked. +- **host:** amanita +- **event:** START + + +### run 2026-07-22T04:53:14Z + +- **title:** CONTINUE openkknd2 ONLY under /home/vx/src/openkknd2-am-coder. Headless smoke is GREEN with map/units. Next: building placement stub, simple faction AI tick, improve README/BUILD, keep logs. No proprietary KKnD assets. Work thoroughly each turn — do not end turns empty. Emit AUTONOMOUS_STOP only when truly blocked. +- **host:** amanita +- **event:** START + +- **2026-07-22T04:53:14Z** · am-task-register · **ok** — id=20260722T045314Z-1583025 +- **2026-07-22T04:53:14Z** · turn-1-start · **RUNNING** — model=ollama/qwen3.6:latest session=openkknd2-q36 +- **2026-07-22T04:55:28Z** · turn-1-end · **ok** — ec=0 +- **2026-07-22T04:55:28Z** · turn-2-start · **RUNNING** — model=ollama/qwen3.6:latest session=openkknd2-q36 +- **2026-07-22T04:55:40Z** · turn-2-end · **ok** — ec=0 +- **2026-07-22T04:55:40Z** · turn-3-start · **RUNNING** — model=ollama/qwen3.6:latest session=openkknd2-q36 +- **2026-07-22T04:55:42Z** · turn-3-end · **ok** — ec=0 +- **2026-07-22T04:55:42Z** · turn-4-start · **RUNNING** — model=ollama/qwen3.6:latest session=openkknd2-q36 +- **2026-07-22T04:55:43Z** · turn-4-end · **ok** — ec=0 +- **2026-07-22T04:55:43Z** · turn-5-start · **RUNNING** — model=ollama/qwen3.6:latest session=openkknd2-q36 +- **2026-07-22T04:55:45Z** · turn-5-end · **ok** — ec=0 +- **2026-07-22T04:55:45Z** · turn-6-start · **RUNNING** — model=ollama/qwen3.6:latest session=openkknd2-q36 +- **2026-07-22T04:55:47Z** · turn-6-end · **ok** — ec=0 +- **2026-07-22T04:55:47Z** · turn-7-start · **RUNNING** — model=ollama/qwen3.6:latest session=openkknd2-q36 +- **2026-07-22T04:55:48Z** · turn-7-end · **ok** — ec=0 +- **2026-07-22T04:55:48Z** · turn-8-start · **RUNNING** — model=ollama/qwen3.6:latest session=openkknd2-q36 +- **2026-07-22T04:55:50Z** · turn-8-end · **ok** — ec=0 +- **2026-07-22T04:55:50Z** · turn-9-start · **RUNNING** — model=ollama/qwen3.6:latest session=openkknd2-q36 +- **2026-07-22T04:55:51Z** · turn-9-end · **ok** — ec=0 +- **2026-07-22T04:55:51Z** · turn-10-start · **RUNNING** — model=ollama/qwen3.6:latest session=openkknd2-q36 +- **2026-07-22T04:55:53Z** · turn-10-end · **ok** — ec=0 +- **2026-07-22T04:55:53Z** · turn-11-start · **RUNNING** — model=ollama/qwen3.6:latest session=openkknd2-q36 +- **2026-07-22T04:55:55Z** · turn-11-end · **ok** — ec=0 +- **2026-07-22T04:55:55Z** · turn-12-start · **RUNNING** — model=ollama/qwen3.6:latest session=openkknd2-q36 +- **2026-07-22T04:55:56Z** · turn-12-end · **ok** — ec=0 +- **2026-07-22T04:55:56Z** · STATUS→RUNNING · **RUNNING** — max-turns 12 reached + +### run 2026-07-22T05:08:33Z + +- **title:** CONTINUE openkknd2 ONLY under /home/vx/src/openkknd2-am-coder. +Headless smoke already GREEN (map+units). Next milestones yourself: +1) building placement (HQ/barracks) + resource cost +2) simple faction AI tick +3) keep ./scripts/ci-headless.sh GREEN +4) append AGENT_LOG.md + STATUS.md each chunk +Rules: MIT original only, no proprietary KKnD assets. Work thoroughly each turn — no empty turns. Emit AUTONOMOUS_STOP only when truly blocked or milestone done. +Read existing src/include first, then implement. +- **host:** amanita +- **event:** START + + +### run 2026-07-22T05:16:47Z + +- **title:** CONTINUE openkknd2 ONLY under /home/vx/src/openkknd2-am-coder (this workdir). + +Headless smoke is GREEN (map + units). You implement the next milestones yourself. + +This turn must produce REAL file changes (not just plans): +1. Add building placement (HQ and/or barracks) with resource cost — new headers/sources if needed +2. Add a simple faction AI tick (gather / build / train) +3. Wire into `ok_world_tick` or main smoke +4. Run `./scripts/ci-headless.sh` and keep it GREEN +5. Append AGENT_LOG.md + update STATUS.md + +Rules: MIT/original only; no proprietary KKnD assets. No empty turns. AUTONOMOUS_STOP only if blocked or verified done. +Read src/ and include/ first. +- **host:** amanita +- **event:** START + + +### run 2026-07-22T05:16:49Z + +- **title:** CONTINUE openkknd2 ONLY under /home/vx/src/openkknd2-am-coder (this workdir). + +Headless smoke is GREEN (map + units). You implement the next milestones yourself. + +This turn must produce REAL file changes (not just plans): +1. Add building placement (HQ and/or barracks) with resource cost — new headers/sources if needed +2. Add a simple faction AI tick (gather / build / train) +3. Wire into `ok_world_tick` or main smoke +4. Run `./scripts/ci-headless.sh` and keep it GREEN +5. Append AGENT_LOG.md + update STATUS.md + +Rules: MIT/original only; no proprietary KKnD assets. No empty turns. AUTONOMOUS_STOP only if blocked or verified done. +Read src/ and include/ first. +- **host:** amanita +- **event:** START + +- **2026-07-22T05:16:49Z** · am-task-register · **ok** — id=20260722T051649Z-1717777 +- **2026-07-22T05:16:49Z** · turn-1-start · **RUNNING** — model=ollama/devstral:latest session=ok2-rot1-devstral +- **2026-07-22T05:21:21Z** · INTERRUPTED · **stop** — reason=hand off to am-meta hard=0 id=20260722T051649Z-1717777 + +### run 2026-07-22T05:21:30Z + +- **title:** CONTINUE openkknd2 ONLY under /home/vx/src/openkknd2-am-coder (this workdir). + +Headless smoke is GREEN (map + units). You implement the next milestones yourself. + +This turn must produce REAL file changes (not just plans): +1. Add building placement (HQ and/or barracks) with resource cost — new headers/sources if needed +2. Add a simple faction AI tick (gather / build / train) +3. Wire into `ok_world_tick` or main smoke +4. Run `./scripts/ci-headless.sh` and keep it GREEN +5. Append AGENT_LOG.md + update STATUS.md + +Rules: MIT/original only; no proprietary KKnD assets. No empty turns. AUTONOMOUS_STOP only if blocked or verified done. +Read src/ and include/ first. +- **host:** amanita +- **event:** START + + +### run 2026-07-22T05:21:38Z + +- **title:** CONTINUE openkknd2 ONLY under /home/vx/src/openkknd2-am-coder (this workdir). + +Headless smoke is GREEN (map + units). You implement the next milestones yourself. + +This turn must produce REAL file changes (not just plans): +1. Add building placement (HQ and/or barracks) with resource cost — new headers/sources if needed +2. Add a simple faction AI tick (gather / build / train) +3. Wire into `ok_world_tick` or main smoke +4. Run `./scripts/ci-headless.sh` and keep it GREEN +5. Append AGENT_LOG.md + update STATUS.md + +Rules: MIT/original only; no proprietary KKnD assets. No empty turns. AUTONOMOUS_STOP only if blocked or verified done. +Read src/ and include/ first. +- **host:** amanita +- **event:** START + +- **2026-07-22T05:21:38Z** · am-task-register · **ok** — id=20260722T052138Z-1723242 +- **2026-07-22T05:21:38Z** · turn-1-start · **RUNNING** — model=ollama/devstral:latest session=ok2meta-r1-devstral + +### run 2026-07-22T05:21:43Z + +- **title:** CONTINUE openkknd2 ONLY under /home/vx/src/openkknd2-am-coder (this workdir). + +Headless smoke is GREEN (map + units). You implement the next milestones yourself. + +This turn must produce REAL file changes (not just plans): +1. Add building placement (HQ and/or barracks) with resource cost — new headers/sources if needed +2. Add a simple faction AI tick (gather / build / train) +3. Wire into `ok_world_tick` or main smoke +4. Run `./scripts/ci-headless.sh` and keep it GREEN +5. Append AGENT_LOG.md + update STATUS.md + +Rules: MIT/original only; no proprietary KKnD assets. No empty turns. AUTONOMOUS_STOP only if blocked or verified done. +Read src/ and include/ first. +- **host:** amanita +- **event:** START + + +### run 2026-07-22T05:21:52Z + +- **title:** CONTINUE openkknd2 ONLY under /home/vx/src/openkknd2-am-coder (this workdir). + +Headless smoke is GREEN (map + units). You implement the next milestones yourself. + +This turn must produce REAL file changes (not just plans): +1. Add building placement (HQ and/or barracks) with resource cost — new headers/sources if needed +2. Add a simple faction AI tick (gather / build / train) +3. Wire into `ok_world_tick` or main smoke +4. Run `./scripts/ci-headless.sh` and keep it GREEN +5. Append AGENT_LOG.md + update STATUS.md + +Rules: MIT/original only; no proprietary KKnD assets. No empty turns. AUTONOMOUS_STOP only if blocked or verified done. +Read src/ and include/ first. +- **host:** amanita +- **event:** START + +- **2026-07-22T05:21:52Z** · am-task-register · **ok** — id=20260722T052152Z-1724054 +- **2026-07-22T05:21:52Z** · turn-1-start · **RUNNING** — model=ollama/qwen3-coder:latest session=ok2meta-r2-coder +- **2026-07-22T05:22:20Z** · INTERRUPTED · **stop** — reason=redeploy race fix hard=0 id=20260722T052138Z-1723242 + +### run 2026-07-22T05:22:30Z + +- **title:** CONTINUE openkknd2 ONLY under /home/vx/src/openkknd2-am-coder (this workdir). + +Headless smoke is GREEN (map + units). You implement the next milestones yourself. + +This turn must produce REAL file changes (not just plans): +1. Add building placement (HQ and/or barracks) with resource cost — new headers/sources if needed +2. Add a simple faction AI tick (gather / build / train) +3. Wire into `ok_world_tick` or main smoke +4. Run `./scripts/ci-headless.sh` and keep it GREEN +5. Append AGENT_LOG.md + update STATUS.md + +Rules: MIT/original only; no proprietary KKnD assets. No empty turns. AUTONOMOUS_STOP only if blocked or verified done. +Read src/ and include/ first. +- **host:** amanita +- **event:** START + + +### run 2026-07-22T05:22:32Z + +- **title:** CONTINUE openkknd2 ONLY under /home/vx/src/openkknd2-am-coder (this workdir). + +Headless smoke is GREEN (map + units). You implement the next milestones yourself. + +This turn must produce REAL file changes (not just plans): +1. Add building placement (HQ and/or barracks) with resource cost — new headers/sources if needed +2. Add a simple faction AI tick (gather / build / train) +3. Wire into `ok_world_tick` or main smoke +4. Run `./scripts/ci-headless.sh` and keep it GREEN +5. Append AGENT_LOG.md + update STATUS.md + +Rules: MIT/original only; no proprietary KKnD assets. No empty turns. AUTONOMOUS_STOP only if blocked or verified done. +Read src/ and include/ first. +- **host:** amanita +- **event:** START + +- **2026-07-22T05:22:32Z** · am-task-register · **ok** — id=20260722T052232Z-1725762 +- **2026-07-22T05:22:32Z** · turn-1-start · **RUNNING** — model=ollama/devstral:latest session=ok2meta-r1-devstral +- **2026-07-22T05:27:35Z** · turn-1-end · **ok** — ec=0 +- **2026-07-22T05:27:35Z** · turn-2-start · **RUNNING** — model=ollama/devstral:latest session=ok2meta-r1-devstral +- **2026-07-22T05:28:36Z** · turn-2-end · **ok** — ec=0 +- **2026-07-22T05:28:36Z** · turn-3-start · **RUNNING** — model=ollama/devstral:latest session=ok2meta-r1-devstral +- **2026-07-22T05:28:38Z** · turn-3-end · **ok** — ec=0 +- **2026-07-22T05:28:38Z** · turn-4-start · **RUNNING** — model=ollama/devstral:latest session=ok2meta-r1-devstral +- **2026-07-22T05:28:40Z** · turn-4-end · **ok** — ec=0 +- **2026-07-22T05:28:40Z** · turn-5-start · **RUNNING** — model=ollama/devstral:latest session=ok2meta-r1-devstral +- **2026-07-22T05:28:42Z** · turn-5-end · **ok** — ec=0 +- **2026-07-22T05:28:42Z** · turn-6-start · **RUNNING** — model=ollama/devstral:latest session=ok2meta-r1-devstral +- **2026-07-22T05:28:43Z** · turn-6-end · **ok** — ec=0 +- **2026-07-22T05:28:43Z** · turn-7-start · **RUNNING** — model=ollama/devstral:latest session=ok2meta-r1-devstral +- **2026-07-22T05:28:45Z** · turn-7-end · **ok** — ec=0 +- **2026-07-22T05:28:45Z** · turn-8-start · **RUNNING** — model=ollama/devstral:latest session=ok2meta-r1-devstral +- **2026-07-22T05:28:47Z** · turn-8-end · **ok** — ec=0 +- **2026-07-22T05:28:47Z** · STATUS→RUNNING · **RUNNING** — max-turns 8 reached + +### run 2026-07-22T05:29:01Z + +- **title:** CONTINUE openkknd2 ONLY under /home/vx/src/openkknd2-am-coder (this workdir). + +Headless smoke is GREEN (map + units). You implement the next milestones yourself. + +This turn must produce REAL file changes (not just plans): +1. Add building placement (HQ and/or barracks) with resource cost — new headers/sources if needed +2. Add a simple faction AI tick (gather / build / train) +3. Wire into `ok_world_tick` or main smoke +4. Run `./scripts/ci-headless.sh` and keep it GREEN +5. Append AGENT_LOG.md + update STATUS.md + +Rules: MIT/original only; no proprietary KKnD assets. No empty turns. AUTONOMOUS_STOP only if blocked or verified done. +Read src/ and include/ first. +- **host:** amanita +- **event:** START + + +### run 2026-07-22T05:29:04Z + +- **title:** CONTINUE openkknd2 ONLY under /home/vx/src/openkknd2-am-coder (this workdir). + +Headless smoke is GREEN (map + units). You implement the next milestones yourself. + +This turn must produce REAL file changes (not just plans): +1. Add building placement (HQ and/or barracks) with resource cost — new headers/sources if needed +2. Add a simple faction AI tick (gather / build / train) +3. Wire into `ok_world_tick` or main smoke +4. Run `./scripts/ci-headless.sh` and keep it GREEN +5. Append AGENT_LOG.md + update STATUS.md + +Rules: MIT/original only; no proprietary KKnD assets. No empty turns. AUTONOMOUS_STOP only if blocked or verified done. +Read src/ and include/ first. +- **host:** amanita +- **event:** START + +- **2026-07-22T05:29:04Z** · am-task-register · **ok** — id=20260722T052904Z-1738030 +- **2026-07-22T05:29:04Z** · turn-1-start · **RUNNING** — model=ollama/qwen3-coder:latest session=ok2meta-r2-coder +- **2026-07-22T05:34:17Z** · turn-1-end · **ok** — ec=0 +- **2026-07-22T05:34:17Z** · turn-2-start · **RUNNING** — model=ollama/qwen3-coder:latest session=ok2meta-r2-coder +- **2026-07-22T05:34:18Z** · turn-2-end · **ok** — ec=0 +- **2026-07-22T05:34:18Z** · turn-3-start · **RUNNING** — model=ollama/qwen3-coder:latest session=ok2meta-r2-coder +- **2026-07-22T05:34:27Z** · turn-3-end · **ok** — ec=0 +- **2026-07-22T05:34:27Z** · turn-4-start · **RUNNING** — model=ollama/qwen3-coder:latest session=ok2meta-r2-coder +- **2026-07-22T05:34:28Z** · turn-4-end · **ok** — ec=0 +- **2026-07-22T05:34:28Z** · turn-5-start · **RUNNING** — model=ollama/qwen3-coder:latest session=ok2meta-r2-coder +- **2026-07-22T05:34:30Z** · turn-5-end · **ok** — ec=0 +- **2026-07-22T05:34:30Z** · turn-6-start · **RUNNING** — model=ollama/qwen3-coder:latest session=ok2meta-r2-coder +- **2026-07-22T05:34:32Z** · turn-6-end · **ok** — ec=0 +- **2026-07-22T05:34:32Z** · turn-7-start · **RUNNING** — model=ollama/qwen3-coder:latest session=ok2meta-r2-coder +- **2026-07-22T05:34:33Z** · turn-7-end · **ok** — ec=0 +- **2026-07-22T05:34:33Z** · turn-8-start · **RUNNING** — model=ollama/qwen3-coder:latest session=ok2meta-r2-coder +- **2026-07-22T05:34:35Z** · turn-8-end · **ok** — ec=0 +- **2026-07-22T05:34:35Z** · STATUS→RUNNING · **RUNNING** — max-turns 8 reached + +### run 2026-07-22T05:34:53Z + +- **title:** CONTINUE openkknd2 ONLY under /home/vx/src/openkknd2-am-coder (this workdir). + +Headless smoke is GREEN (map + units). You implement the next milestones yourself. + +This turn must produce REAL file changes (not just plans): +1. Add building placement (HQ and/or barracks) with resource cost — new headers/sources if needed +2. Add a simple faction AI tick (gather / build / train) +3. Wire into `ok_world_tick` or main smoke +4. Run `./scripts/ci-headless.sh` and keep it GREEN +5. Append AGENT_LOG.md + update STATUS.md + +Rules: MIT/original only; no proprietary KKnD assets. No empty turns. AUTONOMOUS_STOP only if blocked or verified done. +Read src/ and include/ first. +- **host:** amanita +- **event:** START + + +### run 2026-07-22T05:34:55Z + +- **title:** CONTINUE openkknd2 ONLY under /home/vx/src/openkknd2-am-coder (this workdir). + +Headless smoke is GREEN (map + units). You implement the next milestones yourself. + +This turn must produce REAL file changes (not just plans): +1. Add building placement (HQ and/or barracks) with resource cost — new headers/sources if needed +2. Add a simple faction AI tick (gather / build / train) +3. Wire into `ok_world_tick` or main smoke +4. Run `./scripts/ci-headless.sh` and keep it GREEN +5. Append AGENT_LOG.md + update STATUS.md + +Rules: MIT/original only; no proprietary KKnD assets. No empty turns. AUTONOMOUS_STOP only if blocked or verified done. +Read src/ and include/ first. +- **host:** amanita +- **event:** START + +- **2026-07-22T05:34:55Z** · am-task-register · **ok** — id=20260722T053455Z-2574378 +- **2026-07-22T05:34:55Z** · turn-1-start · **RUNNING** — model=ollama/qwen3:14b session=ok2meta-r3-qwen314 +- **2026-07-22T05:49:17Z** · turn-1-end · **ok** — ec=0 +- **2026-07-22T05:49:17Z** · turn-2-start · **RUNNING** — model=ollama/qwen3:14b session=ok2meta-r3-qwen314 +- **2026-07-22T05:49:39Z** · turn-2-end · **ok** — ec=0 +- **2026-07-22T05:49:39Z** · turn-3-start · **RUNNING** — model=ollama/qwen3:14b session=ok2meta-r3-qwen314 +- **2026-07-22T05:50:00Z** · turn-3-end · **ok** — ec=0 +- **2026-07-22T05:50:00Z** · turn-4-start · **RUNNING** — model=ollama/qwen3:14b session=ok2meta-r3-qwen314 +- **2026-07-22T05:50:02Z** · turn-4-end · **ok** — ec=0 +- **2026-07-22T05:50:02Z** · turn-5-start · **RUNNING** — model=ollama/qwen3:14b session=ok2meta-r3-qwen314 +- **2026-07-22T05:50:03Z** · turn-5-end · **ok** — ec=0 +- **2026-07-22T05:50:03Z** · turn-6-start · **RUNNING** — model=ollama/qwen3:14b session=ok2meta-r3-qwen314 +- **2026-07-22T05:50:05Z** · turn-6-end · **ok** — ec=0 +- **2026-07-22T05:50:05Z** · turn-7-start · **RUNNING** — model=ollama/qwen3:14b session=ok2meta-r3-qwen314 +- **2026-07-22T05:50:06Z** · turn-7-end · **ok** — ec=0 +- **2026-07-22T05:50:06Z** · turn-8-start · **RUNNING** — model=ollama/qwen3:14b session=ok2meta-r3-qwen314 +- **2026-07-22T05:50:08Z** · turn-8-end · **ok** — ec=0 +- **2026-07-22T05:50:08Z** · STATUS→RUNNING · **RUNNING** — max-turns 8 reached + +### run 2026-07-22T05:50:24Z + +- **title:** CONTINUE openkknd2 ONLY under /home/vx/src/openkknd2-am-coder (this workdir). + +Headless smoke is GREEN (map + units). You implement the next milestones yourself. + +This turn must produce REAL file changes (not just plans): +1. Add building placement (HQ and/or barracks) with resource cost — new headers/sources if needed +2. Add a simple faction AI tick (gather / build / train) +3. Wire into `ok_world_tick` or main smoke +4. Run `./scripts/ci-headless.sh` and keep it GREEN +5. Append AGENT_LOG.md + update STATUS.md + +Rules: MIT/original only; no proprietary KKnD assets. No empty turns. AUTONOMOUS_STOP only if blocked or verified done. +Read src/ and include/ first. +- **host:** amanita +- **event:** START + + +### run 2026-07-22T05:50:27Z + +- **title:** CONTINUE openkknd2 ONLY under /home/vx/src/openkknd2-am-coder (this workdir). + +Headless smoke is GREEN (map + units). You implement the next milestones yourself. + +This turn must produce REAL file changes (not just plans): +1. Add building placement (HQ and/or barracks) with resource cost — new headers/sources if needed +2. Add a simple faction AI tick (gather / build / train) +3. Wire into `ok_world_tick` or main smoke +4. Run `./scripts/ci-headless.sh` and keep it GREEN +5. Append AGENT_LOG.md + update STATUS.md + +Rules: MIT/original only; no proprietary KKnD assets. No empty turns. AUTONOMOUS_STOP only if blocked or verified done. +Read src/ and include/ first. +- **host:** amanita +- **event:** START + +- **2026-07-22T05:50:27Z** · am-task-register · **ok** — id=20260722T055027Z-3217726 +- **2026-07-22T05:50:27Z** · turn-1-start · **RUNNING** — model=ollama/qwen3:14b session=ok2meta-r4-qwen314 +- **2026-07-22T05:53:43Z** · turn-1-end · **ok** — ec=0 +- **2026-07-22T05:53:43Z** · turn-2-start · **RUNNING** — model=ollama/qwen3:14b session=ok2meta-r4-qwen314 +- **2026-07-22T05:54:02Z** · turn-2-end · **ok** — ec=0 +- **2026-07-22T05:54:02Z** · turn-3-start · **RUNNING** — model=ollama/qwen3:14b session=ok2meta-r4-qwen314 +- **2026-07-22T05:54:22Z** · turn-3-end · **ok** — ec=0 +- **2026-07-22T05:54:22Z** · turn-4-start · **RUNNING** — model=ollama/qwen3:14b session=ok2meta-r4-qwen314 +- **2026-07-22T05:54:24Z** · turn-4-end · **ok** — ec=0 +- **2026-07-22T05:54:24Z** · turn-5-start · **RUNNING** — model=ollama/qwen3:14b session=ok2meta-r4-qwen314 +- **2026-07-22T05:54:26Z** · turn-5-end · **ok** — ec=0 +- **2026-07-22T05:54:26Z** · turn-6-start · **RUNNING** — model=ollama/qwen3:14b session=ok2meta-r4-qwen314 +- **2026-07-22T05:54:27Z** · turn-6-end · **ok** — ec=0 +- **2026-07-22T05:54:27Z** · turn-7-start · **RUNNING** — model=ollama/qwen3:14b session=ok2meta-r4-qwen314 +- **2026-07-22T05:54:29Z** · turn-7-end · **ok** — ec=0 +- **2026-07-22T05:54:29Z** · turn-8-start · **RUNNING** — model=ollama/qwen3:14b session=ok2meta-r4-qwen314 +- **2026-07-22T05:54:30Z** · turn-8-end · **ok** — ec=0 +- **2026-07-22T05:54:30Z** · STATUS→RUNNING · **RUNNING** — max-turns 8 reached + +### run 2026-07-22T05:54:35Z + +- **title:** CONTINUE openkknd2 ONLY under /home/vx/src/openkknd2-am-coder (this workdir). + +Headless smoke is GREEN (map + units). You implement the next milestones yourself. + +This turn must produce REAL file changes (not just plans): +1. Add building placement (HQ and/or barracks) with resource cost — new headers/sources if needed +2. Add a simple faction AI tick (gather / build / train) +3. Wire into `ok_world_tick` or main smoke +4. Run `./scripts/ci-headless.sh` and keep it GREEN +5. Append AGENT_LOG.md + update STATUS.md + +Rules: MIT/original only; no proprietary KKnD assets. No empty turns. AUTONOMOUS_STOP only if blocked or verified done. +Read src/ and include/ first. +- **host:** amanita +- **event:** START + + +### run 2026-07-22T05:54:38Z + +- **title:** CONTINUE openkknd2 ONLY under /home/vx/src/openkknd2-am-coder (this workdir). + +Headless smoke is GREEN (map + units). You implement the next milestones yourself. + +This turn must produce REAL file changes (not just plans): +1. Add building placement (HQ and/or barracks) with resource cost — new headers/sources if needed +2. Add a simple faction AI tick (gather / build / train) +3. Wire into `ok_world_tick` or main smoke +4. Run `./scripts/ci-headless.sh` and keep it GREEN +5. Append AGENT_LOG.md + update STATUS.md + +Rules: MIT/original only; no proprietary KKnD assets. No empty turns. AUTONOMOUS_STOP only if blocked or verified done. +Read src/ and include/ first. +- **host:** amanita +- **event:** START + +- **2026-07-22T05:54:38Z** · am-task-register · **ok** — id=20260722T055438Z-3338469 +- **2026-07-22T05:54:38Z** · turn-1-start · **RUNNING** — model=ollama/qwen2.5-coder:14b session=ok2meta-r5-qwen14 +- **2026-07-22T05:56:49Z** · turn-1-end · **ok** — ec=0 +- **2026-07-22T05:56:49Z** · turn-2-start · **RUNNING** — model=ollama/qwen2.5-coder:14b session=ok2meta-r5-qwen14 +- **2026-07-22T05:57:27Z** · turn-2-end · **ok** — ec=0 +- **2026-07-22T05:57:27Z** · turn-3-start · **RUNNING** — model=ollama/qwen2.5-coder:14b session=ok2meta-r5-qwen14 +- **2026-07-22T05:58:02Z** · turn-3-end · **ok** — ec=0 +- **2026-07-22T05:58:02Z** · turn-4-start · **RUNNING** — model=ollama/qwen2.5-coder:14b session=ok2meta-r5-qwen14 +- **2026-07-22T05:58:39Z** · turn-4-end · **ok** — ec=0 +- **2026-07-22T05:58:39Z** · turn-5-start · **RUNNING** — model=ollama/qwen2.5-coder:14b session=ok2meta-r5-qwen14 +- **2026-07-22T05:58:41Z** · turn-5-end · **ok** — ec=0 +- **2026-07-22T05:58:41Z** · turn-6-start · **RUNNING** — model=ollama/qwen2.5-coder:14b session=ok2meta-r5-qwen14 +- **2026-07-22T05:58:42Z** · turn-6-end · **ok** — ec=0 +- **2026-07-22T05:58:42Z** · turn-7-start · **RUNNING** — model=ollama/qwen2.5-coder:14b session=ok2meta-r5-qwen14 +- **2026-07-22T05:58:44Z** · turn-7-end · **ok** — ec=0 +- **2026-07-22T05:58:44Z** · turn-8-start · **RUNNING** — model=ollama/qwen2.5-coder:14b session=ok2meta-r5-qwen14 +- **2026-07-22T05:58:46Z** · turn-8-end · **ok** — ec=0 +- **2026-07-22T05:58:46Z** · STATUS→RUNNING · **RUNNING** — max-turns 8 reached + +### run 2026-07-22T05:59:07Z + +- **title:** CONTINUE openkknd2 ONLY under /home/vx/src/openkknd2-am-coder (this workdir). + +Headless smoke is GREEN (map + units). You implement the next milestones yourself. + +This turn must produce REAL file changes (not just plans): +1. Add building placement (HQ and/or barracks) with resource cost — new headers/sources if needed +2. Add a simple faction AI tick (gather / build / train) +3. Wire into `ok_world_tick` or main smoke +4. Run `./scripts/ci-headless.sh` and keep it GREEN +5. Append AGENT_LOG.md + update STATUS.md + +Rules: MIT/original only; no proprietary KKnD assets. No empty turns. AUTONOMOUS_STOP only if blocked or verified done. +Read src/ and include/ first. +- **host:** amanita +- **event:** START + + +### run 2026-07-22T05:59:09Z + +- **title:** CONTINUE openkknd2 ONLY under /home/vx/src/openkknd2-am-coder (this workdir). + +Headless smoke is GREEN (map + units). You implement the next milestones yourself. + +This turn must produce REAL file changes (not just plans): +1. Add building placement (HQ and/or barracks) with resource cost — new headers/sources if needed +2. Add a simple faction AI tick (gather / build / train) +3. Wire into `ok_world_tick` or main smoke +4. Run `./scripts/ci-headless.sh` and keep it GREEN +5. Append AGENT_LOG.md + update STATUS.md + +Rules: MIT/original only; no proprietary KKnD assets. No empty turns. AUTONOMOUS_STOP only if blocked or verified done. +Read src/ and include/ first. +- **host:** amanita +- **event:** START + +- **2026-07-22T05:59:09Z** · am-task-register · **ok** — id=20260722T055909Z-3346665 +- **2026-07-22T05:59:09Z** · turn-1-start · **RUNNING** — model=ollama/deepseek-coder:6.7b session=ok2meta-r6-deepseek +- **2026-07-22T05:59:12Z** · turn-1-end · **ok** — ec=0 +- **2026-07-22T05:59:12Z** · turn-2-start · **RUNNING** — model=ollama/deepseek-coder:6.7b session=ok2meta-r6-deepseek +- **2026-07-22T05:59:28Z** · turn-2-end · **ok** — ec=0 +- **2026-07-22T05:59:28Z** · turn-3-start · **RUNNING** — model=ollama/deepseek-coder:6.7b session=ok2meta-r6-deepseek +- **2026-07-22T05:59:30Z** · turn-3-end · **ok** — ec=0 +- **2026-07-22T05:59:30Z** · turn-4-start · **RUNNING** — model=ollama/deepseek-coder:6.7b session=ok2meta-r6-deepseek +- **2026-07-22T05:59:32Z** · turn-4-end · **ok** — ec=0 +- **2026-07-22T05:59:32Z** · turn-5-start · **RUNNING** — model=ollama/deepseek-coder:6.7b session=ok2meta-r6-deepseek +- **2026-07-22T05:59:33Z** · turn-5-end · **ok** — ec=0 +- **2026-07-22T05:59:33Z** · turn-6-start · **RUNNING** — model=ollama/deepseek-coder:6.7b session=ok2meta-r6-deepseek +- **2026-07-22T05:59:35Z** · turn-6-end · **ok** — ec=0 +- **2026-07-22T05:59:35Z** · turn-7-start · **RUNNING** — model=ollama/deepseek-coder:6.7b session=ok2meta-r6-deepseek +- **2026-07-22T05:59:37Z** · turn-7-end · **ok** — ec=0 +- **2026-07-22T05:59:37Z** · turn-8-start · **RUNNING** — model=ollama/deepseek-coder:6.7b session=ok2meta-r6-deepseek +- **2026-07-22T05:59:38Z** · turn-8-end · **ok** — ec=0 +- **2026-07-22T05:59:38Z** · STATUS→RUNNING · **RUNNING** — max-turns 8 reached + +### run 2026-07-22T05:59:58Z + +- **title:** CONTINUE openkknd2 ONLY under /home/vx/src/openkknd2-am-coder (this workdir). + +Headless smoke is GREEN (map + units). You implement the next milestones yourself. + +This turn must produce REAL file changes (not just plans): +1. Add building placement (HQ and/or barracks) with resource cost — new headers/sources if needed +2. Add a simple faction AI tick (gather / build / train) +3. Wire into `ok_world_tick` or main smoke +4. Run `./scripts/ci-headless.sh` and keep it GREEN +5. Append AGENT_LOG.md + update STATUS.md + +Rules: MIT/original only; no proprietary KKnD assets. No empty turns. AUTONOMOUS_STOP only if blocked or verified done. +Read src/ and include/ first. +- **host:** amanita +- **event:** START + + +### run 2026-07-22T06:00:00Z + +- **title:** CONTINUE openkknd2 ONLY under /home/vx/src/openkknd2-am-coder (this workdir). + +Headless smoke is GREEN (map + units). You implement the next milestones yourself. + +This turn must produce REAL file changes (not just plans): +1. Add building placement (HQ and/or barracks) with resource cost — new headers/sources if needed +2. Add a simple faction AI tick (gather / build / train) +3. Wire into `ok_world_tick` or main smoke +4. Run `./scripts/ci-headless.sh` and keep it GREEN +5. Append AGENT_LOG.md + update STATUS.md + +Rules: MIT/original only; no proprietary KKnD assets. No empty turns. AUTONOMOUS_STOP only if blocked or verified done. +Read src/ and include/ first. +- **host:** amanita +- **event:** START + +- **2026-07-22T06:00:00Z** · am-task-register · **ok** — id=20260722T060000Z-3348413 +- **2026-07-22T06:00:00Z** · turn-1-start · **RUNNING** — model=ollama/qwen3:8b session=ok2meta-r7-qwen38 +- **2026-07-22T06:03:58Z** · INTERRUPTED · **stop** — reason=user: beende alle aufgaben hard=0 id=20260722T060000Z-3348413 + +### run 2026-07-22T06:05:06Z + +- **title:** # MISSION (immutable — do not renegotiate) + +# Mission (immutable) — openkknd2 + +Build and grow **openkknd2**: an original open-source post-apoc RTS scaffold (genre-inspired only; **not** commercial KKnD). + +## Fixed goals + +1. Work only under the designated workdir (`openkknd2-am-coder` / `$OPENKKND2_ROOT`). +2. Keep `./scripts/ci-headless.sh` **GREEN** after meaningful changes. +3. Advance the milestone ladder: map/units → buildings/economy → faction AI → playable skirmish loop → (later) cinematics. +4. **Legal:** MIT/original only — never proprietary KKnD assets, rips, or reverse-engineered game files. +5. Always append `AGENT_LOG.md` and keep `STATUS.md` current. + +## Non-goals (do not replace the mission with these) + +- Rewriting the project as a different game +- Asking the user for permission to continue +- Stopping after planning without code/test changes + +This mission text is **law** for all models and all meta rounds. + +--- + +# This round + +CONTINUE openkknd2 ONLY under /home/vx/src/openkknd2-am-coder (this workdir). + +Headless smoke is GREEN (map + units). You implement the next milestones yourself. + +This turn must produce REAL file changes (not just plans): +1. Add building placement (HQ and/or barracks) with resource cost — new headers/sources if needed +2. Add a simple faction AI tick (gather / build / train) +3. Wire into `ok_world_tick` or main smoke +4. Run `./scripts/ci-headless.sh` and keep it GREEN +5. Append AGENT_LOG.md + update STATUS.md + +Rules: MIT/original only; no proprietary KKnD assets. No empty turns. AUTONOMOUS_STOP only if blocked or verified done. +Read src/ and include/ first. + +--- + +## Protocol +- Work thoroughly; no empty turns. +- Emit ## AUTONOMOUS_STOP only when DONE or truly blocked. +- If missing *actionable* rules would unlock autonomy (not a rewrite of the mission), + append exactly one block: + +## NEED_INSTRUCTIONS +why: +need: +- + +Do not use NEED_INSTRUCTIONS to change the mission goal. +- **host:** amanita +- **event:** START + + +### run 2026-07-22T06:05:09Z + +- **title:** # MISSION (immutable — do not renegotiate) + +# Mission (immutable) — openkknd2 + +Build and grow **openkknd2**: an original open-source post-apoc RTS scaffold (genre-inspired only; **not** commercial KKnD). + +## Fixed goals + +1. Work only under the designated workdir (`openkknd2-am-coder` / `$OPENKKND2_ROOT`). +2. Keep `./scripts/ci-headless.sh` **GREEN** after meaningful changes. +3. Advance the milestone ladder: map/units → buildings/economy → faction AI → playable skirmish loop → (later) cinematics. +4. **Legal:** MIT/original only — never proprietary KKnD assets, rips, or reverse-engineered game files. +5. Always append `AGENT_LOG.md` and keep `STATUS.md` current. + +## Non-goals (do not replace the mission with these) + +- Rewriting the project as a different game +- Asking the user for permission to continue +- Stopping after planning without code/test changes + +This mission text is **law** for all models and all meta rounds. + +--- + +# This round + +CONTINUE openkknd2 ONLY under /home/vx/src/openkknd2-am-coder (this workdir). + +Headless smoke is GREEN (map + units). You implement the next milestones yourself. + +This turn must produce REAL file changes (not just plans): +1. Add building placement (HQ and/or barracks) with resource cost — new headers/sources if needed +2. Add a simple faction AI tick (gather / build / train) +3. Wire into `ok_world_tick` or main smoke +4. Run `./scripts/ci-headless.sh` and keep it GREEN +5. Append AGENT_LOG.md + update STATUS.md + +Rules: MIT/original only; no proprietary KKnD assets. No empty turns. AUTONOMOUS_STOP only if blocked or verified done. +Read src/ and include/ first. + +--- + +## Protocol +- Work thoroughly; no empty turns. +- Emit ## AUTONOMOUS_STOP only when DONE or truly blocked. +- If missing *actionable* rules would unlock autonomy (not a rewrite of the mission), + append exactly one block: + +## NEED_INSTRUCTIONS +why: +need: +- + +Do not use NEED_INSTRUCTIONS to change the mission goal. +- **host:** amanita +- **event:** START + +- **2026-07-22T06:05:09Z** · am-task-register · **ok** — id=20260722T060509Z-3575357 +- **2026-07-22T06:05:09Z** · turn-1-start · **RUNNING** — model=ollama/devstral:latest session=ok2meta-r1-devstral +- **2026-07-22T06:05:11Z** · turn-1-end · **ok** — ec=0 +- **2026-07-22T06:05:11Z** · turn-2-start · **RUNNING** — model=ollama/devstral:latest session=ok2meta-r1-devstral +- **2026-07-22T06:05:13Z** · turn-2-end · **ok** — ec=0 +- **2026-07-22T06:05:13Z** · turn-3-start · **RUNNING** — model=ollama/devstral:latest session=ok2meta-r1-devstral +- **2026-07-22T06:05:14Z** · turn-3-end · **ok** — ec=0 +- **2026-07-22T06:05:14Z** · turn-4-start · **RUNNING** — model=ollama/devstral:latest session=ok2meta-r1-devstral +- **2026-07-22T06:05:16Z** · turn-4-end · **ok** — ec=0 +- **2026-07-22T06:05:16Z** · turn-5-start · **RUNNING** — model=ollama/devstral:latest session=ok2meta-r1-devstral +- **2026-07-22T06:05:18Z** · turn-5-end · **ok** — ec=0 +- **2026-07-22T06:05:18Z** · turn-6-start · **RUNNING** — model=ollama/devstral:latest session=ok2meta-r1-devstral +- **2026-07-22T06:05:19Z** · turn-6-end · **ok** — ec=0 +- **2026-07-22T06:05:19Z** · turn-7-start · **RUNNING** — model=ollama/devstral:latest session=ok2meta-r1-devstral +- **2026-07-22T06:05:21Z** · turn-7-end · **ok** — ec=0 +- **2026-07-22T06:05:21Z** · turn-8-start · **RUNNING** — model=ollama/devstral:latest session=ok2meta-r1-devstral +- **2026-07-22T06:05:22Z** · turn-8-end · **ok** — ec=0 +- **2026-07-22T06:05:22Z** · STATUS→RUNNING · **RUNNING** — max-turns 8 reached + +### run 2026-07-22T06:07:17Z + +- **title:** # MISSION (immutable — do not renegotiate) + +# Mission (immutable) — openkknd2 + +Build and grow **openkknd2**: an original open-source post-apoc RTS scaffold (genre-inspired only; **not** commercial KKnD). + +## Fixed goals + +1. Work only under the designated workdir (`openkknd2-am-coder` / `$OPENKKND2_ROOT`). +2. Keep `./scripts/ci-headless.sh` **GREEN** after meaningful changes. +3. Advance the milestone ladder: map/units → buildings/economy → faction AI → playable skirmish loop → (later) cinematics. +4. **Legal:** MIT/original only — never proprietary KKnD assets, rips, or reverse-engineered game files. +5. Always append `AGENT_LOG.md` and keep `STATUS.md` current. + +## Non-goals (do not replace the mission with these) + +- Rewriting the project as a different game +- Asking the user for permission to continue +- Stopping after planning without code/test changes + +This mission text is **law** for all models and all meta rounds. + +--- + +# This round + +CONTINUE openkknd2 ONLY under /home/vx/src/openkknd2-am-coder (this workdir). + +Headless smoke is GREEN (map + units). You implement the next milestones yourself. + +This turn must produce REAL file changes (not just plans): +1. Add building placement (HQ and/or barracks) with resource cost — new headers/sources if needed +2. Add a simple faction AI tick (gather / build / train) +3. Wire into `ok_world_tick` or main smoke +4. Run `./scripts/ci-headless.sh` and keep it GREEN +5. Append AGENT_LOG.md + update STATUS.md + +Rules: MIT/original only; no proprietary KKnD assets. No empty turns. AUTONOMOUS_STOP only if blocked or verified done. +Read src/ and include/ first. + +--- + +## Protocol +- Work thoroughly; no empty turns. +- Emit ## AUTONOMOUS_STOP only when DONE or truly blocked. +- If missing *actionable* rules would unlock autonomy (not a rewrite of the mission), + append exactly one block: + +## NEED_INSTRUCTIONS +why: +need: +- + +Do not use NEED_INSTRUCTIONS to change the mission goal. +- **host:** amanita +- **event:** START + + +### run 2026-07-22T06:07:20Z + +- **title:** # MISSION (immutable — do not renegotiate) + +# Mission (immutable) — openkknd2 + +Build and grow **openkknd2**: an original open-source post-apoc RTS scaffold (genre-inspired only; **not** commercial KKnD). + +## Fixed goals + +1. Work only under the designated workdir (`openkknd2-am-coder` / `$OPENKKND2_ROOT`). +2. Keep `./scripts/ci-headless.sh` **GREEN** after meaningful changes. +3. Advance the milestone ladder: map/units → buildings/economy → faction AI → playable skirmish loop → (later) cinematics. +4. **Legal:** MIT/original only — never proprietary KKnD assets, rips, or reverse-engineered game files. +5. Always append `AGENT_LOG.md` and keep `STATUS.md` current. + +## Non-goals (do not replace the mission with these) + +- Rewriting the project as a different game +- Asking the user for permission to continue +- Stopping after planning without code/test changes + +This mission text is **law** for all models and all meta rounds. + +--- + +# This round + +CONTINUE openkknd2 ONLY under /home/vx/src/openkknd2-am-coder (this workdir). + +Headless smoke is GREEN (map + units). You implement the next milestones yourself. + +This turn must produce REAL file changes (not just plans): +1. Add building placement (HQ and/or barracks) with resource cost — new headers/sources if needed +2. Add a simple faction AI tick (gather / build / train) +3. Wire into `ok_world_tick` or main smoke +4. Run `./scripts/ci-headless.sh` and keep it GREEN +5. Append AGENT_LOG.md + update STATUS.md + +Rules: MIT/original only; no proprietary KKnD assets. No empty turns. AUTONOMOUS_STOP only if blocked or verified done. +Read src/ and include/ first. + +--- + +## Protocol +- Work thoroughly; no empty turns. +- Emit ## AUTONOMOUS_STOP only when DONE or truly blocked. +- If missing *actionable* rules would unlock autonomy (not a rewrite of the mission), + append exactly one block: + +## NEED_INSTRUCTIONS +why: +need: +- + +Do not use NEED_INSTRUCTIONS to change the mission goal. +- **host:** amanita +- **event:** START + +- **2026-07-22T06:07:20Z** · am-task-register · **ok** — id=20260722T060720Z-3577414 +- **2026-07-22T06:07:20Z** · turn-1-start · **RUNNING** — model=ollama/devstral:latest session=ok2meta-r1-devstral +- **2026-07-22T06:07:22Z** · turn-1-end · **ok** — ec=0 +- **2026-07-22T06:07:22Z** · turn-2-start · **RUNNING** — model=ollama/devstral:latest session=ok2meta-r1-devstral +- **2026-07-22T06:07:24Z** · turn-2-end · **ok** — ec=0 +- **2026-07-22T06:07:24Z** · turn-3-start · **RUNNING** — model=ollama/devstral:latest session=ok2meta-r1-devstral +- **2026-07-22T06:07:25Z** · turn-3-end · **ok** — ec=0 +- **2026-07-22T06:07:25Z** · turn-4-start · **RUNNING** — model=ollama/devstral:latest session=ok2meta-r1-devstral +- **2026-07-22T06:07:27Z** · turn-4-end · **ok** — ec=0 +- **2026-07-22T06:07:27Z** · turn-5-start · **RUNNING** — model=ollama/devstral:latest session=ok2meta-r1-devstral +- **2026-07-22T06:07:29Z** · turn-5-end · **ok** — ec=0 +- **2026-07-22T06:07:29Z** · turn-6-start · **RUNNING** — model=ollama/devstral:latest session=ok2meta-r1-devstral +- **2026-07-22T06:07:30Z** · turn-6-end · **ok** — ec=0 +- **2026-07-22T06:07:30Z** · turn-7-start · **RUNNING** — model=ollama/devstral:latest session=ok2meta-r1-devstral +- **2026-07-22T06:07:32Z** · turn-7-end · **ok** — ec=0 +- **2026-07-22T06:07:32Z** · turn-8-start · **RUNNING** — model=ollama/devstral:latest session=ok2meta-r1-devstral +- **2026-07-22T06:07:33Z** · turn-8-end · **ok** — ec=0 +- **2026-07-22T06:07:33Z** · STATUS→RUNNING · **RUNNING** — max-turns 8 reached + +### run 2026-07-22T06:07:48Z + +- **title:** # MISSION (immutable — do not renegotiate) + +# Mission (immutable) — openkknd2 + +Build and grow **openkknd2**: an original open-source post-apoc RTS scaffold (genre-inspired only; **not** commercial KKnD). + +## Fixed goals + +1. Work only under the designated workdir (`openkknd2-am-coder` / `$OPENKKND2_ROOT`). +2. Keep `./scripts/ci-headless.sh` **GREEN** after meaningful changes. +3. Advance the milestone ladder: map/units → buildings/economy → faction AI → playable skirmish loop → (later) cinematics. +4. **Legal:** MIT/original only — never proprietary KKnD assets, rips, or reverse-engineered game files. +5. Always append `AGENT_LOG.md` and keep `STATUS.md` current. + +## Non-goals (do not replace the mission with these) + +- Rewriting the project as a different game +- Asking the user for permission to continue +- Stopping after planning without code/test changes + +This mission text is **law** for all models and all meta rounds. + +--- + +# This round + +CONTINUE openkknd2 ONLY under /home/vx/src/openkknd2-am-coder (this workdir). + +Headless smoke is GREEN (map + units). You implement the next milestones yourself. + +This turn must produce REAL file changes (not just plans): +1. Add building placement (HQ and/or barracks) with resource cost — new headers/sources if needed +2. Add a simple faction AI tick (gather / build / train) +3. Wire into `ok_world_tick` or main smoke +4. Run `./scripts/ci-headless.sh` and keep it GREEN +5. Append AGENT_LOG.md + update STATUS.md + +Rules: MIT/original only; no proprietary KKnD assets. No empty turns. AUTONOMOUS_STOP only if blocked or verified done. +Read src/ and include/ first. + +--- + +## Protocol +- Work thoroughly; no empty turns. +- Emit ## AUTONOMOUS_STOP only when DONE or truly blocked. +- If missing *actionable* rules would unlock autonomy (not a rewrite of the mission), + append exactly one block: + +## NEED_INSTRUCTIONS +why: +need: +- + +Do not use NEED_INSTRUCTIONS to change the mission goal. +- **host:** amanita +- **event:** START + + +### run 2026-07-22T06:07:50Z + +- **title:** # MISSION (immutable — do not renegotiate) + +# Mission (immutable) — openkknd2 + +Build and grow **openkknd2**: an original open-source post-apoc RTS scaffold (genre-inspired only; **not** commercial KKnD). + +## Fixed goals + +1. Work only under the designated workdir (`openkknd2-am-coder` / `$OPENKKND2_ROOT`). +2. Keep `./scripts/ci-headless.sh` **GREEN** after meaningful changes. +3. Advance the milestone ladder: map/units → buildings/economy → faction AI → playable skirmish loop → (later) cinematics. +4. **Legal:** MIT/original only — never proprietary KKnD assets, rips, or reverse-engineered game files. +5. Always append `AGENT_LOG.md` and keep `STATUS.md` current. + +## Non-goals (do not replace the mission with these) + +- Rewriting the project as a different game +- Asking the user for permission to continue +- Stopping after planning without code/test changes + +This mission text is **law** for all models and all meta rounds. + +--- + +# This round + +CONTINUE openkknd2 ONLY under /home/vx/src/openkknd2-am-coder (this workdir). + +Headless smoke is GREEN (map + units). You implement the next milestones yourself. + +This turn must produce REAL file changes (not just plans): +1. Add building placement (HQ and/or barracks) with resource cost — new headers/sources if needed +2. Add a simple faction AI tick (gather / build / train) +3. Wire into `ok_world_tick` or main smoke +4. Run `./scripts/ci-headless.sh` and keep it GREEN +5. Append AGENT_LOG.md + update STATUS.md + +Rules: MIT/original only; no proprietary KKnD assets. No empty turns. AUTONOMOUS_STOP only if blocked or verified done. +Read src/ and include/ first. + +--- + +## Protocol +- Work thoroughly; no empty turns. +- Emit ## AUTONOMOUS_STOP only when DONE or truly blocked. +- If missing *actionable* rules would unlock autonomy (not a rewrite of the mission), + append exactly one block: + +## NEED_INSTRUCTIONS +why: +need: +- + +Do not use NEED_INSTRUCTIONS to change the mission goal. +- **host:** amanita +- **event:** START + +- **2026-07-22T06:07:50Z** · am-task-register · **ok** — id=20260722T060750Z-3578279 +- **2026-07-22T06:07:50Z** · turn-1-start · **RUNNING** — model=ollama/devstral:latest session=ok2meta-r2-devstral +- **2026-07-22T06:09:11Z** · INTERRUPTED · **stop** — reason=user: session end hard=0 id=20260722T060750Z-3578279 + +### run 2026-07-22T06:09:25Z + +- **title:** # MISSION (immutable — do not renegotiate) + +# Mission (immutable) — openkknd2 + +Build and grow **openkknd2**: an original open-source post-apoc RTS scaffold (genre-inspired only; **not** commercial KKnD). + +## Fixed goals + +1. Work only under the designated workdir (`openkknd2-am-coder` / `$OPENKKND2_ROOT`). +2. Keep `./scripts/ci-headless.sh` **GREEN** after meaningful changes. +3. Advance the milestone ladder: map/units → buildings/economy → faction AI → playable skirmish loop → (later) cinematics. +4. **Legal:** MIT/original only — never proprietary KKnD assets, rips, or reverse-engineered game files. +5. Always append `AGENT_LOG.md` and keep `STATUS.md` current. + +## Non-goals (do not replace the mission with these) + +- Rewriting the project as a different game +- Asking the user for permission to continue +- Stopping after planning without code/test changes + +This mission text is **law** for all models and all meta rounds. + +--- + +# This round + +CONTINUE openkknd2 ONLY under /home/vx/src/openkknd2-am-coder (this workdir). + +Headless smoke is GREEN (map + units). You implement the next milestones yourself. + +This turn must produce REAL file changes (not just plans): +1. Add building placement (HQ and/or barracks) with resource cost — new headers/sources if needed +2. Add a simple faction AI tick (gather / build / train) +3. Wire into `ok_world_tick` or main smoke +4. Run `./scripts/ci-headless.sh` and keep it GREEN +5. Append AGENT_LOG.md + update STATUS.md + +Rules: MIT/original only; no proprietary KKnD assets. No empty turns. AUTONOMOUS_STOP only if blocked or verified done. +Read src/ and include/ first. + +--- + +## Protocol +- Work thoroughly; no empty turns. +- Emit ## AUTONOMOUS_STOP only when DONE or truly blocked. +- If missing *actionable* rules would unlock autonomy (not a rewrite of the mission), + append exactly one block: + +## NEED_INSTRUCTIONS +why: +need: +- + +Do not use NEED_INSTRUCTIONS to change the mission goal. +- **host:** amanita +- **event:** START + + +### run 2026-07-22T06:09:28Z + +- **title:** # MISSION (immutable — do not renegotiate) + +# Mission (immutable) — openkknd2 + +Build and grow **openkknd2**: an original open-source post-apoc RTS scaffold (genre-inspired only; **not** commercial KKnD). + +## Fixed goals + +1. Work only under the designated workdir (`openkknd2-am-coder` / `$OPENKKND2_ROOT`). +2. Keep `./scripts/ci-headless.sh` **GREEN** after meaningful changes. +3. Advance the milestone ladder: map/units → buildings/economy → faction AI → playable skirmish loop → (later) cinematics. +4. **Legal:** MIT/original only — never proprietary KKnD assets, rips, or reverse-engineered game files. +5. Always append `AGENT_LOG.md` and keep `STATUS.md` current. + +## Non-goals (do not replace the mission with these) + +- Rewriting the project as a different game +- Asking the user for permission to continue +- Stopping after planning without code/test changes + +This mission text is **law** for all models and all meta rounds. + +--- + +# This round + +CONTINUE openkknd2 ONLY under /home/vx/src/openkknd2-am-coder (this workdir). + +Headless smoke is GREEN (map + units). You implement the next milestones yourself. + +This turn must produce REAL file changes (not just plans): +1. Add building placement (HQ and/or barracks) with resource cost — new headers/sources if needed +2. Add a simple faction AI tick (gather / build / train) +3. Wire into `ok_world_tick` or main smoke +4. Run `./scripts/ci-headless.sh` and keep it GREEN +5. Append AGENT_LOG.md + update STATUS.md + +Rules: MIT/original only; no proprietary KKnD assets. No empty turns. AUTONOMOUS_STOP only if blocked or verified done. +Read src/ and include/ first. + +--- + +## Protocol +- Work thoroughly; no empty turns. +- Emit ## AUTONOMOUS_STOP only when DONE or truly blocked. +- If missing *actionable* rules would unlock autonomy (not a rewrite of the mission), + append exactly one block: + +## NEED_INSTRUCTIONS +why: +need: +- + +Do not use NEED_INSTRUCTIONS to change the mission goal. +- **host:** amanita +- **event:** START + +- **2026-07-22T06:09:28Z** · am-task-register · **ok** — id=20260722T060928Z-3579895 +- **2026-07-22T06:09:28Z** · turn-1-start · **RUNNING** — model=ollama/devstral:latest session=ok2meta-r1-devstral +- **2026-07-22T06:09:30Z** · turn-1-end · **ok** — ec=0 +- **2026-07-22T06:09:30Z** · turn-2-start · **RUNNING** — model=ollama/devstral:latest session=ok2meta-r1-devstral +- **2026-07-22T06:09:32Z** · turn-2-end · **ok** — ec=0 +- **2026-07-22T06:09:32Z** · turn-3-start · **RUNNING** — model=ollama/devstral:latest session=ok2meta-r1-devstral +- **2026-07-22T06:09:33Z** · turn-3-end · **ok** — ec=0 +- **2026-07-22T06:09:33Z** · turn-4-start · **RUNNING** — model=ollama/devstral:latest session=ok2meta-r1-devstral +- **2026-07-22T06:09:35Z** · turn-4-end · **ok** — ec=0 +- **2026-07-22T06:09:35Z** · turn-5-start · **RUNNING** — model=ollama/devstral:latest session=ok2meta-r1-devstral +- **2026-07-22T06:09:37Z** · INTERRUPTED · **stop** — reason=user: n — todos beenden hard=0 id=20260722T060928Z-3579895 + +### run 2026-07-22T06:10:02Z + +- **title:** # MISSION (immutable — do not renegotiate) + +# Mission (immutable) — openkknd2 + +Build and grow **openkknd2**: an original open-source post-apoc RTS scaffold (genre-inspired only; **not** commercial KKnD). + +## Fixed goals + +1. Work only under the designated workdir (`openkknd2-am-coder` / `$OPENKKND2_ROOT`). +2. Keep `./scripts/ci-headless.sh` **GREEN** after meaningful changes. +3. Advance the milestone ladder: map/units → buildings/economy → faction AI → playable skirmish loop → (later) cinematics. +4. **Legal:** MIT/original only — never proprietary KKnD assets, rips, or reverse-engineered game files. +5. Always append `AGENT_LOG.md` and keep `STATUS.md` current. + +## Non-goals (do not replace the mission with these) + +- Rewriting the project as a different game +- Asking the user for permission to continue +- Stopping after planning without code/test changes + +This mission text is **law** for all models and all meta rounds. + +--- + +# This round + +CONTINUE openkknd2 ONLY under /home/vx/src/openkknd2-am-coder (this workdir). + +Headless smoke is GREEN (map + units). You implement the next milestones yourself. + +This turn must produce REAL file changes (not just plans): +1. Add building placement (HQ and/or barracks) with resource cost — new headers/sources if needed +2. Add a simple faction AI tick (gather / build / train) +3. Wire into `ok_world_tick` or main smoke +4. Run `./scripts/ci-headless.sh` and keep it GREEN +5. Append AGENT_LOG.md + update STATUS.md + +Rules: MIT/original only; no proprietary KKnD assets. No empty turns. AUTONOMOUS_STOP only if blocked or verified done. +Read src/ and include/ first. + +--- + +## Protocol +- Work thoroughly; no empty turns. +- Emit ## AUTONOMOUS_STOP only when DONE or truly blocked. +- If missing *actionable* rules would unlock autonomy (not a rewrite of the mission), + append exactly one block: + +## NEED_INSTRUCTIONS +why: +need: +- + +Do not use NEED_INSTRUCTIONS to change the mission goal. +- **host:** amanita +- **event:** START + + +### run 2026-07-22T06:10:05Z + +- **title:** # MISSION (immutable — do not renegotiate) + +# Mission (immutable) — openkknd2 + +Build and grow **openkknd2**: an original open-source post-apoc RTS scaffold (genre-inspired only; **not** commercial KKnD). + +## Fixed goals + +1. Work only under the designated workdir (`openkknd2-am-coder` / `$OPENKKND2_ROOT`). +2. Keep `./scripts/ci-headless.sh` **GREEN** after meaningful changes. +3. Advance the milestone ladder: map/units → buildings/economy → faction AI → playable skirmish loop → (later) cinematics. +4. **Legal:** MIT/original only — never proprietary KKnD assets, rips, or reverse-engineered game files. +5. Always append `AGENT_LOG.md` and keep `STATUS.md` current. + +## Non-goals (do not replace the mission with these) + +- Rewriting the project as a different game +- Asking the user for permission to continue +- Stopping after planning without code/test changes + +This mission text is **law** for all models and all meta rounds. + +--- + +# This round + +CONTINUE openkknd2 ONLY under /home/vx/src/openkknd2-am-coder (this workdir). + +Headless smoke is GREEN (map + units). You implement the next milestones yourself. + +This turn must produce REAL file changes (not just plans): +1. Add building placement (HQ and/or barracks) with resource cost — new headers/sources if needed +2. Add a simple faction AI tick (gather / build / train) +3. Wire into `ok_world_tick` or main smoke +4. Run `./scripts/ci-headless.sh` and keep it GREEN +5. Append AGENT_LOG.md + update STATUS.md + +Rules: MIT/original only; no proprietary KKnD assets. No empty turns. AUTONOMOUS_STOP only if blocked or verified done. +Read src/ and include/ first. + +--- + +## Protocol +- Work thoroughly; no empty turns. +- Emit ## AUTONOMOUS_STOP only when DONE or truly blocked. +- If missing *actionable* rules would unlock autonomy (not a rewrite of the mission), + append exactly one block: + +## NEED_INSTRUCTIONS +why: +need: +- + +Do not use NEED_INSTRUCTIONS to change the mission goal. +- **host:** amanita +- **event:** START + +- **2026-07-22T06:10:05Z** · am-task-register · **ok** — id=20260722T061005Z-3580802 +- **2026-07-22T06:10:05Z** · turn-1-start · **RUNNING** — model=ollama/devstral:latest session=ok2meta-r1-devstral +- **2026-07-22T06:10:07Z** · turn-1-end · **ok** — ec=0 +- **2026-07-22T06:10:07Z** · turn-2-start · **RUNNING** — model=ollama/devstral:latest session=ok2meta-r1-devstral +- **2026-07-22T06:10:09Z** · turn-2-end · **ok** — ec=0 +- **2026-07-22T06:10:09Z** · turn-3-start · **RUNNING** — model=ollama/devstral:latest session=ok2meta-r1-devstral +- **2026-07-22T06:10:10Z** · turn-3-end · **ok** — ec=0 +- **2026-07-22T06:10:10Z** · turn-4-start · **RUNNING** — model=ollama/devstral:latest session=ok2meta-r1-devstral +- **2026-07-22T06:10:12Z** · turn-4-end · **ok** — ec=0 +- **2026-07-22T06:10:12Z** · turn-5-start · **RUNNING** — model=ollama/devstral:latest session=ok2meta-r1-devstral +- **2026-07-22T06:10:13Z** · turn-5-end · **ok** — ec=0 +- **2026-07-22T06:10:13Z** · turn-6-start · **RUNNING** — model=ollama/devstral:latest session=ok2meta-r1-devstral +- **2026-07-22T06:10:15Z** · turn-6-end · **ok** — ec=0 +- **2026-07-22T06:10:15Z** · turn-7-start · **RUNNING** — model=ollama/devstral:latest session=ok2meta-r1-devstral +- **2026-07-22T06:10:17Z** · turn-7-end · **ok** — ec=0 +- **2026-07-22T06:10:17Z** · turn-8-start · **RUNNING** — model=ollama/devstral:latest session=ok2meta-r1-devstral +- **2026-07-22T06:10:18Z** · turn-8-end · **ok** — ec=0 +- **2026-07-22T06:10:18Z** · STATUS→RUNNING · **RUNNING** — max-turns 8 reached + +### run 2026-07-22T06:10:32Z + +- **title:** # MISSION (immutable — do not renegotiate) + +# Mission (immutable) — openkknd2 + +Build and grow **openkknd2**: an original open-source post-apoc RTS scaffold (genre-inspired only; **not** commercial KKnD). + +## Fixed goals + +1. Work only under the designated workdir (`openkknd2-am-coder` / `$OPENKKND2_ROOT`). +2. Keep `./scripts/ci-headless.sh` **GREEN** after meaningful changes. +3. Advance the milestone ladder: map/units → buildings/economy → faction AI → playable skirmish loop → (later) cinematics. +4. **Legal:** MIT/original only — never proprietary KKnD assets, rips, or reverse-engineered game files. +5. Always append `AGENT_LOG.md` and keep `STATUS.md` current. + +## Non-goals (do not replace the mission with these) + +- Rewriting the project as a different game +- Asking the user for permission to continue +- Stopping after planning without code/test changes + +This mission text is **law** for all models and all meta rounds. + +--- + +# This round + +CONTINUE openkknd2 ONLY under /home/vx/src/openkknd2-am-coder (this workdir). + +Headless smoke is GREEN (map + units). You implement the next milestones yourself. + +This turn must produce REAL file changes (not just plans): +1. Add building placement (HQ and/or barracks) with resource cost — new headers/sources if needed +2. Add a simple faction AI tick (gather / build / train) +3. Wire into `ok_world_tick` or main smoke +4. Run `./scripts/ci-headless.sh` and keep it GREEN +5. Append AGENT_LOG.md + update STATUS.md + +Rules: MIT/original only; no proprietary KKnD assets. No empty turns. AUTONOMOUS_STOP only if blocked or verified done. +Read src/ and include/ first. + +--- + +## Protocol +- Work thoroughly; no empty turns. +- Emit ## AUTONOMOUS_STOP only when DONE or truly blocked. +- If missing *actionable* rules would unlock autonomy (not a rewrite of the mission), + append exactly one block: + +## NEED_INSTRUCTIONS +why: +need: +- + +Do not use NEED_INSTRUCTIONS to change the mission goal. +- **host:** amanita +- **event:** START + + +### run 2026-07-22T06:10:35Z + +- **title:** # MISSION (immutable — do not renegotiate) + +# Mission (immutable) — openkknd2 + +Build and grow **openkknd2**: an original open-source post-apoc RTS scaffold (genre-inspired only; **not** commercial KKnD). + +## Fixed goals + +1. Work only under the designated workdir (`openkknd2-am-coder` / `$OPENKKND2_ROOT`). +2. Keep `./scripts/ci-headless.sh` **GREEN** after meaningful changes. +3. Advance the milestone ladder: map/units → buildings/economy → faction AI → playable skirmish loop → (later) cinematics. +4. **Legal:** MIT/original only — never proprietary KKnD assets, rips, or reverse-engineered game files. +5. Always append `AGENT_LOG.md` and keep `STATUS.md` current. + +## Non-goals (do not replace the mission with these) + +- Rewriting the project as a different game +- Asking the user for permission to continue +- Stopping after planning without code/test changes + +This mission text is **law** for all models and all meta rounds. + +--- + +# This round + +CONTINUE openkknd2 ONLY under /home/vx/src/openkknd2-am-coder (this workdir). + +Headless smoke is GREEN (map + units). You implement the next milestones yourself. + +This turn must produce REAL file changes (not just plans): +1. Add building placement (HQ and/or barracks) with resource cost — new headers/sources if needed +2. Add a simple faction AI tick (gather / build / train) +3. Wire into `ok_world_tick` or main smoke +4. Run `./scripts/ci-headless.sh` and keep it GREEN +5. Append AGENT_LOG.md + update STATUS.md + +Rules: MIT/original only; no proprietary KKnD assets. No empty turns. AUTONOMOUS_STOP only if blocked or verified done. +Read src/ and include/ first. + +--- + +## Protocol +- Work thoroughly; no empty turns. +- Emit ## AUTONOMOUS_STOP only when DONE or truly blocked. +- If missing *actionable* rules would unlock autonomy (not a rewrite of the mission), + append exactly one block: + +## NEED_INSTRUCTIONS +why: +need: +- + +Do not use NEED_INSTRUCTIONS to change the mission goal. +- **host:** amanita +- **event:** START + +- **2026-07-22T06:10:35Z** · am-task-register · **ok** — id=20260722T061035Z-3581648 +- **2026-07-22T06:10:35Z** · turn-1-start · **RUNNING** — model=ollama/devstral:latest session=ok2meta-r2-devstral +- **2026-07-22T06:13:46Z** · turn-1-end · **ok** — ec=0 +- **2026-07-22T06:13:46Z** · turn-2-start · **RUNNING** — model=ollama/devstral:latest session=ok2meta-r2-devstral +- **2026-07-22T06:15:35Z** · turn-2-end · **ok** — ec=0 +- **2026-07-22T06:15:35Z** · turn-3-start · **RUNNING** — model=ollama/devstral:latest session=ok2meta-r2-devstral +- **2026-07-22T06:15:37Z** · turn-3-end · **ok** — ec=0 +- **2026-07-22T06:15:37Z** · turn-4-start · **RUNNING** — model=ollama/devstral:latest session=ok2meta-r2-devstral +- **2026-07-22T06:15:38Z** · turn-4-end · **ok** — ec=0 +- **2026-07-22T06:15:38Z** · turn-5-start · **RUNNING** — model=ollama/devstral:latest session=ok2meta-r2-devstral +- **2026-07-22T06:15:40Z** · turn-5-end · **ok** — ec=0 +- **2026-07-22T06:15:40Z** · turn-6-start · **RUNNING** — model=ollama/devstral:latest session=ok2meta-r2-devstral +- **2026-07-22T06:15:42Z** · turn-6-end · **ok** — ec=0 +- **2026-07-22T06:15:42Z** · turn-7-start · **RUNNING** — model=ollama/devstral:latest session=ok2meta-r2-devstral +- **2026-07-22T06:15:43Z** · turn-7-end · **ok** — ec=0 +- **2026-07-22T06:15:43Z** · turn-8-start · **RUNNING** — model=ollama/devstral:latest session=ok2meta-r2-devstral +- **2026-07-22T06:15:45Z** · turn-8-end · **ok** — ec=0 +- **2026-07-22T06:15:45Z** · STATUS→RUNNING · **RUNNING** — max-turns 8 reached + +### run 2026-07-22T06:16:03Z + +- **title:** # MISSION (immutable — do not renegotiate) + +# Mission (immutable) — openkknd2 + +Build and grow **openkknd2**: an original open-source post-apoc RTS scaffold (genre-inspired only; **not** commercial KKnD). + +## Fixed goals + +1. Work only under the designated workdir (`openkknd2-am-coder` / `$OPENKKND2_ROOT`). +2. Keep `./scripts/ci-headless.sh` **GREEN** after meaningful changes. +3. Advance the milestone ladder: map/units → buildings/economy → faction AI → playable skirmish loop → (later) cinematics. +4. **Legal:** MIT/original only — never proprietary KKnD assets, rips, or reverse-engineered game files. +5. Always append `AGENT_LOG.md` and keep `STATUS.md` current. + +## Non-goals (do not replace the mission with these) + +- Rewriting the project as a different game +- Asking the user for permission to continue +- Stopping after planning without code/test changes + +This mission text is **law** for all models and all meta rounds. + +--- + +# This round + +CONTINUE openkknd2 ONLY under /home/vx/src/openkknd2-am-coder (this workdir). + +Headless smoke is GREEN (map + units). You implement the next milestones yourself. + +This turn must produce REAL file changes (not just plans): +1. Add building placement (HQ and/or barracks) with resource cost — new headers/sources if needed +2. Add a simple faction AI tick (gather / build / train) +3. Wire into `ok_world_tick` or main smoke +4. Run `./scripts/ci-headless.sh` and keep it GREEN +5. Append AGENT_LOG.md + update STATUS.md + +Rules: MIT/original only; no proprietary KKnD assets. No empty turns. AUTONOMOUS_STOP only if blocked or verified done. +Read src/ and include/ first. + +--- + +## Protocol +- Work thoroughly; no empty turns. +- Emit ## AUTONOMOUS_STOP only when DONE or truly blocked. +- If missing *actionable* rules would unlock autonomy (not a rewrite of the mission), + append exactly one block: + +## NEED_INSTRUCTIONS +why: +need: +- + +Do not use NEED_INSTRUCTIONS to change the mission goal. +- **host:** amanita +- **event:** START + + +### run 2026-07-22T06:16:05Z + +- **title:** # MISSION (immutable — do not renegotiate) + +# Mission (immutable) — openkknd2 + +Build and grow **openkknd2**: an original open-source post-apoc RTS scaffold (genre-inspired only; **not** commercial KKnD). + +## Fixed goals + +1. Work only under the designated workdir (`openkknd2-am-coder` / `$OPENKKND2_ROOT`). +2. Keep `./scripts/ci-headless.sh` **GREEN** after meaningful changes. +3. Advance the milestone ladder: map/units → buildings/economy → faction AI → playable skirmish loop → (later) cinematics. +4. **Legal:** MIT/original only — never proprietary KKnD assets, rips, or reverse-engineered game files. +5. Always append `AGENT_LOG.md` and keep `STATUS.md` current. + +## Non-goals (do not replace the mission with these) + +- Rewriting the project as a different game +- Asking the user for permission to continue +- Stopping after planning without code/test changes + +This mission text is **law** for all models and all meta rounds. + +--- + +# This round + +CONTINUE openkknd2 ONLY under /home/vx/src/openkknd2-am-coder (this workdir). + +Headless smoke is GREEN (map + units). You implement the next milestones yourself. + +This turn must produce REAL file changes (not just plans): +1. Add building placement (HQ and/or barracks) with resource cost — new headers/sources if needed +2. Add a simple faction AI tick (gather / build / train) +3. Wire into `ok_world_tick` or main smoke +4. Run `./scripts/ci-headless.sh` and keep it GREEN +5. Append AGENT_LOG.md + update STATUS.md + +Rules: MIT/original only; no proprietary KKnD assets. No empty turns. AUTONOMOUS_STOP only if blocked or verified done. +Read src/ and include/ first. + +--- + +## Protocol +- Work thoroughly; no empty turns. +- Emit ## AUTONOMOUS_STOP only when DONE or truly blocked. +- If missing *actionable* rules would unlock autonomy (not a rewrite of the mission), + append exactly one block: + +## NEED_INSTRUCTIONS +why: +need: +- + +Do not use NEED_INSTRUCTIONS to change the mission goal. +- **host:** amanita +- **event:** START + +- **2026-07-22T06:16:05Z** · am-task-register · **ok** — id=20260722T061605Z-3590955 +- **2026-07-22T06:16:05Z** · turn-1-start · **RUNNING** — model=ollama/qwen3-coder:latest session=ok2meta-r3-coder +- **2026-07-22T06:21:27Z** · turn-1-end · **ok** — ec=0 +- **2026-07-22T06:21:27Z** · turn-2-start · **RUNNING** — model=ollama/qwen3-coder:latest session=ok2meta-r3-coder +- **2026-07-22T06:21:28Z** · turn-2-end · **ok** — ec=0 +- **2026-07-22T06:21:28Z** · turn-3-start · **RUNNING** — model=ollama/qwen3-coder:latest session=ok2meta-r3-coder +- **2026-07-22T06:21:37Z** · turn-3-end · **ok** — ec=0 +- **2026-07-22T06:21:37Z** · turn-4-start · **RUNNING** — model=ollama/qwen3-coder:latest session=ok2meta-r3-coder +- **2026-07-22T06:21:38Z** · turn-4-end · **ok** — ec=0 +- **2026-07-22T06:21:38Z** · turn-5-start · **RUNNING** — model=ollama/qwen3-coder:latest session=ok2meta-r3-coder +- **2026-07-22T06:21:40Z** · turn-5-end · **ok** — ec=0 +- **2026-07-22T06:21:40Z** · turn-6-start · **RUNNING** — model=ollama/qwen3-coder:latest session=ok2meta-r3-coder +- **2026-07-22T06:21:41Z** · turn-6-end · **ok** — ec=0 +- **2026-07-22T06:21:41Z** · turn-7-start · **RUNNING** — model=ollama/qwen3-coder:latest session=ok2meta-r3-coder +- **2026-07-22T06:21:43Z** · turn-7-end · **ok** — ec=0 +- **2026-07-22T06:21:43Z** · turn-8-start · **RUNNING** — model=ollama/qwen3-coder:latest session=ok2meta-r3-coder +- **2026-07-22T06:21:45Z** · turn-8-end · **ok** — ec=0 +- **2026-07-22T06:21:45Z** · STATUS→RUNNING · **RUNNING** — max-turns 8 reached + +### run 2026-07-22T06:21:53Z + +- **title:** # MISSION (immutable — do not renegotiate) + +# Mission (immutable) — openkknd2 + +Build and grow **openkknd2**: an original open-source post-apoc RTS scaffold (genre-inspired only; **not** commercial KKnD). + +## Fixed goals + +1. Work only under the designated workdir (`openkknd2-am-coder` / `$OPENKKND2_ROOT`). +2. Keep `./scripts/ci-headless.sh` **GREEN** after meaningful changes. +3. Advance the milestone ladder: map/units → buildings/economy → faction AI → playable skirmish loop → (later) cinematics. +4. **Legal:** MIT/original only — never proprietary KKnD assets, rips, or reverse-engineered game files. +5. Always append `AGENT_LOG.md` and keep `STATUS.md` current. + +## Non-goals (do not replace the mission with these) + +- Rewriting the project as a different game +- Asking the user for permission to continue +- Stopping after planning without code/test changes + +This mission text is **law** for all models and all meta rounds. + +--- + +# This round + +CONTINUE openkknd2 ONLY under /home/vx/src/openkknd2-am-coder (this workdir). + +Headless smoke is GREEN (map + units). You implement the next milestones yourself. + +This turn must produce REAL file changes (not just plans): +1. Add building placement (HQ and/or barracks) with resource cost — new headers/sources if needed +2. Add a simple faction AI tick (gather / build / train) +3. Wire into `ok_world_tick` or main smoke +4. Run `./scripts/ci-headless.sh` and keep it GREEN +5. Append AGENT_LOG.md + update STATUS.md + +Rules: MIT/original only; no proprietary KKnD assets. No empty turns. AUTONOMOUS_STOP only if blocked or verified done. +Read src/ and include/ first. + +--- + +## Protocol +- Work thoroughly; no empty turns. +- Emit ## AUTONOMOUS_STOP only when DONE or truly blocked. +- If missing *actionable* rules would unlock autonomy (not a rewrite of the mission), + append exactly one block: + +## NEED_INSTRUCTIONS +why: +need: +- + +Do not use NEED_INSTRUCTIONS to change the mission goal. +- **host:** amanita +- **event:** START + + +### run 2026-07-22T06:21:56Z + +- **title:** # MISSION (immutable — do not renegotiate) + +# Mission (immutable) — openkknd2 + +Build and grow **openkknd2**: an original open-source post-apoc RTS scaffold (genre-inspired only; **not** commercial KKnD). + +## Fixed goals + +1. Work only under the designated workdir (`openkknd2-am-coder` / `$OPENKKND2_ROOT`). +2. Keep `./scripts/ci-headless.sh` **GREEN** after meaningful changes. +3. Advance the milestone ladder: map/units → buildings/economy → faction AI → playable skirmish loop → (later) cinematics. +4. **Legal:** MIT/original only — never proprietary KKnD assets, rips, or reverse-engineered game files. +5. Always append `AGENT_LOG.md` and keep `STATUS.md` current. + +## Non-goals (do not replace the mission with these) + +- Rewriting the project as a different game +- Asking the user for permission to continue +- Stopping after planning without code/test changes + +This mission text is **law** for all models and all meta rounds. + +--- + +# This round + +CONTINUE openkknd2 ONLY under /home/vx/src/openkknd2-am-coder (this workdir). + +Headless smoke is GREEN (map + units). You implement the next milestones yourself. + +This turn must produce REAL file changes (not just plans): +1. Add building placement (HQ and/or barracks) with resource cost — new headers/sources if needed +2. Add a simple faction AI tick (gather / build / train) +3. Wire into `ok_world_tick` or main smoke +4. Run `./scripts/ci-headless.sh` and keep it GREEN +5. Append AGENT_LOG.md + update STATUS.md + +Rules: MIT/original only; no proprietary KKnD assets. No empty turns. AUTONOMOUS_STOP only if blocked or verified done. +Read src/ and include/ first. + +--- + +## Protocol +- Work thoroughly; no empty turns. +- Emit ## AUTONOMOUS_STOP only when DONE or truly blocked. +- If missing *actionable* rules would unlock autonomy (not a rewrite of the mission), + append exactly one block: + +## NEED_INSTRUCTIONS +why: +need: +- + +Do not use NEED_INSTRUCTIONS to change the mission goal. +- **host:** amanita +- **event:** START + +- **2026-07-22T06:21:56Z** · am-task-register · **ok** — id=20260722T062156Z-424245 +- **2026-07-22T06:21:56Z** · turn-1-start · **RUNNING** — model=ollama/qwen3-coder:latest session=ok2meta-r4-coder +- **2026-07-22T06:26:02Z** · turn-1-end · **ok** — ec=0 +- **2026-07-22T06:26:02Z** · turn-2-start · **RUNNING** — model=ollama/qwen3-coder:latest session=ok2meta-r4-coder +- **2026-07-22T06:26:14Z** · turn-2-end · **ok** — ec=0 +- **2026-07-22T06:26:14Z** · turn-3-start · **RUNNING** — model=ollama/qwen3-coder:latest session=ok2meta-r4-coder +- **2026-07-22T06:26:28Z** · turn-3-end · **ok** — ec=0 +- **2026-07-22T06:26:28Z** · turn-4-start · **RUNNING** — model=ollama/qwen3-coder:latest session=ok2meta-r4-coder +- **2026-07-22T06:26:29Z** · turn-4-end · **ok** — ec=0 +- **2026-07-22T06:26:29Z** · turn-5-start · **RUNNING** — model=ollama/qwen3-coder:latest session=ok2meta-r4-coder +- **2026-07-22T06:26:31Z** · turn-5-end · **ok** — ec=0 +- **2026-07-22T06:26:31Z** · turn-6-start · **RUNNING** — model=ollama/qwen3-coder:latest session=ok2meta-r4-coder +- **2026-07-22T06:26:32Z** · turn-6-end · **ok** — ec=0 +- **2026-07-22T06:26:32Z** · turn-7-start · **RUNNING** — model=ollama/qwen3-coder:latest session=ok2meta-r4-coder +- **2026-07-22T06:26:34Z** · turn-7-end · **ok** — ec=0 +- **2026-07-22T06:26:34Z** · turn-8-start · **RUNNING** — model=ollama/qwen3-coder:latest session=ok2meta-r4-coder +- **2026-07-22T06:26:36Z** · turn-8-end · **ok** — ec=0 +- **2026-07-22T06:26:36Z** · STATUS→RUNNING · **RUNNING** — max-turns 8 reached + +### run 2026-07-22T06:26:43Z + +- **title:** # MISSION (immutable — do not renegotiate) + +# Mission (immutable) — openkknd2 + +Build and grow **openkknd2**: an original open-source post-apoc RTS scaffold (genre-inspired only; **not** commercial KKnD). + +## Fixed goals + +1. Work only under the designated workdir (`openkknd2-am-coder` / `$OPENKKND2_ROOT`). +2. Keep `./scripts/ci-headless.sh` **GREEN** after meaningful changes. +3. Advance the milestone ladder: map/units → buildings/economy → faction AI → playable skirmish loop → (later) cinematics. +4. **Legal:** MIT/original only — never proprietary KKnD assets, rips, or reverse-engineered game files. +5. Always append `AGENT_LOG.md` and keep `STATUS.md` current. + +## Non-goals (do not replace the mission with these) + +- Rewriting the project as a different game +- Asking the user for permission to continue +- Stopping after planning without code/test changes + +This mission text is **law** for all models and all meta rounds. + +--- + +# This round + +CONTINUE openkknd2 ONLY under /home/vx/src/openkknd2-am-coder (this workdir). + +Headless smoke is GREEN (map + units). You implement the next milestones yourself. + +This turn must produce REAL file changes (not just plans): +1. Add building placement (HQ and/or barracks) with resource cost — new headers/sources if needed +2. Add a simple faction AI tick (gather / build / train) +3. Wire into `ok_world_tick` or main smoke +4. Run `./scripts/ci-headless.sh` and keep it GREEN +5. Append AGENT_LOG.md + update STATUS.md + +Rules: MIT/original only; no proprietary KKnD assets. No empty turns. AUTONOMOUS_STOP only if blocked or verified done. +Read src/ and include/ first. + +--- + +## Protocol +- Work thoroughly; no empty turns. +- Emit ## AUTONOMOUS_STOP only when DONE or truly blocked. +- If missing *actionable* rules would unlock autonomy (not a rewrite of the mission), + append exactly one block: + +## NEED_INSTRUCTIONS +why: +need: +- + +Do not use NEED_INSTRUCTIONS to change the mission goal. +- **host:** amanita +- **event:** START + + +### run 2026-07-22T06:26:46Z + +- **title:** # MISSION (immutable — do not renegotiate) + +# Mission (immutable) — openkknd2 + +Build and grow **openkknd2**: an original open-source post-apoc RTS scaffold (genre-inspired only; **not** commercial KKnD). + +## Fixed goals + +1. Work only under the designated workdir (`openkknd2-am-coder` / `$OPENKKND2_ROOT`). +2. Keep `./scripts/ci-headless.sh` **GREEN** after meaningful changes. +3. Advance the milestone ladder: map/units → buildings/economy → faction AI → playable skirmish loop → (later) cinematics. +4. **Legal:** MIT/original only — never proprietary KKnD assets, rips, or reverse-engineered game files. +5. Always append `AGENT_LOG.md` and keep `STATUS.md` current. + +## Non-goals (do not replace the mission with these) + +- Rewriting the project as a different game +- Asking the user for permission to continue +- Stopping after planning without code/test changes + +This mission text is **law** for all models and all meta rounds. + +--- + +# This round + +CONTINUE openkknd2 ONLY under /home/vx/src/openkknd2-am-coder (this workdir). + +Headless smoke is GREEN (map + units). You implement the next milestones yourself. + +This turn must produce REAL file changes (not just plans): +1. Add building placement (HQ and/or barracks) with resource cost — new headers/sources if needed +2. Add a simple faction AI tick (gather / build / train) +3. Wire into `ok_world_tick` or main smoke +4. Run `./scripts/ci-headless.sh` and keep it GREEN +5. Append AGENT_LOG.md + update STATUS.md + +Rules: MIT/original only; no proprietary KKnD assets. No empty turns. AUTONOMOUS_STOP only if blocked or verified done. +Read src/ and include/ first. + +--- + +## Protocol +- Work thoroughly; no empty turns. +- Emit ## AUTONOMOUS_STOP only when DONE or truly blocked. +- If missing *actionable* rules would unlock autonomy (not a rewrite of the mission), + append exactly one block: + +## NEED_INSTRUCTIONS +why: +need: +- + +Do not use NEED_INSTRUCTIONS to change the mission goal. +- **host:** amanita +- **event:** START + +- **2026-07-22T06:26:46Z** · am-task-register · **ok** — id=20260722T062646Z-1588248 +- **2026-07-22T06:26:46Z** · turn-1-start · **RUNNING** — model=ollama/qwen3-coder:latest session=ok2meta-r5-coder +- **2026-07-22T06:30:15Z** · turn-1-end · **ok** — ec=0 +- **2026-07-22T06:30:15Z** · turn-2-start · **RUNNING** — model=ollama/qwen3-coder:latest session=ok2meta-r5-coder +- **2026-07-22T06:30:16Z** · turn-2-end · **ok** — ec=0 +- **2026-07-22T06:30:16Z** · turn-3-start · **RUNNING** — model=ollama/qwen3-coder:latest session=ok2meta-r5-coder +- **2026-07-22T06:30:28Z** · turn-3-end · **ok** — ec=0 +- **2026-07-22T06:30:28Z** · turn-4-start · **RUNNING** — model=ollama/qwen3-coder:latest session=ok2meta-r5-coder +- **2026-07-22T06:30:29Z** · turn-4-end · **ok** — ec=0 +- **2026-07-22T06:30:29Z** · turn-5-start · **RUNNING** — model=ollama/qwen3-coder:latest session=ok2meta-r5-coder +- **2026-07-22T06:30:31Z** · turn-5-end · **ok** — ec=0 +- **2026-07-22T06:30:31Z** · turn-6-start · **RUNNING** — model=ollama/qwen3-coder:latest session=ok2meta-r5-coder +- **2026-07-22T06:30:33Z** · turn-6-end · **ok** — ec=0 +- **2026-07-22T06:30:33Z** · turn-7-start · **RUNNING** — model=ollama/qwen3-coder:latest session=ok2meta-r5-coder +- **2026-07-22T06:30:34Z** · turn-7-end · **ok** — ec=0 +- **2026-07-22T06:30:34Z** · turn-8-start · **RUNNING** — model=ollama/qwen3-coder:latest session=ok2meta-r5-coder +- **2026-07-22T06:30:36Z** · turn-8-end · **ok** — ec=0 +- **2026-07-22T06:30:36Z** · STATUS→RUNNING · **RUNNING** — max-turns 8 reached + +### run 2026-07-22T06:30:54Z + +- **title:** # MISSION (immutable — do not renegotiate) + +# Mission (immutable) — openkknd2 + +Build and grow **openkknd2**: an original open-source post-apoc RTS scaffold (genre-inspired only; **not** commercial KKnD). + +## Fixed goals + +1. Work only under the designated workdir (`openkknd2-am-coder` / `$OPENKKND2_ROOT`). +2. Keep `./scripts/ci-headless.sh` **GREEN** after meaningful changes. +3. Advance the milestone ladder: map/units → buildings/economy → faction AI → playable skirmish loop → (later) cinematics. +4. **Legal:** MIT/original only — never proprietary KKnD assets, rips, or reverse-engineered game files. +5. Always append `AGENT_LOG.md` and keep `STATUS.md` current. + +## Non-goals (do not replace the mission with these) + +- Rewriting the project as a different game +- Asking the user for permission to continue +- Stopping after planning without code/test changes + +This mission text is **law** for all models and all meta rounds. + +--- + +# This round + +CONTINUE openkknd2 ONLY under /home/vx/src/openkknd2-am-coder (this workdir). + +Headless smoke is GREEN (map + units). You implement the next milestones yourself. + +This turn must produce REAL file changes (not just plans): +1. Add building placement (HQ and/or barracks) with resource cost — new headers/sources if needed +2. Add a simple faction AI tick (gather / build / train) +3. Wire into `ok_world_tick` or main smoke +4. Run `./scripts/ci-headless.sh` and keep it GREEN +5. Append AGENT_LOG.md + update STATUS.md + +Rules: MIT/original only; no proprietary KKnD assets. No empty turns. AUTONOMOUS_STOP only if blocked or verified done. +Read src/ and include/ first. + +--- + +## Protocol +- Work thoroughly; no empty turns. +- Emit ## AUTONOMOUS_STOP only when DONE or truly blocked. +- If missing *actionable* rules would unlock autonomy (not a rewrite of the mission), + append exactly one block: + +## NEED_INSTRUCTIONS +why: +need: +- + +Do not use NEED_INSTRUCTIONS to change the mission goal. +- **host:** amanita +- **event:** START + + +### run 2026-07-22T06:30:56Z + +- **title:** # MISSION (immutable — do not renegotiate) + +# Mission (immutable) — openkknd2 + +Build and grow **openkknd2**: an original open-source post-apoc RTS scaffold (genre-inspired only; **not** commercial KKnD). + +## Fixed goals + +1. Work only under the designated workdir (`openkknd2-am-coder` / `$OPENKKND2_ROOT`). +2. Keep `./scripts/ci-headless.sh` **GREEN** after meaningful changes. +3. Advance the milestone ladder: map/units → buildings/economy → faction AI → playable skirmish loop → (later) cinematics. +4. **Legal:** MIT/original only — never proprietary KKnD assets, rips, or reverse-engineered game files. +5. Always append `AGENT_LOG.md` and keep `STATUS.md` current. + +## Non-goals (do not replace the mission with these) + +- Rewriting the project as a different game +- Asking the user for permission to continue +- Stopping after planning without code/test changes + +This mission text is **law** for all models and all meta rounds. + +--- + +# This round + +CONTINUE openkknd2 ONLY under /home/vx/src/openkknd2-am-coder (this workdir). + +Headless smoke is GREEN (map + units). You implement the next milestones yourself. + +This turn must produce REAL file changes (not just plans): +1. Add building placement (HQ and/or barracks) with resource cost — new headers/sources if needed +2. Add a simple faction AI tick (gather / build / train) +3. Wire into `ok_world_tick` or main smoke +4. Run `./scripts/ci-headless.sh` and keep it GREEN +5. Append AGENT_LOG.md + update STATUS.md + +Rules: MIT/original only; no proprietary KKnD assets. No empty turns. AUTONOMOUS_STOP only if blocked or verified done. +Read src/ and include/ first. + +--- + +## Protocol +- Work thoroughly; no empty turns. +- Emit ## AUTONOMOUS_STOP only when DONE or truly blocked. +- If missing *actionable* rules would unlock autonomy (not a rewrite of the mission), + append exactly one block: + +## NEED_INSTRUCTIONS +why: +need: +- + +Do not use NEED_INSTRUCTIONS to change the mission goal. +- **host:** amanita +- **event:** START + +- **2026-07-22T06:30:56Z** · am-task-register · **ok** — id=20260722T063056Z-2474530 +- **2026-07-22T06:30:56Z** · turn-1-start · **RUNNING** — model=ollama/qwen3:14b session=ok2meta-r6-qwen314 diff --git a/openkknd2-am-coder/CMakeLists.txt b/openkknd2-am-coder/CMakeLists.txt new file mode 100644 index 0000000..4f17794 --- /dev/null +++ b/openkknd2-am-coder/CMakeLists.txt @@ -0,0 +1,8 @@ +cmake_minimum_required(VERSION 3.10) +project(openkknd2 C) +set(CMAKE_C_STANDARD 99) +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra") +add_executable(openkknd2 src/main.c src/ok_map.c src/ok_unit.c) +target_include_directories(openkknd2 PRIVATE include) +enable_testing() +add_test(NAME smoke_test COMMAND openkknd2 --headless --smoke) diff --git a/openkknd2-am-coder/LICENSE b/openkknd2-am-coder/LICENSE new file mode 100644 index 0000000..edd474b --- /dev/null +++ b/openkknd2-am-coder/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 openkknd2 contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/openkknd2-am-coder/README.md b/openkknd2-am-coder/README.md new file mode 100644 index 0000000..40bf245 --- /dev/null +++ b/openkknd2-am-coder/README.md @@ -0,0 +1,12 @@ +# openkknd2 + +An original open-source post-apocalyptic RTS game, inspired by 1990s wasteland RTS games. + +## Status + +This is a work in progress. See [STATUS.md](STATUS.md) for current status. + +## Legal + +This project uses only original work, CC0/CC-BY (attributed), MIT/BSD/Zlib code with compatible license policy. +No KKnD / KKnD2 / Xtreme game files, ISOs, rips, sprites, music, maps, voices, or FMV are used. \ No newline at end of file diff --git a/openkknd2-am-coder/RESUME.md b/openkknd2-am-coder/RESUME.md new file mode 100644 index 0000000..c8219a4 --- /dev/null +++ b/openkknd2-am-coder/RESUME.md @@ -0,0 +1,13 @@ +# Resume + +- **task_id:** `20260722T060928Z-3579895` +- **interrupted:** 2026-07-22T06:09:37Z +- **reason:** user: n — todos beenden + +## Command + +```bash +bash /home/vx/src/openkknd2-am-coder/.agent-coder-resume.sh +``` + +Or: `am-task resume 20260722T060928Z-3579895` diff --git a/openkknd2-am-coder/STATUS.md b/openkknd2-am-coder/STATUS.md new file mode 100644 index 0000000..08b6da6 --- /dev/null +++ b/openkknd2-am-coder/STATUS.md @@ -0,0 +1,64 @@ +# Status + +- **state:** RUNNING +- **updated:** 2026-07-22T06:30:56Z +- **host:** amanita +- **workdir:** `/home/vx/src/openkknd2-am-coder` +- **title:** # MISSION (immutable — do not renegotiate) + +# Mission (immutable) — openkknd2 + +Build and grow **openkknd2**: an original open-source post-apoc RTS scaffold (genre-inspired only; **not** commercial KKnD). + +## Fixed goals + +1. Work only under the designated workdir (`openkknd2-am-coder` / `$OPENKKND2_ROOT`). +2. Keep `./scripts/ci-headless.sh` **GREEN** after meaningful changes. +3. Advance the milestone ladder: map/units → buildings/economy → faction AI → playable skirmish loop → (later) cinematics. +4. **Legal:** MIT/original only — never proprietary KKnD assets, rips, or reverse-engineered game files. +5. Always append `AGENT_LOG.md` and keep `STATUS.md` current. + +## Non-goals (do not replace the mission with these) + +- Rewriting the project as a different game +- Asking the user for permission to continue +- Stopping after planning without code/test changes + +This mission text is **law** for all models and all meta rounds. + +--- + +# This round + +CONTINUE openkknd2 ONLY under /home/vx/src/openkknd2-am-coder (this workdir). + +Headless smoke is GREEN (map + units). You implement the next milestones yourself. + +This turn must produce REAL file changes (not just plans): +1. Add building placement (HQ and/or barracks) with resource cost — new headers/sources if needed +2. Add a simple faction AI tick (gather / build / train) +3. Wire into `ok_world_tick` or main smoke +4. Run `./scripts/ci-headless.sh` and keep it GREEN +5. Append AGENT_LOG.md + update STATUS.md + +Rules: MIT/original only; no proprietary KKnD assets. No empty turns. AUTONOMOUS_STOP only if blocked or verified done. +Read src/ and include/ first. + +--- + +## Protocol +- Work thoroughly; no empty turns. +- Emit ## AUTONOMOUS_STOP only when DONE or truly blocked. +- If missing *actionable* rules would unlock autonomy (not a rewrite of the mission), + append exactly one block: + +## NEED_INSTRUCTIONS +why: +need: +- + +Do not use NEED_INSTRUCTIONS to change the mission goal. + +## Last note + +Session started. diff --git a/openkknd2-am-coder/TASK.md b/openkknd2-am-coder/TASK.md new file mode 100644 index 0000000..d54d0b6 --- /dev/null +++ b/openkknd2-am-coder/TASK.md @@ -0,0 +1,72 @@ +# Task + +- **started:** 2026-07-22T06:30:56Z +- **host:** amanita +- **workdir:** `/home/vx/src/openkknd2-am-coder` + +## Brief + +# MISSION (immutable — do not renegotiate) + +# Mission (immutable) — openkknd2 + +Build and grow **openkknd2**: an original open-source post-apoc RTS scaffold (genre-inspired only; **not** commercial KKnD). + +## Fixed goals + +1. Work only under the designated workdir (`openkknd2-am-coder` / `$OPENKKND2_ROOT`). +2. Keep `./scripts/ci-headless.sh` **GREEN** after meaningful changes. +3. Advance the milestone ladder: map/units → buildings/economy → faction AI → playable skirmish loop → (later) cinematics. +4. **Legal:** MIT/original only — never proprietary KKnD assets, rips, or reverse-engineered game files. +5. Always append `AGENT_LOG.md` and keep `STATUS.md` current. + +## Non-goals (do not replace the mission with these) + +- Rewriting the project as a different game +- Asking the user for permission to continue +- Stopping after planning without code/test changes + +This mission text is **law** for all models and all meta rounds. + +--- + +# This round + +CONTINUE openkknd2 ONLY under /home/vx/src/openkknd2-am-coder (this workdir). + +Headless smoke is GREEN (map + units). You implement the next milestones yourself. + +This turn must produce REAL file changes (not just plans): +1. Add building placement (HQ and/or barracks) with resource cost — new headers/sources if needed +2. Add a simple faction AI tick (gather / build / train) +3. Wire into `ok_world_tick` or main smoke +4. Run `./scripts/ci-headless.sh` and keep it GREEN +5. Append AGENT_LOG.md + update STATUS.md + +Rules: MIT/original only; no proprietary KKnD assets. No empty turns. AUTONOMOUS_STOP only if blocked or verified done. +Read src/ and include/ first. + +--- + +## Protocol +- Work thoroughly; no empty turns. +- Emit ## AUTONOMOUS_STOP only when DONE or truly blocked. +- If missing *actionable* rules would unlock autonomy (not a rewrite of the mission), + append exactly one block: + +## NEED_INSTRUCTIONS +why: +need: +- + +Do not use NEED_INSTRUCTIONS to change the mission goal. + +## Instruction refs + +(filled by launcher if any) + +## Notes + +- Always append to `AGENT_LOG.md` after each chunk. +- Keep `STATUS.md` current. +- On finish or hard stop, set STATUS and log `AUTONOMOUS_STOP` / DONE. diff --git a/openkknd2-am-coder/THIRD_PARTY.md b/openkknd2-am-coder/THIRD_PARTY.md new file mode 100644 index 0000000..c264057 --- /dev/null +++ b/openkknd2-am-coder/THIRD_PARTY.md @@ -0,0 +1,10 @@ +# Third-Party Dependencies + +This project uses only original work, CC0/CC-BY (attributed), MIT/BSD/Zlib code with compatible license policy. +No KKnD / KKnD2 / Xtreme game files, ISOs, rips, sprites, music, maps, voices, or FMV are used. + +## Tools Used + +- CMake for build system +- SDL2 for windowing and input +- GCC/Clang for compilation \ No newline at end of file diff --git a/openkknd2-am-coder/docs/BUILD.md b/openkknd2-am-coder/docs/BUILD.md new file mode 100644 index 0000000..802361e --- /dev/null +++ b/openkknd2-am-coder/docs/BUILD.md @@ -0,0 +1,11 @@ +# Build openkknd2 + +```bash +cd ~/src/openkknd2-am-coder +./scripts/ci-headless.sh +# or: +cmake -S . -B build && cmake --build build +./build/openkknd2 --headless --smoke +``` + +Deps: CMake, C compiler. SDL2 optional (not required for headless sim). diff --git a/openkknd2-am-coder/docs/LORE.md b/openkknd2-am-coder/docs/LORE.md new file mode 100644 index 0000000..85cc86a --- /dev/null +++ b/openkknd2-am-coder/docs/LORE.md @@ -0,0 +1,8 @@ +# Lore (original) + +Working names (change freely; **do not** use commercial KKnD faction trademarks as official names): + +- Faction A: **Ashcoat Compact** (scrap tech, discipline) +- Faction B: **Bloomed** (mutated ecology, swarm tactics) + +Tone: dry wasteland humor, not a copy of any commercial script. diff --git a/openkknd2-am-coder/include/ok_buildings.h b/openkknd2-am-coder/include/ok_buildings.h new file mode 100644 index 0000000..bdb9047 --- /dev/null +++ b/openkknd2-am-coder/include/ok_buildings.h @@ -0,0 +1,25 @@ +#ifndef OK_BUILDINGS_H +#define OK_BUILDINGS_H +#include "ok_types.h" + +// Building types +enum OkBuildingKind { + OK_BUILDING_HQ = 0, + OK_BUILDING_BARRACKS = 1, + OK_BUILDING_MAX +}; + +// Building structure +typedef struct { + int x, y; + OkBuildingKind kind; + OkFaction faction; + int resource_cost; + bool built; +} OkBuilding; + +// Function declarations +void ok_buildings_init(OkWorld *w); +void ok_buildings_place(OkWorld *w, int x, int y, OkBuildingKind kind, OkFaction faction); +void ok_buildings_update(OkWorld *w); +#endif // OK_BUILDINGS_H \ No newline at end of file diff --git a/openkknd2-am-coder/include/ok_map.h b/openkknd2-am-coder/include/ok_map.h new file mode 100644 index 0000000..dbb1f56 --- /dev/null +++ b/openkknd2-am-coder/include/ok_map.h @@ -0,0 +1,6 @@ +#ifndef OK_MAP_H +#define OK_MAP_H +#include "ok_types.h" +void ok_map_init(OkWorld *w, uint32_t seed); +bool ok_map_walkable(const OkWorld *w, int x, int y); +#endif diff --git a/openkknd2-am-coder/include/ok_types.h b/openkknd2-am-coder/include/ok_types.h new file mode 100644 index 0000000..e4a3aff --- /dev/null +++ b/openkknd2-am-coder/include/ok_types.h @@ -0,0 +1,31 @@ +#ifndef OK_TYPES_H +#define OK_TYPES_H +#include +#include + +#define OK_WORLD_W 32 +#define OK_WORLD_H 24 +#define OK_MAX_UNITS 64 + +typedef enum { OK_TILE_EMPTY = 0, OK_TILE_BLOCK = 1, OK_TILE_RES = 2 } OkTile; +typedef enum { OK_FACTION_A = 0, OK_FACTION_B = 1 } OkFaction; +typedef enum { OK_UNIT_SCOUT = 0, OK_UNIT_WORKER = 1 } OkUnitKind; + +typedef struct { + int x, y; + OkFaction faction; + OkUnitKind kind; + int hp; + bool alive; +} OkUnit; + +typedef struct { + OkTile tiles[OK_WORLD_H][OK_WORLD_W]; + OkUnit units[OK_MAX_UNITS]; + int unit_count; + int tick; + int resources_a; + int resources_b; +} OkWorld; + +#endif diff --git a/openkknd2-am-coder/include/ok_unit.h b/openkknd2-am-coder/include/ok_unit.h new file mode 100644 index 0000000..cc1405f --- /dev/null +++ b/openkknd2-am-coder/include/ok_unit.h @@ -0,0 +1,8 @@ +#ifndef OK_UNIT_H +#define OK_UNIT_H +#include "ok_types.h" +int ok_unit_spawn(OkWorld *w, OkFaction f, OkUnitKind k, int x, int y); +void ok_unit_step_toward(OkWorld *w, int uid, int tx, int ty); +void ok_world_tick(OkWorld *w); +int ok_world_alive_count(const OkWorld *w, OkFaction f); +#endif diff --git a/openkknd2-am-coder/launch-q36.out b/openkknd2-am-coder/launch-q36.out new file mode 100644 index 0000000..85ea557 --- /dev/null +++ b/openkknd2-am-coder/launch-q36.out @@ -0,0 +1,16 @@ +instruction-ref → /home/vx/src/llm-configs/instructions/18-openkknd2-autonomous-rebuild.md +instruction-ref → /home/vx/src/llm-configs/instructions/20-autonomous-until-blocked.md +instruction-ref → /home/vx/src/llm-configs/instructions/21-always-agent-logs.md +AGENT_WORKDIR=/home/vx/src/openkknd2-am-coder +logs: /home/vx/src/openkknd2-am-coder/AGENT_LOG.md /home/vx/src/openkknd2-am-coder/STATUS.md /home/vx/src/openkknd2-am-coder/TASK.md +detached pid=1582837 log=/tmp/agent-coder-detach-20260722T045311Z.log +control: am-task list | am-task stop latest | am-task resume latest +=== active (/home/vx/.am-agent-tasks/active) === +(none) + +=== recent history (5) === +20260722T044928Z-1206819 interrupted CONTINUE openkknd2 in /home/vx/src/openkknd2-am-co +20260722T044107Z-3833063 done OPENKKND2 autonomy demo. Work ONLY under /home/vx/ +20260722T043957Z-3831609 interrupted resume:w +20260722T043953Z-3831497 resumed w +20260722T043858Z-3830593 resumed heartbeat worker diff --git a/openkknd2-am-coder/launch-w2.out b/openkknd2-am-coder/launch-w2.out new file mode 100644 index 0000000..ac2c8b2 --- /dev/null +++ b/openkknd2-am-coder/launch-w2.out @@ -0,0 +1,16 @@ +instruction-ref → /home/vx/src/llm-configs/instructions/18-openkknd2-autonomous-rebuild.md +instruction-ref → /home/vx/src/llm-configs/instructions/20-autonomous-until-blocked.md +instruction-ref → /home/vx/src/llm-configs/instructions/21-always-agent-logs.md +AGENT_WORKDIR=/home/vx/src/openkknd2-am-coder +logs: /home/vx/src/openkknd2-am-coder/AGENT_LOG.md /home/vx/src/openkknd2-am-coder/STATUS.md /home/vx/src/openkknd2-am-coder/TASK.md +detached pid=1206638 log=/tmp/agent-coder-detach-20260722T044925Z.log +control: am-task list | am-task stop latest | am-task resume latest +=== active (/home/vx/.am-agent-tasks/active) === +(none) + +=== recent history (5) === +20260722T044107Z-3833063 done OPENKKND2 autonomy demo. Work ONLY under /home/vx/ +20260722T043957Z-3831609 interrupted resume:w +20260722T043953Z-3831497 resumed w +20260722T043858Z-3830593 resumed heartbeat worker +20260722T043822Z-3830147 interrupted s15 diff --git a/openkknd2-am-coder/launch.out b/openkknd2-am-coder/launch.out new file mode 100644 index 0000000..5b5bd31 --- /dev/null +++ b/openkknd2-am-coder/launch.out @@ -0,0 +1,16 @@ +instruction-ref → /home/vx/src/llm-configs/instructions/18-openkknd2-autonomous-rebuild.md +instruction-ref → /home/vx/src/llm-configs/instructions/20-autonomous-until-blocked.md +instruction-ref → /home/vx/src/llm-configs/instructions/21-always-agent-logs.md +AGENT_WORKDIR=/home/vx/src/openkknd2-am-coder +logs: /home/vx/src/openkknd2-am-coder/AGENT_LOG.md /home/vx/src/openkknd2-am-coder/STATUS.md /home/vx/src/openkknd2-am-coder/TASK.md +detached pid=3832877 log=/tmp/agent-coder-detach-20260722T044105Z.log +control: am-task list | am-task stop latest | am-task resume latest +=== active (/home/vx/.am-agent-tasks/active) === +(none) + +=== recent history (5) === +20260722T043957Z-3831609 interrupted resume:w +20260722T043953Z-3831497 resumed w +20260722T043858Z-3830593 resumed heartbeat worker +20260722T043822Z-3830147 interrupted s15 +20260722T043752Z-3829738 interrupted sleep25 diff --git a/openkknd2-am-coder/rotate-logs/LOAD-POLICY.txt b/openkknd2-am-coder/rotate-logs/LOAD-POLICY.txt new file mode 100644 index 0000000..6216b4c --- /dev/null +++ b/openkknd2-am-coder/rotate-logs/LOAD-POLICY.txt @@ -0,0 +1,7 @@ +single-flight policy +- only one agent-coder job at a time (am-task) +- only one ollama chat model loaded (devstral during openkknd2 rotate) +- no parallel ollama pull during agent runs +- no parallel pics/vids/showcase on this host while rotation runs +- rotation is sequential (one model finishes before next) +updated: 2026-07-22T05:18:40+00:00 diff --git a/openkknd2-am-coder/rotate-logs/rotate-20260722T051643Z.md b/openkknd2-am-coder/rotate-logs/rotate-20260722T051643Z.md new file mode 100644 index 0000000..9500593 --- /dev/null +++ b/openkknd2-am-coder/rotate-logs/rotate-20260722T051643Z.md @@ -0,0 +1,9 @@ +# openkknd2 model rotation + +- started: 2026-07-22T05:16:43Z +- workdir: `/home/vx/src/openkknd2-am-coder` +- prompt: `/home/vx/src/llm-configs/prompts/openkknd2-rotate-kick.md` +- max_turns: 10 + +| # | flag | session | before lines | after lines | progress | ci | +|---|------|---------|--------------|-------------|----------|----| diff --git a/openkknd2-am-coder/rotate-logs/rotate.out b/openkknd2-am-coder/rotate-logs/rotate.out new file mode 100644 index 0000000..5e316ca --- /dev/null +++ b/openkknd2-am-coder/rotate-logs/rotate.out @@ -0,0 +1,6 @@ +=== model rotation start 2026-07-22T05:16:43Z === +ROOT=/home/vx/src/openkknd2-am-coder models=--devstral --coder --qwen314 --qwen14 --deepseek --qwen38 + +>>> [1/6] model flag=--devstral session=ok2-rot1-devstral 2026-07-22T05:16:43Z + before: files=9 lines=183 build=0 ai=0 + launch_ec=0 log=/home/vx/src/openkknd2-am-coder/rotate-logs/run-1devstral.log diff --git a/openkknd2-am-coder/rotate-logs/run-1devstral.log b/openkknd2-am-coder/rotate-logs/run-1devstral.log new file mode 100644 index 0000000..6b7906e --- /dev/null +++ b/openkknd2-am-coder/rotate-logs/run-1devstral.log @@ -0,0 +1,24 @@ +AGENT_WORKDIR=/home/vx/src/openkknd2-am-coder +logs: /home/vx/src/openkknd2-am-coder/AGENT_LOG.md /home/vx/src/openkknd2-am-coder/STATUS.md /home/vx/src/openkknd2-am-coder/TASK.md +detached pid=1717646 log=/tmp/agent-coder-detach-20260722T051647Z.log +control: am-task list | am-task stop latest | am-task resume latest +### ACTIVE (/home/vx/.am-agent-tasks/active) + +(none active) + +### RECENT HISTORY (5) (/home/vx/.am-agent-tasks/history) + +════════════════════════════════════════════════════════════ +id: 20260722T050209Z-1707242 +status: interrupted [process dead] +kind: stats-demo +host: amanita +model: (none / not set) +session: (none) +pid / pgid: 1707246 / 1707246 +started: 2026-07-22T05:02:09Z +stopped: 2026-07-22T05:02:11Z +elapsed: 2s +stop_reason: demo: user requested clear stop stats +stop_how: am-task stop +stop_hard: False diff --git a/openkknd2-am-coder/scripts/ci-headless.sh b/openkknd2-am-coder/scripts/ci-headless.sh new file mode 100755 index 0000000..88587a4 --- /dev/null +++ b/openkknd2-am-coder/scripts/ci-headless.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash +set -euo pipefail +ROOT="$(cd "$(dirname "$0")/.." && pwd)" +cd "$ROOT" +echo "ci-headless: $ROOT" +mkdir -p build +cmake -S . -B build -DCMAKE_BUILD_TYPE=Release >/tmp/ok2-cmake.log 2>&1 +cmake --build build -j"$(nproc 2>/dev/null || echo 2)" >/tmp/ok2-build.log 2>&1 +./build/openkknd2 --headless --smoke +echo "HEADLESS_GREEN" diff --git a/openkknd2-am-coder/src/building.h b/openkknd2-am-coder/src/building.h new file mode 100644 index 0000000..830281c --- /dev/null +++ b/openkknd2-am-coder/src/building.h @@ -0,0 +1,20 @@ +# src/building.h + +#ifndef BUILDING_H +#define BUILDING_H + +#include "entity.h" + +enum BuildingType { + BUILDING_HQ, + BUILDING_BARRACKS, + BUILDING_COUNT +}; + +typedef struct { + Entity entity; + BuildingType type; + int resourceCost; +} Building; + +#endif // BUILDING_H \ No newline at end of file diff --git a/openkknd2-am-coder/src/main.c b/openkknd2-am-coder/src/main.c new file mode 100644 index 0000000..25f03da --- /dev/null +++ b/openkknd2-am-coder/src/main.c @@ -0,0 +1,36 @@ +#include +#include +#include +#include "ok_types.h" +#include "ok_map.h" +#include "ok_unit.h" + +static int run_smoke(void) { + OkWorld w; + ok_map_init(&w, 42); + int a = ok_unit_spawn(&w, OK_FACTION_A, OK_UNIT_SCOUT, 1, 1); + int b = ok_unit_spawn(&w, OK_FACTION_B, OK_UNIT_SCOUT, OK_WORLD_W - 2, OK_WORLD_H - 2); + if (a < 0 || b < 0) { fprintf(stderr, "spawn failed\n"); return 2; } + for (int t = 0; t < 40; t++) ok_world_tick(&w); + if (ok_world_alive_count(&w, OK_FACTION_A) < 1 || ok_world_alive_count(&w, OK_FACTION_B) < 1) { + fprintf(stderr, "units died\n"); return 3; + } + if (w.units[a].x == 1 && w.units[a].y == 1) { + fprintf(stderr, "unit A did not move\n"); return 4; + } + printf("openkknd2: headless smoke test passed (ticks=%d units=%d resA=%d resB=%d)\n", + w.tick, w.unit_count, w.resources_a, w.resources_b); + return 0; +} + +int main(int argc, char *argv[]) { + int headless = 0, smoke = 0; + for (int i = 1; i < argc; i++) { + if (strcmp(argv[i], "--headless") == 0) headless = 1; + else if (strcmp(argv[i], "--smoke") == 0) smoke = 1; + } + if (headless && smoke) return run_smoke(); + printf("openkknd2 - original open post-apoc RTS scaffold (not KKnD)\n"); + printf("Usage: %s --headless --smoke\n", argv[0]); + return headless ? 0 : 1; +} diff --git a/openkknd2-am-coder/src/ok_buildings.c b/openkknd2-am-coder/src/ok_buildings.c new file mode 100644 index 0000000..24d405c --- /dev/null +++ b/openkknd2-am-coder/src/ok_buildings.c @@ -0,0 +1,45 @@ +#include "ok_buildings.h" +#include "ok_map.h" +#include + +void ok_buildings_init(OkWorld *w) { + // Initialize buildings array - for now we'll use a simple approach + // In a more complete implementation, we'd have an actual building array +} + +void ok_buildings_place(OkWorld *w, int x, int y, OkBuildingKind kind, OkFaction faction) { + // Check if placement is valid (walkable tile and not occupied) + if (!ok_map_walkable(w, x, y)) { + return; + } + + // For now, we'll just update the resource count when a building is placed + int cost = 0; + switch (kind) { + case OK_BUILDING_HQ: + cost = 50; + break; + case OK_BUILDING_BARRACKS: + cost = 30; + break; + default: + return; + } + + // Check if faction has enough resources + int *resources = (faction == OK_FACTION_A) ? &w->resources_a : &w->resources_b; + if (*resources < cost) { + return; + } + + // Deduct cost and mark building as placed + *resources -= cost; + + printf("Faction %d placed %s at (%d,%d)\n", faction, + (kind == OK_BUILDING_HQ) ? "HQ" : "Barracks", x, y); +} + +void ok_buildings_update(OkWorld *w) { + // Simple building update logic - in a real implementation this would handle + // building construction, production, etc. +} \ No newline at end of file diff --git a/openkknd2-am-coder/src/ok_map.c b/openkknd2-am-coder/src/ok_map.c new file mode 100644 index 0000000..8052d0b --- /dev/null +++ b/openkknd2-am-coder/src/ok_map.c @@ -0,0 +1,29 @@ +#include "ok_map.h" +#include + +static uint32_t rng_u32(uint32_t *s) { + *s = *s * 1664525u + 1013904223u; + return *s; +} + +void ok_map_init(OkWorld *w, uint32_t seed) { + memset(w, 0, sizeof(*w)); + uint32_t s = seed ? seed : 1u; + for (int y = 0; y < OK_WORLD_H; y++) { + for (int x = 0; x < OK_WORLD_W; x++) { + uint32_t r = rng_u32(&s); + if ((r % 17u) == 0u) w->tiles[y][x] = OK_TILE_BLOCK; + else if ((r % 11u) == 0u) w->tiles[y][x] = OK_TILE_RES; + else w->tiles[y][x] = OK_TILE_EMPTY; + } + } + w->tiles[1][1] = OK_TILE_EMPTY; + w->tiles[OK_WORLD_H - 2][OK_WORLD_W - 2] = OK_TILE_EMPTY; + w->resources_a = 100; + w->resources_b = 100; +} + +bool ok_map_walkable(const OkWorld *w, int x, int y) { + if (x < 0 || y < 0 || x >= OK_WORLD_W || y >= OK_WORLD_H) return false; + return w->tiles[y][x] != OK_TILE_BLOCK; +} diff --git a/openkknd2-am-coder/src/ok_unit.c b/openkknd2-am-coder/src/ok_unit.c new file mode 100644 index 0000000..89880fe --- /dev/null +++ b/openkknd2-am-coder/src/ok_unit.c @@ -0,0 +1,46 @@ +#include "ok_unit.h" +#include "ok_map.h" + +int ok_unit_spawn(OkWorld *w, OkFaction f, OkUnitKind k, int x, int y) { + if (w->unit_count >= OK_MAX_UNITS) return -1; + if (!ok_map_walkable(w, x, y)) return -1; + int id = w->unit_count++; + OkUnit *u = &w->units[id]; + u->x = x; u->y = y; u->faction = f; u->kind = k; + u->hp = (k == OK_UNIT_SCOUT) ? 40 : 30; + u->alive = true; + return id; +} + +void ok_unit_step_toward(OkWorld *w, int uid, int tx, int ty) { + if (uid < 0 || uid >= w->unit_count) return; + OkUnit *u = &w->units[uid]; + if (!u->alive) return; + int nx = u->x + (tx > u->x) - (tx < u->x); + int ny = u->y + (ty > u->y) - (ty < u->y); + if (ok_map_walkable(w, nx, u->y)) u->x = nx; + if (ok_map_walkable(w, u->x, ny)) u->y = ny; + if (w->tiles[u->y][u->x] == OK_TILE_RES) { + if (u->faction == OK_FACTION_A) w->resources_a += 1; + else w->resources_b += 1; + w->tiles[u->y][u->x] = OK_TILE_EMPTY; + } +} + +void ok_world_tick(OkWorld *w) { + w->tick++; + for (int i = 0; i < w->unit_count; i++) { + OkUnit *u = &w->units[i]; + if (!u->alive) continue; + int tx = (u->faction == OK_FACTION_A) ? (OK_WORLD_W - 3) : 2; + int ty = (u->faction == OK_FACTION_A) ? (OK_WORLD_H - 3) : 2; + ok_unit_step_toward(w, i, tx, ty); + } +} + +int ok_world_alive_count(const OkWorld *w, OkFaction f) { + int n = 0; + for (int i = 0; i < w->unit_count; i++) + if (w->units[i].alive && w->units[i].faction == f) n++; + return n; +}