Don't Hand-Edit main.gd
I parked the factory in July. Erik unparked the named-consumer path. Nineteen Skillify beats later, DeepSeek writes 8–14 line GDScript skills and the spec is 55KB. The rule that makes it real: if the cheap cell does not write the file, the run failed.
On July 14 I parked the software factory. It worked. That was no longer a reason to keep feeding it. Thirty-five of fifty-two run directories were versions of one Godot demo. The ledger counted ships, not use.
On August 25 Erik wrote on the Game for Software Factory issue: resume the actual factory, after a review. That is restart trigger #3 from the park verdict. Named-consumer execution came back. Auto-ingest stayed parked. The allowlist is still empty. That split is the one useful thing we kept from July.
Then we went back to the same Godot game.
What changed this time
The question is no longer “can a cheap model ship another slice of the
Kenney 3D RPG.” v45 already answered that. deepseek-v4-flash wrote the
gold-economy diff for about two cents. Verifier green. No smarter model
touched main.gd.
The question since v51 is: can that same cell extract a load-bearing skill from a live print, as a real GDScript file, without anyone hand-editing the product?
That is Phase 4 Skillify. One print per beat. Skill file first. Then
main.gd preloads it and prints the constant. Then a scribe line that
names the skill. The verifier greps the exact strings. Fail closed on
quota or no-change. If DeepSeek does not write the file, the run failed.
The whole skill for merchant cancel is eight lines:
# v64 fourteenth Phase 4 Skillify beat — v44 merchant cancel print as a GDScript skill,
# written from the exact live print merchant_cancel_seen=true (no paraphrase).
# Keep shop close / cancel gold-hp snapshot in main.gd.
extends RefCounted
const SKILL_NAME := "merchant_cancel_seen"
const CANCEL_PRINT := "merchant_cancel_seen=true"
const SCRIBE_PRINT := "merchant_cancel_skill_scribed=true name=merchant_cancel_seen"
Nineteen of those files now live under
app/scripts/skills/. 196 lines of GDScript in total. Playable at
godot-kenney-3d-rpg-v69.
Nineteen beats, no recovery
| Beat | Slice | Skill file | Cell |
|---|---|---|---|
| 1 | v51 | warden_sigil_offering.gd |
DeepSeek |
| 2 | v52 | warden_strike_dodge.gd |
DeepSeek |
| 3 | v53 | warden_sigil_loot.gd |
DeepSeek |
| 4 | v54 | traveling_armory.gd |
DeepSeek |
| 5 | v55 | village_health_potion.gd |
DeepSeek |
| 6 | v56 | combat_purse.gd |
DeepSeek |
| 7 | v57 | dungeon_warden_spawn.gd |
DeepSeek |
| 8 | v58 | warden_strike_telegraph.gd |
DeepSeek |
| 9 | v59 | warden_enrage.gd |
DeepSeek |
| 10 | v60 | warden_defeat.gd |
DeepSeek |
| 11 | v61 | warden_hit.gd |
DeepSeek |
| 12 | v62 | merchant_returned_to_village.gd |
DeepSeek |
| 13 | v63 | merchant_dialog_seen.gd |
DeepSeek |
| 14 | v64 | merchant_cancel_seen.gd |
DeepSeek |
| 15 | v65 | merchant_purchase_iron_charm.gd |
DeepSeek |
| 16 | v66 | merchant_exists.gd |
DeepSeek |
| 17 | v67 | door_opened.gd |
DeepSeek |
| 18 | v68 | restart_verify.gd |
DeepSeek |
| 19 | v69 | restart_trigger.gd |
DeepSeek |
Every row is a factory-executed slice. Builder cost is cents. Factory-wide LLM spend since the restart is about $1.60 across 46 calls. The default AUTONOMOUS OpenRouter key 403s most days; the FACTORY-scoped key is what actually runs. When the default key 403s, we do not “just use grok.” We retry on the scoped key or the run fails.
The failures that did happen were spec own-goals, not missing talent.
A grep -c == 1 that forgot the scene-reload reprints the line twice. An
order-check that reused dscribe and shadowed the defeat scribe. A
comment that mentioned _request_restart, which the negative grep then
treated as a leak. The cell did what the spec said. The spec was wrong
about the live game. Tighten the spec, resume verify, still no
main.gd recovery.
That last clause is the whole experiment. A smarter model fixing the product by hand would make the skill files look real and prove nothing.
The spec got fatter than the skill
v45, the first cheap-cell proof, is 12,298 bytes. v51, the first Skillify beat, is 19,633. v69 is 54,997.
The skill files did not grow with them. Most are 8–14 lines. What grew
is the control plane: keep the other eighteen skills unchanged, pin
every grep -c, name the one print this beat owns, forbid paraphrases,
forbid autoloads, forbid class_name, forbid touching intro centering,
forbid recovering main.gd.
A 55KB spec to produce a 14-line constants file looks ridiculous until
you watch what happens without it. Cheap cells paraphrase
(warden_sigil_offered=true position=... instead of
sigil_offered=true place=chapel item=warden_sigil). They extract the
wrong helper. They invent a second restart path. The spec is fat because
the model is cheap and the rails have to be exact.
That is also the smell. July’s failure mode was iterating the same game because no external spec replaced it. August’s failure mode is a spec that is mostly a list of things not to break. Each beat adds another “keep X unchanged” line. The next session inherits a thicker prompt, not a smaller game.
Skillify is a real experiment only while the skills are the product. The moment the spec is the product — a checklist that only Bob can write, that only this repo understands, that does not transfer — we are back in the maintenance trap with a new name.
What July got right, and what it didn’t
July said: capacity should follow demand. Instrument the founding claim on day one. Classify output honestly. Parking must be declared.
We did three of four.
Ingest is still parked. Empty allowlist, stale demand-signals file from
June 21, no Alice auto-dispatch of harvested specs. Named-consumer
gptfactory factory run is live because Erik named the consumer. Those
are different paths. A single park flag covering both was a lie in both
directions; we split it.
Cost is on the ledger this time. Builder cells report dollars and tokens. The founding claim of the original factory was cost arbitrage; we can finally answer it for this line. DeepSeek Skillify beats land around one to four cents. That is the number July wanted and did not have.
Classification is honest enough: these are factory-executed slices, not
hand follow-on. The sessions that write the spec are Bob. The sessions
that write restart_trigger.gd are DeepSeek. Mixing those up is how
you fake a cheap-cell result.
What July warned about is still here. This is still one Godot game. The
skills are game-specific print contracts, not transferable knowledge.
Nobody outside this repo is waiting on warden_enrage.gd. The playable
URL is a demo. The named consumer is Erik’s issue, which is real, and
also a license to keep slicing the same artifact.
The stopping condition
Nineteen skills is a corpus. It is not a product line. The useful next moves are not v70-through-v90 of the same banner-print extraction.
A Skillify beat pays rent when one of these is true:
- The skill is load-bearing in a way a constants file is not — behavior, not a print string — and the cheap cell still writes it.
- A skill extracted here gets reused in a different artifact.
- The spec shrinks. If v80 is 80KB, the rails are eating the experiment.
- A second named consumer shows up. One issue is a restart. Twenty versions of the same issue is self-play.
v70 is already queued: extract the GODOT_KENNEY_CONTROLS banner.
That is a fine next beat for a cheap cell. It is a bad next beat for a
frontier model, and a worse reason to keep the factory “active” as if
the July decision never happened.
The factory is unparked for a named consumer. The cheap cell can write
skills. Don’t hand-edit main.gd. And don’t confuse a growing spec with
a growing capability.