Repository files navigation

termcade-games

Every game aviorstudio publishes to the termcade marketplace.

The arcade compiles no games in: these are .tcade packages in the wasm sandbox, same as anybody's. All three are also vendored into the arcade as a starter pack and unpacked on first run, so a fresh termcade is playable before it has an account or a network. They install the ordinary way too:

termcade add aviorstudio/asteroid
termcade add aviorstudio/tetris
termcade add aviorstudio/brickough

The source stays here rather than in the arcade so the two move separately — a game changes without a release of termcade, and the SDK contract is the only thing between them.

The bundling is a real cost, worth naming: a game that arrives prebundled does not exercise resolve/fetch/verify, so the registry path can rot without a first-party game noticing. It has to be covered on its own rather than by these three happening to use it.

To refresh the vendored packs after changing a game, from a termcade checkout beside this one: go generate ./internal/starter.

Releasing

release.yml builds a game, creates and verifies GitHub-hosted provenance for that exact .tcade, tags it, cuts a GitHub release, and tells the marketplace about it — in that order, because publishing is a claim the registry verifies by fetching, so the asset has to exist first.

That last step needs a TERMCADE_TOKEN secret: a publish key scoped to the aviorstudio handle, made with termcade keys new. It publishes and nothing else — it cannot read a library, mint another key, or touch an account — so a leak is bounded by this one handle.

Without the secret the marketplace step fails closed and reports the command needed to finish publication after the scoped key is restored. The release asset is never rebuilt during that recovery.

If registry data is rebuilt without those published rows, run Recover first-party catalog. It republishes only the existing Asteroid, Tetris and Brickough v0.0.2 release assets through the same authenticated publish path. It does not rebuild packages, create releases, apply development seeds or write the registry database directly. The workflow fails closed when the scoped TERMCADE_TOKEN secret is unavailable. Its recovery job runs only for a main dispatch; a non-main dispatch is skipped and is not recovery evidence. Each request includes the package's previously reviewed SHA-256; the registry compares that digest before writing. Exact existing entries are skipped, so retrying after a partial recovery converges. The workflow requires the expected-digest API guard from aviorstudio/termcade-be#69 to be deployed first. Verify the deployed catalog and clients separately afterward.

The games

GameWhat it is
asteroidAsteroids-style rock shooter. Inertial ship on a wrapping field; big rocks split into faster small ones.
tetrisFalling-block stacker. Super Rotation System kicks, a 7-bag randomizer, ghost piece, lock delay, and gravity that tightens by level.
brickoughBreakout-style brick breaker. Steer the ball with paddle english, clear the wall, survive the speed-up.

Layout

One Go module, one directory per game:

asteroid/
├── termcade.toml manifest: id, version, playfield, controls
├── game.go the sdk.Game implementation
├── *_test.go
└── cmd/wasm/main.go the wasip1 entrypoint, ~8 lines

Games depend on github.com/aviorstudio/termcade/sdk and nothing else. One module for all of them is deliberate: they then share one sdk version, so an SDK contract change breaks every game here at once and in CI, rather than drifting game by game until someone tries to rebuild an old one.

Working on a game

Requires mise for the pinned Go (mise install).

go test ./... # every game
termcade dev build asteroid # → asteroid/build/asteroid.tcade
termcade dev install asteroid/build/asteroid.tcade
termcade # play it

go test only proves a game compiles for the host. A game reaches players as a wasip1 reactor module exporting the termcade ABI, and dev build is what checks that — so CI packages every game on every push, not just tests it.

Releasing

Bump version in the game's termcade.toml, merge, then run the Release a game workflow and pick the game.

The manifest decides the version. The workflow reads it, refuses to run if that version is already tagged, and creates <game>-v<version> with the .tcade and its sha256 attached. Nothing else names a version — the registry reads the same field out of the package it fetches, so a tag derived from anywhere else could disagree with what the marketplace records.

The release notes include a gh attestation verify command constrained to this repository, the release workflow, main, the source and workflow commit, the GitHub-hosted runner boundary, and SLSA provenance. This supplements the registry digest and package validation; it does not replace either control.

Tags are asteroid-v0.0.1, not asteroid/v0.0.1: a slash would make Go read the tag as a module in a subdirectory and invent a version of a package nobody imports.

Then point the marketplace at the release:

termcade publish https://github.com/aviorstudio/termcade-games asteroid-v0.0.1 asteroid.tcade

The registry fetches that asset once, validates it against the same manifest rules the arcade enforces, reads the game's id and version out of it, and records its sha256. Players download through the registry, which streams the package — clients never fetch from GitHub — and the arcade verifies the bytes against that digest, so a release asset swapped afterwards fails rather than reaching anyone.

Writing your own

Nothing here is special. termcade dev new you/mygame scaffolds a game that already runs, and the SDK docs are the whole contract — 60 ticks a second, eight keys, a canvas in square logical units, and a frozen wasm ABI if you would rather not write Go.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Repository files navigation

termcade-games

Every game aviorstudio publishes to the termcade marketplace.

The arcade compiles no games in: these are .tcade packages in the wasm sandbox, same as anybody's. All three are also vendored into the arcade as a starter pack and unpacked on first run, so a fresh termcade is playable before it has an account or a network. They install the ordinary way too:

termcade add aviorstudio/asteroid
termcade add aviorstudio/tetris
termcade add aviorstudio/brickough

The source stays here rather than in the arcade so the two move separately — a game changes without a release of termcade, and the SDK contract is the only thing between them.

The bundling is a real cost, worth naming: a game that arrives prebundled does not exercise resolve/fetch/verify, so the registry path can rot without a first-party game noticing. It has to be covered on its own rather than by these three happening to use it.

To refresh the vendored packs after changing a game, from a termcade checkout beside this one: go generate ./internal/starter.

Releasing

release.yml builds a game, creates and verifies GitHub-hosted provenance for that exact .tcade, tags it, cuts a GitHub release, and tells the marketplace about it — in that order, because publishing is a claim the registry verifies by fetching, so the asset has to exist first.

That last step needs a TERMCADE_TOKEN secret: a publish key scoped to the aviorstudio handle, made with termcade keys new. It publishes and nothing else — it cannot read a library, mint another key, or touch an account — so a leak is bounded by this one handle.

Without the secret the marketplace step fails closed and reports the command needed to finish publication after the scoped key is restored. The release asset is never rebuilt during that recovery.

If registry data is rebuilt without those published rows, run Recover first-party catalog. It republishes only the existing Asteroid, Tetris and Brickough v0.0.2 release assets through the same authenticated publish path. It does not rebuild packages, create releases, apply development seeds or write the registry database directly. The workflow fails closed when the scoped TERMCADE_TOKEN secret is unavailable. Its recovery job runs only for a main dispatch; a non-main dispatch is skipped and is not recovery evidence. Each request includes the package's previously reviewed SHA-256; the registry compares that digest before writing. Exact existing entries are skipped, so retrying after a partial recovery converges. The workflow requires the expected-digest API guard from aviorstudio/termcade-be#69 to be deployed first. Verify the deployed catalog and clients separately afterward.

The games

GameWhat it is
asteroidAsteroids-style rock shooter. Inertial ship on a wrapping field; big rocks split into faster small ones.
tetrisFalling-block stacker. Super Rotation System kicks, a 7-bag randomizer, ghost piece, lock delay, and gravity that tightens by level.
brickoughBreakout-style brick breaker. Steer the ball with paddle english, clear the wall, survive the speed-up.

Layout

One Go module, one directory per game:

asteroid/
├── termcade.toml manifest: id, version, playfield, controls
├── game.go the sdk.Game implementation
├── *_test.go
└── cmd/wasm/main.go the wasip1 entrypoint, ~8 lines

Games depend on github.com/aviorstudio/termcade/sdk and nothing else. One module for all of them is deliberate: they then share one sdk version, so an SDK contract change breaks every game here at once and in CI, rather than drifting game by game until someone tries to rebuild an old one.

Working on a game

Requires mise for the pinned Go (mise install).

go test ./... # every game
termcade dev build asteroid # → asteroid/build/asteroid.tcade
termcade dev install asteroid/build/asteroid.tcade
termcade # play it

go test only proves a game compiles for the host. A game reaches players as a wasip1 reactor module exporting the termcade ABI, and dev build is what checks that — so CI packages every game on every push, not just tests it.

Releasing

Bump version in the game's termcade.toml, merge, then run the Release a game workflow and pick the game.

The manifest decides the version. The workflow reads it, refuses to run if that version is already tagged, and creates <game>-v<version> with the .tcade and its sha256 attached. Nothing else names a version — the registry reads the same field out of the package it fetches, so a tag derived from anywhere else could disagree with what the marketplace records.

The release notes include a gh attestation verify command constrained to this repository, the release workflow, main, the source and workflow commit, the GitHub-hosted runner boundary, and SLSA provenance. This supplements the registry digest and package validation; it does not replace either control.

Tags are asteroid-v0.0.1, not asteroid/v0.0.1: a slash would make Go read the tag as a module in a subdirectory and invent a version of a package nobody imports.

Then point the marketplace at the release:

termcade publish https://github.com/aviorstudio/termcade-games asteroid-v0.0.1 asteroid.tcade

The registry fetches that asset once, validates it against the same manifest rules the arcade enforces, reads the game's id and version out of it, and records its sha256. Players download through the registry, which streams the package — clients never fetch from GitHub — and the arcade verifies the bytes against that digest, so a release asset swapped afterwards fails rather than reaching anyone.

Writing your own

Nothing here is special. termcade dev new you/mygame scaffolds a game that already runs, and the SDK docs are the whole contract — 60 ticks a second, eight keys, a canvas in square logical units, and a frozen wasm ABI if you would rather not write Go.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

