This guide is written for the reviewing agent. You audit the open pull requests against
community/effects/ periodically, decide merge / changes / decline, and leave a comment that
the contributor (usually another agent) can act on without asking a question back.
You are the quality bar. CI proves an effect is legal; you decide whether it is good.
# one checkout per PR, in your own worktree - never in the user's
gh pr checkout <number>
cmake --preset default && cmake --build --preset default
ctest --test-dir build -j 8
PYTHONPATH=$PWD/python python -m pytest python/tests -qFind what changed. A contribution is exactly one new file under community/effects/, plus
at most one generator under tools/generators/community/:
git diff --name-status main...HEADAnything else in the diff - engine changes, example changes, a hand-edited
community/index.json, any media file - is an automatic changes requested.
# the contribution check, with previews, on the changed file
python -m aetherfx.community check community/effects/<slug>.json --previews out/review/<slug> --json
# the whole library still has to lint
python -m aetherfx.community lint-library
# no tracked media, licence intact
python -m pytest python/tests/test_repo_hygiene.py -q
# the index is generated, not hand-written
python tools/build_community_index.py --check || true # stale here is fine: CI rebuilds it on mainRead the JSON report. metrics gives you the numbers you will quote in the comment: peak
sprites, peak mesh particles, node count, duration, texture bytes, bake time, determinism.
Never merge an effect you have not watched. The check cannot see whether something reads.
out/review/<slug>/<slug>_sheet.png is six frames across the duration. Ask:
- Silhouette - can you name the effect from frame 2 alone, at thumbnail size?
- Phases - do the six frames tell a story (build, hit, settle), or are they six of the same picture?
- Exposure - is the core a white blob? Is anything invisible?
- Palette - two or three hues with a hot core, or mud?
- Scale - does it sit 1-3 m tall around the origin, framed by its own camera node?
Start your own studio - never port 8770, that is the user's - and capture with frame timings:
python -m aetherfx.studio.server --port 8801 --no-open --generator none \
--output-dir out/review/studio &
python tools/gl_capture.py --url http://127.0.0.1:8801/ --effect "<Effect Name>" \
--times 0.2,0.6,1.2,2.0 --out out/review/<slug>/gl --size 1280x800 \
--perf 6 --cdp-port 9361 --profile out/review/chrome
kill $(lsof -ti tcp:8801 -sTCP:LISTEN); pkill -f "remote-debugging-port=9361"--perf prints render_fps, stream_fps, worst_frame_gap_ms, avg_draw_calls and
avg_triangles. 55-60 render fps is the bar on the reviewer's machine. Anything below 45,
or a worst frame gap over 60 ms, is a performance change request even when the budgets pass.
Look at the captures for what the CPU sheet cannot show: ribbon edges, beam cross-sections, mesh particle shading, bloom behaviour, popping at the loop point.
Six axes. Anything red blocks the merge; two or more amber is changes requested.
| Axis | Green | Amber | Red |
|---|---|---|---|
| Correctness | 0 errors, deterministic, inside budget with headroom | passes but within 10% of a budget | fails the check, non-deterministic, over budget |
| House style | no lint warnings, or warnings that are clearly deliberate | one or two warnings the effect could live without | a lint error, a cross motif, a tube beam, a projectile with no impact |
| Originality | a new element, silhouette or idea | a fresh take on an existing category | a palette swap or a near-duplicate of a library effect |
| Readability | nameable from one thumbnail, clear phases | reads once you know what it is | a bright mess, or invisible at gameplay distance |
| Performance | 55-60 fps, modest draw calls | 45-55 fps | under 45 fps, or frame gaps over 60 ms |
| Metadata | credit, MIT, 3+ artist-named controls, useful tags, honest description | controls named after parameters, thin tags | missing credit or licence, fewer than 3 controls |
A palette swap is the most common decline. Compare the candidate against every effect in
examples/effects/ and community/effects/:
- Node structure - node type counts, layer names and roles, the emitter shapes. Two documents with the same type histogram and the same layer names are the same effect.
- Palette - the dominant
colorandcolor_over_lifehues. A hue shift is not a new effect. - Silhouette and timing - the phase boundaries and what the contact sheet shows.
If the closest match shares structure and silhouette, it is a duplicate however different the colours are. If it shares only the palette, say so and judge the rest on its merits.
A quick starting point:
python - <<'PY'
import json, collections, pathlib
def shape(p):
d = json.loads(pathlib.Path(p).read_text())
return (collections.Counter(n["type"] for n in d["nodes"]),
sorted(l.get("role","") for l in d.get("layers", [])))
cand = shape("community/effects/<slug>.json")
for other in sorted(pathlib.Path("examples/effects").glob("*.json")):
types, roles = shape(other)
shared = sum((cand[0] & types).values()) / max(1, sum(cand[0].values()))
if shared > 0.8 and roles == cand[1]:
print(f"{other.name}: {shared:.0%} of the node histogram, same layer roles")
PYAll green, or green with one amber you can live with.
Merged. Thanks for **<Effect Name>**.
- check: 0 errors, <n> warning(s), deterministic at 4 sample times
- budget: <sprites> sprites, <mesh> mesh particles, <nodes> nodes, <dur> s, <mb> MiB baked
- viewer: <fps> fps, <calls> draw calls
- reads as: <one sentence on what the silhouette says>
It is in the Community view now, credited to @<github>. The published index and previews
rebuild on the next push to main.Then squash-merge with the contributor's Signed-off-by: preserved, and let the main-branch
workflow rebuild community/index.json and the previews.
Anything red that tuning can fix, or two or more ambers. Be specific: name the node, the parameter and the target value. Never write "polish the timing".
Close. Three things before this can land:
1. **<rule>** - `<node id>`: <what is wrong>. <exact change>, e.g. set `detail: 3` and
`width_profile: "bulge"`.
2. **<rule>** - <same shape>.
3. **<rule>** - <same shape>.
Everything else looks good: <one honest positive>. Push to the same branch and CI will
re-run; ping me when it is green.An idea that duplicates the library, or a house-style violation that tuning cannot fix.
Declining this one, and it is not about the craft.
**<Effect Name>** is structurally the same effect as `<existing>`: <the evidence - node
histogram, layer roles, silhouette, palette>. The library already covers that idea.
What would land: <one concrete, different direction - a different element, a different
silhouette, a different phase structure>. `CONTRIBUTING.md` section 2 has the bar. Please do
send another.Always leave the door open. Contributors are agents driven by people; a decline with a direction usually comes back as a better effect.
A community effect that a lot of people load, that has no warnings, and that fills a gap in
examples/effects/ can be promoted.
- Open your own PR; do not rewrite history on the contributor's.
git mv community/effects/<slug>.json examples/effects/<slug>.json.- Keep
metadata.authorexactly as it is. Promotion never removes credit. - Add
metadata.tagsif they are thin, and make sure the controls are named well - Core effects are what new users see first. - Rebuild the index:
python tools/build_community_index.py. python -m aetherfx.community lint-librarymust still pass.- Mention the promotion in the original PR thread so the contributor sees it.
A promoted effect stays MIT and stays credited. That is the whole deal.