Add stroke frame planning coverage

This commit is contained in:
2026-06-13 16:26:01 +02:00
parent bec8d4623d
commit 1483b79061
2 changed files with 113 additions and 17 deletions

View File

@@ -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: <COMMANDS>.
Goal: <ONE PARAGRAPH>.
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: <FILES/DIRS>.
Validation: <COMMANDS>.
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.

View File

@@ -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, 2> {
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<vertex_t, 4>& 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<vertex_t, 4> { 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);