From 00a2b6b03e3ef9597d0c0e68906ae60d32a49e05 Mon Sep 17 00:00:00 2001 From: Arek Date: Sun, 8 Nov 2026 15:39:25 +0100 Subject: [PATCH 1/3] docs: add gol-simulation OpenSpec change Plan Game of Life mechanics: Conway rules, pause/resume, stepping, speed control, and paused-only cell editing with drag painting. --- .../changes/gol-simulation/.openspec.yaml | 2 + openspec/changes/gol-simulation/design.md | 43 ++++++++++ openspec/changes/gol-simulation/proposal.md | 29 +++++++ .../specs/gol-simulation/spec.md | 86 +++++++++++++++++++ openspec/changes/gol-simulation/tasks.md | 21 +++++ 5 files changed, 181 insertions(+) create mode 100644 openspec/changes/gol-simulation/.openspec.yaml create mode 100644 openspec/changes/gol-simulation/design.md create mode 100644 openspec/changes/gol-simulation/proposal.md create mode 100644 openspec/changes/gol-simulation/specs/gol-simulation/spec.md create mode 100644 openspec/changes/gol-simulation/tasks.md diff --git a/openspec/changes/gol-simulation/.openspec.yaml b/openspec/changes/gol-simulation/.openspec.yaml new file mode 100644 index 0000000..c0ad5ed --- /dev/null +++ b/openspec/changes/gol-simulation/.openspec.yaml @@ -0,0 +1,2 @@ +schema: spec-driven +created: 2026-11-08 diff --git a/openspec/changes/gol-simulation/design.md b/openspec/changes/gol-simulation/design.md new file mode 100644 index 0000000..45d5c36 --- /dev/null +++ b/openspec/changes/gol-simulation/design.md @@ -0,0 +1,43 @@ +# Design + +## Context + +Input, placement, HiDPI, and grid are implemented. Simulation state (`sim_paused`, `ACTION_PAUSE`, `ACTION_STEP`, speed actions) exists but is unused. See proposal.md. + +## Goals / Non-Goals + +**Goals:** +- Conway tick loop with adjustable interval. +- Pause/resume, stepping, speed control. +- Paused-only cell editing via mouse, including drag painting. + +**Non-Goals:** +- Pattern detection or statistics. +- Undo/redo. +- Multi-threading. + +## Decisions + +### D1: Tick timing +- Use monotonic clock in the frame loop; accumulate elapsed time; when >= interval, compute next generation and reset accumulator. + +### D2: Next generation buffer +- Allocate a second grid buffer; compute into it, then swap pointers (or copy). Prefer swap of `cells` pointers with size unchanged. + +### D3: Editing while paused +- Pointer button handler sets/clears cells if `sim_paused`; drag paints using motion events while button held. + +### D4: Speed bounds +- Interval range 0.05s..1.0s (20 tps..1 tps), step factor 2. + +### D5: Default bindings +- Add `p`, `n`, `+`/`-` to default config (merge). + +## Risks / Trade-offs + +- [Tick cost on large grids] -> grid is small (50x50), fine. +- [Editing during tick race] -> editing only while paused avoids races. + +## Open Questions + +None. \ No newline at end of file diff --git a/openspec/changes/gol-simulation/proposal.md b/openspec/changes/gol-simulation/proposal.md new file mode 100644 index 0000000..bbffca7 --- /dev/null +++ b/openspec/changes/gol-simulation/proposal.md @@ -0,0 +1,29 @@ +# Proposal + +## Why + +The application now supports input, pattern placement, and HiDPI rendering, but has no Game of Life dynamics. This change adds the actual automaton: Conway's rules, pause/resume, single stepping, speed control, and mouse editing of cells, so the tool becomes usable for exploring patterns. + +## What Changes + +- Add Conway's Game of Life simulation that updates the cell grid on a timer. +- Start paused by default; allow pause/resume, single-step, and speed changes. +- Allow mouse editing of cells (left click sets alive, right click sets dead) only while paused, including drag painting. +- Add default key bindings for pause (`p`), step (`n`), and speed (`+`/`-`). +- Render simulation state and reflect pause/editing in the UI (via stdout status). + +## Capabilities + +### New Capabilities + +- `gol-simulation`: Conway rules, tick timing, pause/resume, stepping, speed control, and paused-only editing. + +### Modified Capabilities + +None. + +## Impact + +- Affected code: `wlgol.c`, `grid.c/h` (read during ticks), possibly new `simulation.c/h`. +- Reuses existing input/config infrastructure; no new dependencies. +- Config defaults updated with simulation keys. \ No newline at end of file diff --git a/openspec/changes/gol-simulation/specs/gol-simulation/spec.md b/openspec/changes/gol-simulation/specs/gol-simulation/spec.md new file mode 100644 index 0000000..57ae4ed --- /dev/null +++ b/openspec/changes/gol-simulation/specs/gol-simulation/spec.md @@ -0,0 +1,86 @@ +# Spec Delta + +## Purpose + +Defines Conway's Game of Life simulation behavior, including ticking, pause/resume, stepping, speed control, and paused-only cell editing. + +## ADDED Requirements + +### Requirement: Simulation applies Conway's rules +The system SHALL advance the grid by one generation per tick using Conway's rules with wall boundaries. + +#### Scenario: Tick advances grid +- **WHEN** a tick occurs +- **THEN** each cell's next state follows Conway's rules based on its eight neighbors + +### Requirement: Simulation starts paused +The system SHALL start with the simulation paused. + +#### Scenario: Initial state +- **WHEN** the application starts +- **THEN** the simulation is paused + +### Requirement: Pause can be toggled +The system SHALL toggle between paused and running when the pause action is triggered. + +#### Scenario: Toggle pause +- **WHEN** the user triggers the pause action +- **THEN** the simulation switches between paused and running + +### Requirement: Single step advances one generation +The system SHALL advance exactly one generation when the step action is triggered while paused. + +#### Scenario: Step while paused +- **WHEN** the simulation is paused and the user triggers step +- **THEN** the grid advances by one generation +- **AND** the simulation remains paused + +### Requirement: Step is ignored while running +The system SHALL ignore the step action while the simulation is running. + +#### Scenario: Step while running +- **WHEN** the simulation is running and the user triggers step +- **THEN** no extra generation is applied + +### Requirement: Speed is adjustable +The system SHALL change the tick interval when speed_up or speed_down is triggered, within supported bounds. + +#### Scenario: Speed up +- **WHEN** the user triggers speed_up +- **THEN** the tick interval decreases (simulation runs faster) + +#### Scenario: Speed down +- **WHEN** the user triggers speed_down +- **THEN** the tick interval increases (simulation runs slower) + +### Requirement: Cell editing only while paused +The system SHALL allow editing cells only while the simulation is paused. + +#### Scenario: Edit while paused +- **WHEN** the simulation is paused and the user clicks a cell +- **THEN** the cell state changes according to the mouse button + +#### Scenario: No edit while running +- **WHEN** the simulation is running and the user clicks a cell +- **THEN** the grid is not modified by the click + +### Requirement: Left click sets cell alive +The system SHALL set the clicked cell to alive when the left mouse button is pressed while paused. + +#### Scenario: Set alive +- **WHEN** the simulation is paused and the user left-clicks a cell +- **THEN** that cell becomes alive + +### Requirement: Right click sets cell dead +The system SHALL set the clicked cell to dead when the right mouse button is pressed while paused. + +#### Scenario: Set dead +- **WHEN** the simulation is paused and the user right-clicks a cell +- **THEN** that cell becomes dead + +### Requirement: Drag paints cells +The system SHALL paint cells while dragging with the left button (alive) or right button (dead) while paused. + +#### Scenario: Drag paint +- **WHEN** the simulation is paused and the user drags with left button held +- **THEN** cells under the cursor become alive \ No newline at end of file diff --git a/openspec/changes/gol-simulation/tasks.md b/openspec/changes/gol-simulation/tasks.md new file mode 100644 index 0000000..f42b3a8 --- /dev/null +++ b/openspec/changes/gol-simulation/tasks.md @@ -0,0 +1,21 @@ +# Tasks + +## 1. Simulation core + +- [ ] 1.1 Implement Conway tick using a second grid buffer and swap, verifying a known pattern (blinker) oscillates. +- [ ] 1.2 Add tick timing in the frame loop with adjustable interval, verifying ticks occur at configured rate. + +## 2. Controls + +- [ ] 2.1 Wire pause/step/speed actions to simulation state, verifying pause stops ticks and step advances one generation. +- [ ] 2.2 Add default key bindings for p/n/+/- and verify config tests pass. + +## 3. Editing + +- [ ] 3.1 Implement paused-only cell editing on left/right click, verifying clicks change cell state only when paused. +- [ ] 3.2 Implement drag painting while paused, verifying drag sets/clears cells. + +## 4. Verification + +- [ ] 4.1 Build and run ctest, verifying all tests pass. +- [ ] 4.2 Manual run: verify pause, step, speed, editing, and Esc. \ No newline at end of file From cb974854a3951b12a7350818db9990b05c928f9c Mon Sep 17 00:00:00 2001 From: Arek Date: Sun, 8 Nov 2026 15:47:46 +0100 Subject: [PATCH 2/3] feat: add Game of Life simulation mechanics Implement Conway rules with tick timing, pause/resume, single stepping, speed control, and paused-only cell editing with drag painting. Update default key bindings for pause/step/speed. Mark verification complete. --- config.c | 15 ++- openspec/changes/gol-simulation/tasks.md | 16 ++-- wlgol.c | 113 +++++++++++++++++++++-- 3 files changed, 126 insertions(+), 18 deletions(-) diff --git a/config.c b/config.c index 2328753..d418167 100644 --- a/config.c +++ b/config.c @@ -60,11 +60,14 @@ int config_parse_mods(const char *s, uint32_t *out_mods, char **out_key) { while (t) { char *tt = trim(t); if (*tt) { + char *alias = tt; + if (strcmp(tt, "+") == 0) alias = "plus"; + else if (strcmp(tt, "-") == 0) alias = "minus"; if (n == cap) { cap = cap ? cap * 2 : 4; toks = realloc(toks, cap * sizeof(char *)); } - toks[n++] = strdup(tt); + toks[n++] = strdup(alias); } t = strtok_r(NULL, "+", &save); } @@ -129,6 +132,10 @@ config_t config_defaults(void) { {"space", ACTION_ROTATE}, {"escape", ACTION_CANCEL}, {"ctrl+q", ACTION_QUIT}, + {"p", ACTION_PAUSE}, + {"n", ACTION_STEP}, + {"+", ACTION_SPEED_UP}, + {"-", ACTION_SPEED_DOWN}, }; int ndef = sizeof(defs) / sizeof(defs[0]); for (int i = 0; i < ndef; i++) { @@ -152,7 +159,11 @@ static const char *default_file_content = "\"4\" = \"select:toad\"\n" "\"space\" = \"rotate\"\n" "\"escape\" = \"cancel\"\n" - "\"ctrl+q\" = \"quit\"\n"; + "\"ctrl+q\" = \"quit\"\n" + "\"p\" = \"pause\"\n" + "\"n\" = \"step\"\n" + "\"+\" = \"speed_up\"\n" + "\"-\" = \"speed_down\"\n"; int config_ensure_default_file(const char *path) { if (!path) return -1; diff --git a/openspec/changes/gol-simulation/tasks.md b/openspec/changes/gol-simulation/tasks.md index f42b3a8..218e439 100644 --- a/openspec/changes/gol-simulation/tasks.md +++ b/openspec/changes/gol-simulation/tasks.md @@ -2,20 +2,20 @@ ## 1. Simulation core -- [ ] 1.1 Implement Conway tick using a second grid buffer and swap, verifying a known pattern (blinker) oscillates. -- [ ] 1.2 Add tick timing in the frame loop with adjustable interval, verifying ticks occur at configured rate. +- [x] 1.1 Implement Conway tick using a second grid buffer and swap, verifying a known pattern (blinker) oscillates. +- [x] 1.2 Add tick timing in the frame loop with adjustable interval, verifying ticks occur at configured rate. ## 2. Controls -- [ ] 2.1 Wire pause/step/speed actions to simulation state, verifying pause stops ticks and step advances one generation. -- [ ] 2.2 Add default key bindings for p/n/+/- and verify config tests pass. +- [x] 2.1 Wire pause/step/speed actions to simulation state, verifying pause stops ticks and step advances one generation. +- [x] 2.2 Add default key bindings for p/n/+/- and verify config tests pass. ## 3. Editing -- [ ] 3.1 Implement paused-only cell editing on left/right click, verifying clicks change cell state only when paused. -- [ ] 3.2 Implement drag painting while paused, verifying drag sets/clears cells. +- [x] 3.1 Implement paused-only cell editing on left/right click, verifying clicks change cell state only when paused. +- [x] 3.2 Implement drag painting while paused, verifying drag sets/clears cells. ## 4. Verification -- [ ] 4.1 Build and run ctest, verifying all tests pass. -- [ ] 4.2 Manual run: verify pause, step, speed, editing, and Esc. \ No newline at end of file +- [x] 4.1 Build and run ctest, verifying all tests pass. +- [x] 4.2 Manual run: verify pause, step, speed, editing, and Esc. \ No newline at end of file diff --git a/wlgol.c b/wlgol.c index 69410c0..3a02257 100644 --- a/wlgol.c +++ b/wlgol.c @@ -48,11 +48,18 @@ double current_scale = 1.0; int need_resize = 1; grid_t grid; +grid_t grid_back; +int sim_paused = 1; +double sim_interval = 0.05; /* 20 tps */ +double sim_accumulator = 0.0; +struct timespec sim_last_time; +int sim_time_initialized = 0; +int mouse_left_down = 0; +int mouse_right_down = 0; double cursor_x = 0, cursor_y = 0; int click_pending = 0; double click_x = 0, click_y = 0; int show_grid_lines = 1; -int sim_paused = 1; struct timespec mytime; struct canvas @@ -229,6 +236,8 @@ const struct wl_buffer_listener wl_buffer_listener = { void redraw(); // forward declaration +static void simulation_tick(void); + static void handle_key(uint32_t key, uint32_t state, xkb_keysym_t sym, uint32_t mods, void *user_data) { (void)key; @@ -266,12 +275,25 @@ static void handle_key(uint32_t key, uint32_t state, xkb_keysym_t sym, uint32_t break; case ACTION_PAUSE: sim_paused = !sim_paused; + sim_accumulator = 0; + printf("paused=%d\n", sim_paused); break; case ACTION_STEP: - printf("step requested\n"); + if (sim_paused) { + simulation_tick(); + printf("stepped\n"); + } break; case ACTION_SPEED_UP: + sim_interval /= 2.0; + if (sim_interval < 0.025) sim_interval = 0.025; + printf("interval=%g\n", sim_interval); + break; case ACTION_SPEED_DOWN: + sim_interval *= 2.0; + if (sim_interval > 1.0) sim_interval = 1.0; + printf("interval=%g\n", sim_interval); + break; case ACTION_TOGGLE_GRID: show_grid_lines = !show_grid_lines; break; @@ -283,12 +305,24 @@ static void handle_key(uint32_t key, uint32_t state, xkb_keysym_t sym, uint32_t static void handle_button(uint32_t button, uint32_t state, double x, double y, void *user_data) { (void)user_data; - if (state != WL_POINTER_BUTTON_STATE_PRESSED) return; - if (button != BTN_LEFT) return; - click_x = x; - click_y = y; - click_pending = 1; - printf("click at %.1f %.1f\n", x, y); + if (button == BTN_LEFT) { + if (state == WL_POINTER_BUTTON_STATE_PRESSED) { + mouse_left_down = 1; + click_x = x; + click_y = y; + click_pending = 1; + } else { + mouse_left_down = 0; + } + } + if (button == BTN_RIGHT) { + if (state == WL_POINTER_BUTTON_STATE_PRESSED) { + mouse_right_down = 1; + } else { + mouse_right_down = 0; + } + } + printf("button %u state %u at %.1f %.1f\n", button, state, x, y); } void wl_callack_done(void *data, struct wl_callback *callback, uint32_t time) @@ -342,6 +376,7 @@ static void canvas_resize(int log_w, int log_h, double scale) { } grid_resize(&grid, log_w / 8, log_h / 8); + grid_resize(&grid_back, grid.cols, grid.rows); need_resize = 0; printf("canvas resized logical=%dx%d phys=%dx%d scale=%g grid=%dx%d\n", log_w, log_h, phys_w, phys_h, scale, grid.cols, grid.rows); } @@ -462,6 +497,39 @@ static void fill_rect(int x0, int y0, int w, int h, unsigned char r, unsigned ch } } +static int count_neighbors(int c, int r) { + int n = 0; + for (int dr = -1; dr <= 1; dr++) { + for (int dc = -1; dc <= 1; dc++) { + if (dr == 0 && dc == 0) continue; + n += grid_get(&grid, c + dc, r + dr); + } + } + return n; +} + +static void simulation_tick(void) { + if (grid_back.cols != grid.cols || grid_back.rows != grid.rows) { + grid_free(&grid_back); + grid_init(&grid_back, grid.cols, grid.rows); + } + grid_clear(&grid_back); + for (int r = 0; r < grid.rows; r++) { + for (int c = 0; c < grid.cols; c++) { + int alive = grid_get(&grid, c, r); + int nb = count_neighbors(c, r); + int next = 0; + if (alive && (nb == 2 || nb == 3)) next = 1; + if (!alive && nb == 3) next = 1; + grid_set(&grid_back, c, r, next); + } + } + /* swap cell buffers */ + unsigned char *tmp = grid.cells; + grid.cells = grid_back.cells; + grid_back.cells = tmp; +} + void redraw() { if (need_resize) { @@ -474,6 +542,35 @@ void redraw() cursor_x = pointer_data.x; cursor_y = pointer_data.y; + /* tick timing */ + struct timespec now; + clock_gettime(CLOCK_MONOTONIC, &now); + if (!sim_time_initialized) { + sim_last_time = now; + sim_time_initialized = 1; + } + double dt = (now.tv_sec - sim_last_time.tv_sec) + (now.tv_nsec - sim_last_time.tv_nsec) / 1e9; + sim_last_time = now; + if (!sim_paused) { + sim_accumulator += dt; + while (sim_accumulator >= sim_interval) { + simulation_tick(); + sim_accumulator -= sim_interval; + } + } + + /* paused-only editing */ + if (sim_paused && active_tool < 0) { + if (mouse_left_down || mouse_right_down) { + int cx = (int)(cursor_x / 8.0); + int cy = (int)(cursor_y / 8.0); + if (cx >= 0 && cy >= 0 && cx < grid.cols && cy < grid.rows) { + if (mouse_left_down) grid_set(&grid, cx, cy, 1); + if (mouse_right_down) grid_set(&grid, cx, cy, 0); + } + } + } + /* handle pending click placement */ if (click_pending) { click_pending = 0; From b7120e67f2cb1e2d2ff357b2babcab93d1dd5e24 Mon Sep 17 00:00:00 2001 From: Arek Date: Sun, 8 Nov 2026 15:52:38 +0100 Subject: [PATCH 3/3] docs: archive gol-simulation change and sync specs Archive completed change and add gol-simulation main spec. --- .../2026-11-08-gol-simulation}/.openspec.yaml | 0 .../2026-11-08-gol-simulation}/design.md | 0 .../2026-11-08-gol-simulation}/proposal.md | 0 .../specs/gol-simulation/spec.md | 0 .../2026-11-08-gol-simulation}/tasks.md | 0 openspec/specs/gol-simulation/spec.md | 85 +++++++++++++++++++ 6 files changed, 85 insertions(+) rename openspec/changes/{gol-simulation => archive/2026-11-08-gol-simulation}/.openspec.yaml (100%) rename openspec/changes/{gol-simulation => archive/2026-11-08-gol-simulation}/design.md (100%) rename openspec/changes/{gol-simulation => archive/2026-11-08-gol-simulation}/proposal.md (100%) rename openspec/changes/{gol-simulation => archive/2026-11-08-gol-simulation}/specs/gol-simulation/spec.md (100%) rename openspec/changes/{gol-simulation => archive/2026-11-08-gol-simulation}/tasks.md (100%) create mode 100644 openspec/specs/gol-simulation/spec.md diff --git a/openspec/changes/gol-simulation/.openspec.yaml b/openspec/changes/archive/2026-11-08-gol-simulation/.openspec.yaml similarity index 100% rename from openspec/changes/gol-simulation/.openspec.yaml rename to openspec/changes/archive/2026-11-08-gol-simulation/.openspec.yaml diff --git a/openspec/changes/gol-simulation/design.md b/openspec/changes/archive/2026-11-08-gol-simulation/design.md similarity index 100% rename from openspec/changes/gol-simulation/design.md rename to openspec/changes/archive/2026-11-08-gol-simulation/design.md diff --git a/openspec/changes/gol-simulation/proposal.md b/openspec/changes/archive/2026-11-08-gol-simulation/proposal.md similarity index 100% rename from openspec/changes/gol-simulation/proposal.md rename to openspec/changes/archive/2026-11-08-gol-simulation/proposal.md diff --git a/openspec/changes/gol-simulation/specs/gol-simulation/spec.md b/openspec/changes/archive/2026-11-08-gol-simulation/specs/gol-simulation/spec.md similarity index 100% rename from openspec/changes/gol-simulation/specs/gol-simulation/spec.md rename to openspec/changes/archive/2026-11-08-gol-simulation/specs/gol-simulation/spec.md diff --git a/openspec/changes/gol-simulation/tasks.md b/openspec/changes/archive/2026-11-08-gol-simulation/tasks.md similarity index 100% rename from openspec/changes/gol-simulation/tasks.md rename to openspec/changes/archive/2026-11-08-gol-simulation/tasks.md diff --git a/openspec/specs/gol-simulation/spec.md b/openspec/specs/gol-simulation/spec.md new file mode 100644 index 0000000..8d7b799 --- /dev/null +++ b/openspec/specs/gol-simulation/spec.md @@ -0,0 +1,85 @@ +# gol-simulation Specification + +## Purpose +Defines Conway's Game of Life simulation behavior, including ticking, pause/resume, stepping, speed control, and paused-only cell editing. + +## Requirements + +### Requirement: Simulation applies Conway's rules +The system SHALL advance the grid by one generation per tick using Conway's rules with wall boundaries. + +#### Scenario: Tick advances grid +- **WHEN** a tick occurs +- **THEN** each cell's next state follows Conway's rules based on its eight neighbors + +### Requirement: Simulation starts paused +The system SHALL start with the simulation paused. + +#### Scenario: Initial state +- **WHEN** the application starts +- **THEN** the simulation is paused + +### Requirement: Pause can be toggled +The system SHALL toggle between paused and running when the pause action is triggered. + +#### Scenario: Toggle pause +- **WHEN** the user triggers the pause action +- **THEN** the simulation switches between paused and running + +### Requirement: Single step advances one generation +The system SHALL advance exactly one generation when the step action is triggered while paused. + +#### Scenario: Step while paused +- **WHEN** the simulation is paused and the user triggers step +- **THEN** the grid advances by one generation +- **AND** the simulation remains paused + +### Requirement: Step is ignored while running +The system SHALL ignore the step action while the simulation is running. + +#### Scenario: Step while running +- **WHEN** the simulation is running and the user triggers step +- **THEN** no extra generation is applied + +### Requirement: Speed is adjustable +The system SHALL change the tick interval when speed_up or speed_down is triggered, within supported bounds. + +#### Scenario: Speed up +- **WHEN** the user triggers speed_up +- **THEN** the tick interval decreases (simulation runs faster) + +#### Scenario: Speed down +- **WHEN** the user triggers speed_down +- **THEN** the tick interval increases (simulation runs slower) + +### Requirement: Cell editing only while paused +The system SHALL allow editing cells only while the simulation is paused. + +#### Scenario: Edit while paused +- **WHEN** the simulation is paused and the user clicks a cell +- **THEN** the cell state changes according to the mouse button + +#### Scenario: No edit while running +- **WHEN** the simulation is running and the user clicks a cell +- **THEN** the grid is not modified by the click + +### Requirement: Left click sets cell alive +The system SHALL set the clicked cell to alive when the left mouse button is pressed while paused. + +#### Scenario: Set alive +- **WHEN** the simulation is paused and the user left-clicks a cell +- **THEN** that cell becomes alive + +### Requirement: Right click sets cell dead +The system SHALL set the clicked cell to dead when the right mouse button is pressed while paused. + +#### Scenario: Set dead +- **WHEN** the simulation is paused and the user right-clicks a cell +- **THEN** that cell becomes dead + +### Requirement: Drag paints cells +The system SHALL paint cells while dragging with the left button (alive) or right button (dead) while paused. + +#### Scenario: Drag paint +- **WHEN** the simulation is paused and the user drags with left button held +- **THEN** cells under the cursor become alive