Skip to content

Document React failure and Vue/Vue Vapor PSP benchmarks - #6

Draft
doodlewind wants to merge 8 commits into
mainfrom
flax-chevre
Draft

Document React failure and Vue/Vue Vapor PSP benchmarks#6
doodlewind wants to merge 8 commits into
mainfrom
flax-chevre

Conversation

@doodlewind

@doodlewind doodlewind commented Jul 4, 2026

Copy link
Copy Markdown
Collaborator

Do not merge yet

This is still an experimental renderer migration PR and is intentionally kept as a draft with the dont merge label.

The highest-priority finding is still React: original/off-the-shelf React does not have a valid runnable PSP/PPSSPP renderer path in this investigation. The local --engine=react path is a project-local React-shaped JSX compatibility shim and must not be counted as original React performance.

Refs #5.

Correction: the earlier Vue Vapor benchmark was invalid

A PPSSPP capture showed the earlier Vue Vapor output was not a usable UI: it rendered a black screen with text collapsed at the top. That old Vapor chart/benchmark must not be used as evidence because it did not have feature parity.

This PR now includes the fix and a corrected benchmark run from the fixed state.

What changed

  • Demo labels now show the active framework name, including Vue, Vue Vapor, or Solid.
  • The Vue Vapor adapter now reads JSX attrs from ctx.attrs, which is where Vapor places undeclared props.
  • Callback/ref props are preserved instead of being accidentally invoked during prop cleanup.
  • Vapor class/style arrays and object class values are normalized into PocketJS host props.
  • createFor(...) usage now matches the Vapor runtime: render receives refs, key extraction receives raw item/key/index values.
  • Null/false/undefined slot children are filtered before calling Vapor insert(...), fixing launcher/list crashes such as cannot read property 'anchor' of undefined.
  • Vue Vapor PPSSPP goldens were added for byte-exact visual regression coverage.
  • The benchmark chart was regenerated from the corrected feature-parity run.

Validation

bunx tsc --noEmit
PSP_SDK=/Users/evan/code/dreamcart/mipsel-sony-psp \
  E2E_PPSSPP_TIMEOUT=60 \
  UPDATE=1 \
  bun test/e2e-ppsspp.ts --engine=vue-vapor

PSP_SDK=/Users/evan/code/dreamcart/mipsel-sony-psp \
  E2E_PPSSPP_TIMEOUT=60 \
  bun test/e2e-ppsspp.ts --engine=vue-vapor

Result: E2E OK; hero, cards, stats, library, settings, notifications, and music all produced complete PPSSPP capture windows and passed byte-exact comparison against the new Vue Vapor goldens.

Launcher was validated separately because it is not in the E2E spec table:

  • launcher --engine=vue-vapor --capture produced 40/40 frames;
  • trace reached JS_Eval ok and the frame loop;
  • captured image showed the launcher modal with the Vue Vapor framework label.

All 8 Vue Vapor PSP memory-stick builds completed:

PSP_SDK=/Users/evan/code/dreamcart/mipsel-sony-psp \
  bun scripts/psp-all.ts --engine=vue-vapor

The resulting psp-ui-* demos were copied to /Volumes/NO NAME/PSP/GAME, and source/destination EBOOT.PBP SHA-256 hashes matched for all 8 demos.

Corrected benchmark

PSP_SDK=/Users/evan/code/dreamcart/mipsel-sony-psp \
  BENCH_PPSSPP_TIMEOUT=60 \
  bun scripts/bench-ppsspp.ts \
    --engines=vue-vapor,vue,solid \
    --samples=7 \
    --timeout=60 \
    --bootstrap=5000

Local report paths from the corrected run:

  • Markdown: dist/bench/ppsspp-bench-2026-07-04T13-16-19-386Z.md
  • JSON summary: dist/bench/ppsspp-bench-2026-07-04T13-16-19-386Z.json
  • Raw samples: dist/bench/ppsspp-bench-2026-07-04T13-16-19-386Z.raw.jsonl

Metadata:

  • Generated: 2026-07-04T13:17:55.024Z
  • Samples per app/engine: 7
  • PPSSPP revision: 676724ee5e02
  • Git revision at measurement: e824ea858673 (dirty)

PPSSPP renderer matrix chart

Matrix chart files: PNG · SVG

Benchmark headline

Lower is better for every metric.

metric Vue Vapor/Solid geometric mean non-Vapor Vue/Solid geometric mean Vue Vapor/non-Vapor Vue geometric mean
eval_us 3.488x 2.467x 1.414x
boot_to_frame0_us 3.140x 2.314x 1.357x
avg_work_us 1.467x 3.732x 0.393x
host_wall_ms 1.480x 2.159x 0.686x
bundle_bytes 3.651x 2.442x 1.495x

After the feature-parity fix, Vue Vapor is no longer below Solid on average frame work. It is about 1.47x heavier than Solid across demos, but still much better than non-Vapor Vue: about 0.39x the frame work of non-Vapor Vue.

Frame-work result by demo

app Vue Vapor avg work non-Vapor Vue avg work Solid avg work
hero 16.74 ms 90.75 ms 15.15 ms
cards 19.44 ms 20.31 ms 18.60 ms
stats 43.87 ms 143.56 ms 20.16 ms
library 14.41 ms 32.80 ms 7.47 ms
settings 24.70 ms 27.82 ms 22.77 ms
notifications 18.39 ms 93.13 ms 15.86 ms
music 41.39 ms 118.36 ms 17.23 ms

Current conclusion

Original React remains not viable and must not receive credit through the local React-compatible shim.

Non-Vapor Vue runs, but misses the PSP smoothness bar in most demos and remains a negative result.

Vue Vapor now has a valid JSX + Tailwind feature-parity path, but the corrected numbers are mixed: it substantially improves over non-Vapor Vue frame work, yet it has worse startup/bundle size than Solid and higher cross-demo average frame work than Solid.

@doodlewind doodlewind added the dont merge Do not merge until explicitly cleared label Jul 4, 2026 — with ChatGPT Codex Connector
@doodlewind doodlewind changed the title Experiment with Vue renderer and PPSSPP benchmarks Document React failure and Vue renderer benchmarks Jul 4, 2026
@doodlewind doodlewind changed the title Document React failure and Vue renderer benchmarks Document React failure, non-Vapor Vue benchmarks, and Vapor caveat Jul 4, 2026
@doodlewind doodlewind changed the title Document React failure, non-Vapor Vue benchmarks, and Vapor caveat Document React failure and Vue/Vue Vapor PSP benchmarks Jul 4, 2026
doodlewind added a commit that referenced this pull request Jul 30, 2026
…ly -O0 QuickJS builds

Real-hardware testing showed hero-octane under 1 fps. Two root causes:

1. Every PSP QuickJS build was -O0: CRATE_CC_NO_DEFAULTS=1 drops the cc
   crate's synthesized flags including build.rs opt_level(), and the pin
   predated quickjs-rs PR #6 anyway. -O2 now lives in TARGET_CFLAGS and
   libquickjs-sys is repinned to master (ba5bdd0d) registry-wide
   (psp + vita Cargo, psp/symbian toolchain manifests, pin tests).

2. An Octane state commit replays the whole root regardless of where the
   state lives (~2-4ms desktop, engine-bound on PSP), so per-frame state is
   unaffordable there. Continuous motion now rides the native channels:
   sprite atlases (hero/gallery/library spinners via sprites.json + a new
   spinner-atlas.svg), baked keyframe timelines (music equalizer,
   apps/music/pocket.config.ts), animate()/jump() one-shots (stats systems
   reveal), ref-based phase timers (notifications, library loading), and a
   new setTextContent() escape hatch - the text-shaped sibling of animate() -
   for count-ups and percentages (stats tiles, music progress).

useSpriteAnimation also stops committing state on ticks that do not change
the visible sprite frame.

PPSSPP avg frame work, before -> after (budget 16.7ms):
hero 387.8 -> 6.5ms, stats 279.9 -> 9.2ms, music 282.7 -> 12.9ms,
notifications 271.2 -> 13.8ms, library 50.8 -> 4.9ms, settings 27.4 ->
14.5ms, cards 9.7 -> 6.6ms. All three frameworks' e2e suites pass; only
the six goldens whose pixels legitimately changed were re-baselined.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
doodlewind added a commit that referenced this pull request Jul 30, 2026
* feat(framework): add Octane as the third first-class framework

Octane (React's programming model, compiled) joins Solid and Vue Vapor:
universal-driver renderer over the native tree, full 15-component surface,
use-prefixed frame hooks, app.octane.tsx variants for all 8 demos, playground
in-browser compilation, byte-exact PPSSPP goldens, and a three-framework
PPSSPP benchmark (docs/bench/) per the PR #6 methodology. The PSP host gains
an arena-pressure GC; framework=octane builds stub octane/profiling to dodge
a WeakMap pinning pathology in the pinned QuickJS.

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

* perf(octane): move continuous motion onto native channels; fix silently -O0 QuickJS builds

Real-hardware testing showed hero-octane under 1 fps. Two root causes:

1. Every PSP QuickJS build was -O0: CRATE_CC_NO_DEFAULTS=1 drops the cc
   crate's synthesized flags including build.rs opt_level(), and the pin
   predated quickjs-rs PR #6 anyway. -O2 now lives in TARGET_CFLAGS and
   libquickjs-sys is repinned to master (ba5bdd0d) registry-wide
   (psp + vita Cargo, psp/symbian toolchain manifests, pin tests).

2. An Octane state commit replays the whole root regardless of where the
   state lives (~2-4ms desktop, engine-bound on PSP), so per-frame state is
   unaffordable there. Continuous motion now rides the native channels:
   sprite atlases (hero/gallery/library spinners via sprites.json + a new
   spinner-atlas.svg), baked keyframe timelines (music equalizer,
   apps/music/pocket.config.ts), animate()/jump() one-shots (stats systems
   reveal), ref-based phase timers (notifications, library loading), and a
   new setTextContent() escape hatch - the text-shaped sibling of animate() -
   for count-ups and percentages (stats tiles, music progress).

useSpriteAnimation also stops committing state on ticks that do not change
the visible sprite frame.

PPSSPP avg frame work, before -> after (budget 16.7ms):
hero 387.8 -> 6.5ms, stats 279.9 -> 9.2ms, music 282.7 -> 12.9ms,
notifications 271.2 -> 13.8ms, library 50.8 -> 4.9ms, settings 27.4 ->
14.5ms, cards 9.7 -> 6.6ms. All three frameworks' e2e suites pass; only
the six goldens whose pixels legitimately changed were re-baselined.

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

* feat(psp): XMB cover art for all eight showcase demos; refresh bench + blog to post-fix numbers

- tools/gen-demo-covers.ts bakes the pocket app-family cover convention for
  every showcase demo: ICON0 as a geometry-drawn family tile, PIC1 as a real
  sim-pumped frame of the app with the XMB legibility dim. apps/<demo>/psp/
  fragments apply to every framework build of that demo.
- docs/bench: archive the 2026-07-30 post-optimization dataset (geomean
  avg_work octane 1.66x vs solid, was 15.58x; all 21 cells inside the 16.7ms
  budget) alongside the pre-fix 07-29 dataset.
- blog: add "The hardware said no" chapter (the -O0-since-2021 discovery,
  replay-scope-independence, the native-channel rule, per-demo before/after),
  refresh the benchmark section to the new dataset with a linear-scale chart,
  and re-count byte-identical goldens (14/23).
- playground compiler-entry merges the music demo's equalizer keyframes into
  the playground-wide animation theme.

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

* docs(blog): rewrite the Octane post's index blurb for outside readers

No repo-internal references, leads with the machine and the three findings;
post dated to its publish day.

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

* fix(compiler): hard-error on <g transform> in bake-svg; flatten the spinner atlas

The svg baker scans shapes flat, so the spinner atlas's per-cell
<g transform="translate(...)"> was silently dropped - all eight frames baked
into cell 0 and cells 1-7 shipped blank. On hardware the spinner sat
invisible and "flashed" once per cycle when the frame index wrapped through
the one painted cell; the golden re-baseline had baked the bug in.

The atlas now carries absolute per-cell circle coordinates (no groups), and
bake-svg refuses <g transform> loudly instead of mis-baking. All four
affected goldens re-baked and visually verified: the spinner is present at a
distinct phase in every capture.

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

* docs(blog): record the hardware confirmation and the blank-atlas incident

The post-fix builds are verified on the same physical PSP that failed the
first run; the sprite-cycling bullet now carries the honest beat about the
<g transform> atlas bake and the golden re-baseline that hid it.

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

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dont merge Do not merge until explicitly cleared

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant