Skip to content

feat(phaser-assets): inspect pack preparation costs before loading - #197

Merged
imjlk merged 6 commits into
mainfrom
codex/inspect-pack-preparation
Sep 20, 2026
Merged

imjlk merged 6 commits into
mainfrom
codex/inspect-pack-preparation

Conversation

@imjlk

@imjlk imjlk commented Sep 19, 2026 •

Copy link
Copy Markdown
Owner

Summary

delivery.inspectPreparation(packId) answers, before any download starts, what files a prepare would need and whether it fits the staging budget. Stacked on #195 and #196 — the diff reduces to this feature once those merge to main.

API shape — one small read-only method, no CLI planner, no second public planner:

const plan = delivery.inspectPreparation('grove');
plan.closure;                       // dependency-first order (staging order)
plan.coldArtifacts;                 // per pack: ZIP archive or each files file
plan.coldObjectCount;               // 1 per ZIP archive; entries are not objects
plan.coldBodyBytes;                 // manifest body bytes assuming no caches
plan.zipExpandedBytes;              // closure ZIP expanded original bytes
plan.stagedZipPacks;                // already staged right now
plan.missingZipPacks;               // a prepare would stage these
plan.additionalReservationBytes;    // archive+expanded of missing packs
plan.stagingUsedBytes / projectedStagingBytes / stagingBudgetBytes / fitsBudget;
plan.bus;                           // a preparation is running right now
plan.accountingModel;               // 'archive-plus-expanded-v1'

Side-effect free by construction: the planner is a pure helper taking the closure, the staged set, current usage and the budget — no scene, DOM, fetch, worker, cache or timer. A test pins snapshot/requests/worker-posts equality across inspections, and disposed deliveries reject with the existing code.

Shared formula: prepare's budget admission now runs the same pure planner on live state (replacing its inline copy), so published numbers and enforced admission cannot drift; an inspection is never a reservation, and the exact-boundary/one-byte-over unit test asserts inspect and prepare agree. Already-staged shared dependencies are not re-reserved, and the plan updates correctly after release (staging dropped) and re-inspection.

Number semantics: cold bytes assume no HTTP/local cache (not wire bytes); a ZIP is one object; duplicate artifacts follow the manifest's identity rules (never content-hash guessing); files-only closures report zero ZIP cost while keeping their full cold file cost; resident textures are never inspected (no "already loaded so no download" guessing); byte sums are overflow-checked (oversized manifests are config errors, never small-looking costs). The accountingModel identifier documents that the reservation covers staging only — excluding transport snapshots/copies, transient Blob conversions, WebCrypto/decoder-internal memory, decoded pixels and GPU resources. No staging-policy changes.

Kit example: each selection computes the plan once (never per frame) and shows required packs, artifact bytes, projected staging and budget fit beside the observed delivery phases; the browser acceptance asserts the displayed plan agrees with the prepare outcome in both the fits scenario and the over-budget scenario (staging=1000), while prepare's internal admission stays authoritative.

Verification

  • 9 new unit tests: files-only zero-ZIP cost, ZIP/mixed closure order, shared-dependency dedup, staged exclusion + release-time re-plan, no-side-effect proof, busy flag, exact budget boundary and one-byte-over parity with prepare, unknown packId + safe-integer overflow, external snapshot mutation isolation. Suite: 339/339.
  • Browser acceptance extended (plan vs outcome in fits and over-budget scenarios) and passing.
  • pnpm check (incl. validate:public), pnpm build:packages, off-kit packed-tarball consumption (runtime + generated .d.ts types) pass; local ocr review 0 findings.

Changeset

.sampo/changesets/inspect-pack-preparation.md — @mpgd/phaser-assets minor.

Excluded by design: CLI planner, HTTP/IndexedDB cache probes, auto pack splitting, compression prediction, GPU memory measurement, schedulers, prefetch. Release PR #189 untouched.

Summary by CodeRabbit

  • New Features
    • Added delivery progress observation with operation tracking, stage updates, measured byte and entry progress, and terminal statuses.
    • Added preparation inspection to preview dependency costs, staging usage, and budget fit before loading.
    • Added structured details to delivery errors for clearer diagnostics.
  • Bug Fixes
    • Preparation deadlines now use monotonic timing and prevent work from starting after the budget expires.
  • Documentation
    • Updated delivery documentation and examples to explain and demonstrate these capabilities.

The whole pack-preparation budget now runs on one monotonic clock
(performance.now) instead of Date.now, so a system clock correction
mid-prepare can neither extend nor prematurely exhaust the
prepareTimeoutMs budget:

- The deadline is computed once per prepare on the monotonic clock and
  never restarted by a later archive; stagePack's remainder reads the
  same clock, and the fractional remainder is floored to the decoder's
  integer deadline contract only after the spent check, so rounding can
  never mint budget.
- No new archive request or decode starts once the monotonic clock says
  the budget is spent, even when the abort timer's callback is still
  queued.
- A decode that resolves after the budget is spent no longer earns
  staging handles: the final acceptance gate re-checks the budget and
  the abort state before handles are acquired.

prepareTimeoutMs, requestTimeoutMs and the #191 decode deadline keep
their existing responsibilities; user-cancel, dispose and deadline
causes stay distinct. Tests drive a manual monotonic clock with gated
downloads and a step-driven worker, covering forward and backward
wall-clock jumps, one shared budget across dependency archives, download
time reducing the decoder budget, zero decode posts after the budget is
spent, refusal of success handles on a late decode, and the existing
cancel/dispose/files-only regressions.
The pre-request budget gate ran before stagePack invoked the synchronous
resolveURL callback, so user code in the resolver could spend the
remaining budget while the archive request still started. The gate now
sits immediately before the request it guards, after URL resolution, and
a regression test advances the fake monotonic clock inside resolveURL:
zero archive requests and zero decode posts, deadline failure.
The gated test origin now answers with Connection: close so each
phase-gated download uses a fresh connection — a pooled socket reused
across gates added runner-dependent transport behavior the test does
not measure and intermittently failed the second fetch on CI. The
prepare settles through an always-handled wrapper so any transport
failure fails the final assertion loudly instead of floating as an
unhandled rejection while a gate wait spins.
…details

Add delivery.subscribe(listener) as the single observation surface for
pack delivery work. Listeners receive prepare and files-delivery read
events carrying an operationId (unique per operation within the
delivery's lifetime), the operation kind, per-operation sequencing, pack
and asset context, the observed phase (planning, downloading,
decoding-and-verifying, and exactly one terminal of prepared/completed,
failed, cancelled or disposed) and strictly measured progress: network
body bytes against the manifest's declared size, verified entry counts
and byte totals. Internal hashing/inflate percentages are not observable
through the #191 contract and are never fabricated; absent fields mean
unknown and per-stage numbers never merge into one synthetic percent.

Observation is containment-safe: throwing or rejecting listeners never
change results and are never awaited, unsubscribe is idempotent,
terminals fire exactly once per operation, and late events from
superseded operations are dropped. Files-only prepare stays a light
two-event operation (prepared means staging, never images loaded), and
staged-entry local reads emit nothing, keeping origin downloads and
local reads distinguishable.

PhaserPackDeliveryError gains an additive optional details field (stage,
operationId, pack/asset identity, httpStatus, decoderStatus/decoderCode,
expectedBytes/receivedBytes) captured at the failing execution point;
codes, messages and existing constructor calls are unchanged, and no URL
or response data ever enters events or details. readCappedDeliveryBody
accepts an optional onBodyBytes observation hook.

The kit example surfaces the observed phases and numbers beside the
loader's own texture progress, keeps stale operation events from
overwriting new selections, unsubscribes on scene shutdown (distinct
from an explicit cancel), and formats failures from the structured
details; the browser acceptance asserts staging observation is distinct
from texture readiness.
- The prepare boundary no longer overwrites pack identity the failing
  site recorded: a dependency archive failure keeps naming that
  dependency in details (regression covered), while correlation fields
  still merge in.
- The unsupported worker-environment failure maps to its documented
  decoderStatus ('unsupported', not 'error'), and a worker-error decode
  failure records decoderStatus 'worker-error'; core digest failures
  still leave decoderStatus absent (unknown), per the
  absent-means-unknown contract.
- The kit sample renders delivery failures from the stable fields only
  (code plus structured details), never message text, and the browser
  acceptance matches that display.
- Published docs describe the bounded ZIP decoder instead of the opaque
  internal PR number.
Add delivery.inspectPreparation(packId): a read-only cost plan for a
pack and its dependency closure, derived from the manifest snapshot and
the current staging state. It starts nothing, acquires nothing,
reserves nothing, and touches no network, cache, worker or timer.

The plan distinguishes the requested pack and revision, the
dependency-first closure, per-pack cold artifacts (one object per ZIP
archive — entries are not download objects — and one per files-delivery
file), the cold object count and manifest body-bytes sum (assuming no
caches; not wire bytes), the ZIP packs already staged versus the ones a
prepare would stage, the additional staging reservation under the
current archive-plus-expanded policy, current and projected staging
usage against the configured budget with fitsBudget, and the busy
state. Duplicate artifacts follow the manifest's identity rules, a
files-only closure reports zero ZIP cost while keeping its full cold
file cost, resident textures are never inspected, byte sums are
overflow-checked, and the accountingModel identifier pins that the
reservation covers staging only — not transport copies, decoder
internals, decoded pixels or GPU memory.

inspectPreparation and prepare's budget admission share one pure
planner (closure + staged set + usage + budget in, plan out), so the
published numbers and the enforced admission cannot drift; an
inspection is never a reservation. The kit example computes the plan
once per selection and shows packs, artifact bytes, projected staging
and budget fit next to the observed delivery phases; the browser
acceptance asserts the displayed plan agrees with the prepare outcome
in both the fits and the one-byte-over budget scenarios.
@imjlk

imjlk commented Sep 19, 2026

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, add credits to your account and enable them for code reviews in your settings.

@sampo-s-bot

sampo-s-bot Bot commented Sep 19, 2026

Copy link
Copy Markdown

🧭 Changeset detected

Merging this PR will release the following updates:

@mpgd/phaser-assets (npm) — minor version bump

Minor changes

  • Add delivery.subscribe(listener) as the single observation surface for
    pack delivery: listeners receive prepare and files-delivery read events
    with an operation correlation id, per-operation sequencing, observed
    phases (planning, downloading, decoding-and-verifying, one terminal of
    prepared/completed, failed, cancelled or disposed) and strictly measured
    progress — network-delivered body bytes against the manifest's declared
    size, and verified entry counts and byte totals. Observation is
    containment-safe: throwing or rejecting listeners never change delivery
    results, unsubscription is idempotent, terminals fire exactly once per
    operation, and late events from superseded operations are dropped.

    PhaserPackDeliveryError gains an optional additive details field
    (stage, operationId, pack/asset identity, httpStatus,
    decoderStatus/decoderCode, expectedBytes/receivedBytes) captured at the
    failing execution point — no message parsing, no URL or response data.
    readCappedDeliveryBody accepts an optional onBodyBytes observation
    hook. Existing codes, constructor calls and behavior are unchanged.

  • Add delivery.inspectPreparation(packId): a read-only preparation cost
    plan computed from the manifest snapshot and the current staging state —
    dependency-ordered closure, per-pack cold artifact list (one object per
    ZIP archive, one per files-delivery file), cold object count and
    manifest body-bytes sum, ZIP packs already staged versus the ones a
    prepare would stage, the additional staging reservation under the
    current archive-plus-expanded policy, current/projected staging usage
    against the budget with fitsBudget, and the current busy state. The
    inspection starts nothing and reserves nothing; prepare runs the same
    pure planner on live state so a stale inspection can never bypass
    admission. Byte sums are overflow-checked, and an accountingModel
    identifier pins what the reservation numbers cover (staging only — not
    transport copies, decoder internals, decoded pixels or GPU resources).

Patch changes

  • Measure the whole pack-preparation budget on one monotonic clock
    (performance.now) instead of Date.now, so a system clock correction
    during prepare can no longer extend or prematurely exhaust the
    prepareTimeoutMs budget. The decoder still receives only the unspent
    remainder, now floored to its integer deadline contract so rounding can
    never mint budget; no new archive request or decode starts once the
    monotonic clock says the budget is spent, and a decode that resolves
    after the budget is spent no longer earns staging handles. Existing
    responsibilities are unchanged: prepareTimeoutMs still covers one
    whole prepare, requestTimeoutMs still ends with each HTTP body, and
    user-cancel, dispose and deadline causes stay distinct.

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, add credits to your account and enable them for code reviews in your settings.

@coderabbitai

coderabbitai Bot commented Sep 19, 2026 •

Copy link
Copy Markdown

Review Change StackReview Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Advanced

Run ID: cbf2aec1-5b8f-4395-8877-dc39cf14e3bc

📥 Commits

Reviewing files that changed from the base of the PR and between aaa69ef and 9f62678.

📒 Files selected for processing (9)
  • .sampo/changesets/delivery-monotonic-deadline.md
  • .sampo/changesets/delivery-progress-details.md
  • .sampo/changesets/inspect-pack-preparation.md
  • docs/ASSET_PACK_DELIVERY.md
  • examples/asset-packs/src/main.ts
  • examples/asset-packs/test/browser.mjs
  • packages/phaser-assets/README.md
  • packages/phaser-assets/src/delivery.ts
  • packages/phaser-assets/test/delivery.test.ts

Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review.


📝 Walkthrough

Walkthrough

The delivery API adds read-only preparation inspection, sequenced progress events, structured error details, and monotonic deadline enforcement. The sample displays these results, and tests cover planning, observation, deadlines, errors, and lifecycle behavior.

Changes

Phaser pack delivery

Layer / File(s) Summary
Preparation planning contracts
packages/phaser-assets/src/delivery.ts, packages/phaser-assets/test/delivery.test.ts, packages/phaser-assets/README.md, .sampo/changesets/inspect-pack-preparation.md
Adds inspectPreparation(packId) and a pure planner for dependency closure, cold artifacts, staging reservations, projected usage, and fitsBudget.
Observation and structured errors
packages/phaser-assets/src/delivery.ts, packages/phaser-assets/test/delivery.test.ts, packages/phaser-assets/README.md, .sampo/changesets/delivery-progress-details.md
Adds subscribe(listener), sequenced prepare and file-read events, measured progress, containment-safe listeners, and structured PhaserPackDeliveryError.details.
Monotonic preparation deadlines
packages/phaser-assets/src/delivery.ts, packages/phaser-assets/test/delivery.test.ts, packages/phaser-assets/README.md, .sampo/changesets/delivery-monotonic-deadline.md
Uses performance.now() for the full prepare budget, floors decoder time, blocks late requests and decodes, and rejects staging after the deadline.
Sample integration and documentation
examples/asset-packs/src/main.ts, examples/asset-packs/test/browser.mjs, docs/ASSET_PACK_DELIVERY.md
Displays observed delivery state and preparation plans, formats structured errors, manages observer teardown, and updates browser assertions and integration documentation.

Priority: ➖ Normal

Estimated code review effort: 4 (Complex) | ~60 minutes

Change: Feature

Sequence Diagram(s)

sequenceDiagram
  participant Sample as Asset pack sample
  participant Delivery as PhaserPackDelivery
  participant Network as Delivery network
  participant Decoder as Archive decoder
  Sample->>Delivery: inspectPreparation(packId)
  Delivery-->>Sample: preparation plan
  Sample->>Delivery: prepare(packId)
  Delivery->>Network: request archive
  Network-->>Delivery: measured body bytes
  Delivery->>Decoder: decode with remaining deadline
  Decoder-->>Delivery: verified entries
  Delivery-->>Sample: progress and terminal events
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 16.67% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 6 functions across 4 files. (5 skipped: 5… Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the primary change: adding pack preparation cost inspection before loading.
Description check ✅ Passed The description is detailed and covers the feature, design, scope, tests, and verification results. It includes Summary and validation information, although it uses a Verification heading instead of t…
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Full details: Docstring Coverage

Explanation

Docstring coverage is 16.67% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 6 functions across 4 files. (5 skipped: 5 unsupported.)

  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

A rabbit watched the bytes hop by
Plans bloomed before downloads flew
Deadlines kept their steady time
Errors wore details, neat and true
The sample danced from staged to new

Comment @coderabbitai help to get the list of available commands.

@imjlk
imjlk merged commit 9a567a5 into main Sep 20, 2026
6 checks passed
@imjlk
imjlk deleted the codex/inspect-pack-preparation branch October 27, 2026 11:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant