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 0000000..5b941d6 Binary files /dev/null and b/engine-c/gallery/m0_evolved.png differ diff --git a/engine-c/gallery/m0_series9.png b/engine-c/gallery/m0_series9.png new file mode 100644 index 0000000..d6c8f69 Binary files /dev/null and b/engine-c/gallery/m0_series9.png differ diff --git a/engine-c/gallery/m0_survivor.png b/engine-c/gallery/m0_survivor.png new file mode 100644 index 0000000..3959fe7 Binary files /dev/null and b/engine-c/gallery/m0_survivor.png differ diff --git a/engine-c/gallery/m1_evolved.png b/engine-c/gallery/m1_evolved.png new file mode 100644 index 0000000..6043d7d Binary files /dev/null and b/engine-c/gallery/m1_evolved.png differ diff --git a/engine-c/gallery/m1_series9.png b/engine-c/gallery/m1_series9.png new file mode 100644 index 0000000..6c98020 Binary files /dev/null and b/engine-c/gallery/m1_series9.png differ diff --git a/engine-c/gallery/m1_survivor.png b/engine-c/gallery/m1_survivor.png new file mode 100644 index 0000000..6633709 Binary files /dev/null and b/engine-c/gallery/m1_survivor.png differ diff --git a/engine-c/gallery/m2_evolved.png b/engine-c/gallery/m2_evolved.png new file mode 100644 index 0000000..7278387 Binary files /dev/null and b/engine-c/gallery/m2_evolved.png differ diff --git a/engine-c/gallery/m2_series9.png b/engine-c/gallery/m2_series9.png new file mode 100644 index 0000000..d188b5b Binary files /dev/null and b/engine-c/gallery/m2_series9.png differ diff --git a/engine-c/gallery/m2_survivor.png b/engine-c/gallery/m2_survivor.png new file mode 100644 index 0000000..d6bcaa7 Binary files /dev/null and b/engine-c/gallery/m2_survivor.png differ diff --git a/engine-c/gallery/m3_evolved.png b/engine-c/gallery/m3_evolved.png new file mode 100644 index 0000000..1f339c9 Binary files /dev/null and b/engine-c/gallery/m3_evolved.png differ diff --git a/engine-c/gallery/m3_series9.png b/engine-c/gallery/m3_series9.png new file mode 100644 index 0000000..7177489 Binary files /dev/null and b/engine-c/gallery/m3_series9.png differ diff --git a/engine-c/gallery/m3_survivor.png b/engine-c/gallery/m3_survivor.png new file mode 100644 index 0000000..69aba68 Binary files /dev/null and b/engine-c/gallery/m3_survivor.png differ diff --git a/engine-c/gallery/m4_evolved.png b/engine-c/gallery/m4_evolved.png new file mode 100644 index 0000000..721cbd0 Binary files /dev/null and b/engine-c/gallery/m4_evolved.png differ diff --git a/engine-c/gallery/m4_series9.png b/engine-c/gallery/m4_series9.png new file mode 100644 index 0000000..e2a3b6a Binary files /dev/null and b/engine-c/gallery/m4_series9.png differ diff --git a/engine-c/gallery/m4_survivor.png b/engine-c/gallery/m4_survivor.png new file mode 100644 index 0000000..75c1e56 Binary files /dev/null and b/engine-c/gallery/m4_survivor.png differ 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 0000000..6cac52b Binary files /dev/null and b/engine-c/openkknd differ diff --git a/engine-c/openkknd_dbg b/engine-c/openkknd_dbg new file mode 100755 index 0000000..9a9977c Binary files /dev/null and b/engine-c/openkknd_dbg differ diff --git a/engine-c/savegame.kknd b/engine-c/savegame.kknd new file mode 100644 index 0000000..9b8126e Binary files /dev/null and b/engine-c/savegame.kknd differ diff --git a/engine-c/shot_0001.png b/engine-c/shot_0001.png new file mode 100644 index 0000000..e2a3b6a Binary files /dev/null and b/engine-c/shot_0001.png differ 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; +}