termcade-games

Every game aviorstudio publishes to the termcade marketplace.

The arcade compiles no games in: these are .tcade packages in the wasm sandbox, same as anybody's. All three are also vendored into the arcade as a starter pack and unpacked on first run, so a fresh termcade is playable before it has an account or a network. They install the ordinary way too:

termcade add aviorstudio/asteroid
termcade add aviorstudio/tetris
termcade add aviorstudio/brickough

The source stays here rather than in the arcade so the two move separately — a game changes without a release of termcade, and the SDK contract is the only thing between them.

The bundling is a real cost, worth naming: a game that arrives prebundled does not exercise resolve/fetch/verify, so the registry path can rot without a first-party game noticing. It has to be covered on its own rather than by these three happening to use it.

To refresh the vendored packs after changing a game, from a termcade checkout beside this one: go generate ./internal/starter.

Releasing

release.yml builds a game, creates and verifies GitHub-hosted provenance for that exact .tcade, tags it, cuts a GitHub release, and tells the marketplace about it — in that order, because publishing is a claim the registry verifies by fetching, so the asset has to exist first.

That last step needs a TERMCADE_TOKEN secret: a publish key scoped to the aviorstudio handle, made with termcade keys new. It publishes and nothing else — it cannot read a library, mint another key, or touch an account — so a leak is bounded by this one handle.

Without the secret the marketplace step fails closed and reports the command needed to finish publication after the scoped key is restored. The release asset is never rebuilt during that recovery.

If registry data is rebuilt without those published rows, run Recover first-party catalog. It republishes only the existing Asteroid, Tetris and Brickough v0.0.2 release assets through the same authenticated publish path. It does not rebuild packages, create releases, apply development seeds or write the registry database directly. The workflow fails closed when the scoped TERMCADE_TOKEN secret is unavailable. Its recovery job runs only for a main dispatch; a non-main dispatch is skipped and is not recovery evidence. Each request includes the package's previously reviewed SHA-256; the registry compares that digest before writing. Exact existing entries are skipped, so retrying after a partial recovery converges. The workflow requires the expected-digest API guard from aviorstudio/termcade-be#69 to be deployed first. Verify the deployed catalog and clients separately afterward.

The games

GameWhat it is
asteroidAsteroids-style rock shooter. Inertial ship on a wrapping field; big rocks split into faster small ones.
tetrisFalling-block stacker. Super Rotation System kicks, a 7-bag randomizer, ghost piece, lock delay, and gravity that tightens by level.
brickoughBreakout-style brick breaker. Steer the ball with paddle english, clear the wall, survive the speed-up.

Layout

One Go module, one directory per game:

asteroid/
├── termcade.toml manifest: id, version, playfield, controls
├── game.go the sdk.Game implementation
├── *_test.go
└── cmd/wasm/main.go the wasip1 entrypoint, ~8 lines

Games depend on github.com/aviorstudio/termcade/sdk and nothing else. One module for all of them is deliberate: they then share one sdk version, so an SDK contract change breaks every game here at once and in CI, rather than drifting game by game until someone tries to rebuild an old one.

Working on a game

Requires mise for the pinned Go (mise install).

go test ./... # every game
termcade dev build asteroid # → asteroid/build/asteroid.tcade
termcade dev install asteroid/build/asteroid.tcade
termcade # play it

go test only proves a game compiles for the host. A game reaches players as a wasip1 reactor module exporting the termcade ABI, and dev build is what checks that — so CI packages every game on every push, not just tests it.

Releasing

Bump version in the game's termcade.toml, merge, then run the Release a game workflow and pick the game.

The manifest decides the version. The workflow reads it, refuses to run if that version is already tagged, and creates <game>-v<version> with the .tcade and its sha256 attached. Nothing else names a version — the registry reads the same field out of the package it fetches, so a tag derived from anywhere else could disagree with what the marketplace records.

The release notes include a gh attestation verify command constrained to this repository, the release workflow, main, the source and workflow commit, the GitHub-hosted runner boundary, and SLSA provenance. This supplements the registry digest and package validation; it does not replace either control.

Tags are asteroid-v0.0.1, not asteroid/v0.0.1: a slash would make Go read the tag as a module in a subdirectory and invent a version of a package nobody imports.

Then point the marketplace at the release:

termcade publish https://github.com/aviorstudio/termcade-games asteroid-v0.0.1 asteroid.tcade

The registry fetches that asset once, validates it against the same manifest rules the arcade enforces, reads the game's id and version out of it, and records its sha256. Players download through the registry, which streams the package — clients never fetch from GitHub — and the arcade verifies the bytes against that digest, so a release asset swapped afterwards fails rather than reaching anyone.

Writing your own

Nothing here is special. termcade dev new you/mygame scaffolds a game that already runs, and the SDK docs are the whole contract — 60 ticks a second, eight keys, a canvas in square logical units, and a frozen wasm ABI if you would rather not write Go.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

termcade-games

Every game aviorstudio publishes to the termcade marketplace.

The arcade compiles no games in: these are .tcade packages in the wasm sandbox, same as anybody's. All three are also vendored into the arcade as a starter pack and unpacked on first run, so a fresh termcade is playable before it has an account or a network. They install the ordinary way too:

termcade add aviorstudio/asteroid
termcade add aviorstudio/tetris
termcade add aviorstudio/brickough

The source stays here rather than in the arcade so the two move separately — a game changes without a release of termcade, and the SDK contract is the only thing between them.

The bundling is a real cost, worth naming: a game that arrives prebundled does not exercise resolve/fetch/verify, so the registry path can rot without a first-party game noticing. It has to be covered on its own rather than by these three happening to use it.

To refresh the vendored packs after changing a game, from a termcade checkout beside this one: go generate ./internal/starter.

Releasing

release.yml builds a game, creates and verifies GitHub-hosted provenance for that exact .tcade, tags it, cuts a GitHub release, and tells the marketplace about it — in that order, because publishing is a claim the registry verifies by fetching, so the asset has to exist first.

That last step needs a TERMCADE_TOKEN secret: a publish key scoped to the aviorstudio handle, made with termcade keys new. It publishes and nothing else — it cannot read a library, mint another key, or touch an account — so a leak is bounded by this one handle.

Without the secret the marketplace step fails closed and reports the command needed to finish publication after the scoped key is restored. The release asset is never rebuilt during that recovery.

If registry data is rebuilt without those published rows, run Recover first-party catalog. It republishes only the existing Asteroid, Tetris and Brickough v0.0.2 release assets through the same authenticated publish path. It does not rebuild packages, create releases, apply development seeds or write the registry database directly. The workflow fails closed when the scoped TERMCADE_TOKEN secret is unavailable. Its recovery job runs only for a main dispatch; a non-main dispatch is skipped and is not recovery evidence. Each request includes the package's previously reviewed SHA-256; the registry compares that digest before writing. Exact existing entries are skipped, so retrying after a partial recovery converges. The workflow requires the expected-digest API guard from aviorstudio/termcade-be#69 to be deployed first. Verify the deployed catalog and clients separately afterward.

The games

GameWhat it is
asteroidAsteroids-style rock shooter. Inertial ship on a wrapping field; big rocks split into faster small ones.
tetrisFalling-block stacker. Super Rotation System kicks, a 7-bag randomizer, ghost piece, lock delay, and gravity that tightens by level.
brickoughBreakout-style brick breaker. Steer the ball with paddle english, clear the wall, survive the speed-up.

Layout

One Go module, one directory per game:

asteroid/
├── termcade.toml manifest: id, version, playfield, controls
├── game.go the sdk.Game implementation
├── *_test.go
└── cmd/wasm/main.go the wasip1 entrypoint, ~8 lines

Games depend on github.com/aviorstudio/termcade/sdk and nothing else. One module for all of them is deliberate: they then share one sdk version, so an SDK contract change breaks every game here at once and in CI, rather than drifting game by game until someone tries to rebuild an old one.

Working on a game

Requires mise for the pinned Go (mise install).

go test ./... # every game
termcade dev build asteroid # → asteroid/build/asteroid.tcade
termcade dev install asteroid/build/asteroid.tcade
termcade # play it

go test only proves a game compiles for the host. A game reaches players as a wasip1 reactor module exporting the termcade ABI, and dev build is what checks that — so CI packages every game on every push, not just tests it.

Releasing

Bump version in the game's termcade.toml, merge, then run the Release a game workflow and pick the game.

The manifest decides the version. The workflow reads it, refuses to run if that version is already tagged, and creates <game>-v<version> with the .tcade and its sha256 attached. Nothing else names a version — the registry reads the same field out of the package it fetches, so a tag derived from anywhere else could disagree with what the marketplace records.

The release notes include a gh attestation verify command constrained to this repository, the release workflow, main, the source and workflow commit, the GitHub-hosted runner boundary, and SLSA provenance. This supplements the registry digest and package validation; it does not replace either control.

Tags are asteroid-v0.0.1, not asteroid/v0.0.1: a slash would make Go read the tag as a module in a subdirectory and invent a version of a package nobody imports.

Then point the marketplace at the release:

termcade publish https://github.com/aviorstudio/termcade-games asteroid-v0.0.1 asteroid.tcade

The registry fetches that asset once, validates it against the same manifest rules the arcade enforces, reads the game's id and version out of it, and records its sha256. Players download through the registry, which streams the package — clients never fetch from GitHub — and the arcade verifies the bytes against that digest, so a release asset swapped afterwards fails rather than reaching anyone.

Writing your own

Nothing here is special. termcade dev new you/mygame scaffolds a game that already runs, and the SDK docs are the whole contract — 60 ticks a second, eight keys, a canvas in square logical units, and a frozen wasm ABI if you would rather not write Go.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Repository files navigation

termcade-games

Every game aviorstudio publishes to the termcade marketplace.

The arcade compiles no games in: these are .tcade packages in the wasm sandbox, same as anybody's. All three are also vendored into the arcade as a starter pack and unpacked on first run, so a fresh termcade is playable before it has an account or a network. They install the ordinary way too:

termcade add aviorstudio/asteroid
termcade add aviorstudio/tetris
termcade add aviorstudio/brickough

The source stays here rather than in the arcade so the two move separately — a game changes without a release of termcade, and the SDK contract is the only thing between them.

The bundling is a real cost, worth naming: a game that arrives prebundled does not exercise resolve/fetch/verify, so the registry path can rot without a first-party game noticing. It has to be covered on its own rather than by these three happening to use it.

To refresh the vendored packs after changing a game, from a termcade checkout beside this one: go generate ./internal/starter.

Releasing

release.yml builds a game, creates and verifies GitHub-hosted provenance for that exact .tcade, tags it, cuts a GitHub release, and tells the marketplace about it — in that order, because publishing is a claim the registry verifies by fetching, so the asset has to exist first.

That last step needs a TERMCADE_TOKEN secret: a publish key scoped to the aviorstudio handle, made with termcade keys new. It publishes and nothing else — it cannot read a library, mint another key, or touch an account — so a leak is bounded by this one handle.

Without the secret the marketplace step fails closed and reports the command needed to finish publication after the scoped key is restored. The release asset is never rebuilt during that recovery.

If registry data is rebuilt without those published rows, run Recover first-party catalog. It republishes only the existing Asteroid, Tetris and Brickough v0.0.2 release assets through the same authenticated publish path. It does not rebuild packages, create releases, apply development seeds or write the registry database directly. The workflow fails closed when the scoped TERMCADE_TOKEN secret is unavailable. Its recovery job runs only for a main dispatch; a non-main dispatch is skipped and is not recovery evidence. Each request includes the package's previously reviewed SHA-256; the registry compares that digest before writing. Exact existing entries are skipped, so retrying after a partial recovery converges. The workflow requires the expected-digest API guard from aviorstudio/termcade-be#69 to be deployed first. Verify the deployed catalog and clients separately afterward.

The games

GameWhat it is
asteroidAsteroids-style rock shooter. Inertial ship on a wrapping field; big rocks split into faster small ones.
tetrisFalling-block stacker. Super Rotation System kicks, a 7-bag randomizer, ghost piece, lock delay, and gravity that tightens by level.
brickoughBreakout-style brick breaker. Steer the ball with paddle english, clear the wall, survive the speed-up.

Layout

One Go module, one directory per game:

asteroid/
├── termcade.toml manifest: id, version, playfield, controls
├── game.go the sdk.Game implementation
├── *_test.go
└── cmd/wasm/main.go the wasip1 entrypoint, ~8 lines

Games depend on github.com/aviorstudio/termcade/sdk and nothing else. One module for all of them is deliberate: they then share one sdk version, so an SDK contract change breaks every game here at once and in CI, rather than drifting game by game until someone tries to rebuild an old one.

Working on a game

Requires mise for the pinned Go (mise install).

go test ./... # every game
termcade dev build asteroid # → asteroid/build/asteroid.tcade
termcade dev install asteroid/build/asteroid.tcade
termcade # play it

go test only proves a game compiles for the host. A game reaches players as a wasip1 reactor module exporting the termcade ABI, and dev build is what checks that — so CI packages every game on every push, not just tests it.

Releasing

Bump version in the game's termcade.toml, merge, then run the Release a game workflow and pick the game.

The manifest decides the version. The workflow reads it, refuses to run if that version is already tagged, and creates <game>-v<version> with the .tcade and its sha256 attached. Nothing else names a version — the registry reads the same field out of the package it fetches, so a tag derived from anywhere else could disagree with what the marketplace records.

The release notes include a gh attestation verify command constrained to this repository, the release workflow, main, the source and workflow commit, the GitHub-hosted runner boundary, and SLSA provenance. This supplements the registry digest and package validation; it does not replace either control.

Tags are asteroid-v0.0.1, not asteroid/v0.0.1: a slash would make Go read the tag as a module in a subdirectory and invent a version of a package nobody imports.

Then point the marketplace at the release:

termcade publish https://github.com/aviorstudio/termcade-games asteroid-v0.0.1 asteroid.tcade

The registry fetches that asset once, validates it against the same manifest rules the arcade enforces, reads the game's id and version out of it, and records its sha256. Players download through the registry, which streams the package — clients never fetch from GitHub — and the arcade verifies the bytes against that digest, so a release asset swapped afterwards fails rather than reaching anyone.

Writing your own

Nothing here is special. termcade dev new you/mygame scaffolds a game that already runs, and the SDK docs are the whole contract — 60 ticks a second, eight keys, a canvas in square logical units, and a frozen wasm ABI if you would rather not write Go.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

termcade-games

Every game aviorstudio publishes to the termcade marketplace.

The arcade compiles no games in: these are .tcade packages in the wasm sandbox, same as anybody's. All three are also vendored into the arcade as a starter pack and unpacked on first run, so a fresh termcade is playable before it has an account or a network. They install the ordinary way too:

termcade add aviorstudio/asteroid
termcade add aviorstudio/tetris
termcade add aviorstudio/brickough

The source stays here rather than in the arcade so the two move separately — a game changes without a release of termcade, and the SDK contract is the only thing between them.

The bundling is a real cost, worth naming: a game that arrives prebundled does not exercise resolve/fetch/verify, so the registry path can rot without a first-party game noticing. It has to be covered on its own rather than by these three happening to use it.

To refresh the vendored packs after changing a game, from a termcade checkout beside this one: go generate ./internal/starter.

Releasing

release.yml builds a game, creates and verifies GitHub-hosted provenance for that exact .tcade, tags it, cuts a GitHub release, and tells the marketplace about it — in that order, because publishing is a claim the registry verifies by fetching, so the asset has to exist first.

That last step needs a TERMCADE_TOKEN secret: a publish key scoped to the aviorstudio handle, made with termcade keys new. It publishes and nothing else — it cannot read a library, mint another key, or touch an account — so a leak is bounded by this one handle.

Without the secret the marketplace step fails closed and reports the command needed to finish publication after the scoped key is restored. The release asset is never rebuilt during that recovery.

If registry data is rebuilt without those published rows, run Recover first-party catalog. It republishes only the existing Asteroid, Tetris and Brickough v0.0.2 release assets through the same authenticated publish path. It does not rebuild packages, create releases, apply development seeds or write the registry database directly. The workflow fails closed when the scoped TERMCADE_TOKEN secret is unavailable. Its recovery job runs only for a main dispatch; a non-main dispatch is skipped and is not recovery evidence. Each request includes the package's previously reviewed SHA-256; the registry compares that digest before writing. Exact existing entries are skipped, so retrying after a partial recovery converges. The workflow requires the expected-digest API guard from aviorstudio/termcade-be#69 to be deployed first. Verify the deployed catalog and clients separately afterward.

The games

GameWhat it is
asteroidAsteroids-style rock shooter. Inertial ship on a wrapping field; big rocks split into faster small ones.
tetrisFalling-block stacker. Super Rotation System kicks, a 7-bag randomizer, ghost piece, lock delay, and gravity that tightens by level.
brickoughBreakout-style brick breaker. Steer the ball with paddle english, clear the wall, survive the speed-up.

Layout

One Go module, one directory per game:

asteroid/
├── termcade.toml manifest: id, version, playfield, controls
├── game.go the sdk.Game implementation
├── *_test.go
└── cmd/wasm/main.go the wasip1 entrypoint, ~8 lines

Games depend on github.com/aviorstudio/termcade/sdk and nothing else. One module for all of them is deliberate: they then share one sdk version, so an SDK contract change breaks every game here at once and in CI, rather than drifting game by game until someone tries to rebuild an old one.

Working on a game

Requires mise for the pinned Go (mise install).

go test ./... # every game
termcade dev build asteroid # → asteroid/build/asteroid.tcade
termcade dev install asteroid/build/asteroid.tcade
termcade # play it

go test only proves a game compiles for the host. A game reaches players as a wasip1 reactor module exporting the termcade ABI, and dev build is what checks that — so CI packages every game on every push, not just tests it.

Releasing

Bump version in the game's termcade.toml, merge, then run the Release a game workflow and pick the game.

The manifest decides the version. The workflow reads it, refuses to run if that version is already tagged, and creates <game>-v<version> with the .tcade and its sha256 attached. Nothing else names a version — the registry reads the same field out of the package it fetches, so a tag derived from anywhere else could disagree with what the marketplace records.

The release notes include a gh attestation verify command constrained to this repository, the release workflow, main, the source and workflow commit, the GitHub-hosted runner boundary, and SLSA provenance. This supplements the registry digest and package validation; it does not replace either control.

Tags are asteroid-v0.0.1, not asteroid/v0.0.1: a slash would make Go read the tag as a module in a subdirectory and invent a version of a package nobody imports.

Then point the marketplace at the release:

termcade publish https://github.com/aviorstudio/termcade-games asteroid-v0.0.1 asteroid.tcade

The registry fetches that asset once, validates it against the same manifest rules the arcade enforces, reads the game's id and version out of it, and records its sha256. Players download through the registry, which streams the package — clients never fetch from GitHub — and the arcade verifies the bytes against that digest, so a release asset swapped afterwards fails rather than reaching anyone.

Writing your own

Nothing here is special. termcade dev new you/mygame scaffolds a game that already runs, and the SDK docs are the whole contract — 60 ticks a second, eight keys, a canvas in square logical units, and a frozen wasm ABI if you would rather not write Go.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

termcade-games

Every game aviorstudio publishes to the termcade marketplace.

The arcade compiles no games in: these are .tcade packages in the wasm sandbox, same as anybody's. All three are also vendored into the arcade as a starter pack and unpacked on first run, so a fresh termcade is playable before it has an account or a network. They install the ordinary way too:

termcade add aviorstudio/asteroid
termcade add aviorstudio/tetris
termcade add aviorstudio/brickough

The source stays here rather than in the arcade so the two move separately — a game changes without a release of termcade, and the SDK contract is the only thing between them.

The bundling is a real cost, worth naming: a game that arrives prebundled does not exercise resolve/fetch/verify, so the registry path can rot without a first-party game noticing. It has to be covered on its own rather than by these three happening to use it.

To refresh the vendored packs after changing a game, from a termcade checkout beside this one: go generate ./internal/starter.

Releasing

release.yml builds a game, creates and verifies GitHub-hosted provenance for that exact .tcade, tags it, cuts a GitHub release, and tells the marketplace about it — in that order, because publishing is a claim the registry verifies by fetching, so the asset has to exist first.

That last step needs a TERMCADE_TOKEN secret: a publish key scoped to the aviorstudio handle, made with termcade keys new. It publishes and nothing else — it cannot read a library, mint another key, or touch an account — so a leak is bounded by this one handle.

Without the secret the marketplace step fails closed and reports the command needed to finish publication after the scoped key is restored. The release asset is never rebuilt during that recovery.

If registry data is rebuilt without those published rows, run Recover first-party catalog. It republishes only the existing Asteroid, Tetris and Brickough v0.0.2 release assets through the same authenticated publish path. It does not rebuild packages, create releases, apply development seeds or write the registry database directly. The workflow fails closed when the scoped TERMCADE_TOKEN secret is unavailable. Its recovery job runs only for a main dispatch; a non-main dispatch is skipped and is not recovery evidence. Each request includes the package's previously reviewed SHA-256; the registry compares that digest before writing. Exact existing entries are skipped, so retrying after a partial recovery converges. The workflow requires the expected-digest API guard from aviorstudio/termcade-be#69 to be deployed first. Verify the deployed catalog and clients separately afterward.

The games

GameWhat it is
asteroidAsteroids-style rock shooter. Inertial ship on a wrapping field; big rocks split into faster small ones.
tetrisFalling-block stacker. Super Rotation System kicks, a 7-bag randomizer, ghost piece, lock delay, and gravity that tightens by level.
brickoughBreakout-style brick breaker. Steer the ball with paddle english, clear the wall, survive the speed-up.

Layout

One Go module, one directory per game:

asteroid/
├── termcade.toml manifest: id, version, playfield, controls
├── game.go the sdk.Game implementation
├── *_test.go
└── cmd/wasm/main.go the wasip1 entrypoint, ~8 lines

Games depend on github.com/aviorstudio/termcade/sdk and nothing else. One module for all of them is deliberate: they then share one sdk version, so an SDK contract change breaks every game here at once and in CI, rather than drifting game by game until someone tries to rebuild an old one.

Working on a game

Requires mise for the pinned Go (mise install).

go test ./... # every game
termcade dev build asteroid # → asteroid/build/asteroid.tcade
termcade dev install asteroid/build/asteroid.tcade
termcade # play it

go test only proves a game compiles for the host. A game reaches players as a wasip1 reactor module exporting the termcade ABI, and dev build is what checks that — so CI packages every game on every push, not just tests it.

Releasing

Bump version in the game's termcade.toml, merge, then run the Release a game workflow and pick the game.

The manifest decides the version. The workflow reads it, refuses to run if that version is already tagged, and creates <game>-v<version> with the .tcade and its sha256 attached. Nothing else names a version — the registry reads the same field out of the package it fetches, so a tag derived from anywhere else could disagree with what the marketplace records.

The release notes include a gh attestation verify command constrained to this repository, the release workflow, main, the source and workflow commit, the GitHub-hosted runner boundary, and SLSA provenance. This supplements the registry digest and package validation; it does not replace either control.

Tags are asteroid-v0.0.1, not asteroid/v0.0.1: a slash would make Go read the tag as a module in a subdirectory and invent a version of a package nobody imports.

Then point the marketplace at the release:

termcade publish https://github.com/aviorstudio/termcade-games asteroid-v0.0.1 asteroid.tcade

The registry fetches that asset once, validates it against the same manifest rules the arcade enforces, reads the game's id and version out of it, and records its sha256. Players download through the registry, which streams the package — clients never fetch from GitHub — and the arcade verifies the bytes against that digest, so a release asset swapped afterwards fails rather than reaching anyone.

Writing your own

Nothing here is special. termcade dev new you/mygame scaffolds a game that already runs, and the SDK docs are the whole contract — 60 ticks a second, eight keys, a canvas in square logical units, and a frozen wasm ABI if you would rather not write Go.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Repository files navigation

