Case Studies
Two points on one design continuum: a minimal, fail-closed, Model-B system and an elaborate, fail-open, Model-A-client/Model-B-server system.
Gizmo AI Unlimited
Shape: One monorepo: patcher/ (Node CLI, its own lockfile) and src/
(Plasmo MV3 extension) side by side. A single GitHub Action drives the patcher
on a 2-hour schedule.
Evolution: 0.1.0 Plasmo scaffold → 0.2.0 SPA-nav/UI → 1.0.0 drop
over-broad CSS selectors → 2.0.0 adopt declarativeNetRequest → 2.0.1
Model A → 2.1.0 broaden reload to all gizmo.ai subdomains → 2.2.x
Model B.
Payload: Just two subscription regexes: one that gates the paywall UI
(isSubscribedStore) and one that gates the game mechanics
(state.snapshot.subscription.status). The functional surface is tiny; almost
all the engineering is in delivery and resilience, not in the patch itself.
// Force isSubscribedStore reads to return true (paywall UI)
{ id: "is-subscribed",
find: "\\br\\(_?d\\[\\d+\\]\\)\\.isSubscribedStore\\.get\\(\\)",
flags: "g", replace: "(!0)", minMatches: 1 }
// Force the game-state subscription status check to 'subscribed'
// (unlimited hearts/hints, no cooldown, no life/hint consumption)
{ id: "subscription-status",
find: "'subscribed'===\\(0,[\\w$]+\\.default\\)\\(this,[\\w$]+\\)\\[[\\w$]+\\]\\.state\\.snapshot\\?\\.subscription\\?\\.status",
flags: "g", replace: "(!0)", minMatches: 1 }(!0) is chosen as the universal replacement because it is a self-contained
primary expression evaluating to true that stays grammatically valid in every
surrounding context the minifier produces: in a ternary, a logical-OR, a
negation, or a bare if. A naive ||!0 appended to a ??-laden expression
would form the illegal ||…?? mix and brick the bundle.
Distribution today: Model B. The primary artifact is patches.json at 756 B.
The 19 MB entry.min.js is retained only as a verification copy.
Fail policy: Fail-closed: if any rule matches fewer than minMatches times,
the patcher throws, refuses to write, and the CI job opens a deduplicated GitHub
issue while leaving the last-known-good artifact in place.
Source → github.com/alexey-max-fedorov/gizmo-ai-unlimited
Play Origin / P-NP
Shape: Two repos. P-NP is the server-side patcher. ProdigyOrigin is
the client extension and a second build artifact: originGUI/, the in-game
mod menu, bundled with esbuild and committed so the patcher suffix can fetch it
at runtime. typings/ holds reverse-engineered *.d.ts files for the game.
This split mirrors the Gizmo monorepo's internal patcher/ vs src/ boundary
but across repository lines, letting the patcher and the extension version,
release, and even rebrand independently.
A richer patch payload
Where Gizmo flips two booleans, P-NP installs six structural hooks, with $n
back-references resolved against regex capture groups in the replacement string:
{ id: "singleton-exposure",
find: "(_instance=this\\),this\\._game=([A-Za-z_$][\\w$]*)\\}destroy\\(\\)\\{)",
flags: "", replace: "_instance=this),window.__PNP__=this,this._game=$2}destroy(){", minMatches: 1 }
{ id: "answer-question-bypass", /* auto-answer before the isOpen check */ }
{ id: "external-factory-bypass", /* … */ }
{ id: "open-question-bypass", /* … */ }
{ id: "safe-bind",
/* idempotent Inversify (re)bind wrapper - matched 9× live */ }
{ id: "expose-constants",
/* alias the GameConstants map to window */ }The runtime wrapper: a second patch layer
Beyond find/replace, P-NP wraps the patched core in a generated prefix and
suffix (src/wrappers.ts) that build an entire modding runtime at load time,
designed to stay resilient to the game's minifier churn.
DI service discovery by behavioral shape: Instead of hard-coding service IDs
that drift every game build, __pnp_discoverService enumerates the Inversify
container's binding dictionary and matches services by duck-typing:
var nm = __pnp_discoverService(gc, (inst) =>
typeof inst.getCharData === 'function'
&& ('processPlayer' in inst)
&& typeof inst.sendZoneEvent === 'function', "NetworkManager");The same technique discovers PlayerService and MembershipService. This is
the resilience philosophy of the patcher (don't bind to volatile identifiers)
carried into the runtime.
A self-healing window._ facade: Lodash loads as a separate script after
game.min.js and overwrites window._; the game keeps reassigning it too. The
suffix installs accessor-based properties (_.instance, _.player,
_.network, _.membership, _.constants, …) and a 500 ms setInterval
that re-applies them whenever they go missing, plus a singleton auto-fix that
swaps __PNP__ to the real fully-initialized instance once #prodigy is set.
Deep membership bypass: _.functions.setMembership(true) rewrites the
discovered membership service's _data and forces hasFeatureAccess() to
return true, auto-promoting to Ultra tier (derived from three feature checks).
Runtime mod-menu loader: 15 seconds after load, the suffix evals the menu
bundle fetched from window.__ORIGIN_MENU_URL__, supplied by the extension via
manifest.defaultMenuUrl, with a baked-in fallback URL pointing to the
committed originGUI/dist/bundle.js.
Branding split without code churn
Play Origin rebranded from "Prodigy Origin" in May 2026 but deliberately froze
all code-level identifiers (repo names, window.__PNP__* globals, the
%cP-NP Patcher DevTools banner, data-origin-* attributes,
originGameUrl/originGuiUrl storage keys) because renaming any of them
would break raw.githubusercontent.com URLs, clone paths, or require a
storage-migration shim. Only the most user-visible string changed: the [P-NP]
→ [Play Origin] console prefix. User-facing brand and technical identity can
and should be decoupled in any system whose URLs are load-bearing.
Cross-system comparison
| Axis | Gizmo AI Unlimited | Play Origin (ProdigyOrigin + P-NP) |
|---|---|---|
| Repo topology | Monorepo (patcher + extension) | Split: extension (ProdigyOrigin) ↔ patcher (P-NP) |
| Distribution model (today) | Model B: rules + local apply | Model A: file-pull, with Model B staged server-side |
| Patch payload | 2 regex rules (boolean flips) | 6 regex rules + a generated runtime prefix/suffix |
| Rule engine | Plain find/replace | Find/replace with $1…$n back-references |
| Fail policy | Fail-closed: throw, keep last good | Fail-open: patchDegraded flag, still publish |
| Primary artifact | patches.json (756 B) | manifest.json (~20 KB: rules + prefix + suffix + menu URL) |
| Client cache | Yes (~18 MB, unlimitedStorage) | None: file-pull on each load |
| Bridge mechanism | Request/response CustomEvents | data-origin-* attributes + data-origin-ready handshake |
| Extra DNR use | BLOCK only | BLOCK + strip CSP/XFO + 2 cosmetic redirects |
| Runtime extras | Console markers | DI service discovery, membership bypass, mod-menu eval-loader |
| Common primitive | onreset + var URL=window.URL; | onreset + var URL=window.URL; (+ semaphore line) |
| Self-heal cadence | Every 2 h, issue on failure | Every 2 h, issue on failure |
The systems are two points on one design continuum. Gizmo demonstrates the
minimal, fail-closed, Model-B end; Play Origin demonstrates the elaborate,
fail-open, Model-A-client/Model-B-server end. Both share the same spine: a
scheduled re-deriving patcher, network-level blocking, MAIN/ISOLATED separation,
and the onreset injection primitive.