kitchen: README + MISSIONS/engine-c keep-drop notes

This commit is contained in:
Hernâni Marques 2026-09-17 21:48:48 +02:00
parent 1d351918da
commit 575d7d29b8
No known key found for this signature in database
130 changed files with 9817 additions and 1 deletions

35
KEEP-DROP.md Normal file
View file

@ -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.

View file

@ -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' Destroystyle **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

View file

@ -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' Destroystyle **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.

26
MISSIONS/qwen.MISSION.md Normal file
View file

@ -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' Destroystyle **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.

View file

@ -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' Destroystyle **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

22
engine-c/build.sh Executable file
View file

@ -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]"

Binary file not shown.

After

Width:  |  Height:  |  Size: 7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

642
engine-c/include/kknd.h Normal file
View file

@ -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 <stdint.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <time.h>
#include <SDL2/SDL.h>
#include <SDL2/SDL_image.h>
#include <SDL2/SDL_ttf.h>
#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 */

View file

@ -0,0 +1,83 @@
// openkknd - Unit Card System (SIMD Optimized)
// Header file for unit card implementation
#include <stdint.h>
#include <immintrin.h>
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

BIN
engine-c/openkknd Executable file

Binary file not shown.

BIN
engine-c/openkknd_dbg Executable file

Binary file not shown.

BIN
engine-c/savegame.kknd Normal file

Binary file not shown.

BIN
engine-c/shot_0001.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

124
engine-c/src/ai.c Normal file
View file

@ -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);
}

76
engine-c/src/audio.c Normal file
View file

@ -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++;
}

214
engine-c/src/buildings.c Normal file
View file

@ -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;
}

273
engine-c/src/config.c Normal file
View file

@ -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);
}

View file

@ -0,0 +1,55 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#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;
}

188
engine-c/src/engine.c Normal file
View file

@ -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);
}

292
engine-c/src/input.c Normal file
View file

@ -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;
}

167
engine-c/src/main.c Normal file
View file

@ -0,0 +1,167 @@
#include "kknd.h"
#include <unistd.h>
/* =========================================================================
* 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;
}

407
engine-c/src/map.c Normal file
View file

@ -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;
}

83
engine-c/src/png.c Normal file
View file

@ -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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "kknd.h"
#include <zlib.h>
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;
}

106
engine-c/src/projectiles.c Normal file
View file

@ -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--;
}
}
}

631
engine-c/src/render.c Normal file
View file

@ -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; }
}

42
engine-c/src/rng.c Normal file
View file

@ -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);
}

87
engine-c/src/save.c Normal file
View file

@ -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;
}

148
engine-c/src/snapshot.c Normal file
View file

@ -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;k<t->scrap/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;k<rad;k++){ int xx=sx+(hx-sx)*k/rad, yy=sy+(hy-sy)*k/rad; px(xx,yy,230,230,230); }
if (u->selected) { 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;ty<MAP_W;ty+=2) for (int tx=0;tx<MAP_W;tx+=2) {
int f=g->map.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;i<g->bld_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;i<g->unit_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);
}

341
engine-c/src/sprites.c Normal file
View file

@ -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, (W<H?W:H)/2 - 3, 2, dark.r, dark.g, dark.b, 255);
switch (t) {
case BT_BASE:
disc(s, W/2, H/2, W/4, base.r, base.g, base.b, 255);
disc(s, W/2, H/2, W/6, lite.r, lite.g, lite.b, 255);
line(s, W/2, H/2, W/2, 4, 200, 200, 60, 255);
break;
case BT_WARFACTORY:
fill_rect(s, 8, 8, W-16, H-16, dark.r, dark.g, dark.b, 255);
fill_rect(s, 12, 12, W-24, 10, base.r, base.g, base.b, 255);
fill_rect(s, 12, H-24, W-24, 10, base.r, base.g, base.b, 255);
break;
case BT_REFINERY:
disc(s, W/2, H/2, W/4, 120, 100, 60, 255);
ring(s, W/2, H/2, W/4 - 3, 3, 60, 50, 40, 255);
break;
case BT_POWER:
disc(s, W/2, H/2, W/3, 80, 120, 160, 255);
line(s, W/2, 4, W/2, H-4, 220, 240, 255, 255);
line(s, 4, H/2, W-4, H/2, 220, 240, 255, 255);
break;
case BT_TURRET:
disc(s, W/2, H/2, W/3, dark.r, dark.g, dark.b, 255);
disc(s, W/2, H/2, W/5, lite.r, lite.g, lite.b, 255);
line(s, W/2, H/2, W/2 + 12, H/2 - 12, 40, 40, 45, 255);
break;
case BT_WALL:
fill_rect(s, 2, 2, W-4, H-4, 70, 66, 60, 255);
line(s, 2, 2, W-2, H-2, 40, 38, 34, 255);
break;
case BT_REPAIR:
fill_rect(s, 8, 8, W-16, H-16, dark.r, dark.g, dark.b, 255);
ring(s, W/2, H/2, W/4, 3, 120, 220, 120, 255);
line(s, W/2 - 12, H/2, W/2 + 12, H/2, 120, 220, 120, 255);
line(s, W/2, H/2 - 12, W/2, H/2 + 12, 120, 220, 120, 255);
break;
case BT_RESEARCH:
fill_rect(s, 8, 8, W-16, H-16, dark.r, dark.g, dark.b, 255);
disc(s, W/2, H/2, W/5, 180, 150, 60, 255);
line(s, W/2, 6, W/2, H-6, 220, 220, 150, 255);
line(s, 6, H/2, W-6, H/2, 220, 220, 150, 255);
break;
default:
break;
}
SDL_Texture *tex = SDL_CreateTextureFromSurface(r, s);
SDL_FreeSurface(s);
return tex;
}
/* ---------------- terrain tiles ---------------- */
SDL_Texture *sprites_make_terrain(SDL_Renderer *r, TerrainType t) {
SDL_Surface *s = new_surf(TILE, TILE);
Uint8 tr=0,tg=0,tb=0;
switch (t) {
case T_WASTELAND: tr=120; tg=104; tb=78; break;
case T_RUBBLE: tr=96; tg=86; tb=74; break;
case T_ROAD: tr=70; tg=68; tb=64; break;
case T_GRASS: tr=86; tg=120; tb=64; break;
case T_WATER: tr=44; tg=78; tb=120; break;
case T_ROCK: tr=110; tg=104; tb=98; break;
case T_SCRAP: tr=110; tg=96; tb=70; break;
default: tr=120; tg=104; tb=78; break;
}
fill_rect(s, 0, 0, TILE, TILE, tr, tg, tb, 255);
/* speckle for texture */
for (int i = 0; i < 40; i++) {
int x = (i*73) % TILE, y = (i*131) % TILE;
int v = ((i*53) % 24) - 12;
px(s, x, y,
(Uint8)kknd_clampi(tr+v,0,255),
(Uint8)kknd_clampi(tg+v,0,255),
(Uint8)kknd_clampi(tb+v,0,255), 255);
}
if (t == T_SCRAP) {
for (int i = 0; i < 10; i++) {
int x = (i*97) % TILE, y = (i*61) % TILE;
disc(s, x, y, 2, 180, 150, 60, 255);
}
}
if (t == T_WATER) {
for (int i = 0; i < TILE; i += 4)
line(s, 0, i, TILE, i, 60, 100, 150, 255);
}
if (t == T_ROCK) {
for (int i = 0; i < 12; i++) {
int x = (i*89) % TILE, y = (i*149) % TILE;
disc(s, x, y, 3, 130, 124, 118, 255);
}
}
SDL_Texture *tex = SDL_CreateTextureFromSurface(r, s);
SDL_FreeSurface(s);
return tex;
}
/* ---------------- bullets ---------------- */
static SDL_Texture *make_bullet(SDL_Renderer *r, int kind) {
int S = 10;
SDL_Surface *s = new_surf(S, S);
switch (kind) {
case 0: disc(s, 5, 5, 3, 255, 230, 120, 255); break; /* MG tracer */
case 1: disc(s, 5, 5, 4, 255, 160, 60, 255); break; /* shell */
case 2: disc(s, 5, 5, 3, 120, 255, 160, 255); break; /* laser */
case 3: disc(s, 5, 5, 4, 255, 90, 90, 255); break; /* missile */
default: disc(s, 5, 5, 3, 255, 255, 255, 255);
}
SDL_Texture *tex = SDL_CreateTextureFromSurface(r, s);
SDL_FreeSurface(s);
return tex;
}
int sprites_init(Sprites *sp, SDL_Renderer *r) {
memset(sp, 0, sizeof(Sprites));
for (int t = 0; t < T_COUNT; t++)
sp->terrain[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);
}

334
engine-c/src/unit_card.c Normal file
View file

@ -0,0 +1,334 @@
#include "unit_card.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <immintrin.h>
// 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");
}

585
engine-c/src/units.c Normal file
View file

@ -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 = x0<x1?x0:x1, maxx = x0>x1?x0:x1;
int miny = y0<y1?y0:y1, maxy = y0>y1?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);
}

42
kitchen/COLLAB-SIGNUP.md Normal file
View file

@ -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/<experte>-L<linie>-<modell>` (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 |
<!-- Example row:
| 2026-09-17T12:00Z | local-model/engine-L2-jan-gemma4 | A | engine-c/… | ACTIVE | KEEP landed; unit X |
| 2026-09-17T13:10Z | local-model/engine-L2-jan-gemma4 | A | engine-c/… | DONE | pushed abc1234; smoke PASS |
-->
## 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 G1G6); 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`

View file

@ -8,7 +8,8 @@ Thema: Freude am Spieleentwickeln. Ablauf & Kanäle: [`PLAYBOOK.md`](PLAYBOOK.md
| `PERSONAS.md` | P1P7 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 |

View file

@ -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]

View file

@ -0,0 +1,2 @@
[hermes3:8b · 12616ms · ok]

View file

@ -0,0 +1,2 @@
<SCRATCHPAD>
{"arguments": {"path": "/home/vx/src/openkknd-hermes"}, "name": "read"}

View file

@ -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
<SCRATCHPAD>
{"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 ====

View file

@ -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
<SCRATCHPAD>
{"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 ====

View file

@ -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.

View file

@ -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.

View file

@ -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]

View file

@ -0,0 +1,2 @@
[qwen2.5-coder:14b · 32316ms · ok]

View file

@ -0,0 +1,6 @@
{
"name": "exec",
"arguments": {
"command": "cd /home/vx/src/openkknd-qwen && ./build.sh"
}
}

View file

@ -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"

View file

@ -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]

View file

@ -0,0 +1,2 @@
[deepseek-coder:6.7b · 1005ms · ok]

View file

@ -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.

View file

@ -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

View file

@ -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

View file

@ -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.

View file

@ -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.

View file

@ -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]

View file

@ -0,0 +1,2 @@
[qwen2.5-coder:14b · 97809ms · ok]

View file

@ -0,0 +1,8 @@
```json
{
"name": "exec",
"arguments": {
"command": "cd /home/vx/src/openkknd-qwen14b && ./build.sh"
}
}
```

View file

@ -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"

View file

@ -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-<tool>
```
## 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 1819).
## Instructions
- [../instructions/18-openkknd2-autonomous-rebuild.md](../instructions/18-openkknd2-autonomous-rebuild.md)
- [../instructions/19-openkknd2-cinematics.md](../instructions/19-openkknd2-cinematics.md)

View file

@ -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" <<R
# openkknd2 (worktree)
Autonomous agent worktree. **Not** a KKnD remake.
\`\`\`bash
export OPENKKND2_ROOT=$root
# build/test TBD by agent
./scripts/ci-headless.sh
\`\`\`
Instructions: \`\$LLM_CONFIGS/instructions/18-openkknd2-autonomous-rebuild.md\`
R
fi
export OPENKKND2_ROOT="$root"
echo "OPENKKND2_ROOT=$OPENKKND2_ROOT"
}
openkknd2_print_brief() {
local tool="$1"
cat <<EOF
=== openkknd2 agent brief ($tool) ===
OPENKKND2_ROOT=$OPENKKND2_ROOT
LLM_CONFIGS=${LLM_CONFIGS:-$HOME/src/llm-configs}
Knowledge: \$HOME/OsaurusKnowledge/openkknd2/ (or mirror in llm-configs)
Read and follow:
\$LLM_CONFIGS/instructions/18-openkknd2-autonomous-rebuild.md
\$LLM_CONFIGS/instructions/19-openkknd2-cinematics.md
Rules: original/permissive assets only; autonomous; headless tests after each chunk.
Cinematics params: MEDIA_WIDTH/HEIGHT/FPS/SECONDS via llm-configs/media/
EOF
}

View file

@ -0,0 +1,240 @@
#!/usr/bin/env bash
# rotate-models-amanita.sh — try coding models one-by-one until openkknd2 progresses.
#
# Run ON amanita (or: ssh amanita bash -s < this-script).
# Uses short Zusatzprompt; fresh OpenClaw session per model; progress gate on tree.
#
# OPENKKND2_ROOT=~/src/openkknd2-am-coder \
# LLM_CONFIGS=~/src/llm-configs \
# bash rotate-models-amanita.sh
#
set -euo pipefail
export PATH="${HOME}/.local/bin:/usr/local/bin:/usr/bin:/bin:${PATH}"
export LLM_CONFIGS="${LLM_CONFIGS:-$HOME/src/llm-configs}"
ROOT="${OPENKKND2_ROOT:-$HOME/src/openkknd2-am-coder}"
LOG_DIR="${ROTATE_LOG_DIR:-$ROOT/rotate-logs}"
PROMPT_FILE="${PROMPT_FILE:-$LLM_CONFIGS/prompts/openkknd2-continue-short.md}"
MAX_TURNS="${ROTATE_MAX_TURNS:-12}"
AGENT="${AGENT_CODER:-$HOME/agent-coder}"
# Single-flight load policy: never run next model until previous fully stopped;
# unload other ollama models between slots; no parallel pulls.
SINGLE_FLIGHT="${ROTATE_SINGLE_FLIGHT:-1}"
mkdir -p "$LOG_DIR"
cd "$ROOT"
unload_other_ollama() {
# Keep the model we are about to use; stop everything else loaded.
local keep="${1:-}"
command -v ollama >/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

View file

@ -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

View file

@ -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'"

View file

@ -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/"

View file

@ -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"

View file

@ -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"

View file

@ -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.

View file

@ -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

View file

@ -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.

View file

@ -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

View file

@ -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)

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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 <id> | 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

View file

@ -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 <id> | 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

View file

@ -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 <id> | 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

View file

@ -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 <id> | 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

View file

@ -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 <id> | 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

View file

@ -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 <id> | 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

View file

@ -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 <id> | 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

View file

@ -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 <id> | 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

View file

@ -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 <id> | 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

View file

@ -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 <id> | 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

View file

@ -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 <id> | 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

View file

@ -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 <id> | 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

View file

@ -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 <id> | 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

View file

@ -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 <id> | 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

View file

@ -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
<stdin>: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
<stdin>: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
<stdin>: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
<stdin>: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

Some files were not shown because too many files have changed in this diff Show more