termcade-games

Every game aviorstudio publishes to the termcade marketplace.

The arcade compiles no games in: these are .tcade packages in the wasm sandbox, same as anybody's. All three are also vendored into the arcade as a starter pack and unpacked on first run, so a fresh termcade is playable before it has an account or a network. They install the ordinary way too:

termcade add aviorstudio/asteroid
termcade add aviorstudio/tetris
termcade add aviorstudio/brickough

The source stays here rather than in the arcade so the two move separately — a game changes without a release of termcade, and the SDK contract is the only thing between them.

The bundling is a real cost, worth naming: a game that arrives prebundled does not exercise resolve/fetch/verify, so the registry path can rot without a first-party game noticing. It has to be covered on its own rather than by these three happening to use it.

To refresh the vendored packs after changing a game, from a termcade checkout beside this one: go generate ./internal/starter.

Releasing

release.yml builds a game, creates and verifies GitHub-hosted provenance for that exact .tcade, tags it, cuts a GitHub release, and tells the marketplace about it — in that order, because publishing is a claim the registry verifies by fetching, so the asset has to exist first.

That last step needs a TERMCADE_TOKEN secret: a publish key scoped to the aviorstudio handle, made with termcade keys new. It publishes and nothing else — it cannot read a library, mint another key, or touch an account — so a leak is bounded by this one handle.

Without the secret the marketplace step fails closed and reports the command needed to finish publication after the scoped key is restored. The release asset is never rebuilt during that recovery.

If registry data is rebuilt without those published rows, run Recover first-party catalog. It republishes only the existing Asteroid, Tetris and Brickough v0.0.2 release assets through the same authenticated publish path. It does not rebuild packages, create releases, apply development seeds or write the registry database directly. The workflow fails closed when the scoped TERMCADE_TOKEN secret is unavailable. Its recovery job runs only for a main dispatch; a non-main dispatch is skipped and is not recovery evidence. Each request includes the package's previously reviewed SHA-256; the registry compares that digest before writing. Exact existing entries are skipped, so retrying after a partial recovery converges. The workflow requires the expected-digest API guard from aviorstudio/termcade-be#69 to be deployed first. Verify the deployed catalog and clients separately afterward.

The games

GameWhat it is
asteroidAsteroids-style rock shooter. Inertial ship on a wrapping field; big rocks split into faster small ones.
tetrisFalling-block stacker. Super Rotation System kicks, a 7-bag randomizer, ghost piece, lock delay, and gravity that tightens by level.
brickoughBreakout-style brick breaker. Steer the ball with paddle english, clear the wall, survive the speed-up.

Layout

One Go module, one directory per game:

asteroid/
├── termcade.toml manifest: id, version, playfield, controls
├── game.go the sdk.Game implementation
├── *_test.go
└── cmd/wasm/main.go the wasip1 entrypoint, ~8 lines

Games depend on github.com/aviorstudio/termcade/sdk and nothing else. One module for all of them is deliberate: they then share one sdk version, so an SDK contract change breaks every game here at once and in CI, rather than drifting game by game until someone tries to rebuild an old one.

Working on a game

Requires mise for the pinned Go (mise install).

go test ./... # every game
termcade dev build asteroid # → asteroid/build/asteroid.tcade
termcade dev install asteroid/build/asteroid.tcade
termcade # play it

go test only proves a game compiles for the host. A game reaches players as a wasip1 reactor module exporting the termcade ABI, and dev build is what checks that — so CI packages every game on every push, not just tests it.

Releasing

Bump version in the game's termcade.toml, merge, then run the Release a game workflow and pick the game.

The manifest decides the version. The workflow reads it, refuses to run if that version is already tagged, and creates <game>-v<version> with the .tcade and its sha256 attached. Nothing else names a version — the registry reads the same field out of the package it fetches, so a tag derived from anywhere else could disagree with what the marketplace records.

The release notes include a gh attestation verify command constrained to this repository, the release workflow, main, the source and workflow commit, the GitHub-hosted runner boundary, and SLSA provenance. This supplements the registry digest and package validation; it does not replace either control.

Tags are asteroid-v0.0.1, not asteroid/v0.0.1: a slash would make Go read the tag as a module in a subdirectory and invent a version of a package nobody imports.

Then point the marketplace at the release:

termcade publish https://github.com/aviorstudio/termcade-games asteroid-v0.0.1 asteroid.tcade

The registry fetches that asset once, validates it against the same manifest rules the arcade enforces, reads the game's id and version out of it, and records its sha256. Players download through the registry, which streams the package — clients never fetch from GitHub — and the arcade verifies the bytes against that digest, so a release asset swapped afterwards fails rather than reaching anyone.

Writing your own

Nothing here is special. termcade dev new you/mygame scaffolds a game that already runs, and the SDK docs are the whole contract — 60 ticks a second, eight keys, a canvas in square logical units, and a frozen wasm ABI if you would rather not write Go.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages