Skip to content

chore(release): OpenScene v0.6.0 - #313

Merged
sjungwon03 merged 5 commits into
mainfrom
dev
Aug 30, 2026
Merged

chore(release): OpenScene v0.6.0#313
sjungwon03 merged 5 commits into
mainfrom
dev

Conversation

@sjungwon03

Copy link
Copy Markdown
Member

Promotes dev to main, which is the release trigger. package.json says 0.6.0 and v0.6.0 is not tagged, so this publishes; a later push to main without a version change would do nothing.

What ships:

Also in this promotion: 0.6.0 in package.json and mobile/app.json, ios.buildNumber 9, android.versionCode 8, and store notes for both stores in both languages.

Merging tags v0.6.0, packages the desktop app on all three runners, publishes the GitHub release, and hands the mobile builds to Google Play and App Store Connect.

🤖 Generated with Claude Code

sjungwon03 and others added 5 commits August 28, 2026 17:01
* feat(export): read the finished file back before calling it done

An export was reported as a success on the strength of the encoder exiting
zero and a file existing. Nothing read the file. Every serious export defect
found on a device this year got past exactly that: a cut truncated to 2.5s of
6.6s by a zero-sized title overlay, a layer dropped without a word, a clip that
came out silent because its audio was never mapped. All of them exited zero and
wrote a file.

The plan already says what the file is supposed to be, so the file is measured
and compared with it. The comparison is one shared rule, because the answer has
to be the same on both surfaces; measuring is each platform's own business,
because they have different tools for it. Desktop uses the ffprobe that ships
beside the FFmpeg it already discovers; the phone uses MediaMetadataRetriever
and AVAsset. A machine that cannot measure reports the export unchecked rather
than failing it — being unable to inspect a file says nothing about the file.

Rotation is applied before the shape is compared, or every upright phone export
would be reported as the wrong shape. Sound is checked one way round only: a
timeline with nothing to hear may still be written with a silent track, and
there is nothing wrong with that.

Measured, not assumed: against real files here, a good 1280x720 six-second
export passes, a two-second truncation of it is caught, and a machine without
ffprobe comes back unchecked.

Closes #303

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(export): await the measurement before unwrapping it

XCTUnwrap takes an autoclosure, and an await inside one does not compile.
Caught by ios-export, which is the only thing on this project that compiles
the Swift.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
…hat it did (#306)

* feat(spend): a ceiling on what generation may cost, and a record of what it did

Both surfaces already ask before a charge and price the job first. Neither had
a memory or a limit. Once someone answers "always" on the phone, nothing bounds
it again; on the desktop an agent asked for a ten-shot plan runs all ten. The
bill is where either is discovered.

So: a ledger of charges incurred, a month-to-date total, and a check that
refuses the job that would cross the ceiling and says by how much. The rule is
shared because a ceiling only one surface honours is not a ceiling — the phone
guards its image and shot seams, the desktop guards all three of its job
creators, and both record the charge as the request goes to the provider rather
than when the job is queued. A job refused for a missing key cost nothing, and
charging someone's ceiling for it would lock them out over nothing.

A charge nobody can price cannot be kept under a ceiling, so with a limit set
an unpriced model is refused unless the user takes it deliberately. That is the
rule mediaGenerationPricing already follows for prices, applied to money.

The agent can read the limit and not set it. An agent that can raise its own
ceiling does not have one.

Closes #305

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(spend): take the room out of the ceiling as it is checked

Review caught the hole: checking the total and writing the charge were two
moments, so two jobs asked for at once both read the same total, both passed,
and both spent. The file made it worse — a read-modify-write drops whichever
entry was written second. A limit that holds for one job at a time is not a
limit.

A job now reserves its room as the ceiling is checked, in one step, and settles
it afterwards: kept when the request goes to a provider, handed back when it
never does. Both states count against the ceiling, because an in-flight job is
money on its way out. The desktop serializes every ledger operation through one
queue; the phone reserves in a single synchronous read-check-write, which its
one thread cannot interleave.

A reservation nobody settled — an app killed mid-generation — is treated as a
charge after six hours rather than held forever. The gap between reserving and
dispatching is a key lookup; the gap between dispatching and settling is the
whole generation, so a stale reservation was almost certainly already spent.
That errs toward the user's wallet.

Both races are pinned by tests that fail without the queue.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
The finished file is read back and compared with the cut. This is the other
half: the reasons an export would not come out right, found before anything is
rendered.

Each of these was a separate late failure with its own wording. Two video clips
over the same moment failed inside the Android renderer once the export was
already running — and before that, quietly, as a layer missing from the file. A
clip whose range ran past the end of its own file produced a frozen tail or a
short segment and said nothing at all. A missing asset failed in staging on one
surface and in URI resolution on the other. Stills were refused on the phone
only, by a rule living in that surface's export bridge.

They are one question — can this renderer make this cut — so it is asked in one
place, and both surfaces refuse with the same sentence. What a renderer can do
is reported by the renderer: Android says it does not composite layers, iOS
says it does, and a build that was never asked says no, which is what every
older one means.

A clip past the end of its source is refused rather than trimmed. Silently
shortening someone's clip is a different edit from the one they made.

Closes #307

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Generating a sequence worked. Everything after the first take did not: the
scenario prompt was reused verbatim for every shot with "shot 3 of 5" glued on,
built inline inside one screen, so a shot could not carry its own description,
a take that came back wrong could only be made again by paying for the whole
plan, and saying what to change meant retyping the prompt from memory.

The prompt is now composed by a shared rule, because three callers have to send
the same thing — the phone, the desktop studio and the agent — and a shot asked
for differently is a different shot. Each shot can carry a description on top of
the scenario.

Refining keeps the previous prompt whole and adds the change to it. That is
deliberately arithmetic and not a model: asking a model to rewrite a prompt
loses the parts nobody mentioned — the wardrobe, the lens, the location — which
is exactly what continuity is made of. Notes accumulate in the order they were
asked for, the same note twice is not asked twice, and a prompt grown past what
this app will send is refused rather than truncated at the end, where the newest
change lives.

On the phone a redo runs that shot alone and stands the new take where the old
one was, through a new shared rule that keeps the clip's place and length —
refused rather than retimed when the take is too short to cover the shot,
because silently shortening a cut is a different edit from the one anyone made.

Closes #309

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Four features since v0.5.2, no breaking changes: the finished export is read
back and compared with the cut (#304), generation has a monthly ceiling and a
record of what it spent (#306), a cut that cannot be rendered here is refused
before the render starts (#308), and a shot can carry its own description and
be asked for again with a note (#310).

The build number and version code go up as well as the version: both stores
refuse an upload whose build number is not higher than the last one, even for a
submission that was rejected.

Store notes for both stores in both languages, with the same first line per
language — a release described one way on one store and another elsewhere is
two accounts of one build. The App Store files name no other platform, which is
what Guideline 2.3.10 rejected a build for once.

Closes #311

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>

@sjungwon03-ai sjungwon03-ai left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The v0.6.0 release preparation aligns version metadata and release notes with the included features. Required release and CI checks passed on the reviewed head.

@sjungwon03-ai sjungwon03-ai added the review:approved Latest-head review approved label Aug 29, 2026
@sjungwon03
sjungwon03 merged commit 576107e into main Aug 30, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

review:approved Latest-head review approved

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants