From 1483b7906114c20288bcff2454fbbcab5f8893b5 Mon Sep 17 00:00:00 2001 From: omigamedev Date: Sat, 13 Jun 2026 16:26:01 +0200 Subject: [PATCH] Add stroke frame planning coverage --- docs/modernization/director-workflow.md | 51 ++++++++---- .../paint_renderer/stroke_execution_tests.cpp | 79 +++++++++++++++++++ 2 files changed, 113 insertions(+), 17 deletions(-) diff --git a/docs/modernization/director-workflow.md b/docs/modernization/director-workflow.md index f3a6f55..3aa546a 100644 --- a/docs/modernization/director-workflow.md +++ b/docs/modernization/director-workflow.md @@ -17,6 +17,10 @@ a task is complex. The default is still one agent executing one task from - Avoid merge conflicts by giving every agent a disjoint task and file scope. - Keep teams rolling: when one team finishes, integrate or park its result and start the next disjoint team without waiting for every other team to finish. +- Prefer parallel execution whenever scopes are disjoint: run multiple teams at + once, and give each team 2 or 3 workers when the work can be split cleanly. +- Keep communication terse: no fillers, no cheerleading, no narrative padding. + Use direct technical wording only. ## Roles @@ -47,8 +51,8 @@ captain or worker whenever the scope can be made clear. ### Team Captain -Use `gpt-5.4` for each captain. A captain owns one coherent task group, for -example: +Use `gpt-5.4` for each captain. A captain owns one coherent task group and is +responsible for domain-level planning, for example: - renderer/export boundary - legacy adapter retirement @@ -60,13 +64,13 @@ example: The captain owns implementation for its assigned task group. A team should be a captain plus multiple workers when the work can be split into disjoint files. The captain turns one task-group objective, or a small coherent run of adjacent -task rows, into smaller disjoint subtasks, spawns or requests workers when -useful, reviews their changes, runs focused validation when cheap, and returns -an integration-ready result. If nested subagents are available, the captain may -spawn workers and continue through the next compatible subtask after each worker -returns. If nested subagents are not available in the current surface, the -captain returns a worker plan and the director performs the second-level spawns -without taking over implementation. +task rows, into 2 or 3 smaller disjoint subtasks when possible, spawns or +requests workers when useful, reviews their changes, runs focused validation +when cheap, and returns an integration-ready result. If nested subagents are +available, the captain may spawn workers and continue through the next +compatible subtask after each worker returns. If nested subagents are not +available in the current surface, the captain returns a worker plan and the +director performs the second-level spawns without taking over implementation. The captain must not edit broad shared files unless assigned. The captain must not rewrite the task tracker or roadmap except for a requested status note. @@ -81,13 +85,16 @@ Every worker and explorer must be told: - this repository may have other agents working in parallel - do not revert or overwrite unrelated changes - stay inside the assigned scope +- focus on the assigned task first; do not start by reading unrelated docs or + broad repository areas unless the task explicitly requires it - report changed files, validation run, and blockers ## Model Selection | Work Type | Model | Reasoning Effort | Use | | --- | --- | --- | --- | -| Captain for a task group | `gpt-5.4` | `medium` default, `high` for renderer/platform architecture | Split task, supervise workers, summarize integration. | +| Director orchestration and integration | `gpt-5.4-mini` | `low` or `medium` | Scope selection, task routing, conflict checks, validation, docs/debt updates, commits, pushes. | +| Team captain for a domain slice | `gpt-5.4` | `medium` default, `high` for renderer/platform architecture | Split a broad domain slice into worker-sized tasks, supervise workers, review results, and integrate changes. | | Risky implementation with cross-file C++ behavior | `gpt-5.4` | `medium` or `high` | Code changes touching contracts, build graph, or live adapters. | | Bounded implementation in known files | `gpt-5.4-mini` | `medium` | Localized tests, simple adapters, docs/debt updates, small refactors. | | Fast lookup or inventory | `gpt-5.3-codex-spark` | `low` or `medium` | `rg` inventory, file ownership map, simple grep-based answers. | @@ -96,7 +103,9 @@ Every worker and explorer must be told: Prefer the inherited model unless the user requested this director workflow or there is a clear task-specific reason to override. In this workflow, the user -has requested `gpt-5.4` captains, so use that override for captains. +has requested `gpt-5.4` team leads, so use that override for captains and keep +the director on `gpt-5.4-mini` for orchestration unless a higher-reasoning +escalation is justified. ## Token Discipline @@ -112,10 +121,13 @@ has requested `gpt-5.4` captains, so use that override for captains. workers/captains unless the task truly needs more. - Ask for compact final reports: changed files, result, validation, blockers, next recommendation. +- Keep final reports minimal and technical. - Close completed agents after their results are integrated or rejected. - Prefer the smallest number of teams that keeps disjoint work moving. Multiple captains are appropriate when task rows have non-overlapping write scopes and - can validate independently. Avoid many agents in one file family. + can validate independently. Avoid many agents in one file family, but do use + multiple teams when the scopes are disjoint and the work can advance in + parallel. - Do not synchronize all teams at a barrier unless validation or merge risk requires it. Use rolling integration: wait for whichever team finishes first, process that result, then immediately start another disjoint team if ready @@ -125,7 +137,9 @@ has requested `gpt-5.4` captains, so use that override for captains. 1. Director picks two or more `Ready` tasks from `docs/modernization/tasks.md` with disjoint write scopes. -2. Director assigns each independent task group to a `gpt-5.4` captain. +2. Director assigns each independent task group to a `gpt-5.4` captain, with + preference for 2 or 3 worker subtasks per team when the scope can be split + cleanly. 3. Captains implement directly or coordinate workers/explorers for disjoint subtasks, and may continue through a coherent sequence of adjacent tasks within the assigned scope. @@ -151,10 +165,11 @@ Validation: . Goal: . -Own this task group through implementation. Build a small team of workers for -disjoint subtasks when possible, review their outputs, and keep looping through -the assigned coherent task sequence until the scope is done, blocked, or no -longer safe to continue. Use gpt-5.4 only for risky C++ behavior changes. Keep +Own this task group through planning and implementation. Build a small team of +`gpt-5.4-mini` workers for disjoint subtasks when possible, review their +outputs, and keep looping through the assigned coherent task sequence until the +scope is done, blocked, or no longer safe to continue. Use `gpt-5.4` for broad +domain planning, interface decisions, and risky C++ behavior changes. Keep tasks small enough to validate. Do not edit outside the assigned scope. Other agents may be working in parallel; do not revert unrelated changes. @@ -179,6 +194,8 @@ Read scope: . Validation: . Make the smallest behavior-preserving change that satisfies the done checks. +Do not spend tokens on broad document review or inventory outside the assigned +scope unless the task requires it. Update tests/docs only if listed. If the task is larger than expected, stop and report the split instead of broadening scope. diff --git a/tests/paint_renderer/stroke_execution_tests.cpp b/tests/paint_renderer/stroke_execution_tests.cpp index d41ff1e..ffef910 100644 --- a/tests/paint_renderer/stroke_execution_tests.cpp +++ b/tests/paint_renderer/stroke_execution_tests.cpp @@ -605,6 +605,82 @@ void retained_stroke_frame_samples_with_dirty_tracking_updates_after_finish(pp:: PP_EXPECT(h, nearly_equal(pass_dirty_boxes[4].w, 10.0F)); } +void retained_stroke_frame_planner_uses_previous_sample_and_projection_mode(pp::tests::Harness& h) +{ + const auto frames = pp::panopainter::plan_legacy_canvas_stroke_frames( + pp::panopainter::LegacyCanvasStrokeComputeRequest { + .previous_sample = StrokeSample { + .col = { 0.25F, 0.5F, 0.75F }, + .pos = { 10.0F, 20.0F, 0.0F }, + .origin = { 0.0F, 0.0F, 0.0F }, + .scale = { 1.0F, 1.0F }, + .size = 4.0F, + .flow = 0.5F, + .opacity = 0.75F, + .angle = 0.0F, + }, + .samples = std::array { + StrokeSample { + .col = { 1.0F, 0.0F, 0.0F }, + .pos = { 14.0F, 26.0F, 0.0F }, + .origin = { 0.0F, 0.0F, 0.0F }, + .scale = { 1.0F, 1.0F }, + .size = 2.0F, + .flow = 0.6F, + .opacity = 0.7F, + .angle = 0.0F, + }, + StrokeSample { + .col = { 0.0F, 1.0F, 0.0F }, + .pos = { 18.0F, 30.0F, 2.0F }, + .origin = { 0.0F, 0.0F, 0.0F }, + .scale = { 1.0F, 1.0F }, + .size = 6.0F, + .flow = 0.8F, + .opacity = 0.9F, + .angle = 0.0F, + }, + }, + .zoom = 1.0F, + .mixer_size = glm::vec2(64.0F, 64.0F), + .model_view = glm::mat4(1.0F), + }, + [&](std::array& brush_quad, bool project_3d, glm::mat4 model_view) { + if (project_3d) { + PP_EXPECT(h, nearly_equal(model_view[0][0], 1.0F)); + } + for (const auto& vertex : brush_quad) { + PP_EXPECT(h, !glm::any(glm::isnan(vertex.pos))); + } + return std::array { brush_quad }; + }, + [](glm::vec4 mixer_rect, glm::vec4 color, float flow, float opacity, auto&& shapes) { + return StrokeFrame { + .col = color, + .flow = flow, + .opacity = opacity, + .shapes = std::move(shapes), + .m_mixer_rect = mixer_rect, + }; + }); + + PP_EXPECT(h, frames.size() == 2U); + PP_EXPECT(h, nearly_equal(frames[0].m_mixer_rect.x, 8.0F)); + PP_EXPECT(h, nearly_equal(frames[0].m_mixer_rect.y, 18.0F)); + PP_EXPECT(h, nearly_equal(frames[0].m_mixer_rect.z, 4.0F)); + PP_EXPECT(h, nearly_equal(frames[0].m_mixer_rect.w, 4.0F)); + PP_EXPECT(h, nearly_equal(frames[0].col.r, 1.0F)); + PP_EXPECT(h, nearly_equal(frames[0].flow, 0.6F)); + PP_EXPECT(h, nearly_equal(frames[0].opacity, 0.7F)); + PP_EXPECT(h, nearly_equal(frames[1].m_mixer_rect.x, 12.0F)); + PP_EXPECT(h, nearly_equal(frames[1].m_mixer_rect.y, 16.0F)); + PP_EXPECT(h, nearly_equal(frames[1].m_mixer_rect.z, 6.0F)); + PP_EXPECT(h, nearly_equal(frames[1].m_mixer_rect.w, 6.0F)); + PP_EXPECT(h, nearly_equal(frames[1].col.g, 1.0F)); + PP_EXPECT(h, nearly_equal(frames[1].flow, 0.8F)); + PP_EXPECT(h, nearly_equal(frames[1].opacity, 0.9F)); +} + void retained_stroke_live_pass_with_face_framebuffers_preserves_order_and_dirty_tracking(pp::tests::Harness& h) { StrokeFrame frame; @@ -961,6 +1037,9 @@ int main() harness.run( "retained_stroke_frame_samples_with_dirty_tracking_updates_after_finish", retained_stroke_frame_samples_with_dirty_tracking_updates_after_finish); + harness.run( + "retained_stroke_frame_planner_uses_previous_sample_and_projection_mode", + retained_stroke_frame_planner_uses_previous_sample_and_projection_mode); harness.run( "retained_stroke_live_pass_with_face_framebuffers_preserves_order_and_dirty_tracking", retained_stroke_live_pass_with_face_framebuffers_preserves_order_and_dirty_tracking);