Document React failure and Vue/Vue Vapor PSP benchmarks - #6
Draft
doodlewind wants to merge 8 commits into
Draft
Conversation
8 tasks
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Do not merge yet
This is still an experimental renderer migration PR and is intentionally kept as a draft with the
dont mergelabel.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=reactpath 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
Vue,Vue Vapor, orSolid.ctx.attrs, which is where Vapor places undeclared props.createFor(...)usage now matches the Vapor runtime: render receives refs, key extraction receives raw item/key/index values.insert(...), fixing launcher/list crashes such ascannot read property 'anchor' of undefined.Validation
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 --captureproduced40/40frames;JS_Eval okand the frame loop;Vue Vaporframework label.All 8 Vue Vapor PSP memory-stick builds completed:
The resulting
psp-ui-*demos were copied to/Volumes/NO NAME/PSP/GAME, and source/destinationEBOOT.PBPSHA-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=5000Local report paths from the corrected run:
dist/bench/ppsspp-bench-2026-07-04T13-16-19-386Z.mddist/bench/ppsspp-bench-2026-07-04T13-16-19-386Z.jsondist/bench/ppsspp-bench-2026-07-04T13-16-19-386Z.raw.jsonlMetadata:
2026-07-04T13:17:55.024Z7676724ee5e02e824ea858673 (dirty)Matrix chart files: PNG · SVG
Benchmark headline
Lower is better for every metric.
eval_usboot_to_frame0_usavg_work_ushost_wall_msbundle_bytesAfter the feature-parity fix, Vue Vapor is no longer below Solid on average frame work. It is about
1.47xheavier than Solid across demos, but still much better than non-Vapor Vue: about0.39xthe frame work of non-Vapor Vue.Frame-work result by demo
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.