Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/release-sdk.yml
Original file line number Diff line number Diff line change
Expand Up @@ -97,5 +97,5 @@ jobs:
See README.md for the author quickstart and the
wasm ABI this SDK targets.
NOTES
gh release create "$TAG" --target "$GITHUB_SHA" \
gh release create "$TAG" --target "$GITHUB_SHA" --latest=false \
--title "$TAG" --notes-file .artifacts/sdk-release-notes.txt
1 change: 1 addition & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -123,5 +123,6 @@ jobs:
with:
tools-ref: f412061a7a3f0914db9e09d1159e69504aee8706
binary: termcade
extra-files: THIRD_PARTY_NOTICES.txt
tag: ${{ needs.identity.outputs.tag }}
artifact-id: ${{ needs.accept-cli.outputs.artifact-id }}
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ games/*/build/
# …except the starter pack, embedded in the Rust binary. These committed
# content assets come from termcade-games; ABI-v1 packages remain compatible.
!internal/starter/packs/*.tcade
!internal/plugin/testdata/legacy-starters/*.tcade
game.wasm

.artifacts/
Expand All @@ -15,3 +16,5 @@ __pycache__/

/target/
/internal/plugin/testdata/*/target/

.playwright-cli/
4 changes: 2 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ resolver = "3"

[package]
name = "termcade"
version = "0.1.0"
version = "0.1.1"
edition = "2024"
rust-version = "1.94"
publish = false
Expand Down
32 changes: 32 additions & 0 deletions THIRD_PARTY_NOTICES.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
The seeded random compatibility implementation and seeding data in
internal/random derive from the Go 1.27.3 math/rand implementation.
Sources: src/math/rand/rng.go and src/math/rand/rand.go in the Go distribution.
https://go.googlesource.com/go/+/refs/tags/go1.27.3/src/math/rand/

Copyright 2009 The Go Authors.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:

* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google LLC nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
2 changes: 1 addition & 1 deletion internal/plugin/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ impl Runtime {
{
bail!("invalid host playfield requirements");
}
if shape.cols == 0 || shape.rows == 0 || shape.cols > 3 || shape.rows > 3 {
if shape.cols == 0 || shape.rows == 0 || shape.cols > 32 || shape.rows > 32 {
bail!("invalid host cell subdivision");
}
let module = self.compile(key, wasm)?;
Expand Down
50 changes: 48 additions & 2 deletions internal/plugin/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,55 @@ async fn rust_sdk_guest_roundtrip_keeps_ticks_keys_pixels_hud_gameover_and_reset
}

#[tokio::test]
async fn original_published_go_starters_run_in_the_rust_host_at_every_density() {
async fn rust_sdk_guest_supports_browser_framebuffers_above_terminal_density() {
let runtime = Runtime::new().unwrap();
let packs = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../starter/packs");
let wasm = fs::read(fixture("probe")).expect("build the locked Rust WASIp1 probe first");
for scale in [4, 8, 16, 32] {
let shape = CellShape::host(scale, scale);
let mut game = runtime
.load("browser-probe", &wasm, info(), shape.clone())
.await
.unwrap();
let mut canvas = Canvas::new(16, 8, BLACK, shape);
assert_eq!(canvas.pixel_size(), (16 * scale, 4 * scale));
game.reset().await.unwrap();
game.key(Key::A, true).await.unwrap();
game.update().await.unwrap();
game.draw(&mut canvas).await.unwrap();
assert_eq!(game.score().await.unwrap(), 10);
assert_eq!(canvas.pixels().iter().filter(|&&p| p != BLACK).count(), 2);
game.key(Key::A, false).await.unwrap();
game.draw(&mut canvas).await.unwrap();
assert_eq!(canvas.pixels().iter().filter(|&&p| p != BLACK).count(), 1);
assert!(game.error().is_none());
}
}

#[tokio::test]
async fn legacy_go_starter_fixtures_run_in_the_rust_host_at_every_density() {
let root = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("testdata/legacy-starters");
let source: serde_json::Value =
serde_json::from_slice(&fs::read(root.join("SOURCE.json")).unwrap()).unwrap();
for row in source["packages"].as_array().unwrap() {
assert_eq!(
format!(
"{:x}",
Sha256::digest(fs::read(root.join(row["file"].as_str().unwrap())).unwrap())
),
row["sha256"].as_str().unwrap()
);
}
check_game_packages("testdata/legacy-starters").await;
}

#[tokio::test]
async fn current_starters_run_in_the_rust_host_at_every_density() {
check_game_packages("../starter/packs").await;
}

async fn check_game_packages(relative: &str) {
let runtime = Runtime::new().unwrap();
let packs = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(relative);
let mut count = 0;
for entry in fs::read_dir(packs).unwrap() {
let path = entry.unwrap().path();
Expand Down
2 changes: 1 addition & 1 deletion internal/plugin/testdata/hang-rust/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

24 changes: 24 additions & 0 deletions internal/plugin/testdata/legacy-starters/SOURCE.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"origin": "Termcade vendored Go starter packages preserved from main 9de13d1af144a89ba2ec1123b73b707a7f1bff60",
"purpose": "immutable legacy ABI compatibility fixtures; never installed as current starters",
"packages": [
{
"file": "asteroid.tcade",
"id": "aviorstudio/asteroid",
"version": "0.0.2",
"sha256": "0d705e21e932e3c0e6bc8cc7534caad47fbdb8b701998928d73a4169f53385a0"
},
{
"file": "brickough.tcade",
"id": "aviorstudio/brickough",
"version": "0.0.2",
"sha256": "3896be61e1499687a9fb26241923a3a79a2bd5c56d2875c3091016ec17ee50b5"
},
{
"file": "tetris.tcade",
"id": "aviorstudio/tetris",
"version": "0.0.2",
"sha256": "cb5a5d8832bebb9501445ae03889ca5f3bcf9cb28da56344befed57b644a29be"
}
]
}
Binary file not shown.
Binary file not shown.
Binary file not shown.
2 changes: 1 addition & 1 deletion internal/plugin/testdata/probe-rust/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

46 changes: 46 additions & 0 deletions internal/starter/SOURCE.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
{
"repository": "https://github.com/aviorstudio/termcade-games",
"source_commit": "863a053cab6672c77b4863d4876a69ffbad687e7",
"implementation": "Rust WASIp1, ABI 1",
"packages": [
{
"file": "asteroid.tcade",
"id": "aviorstudio/asteroid",
"version": "0.1.0",
"tag": "asteroid-v0.1.0",
"sha256": "7fbafb0f2b2b3100c41dfb225d26ee512ada85a57c3518ea957864d7aaad69bb",
"bytes": 42780,
"width": 72,
"height": 40,
"release_ci": 41228408956,
"attestation_verified": true,
"marketplace_verified": true
},
{
"file": "brickough.tcade",
"id": "aviorstudio/brickough",
"version": "0.1.0",
"tag": "brickough-v0.1.0",
"sha256": "7289719ea5a66e607188b5623ac61bae492f8a2f90a559abcfa83083b6cf148c",
"bytes": 31472,
"width": 64,
"height": 40,
"release_ci": 41228411148,
"attestation_verified": true,
"marketplace_verified": true
},
{
"file": "tetris.tcade",
"id": "aviorstudio/tetris",
"version": "0.1.0",
"tag": "tetris-v0.1.0",
"sha256": "33eb430858972d7336013a8848ad42fc9748d67089d234fcf579f7dcf72c1384",
"bytes": 36061,
"width": 32,
"height": 40,
"release_ci": 41228413496,
"attestation_verified": true,
"marketplace_verified": true
}
]
}
Binary file modified internal/starter/packs/asteroid.tcade
Binary file not shown.
Binary file modified internal/starter/packs/brickough.tcade
Binary file not shown.
Binary file modified internal/starter/packs/tetris.tcade
Binary file not shown.
3 changes: 3 additions & 0 deletions scripts/build-native.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,6 @@
assert system in ('Linux','Darwin','Windows') and arch in ('x86_64','arm64')
env=dict(os.environ,RELEASE_PLATFORM=system,RELEASE_ARCH=arch,RELEASE_EXTENSION='zip' if system=='Windows' else 'tar.gz')
subprocess.run(['python3','scripts/package-cli.py'],env=env,check=True)

# Carry the notice for seed data embedded by the Rust starter games.
shutil.copyfile("THIRD_PARTY_NOTICES.txt", "dist/THIRD_PARTY_NOTICES.txt")
4 changes: 3 additions & 1 deletion scripts/check_archives.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,9 @@ def main():
if not os.environ.get('NATIVE_CLI_ARCHIVES'):expected={host_archive().name}
actual={p.name for p in Path('dist').iterdir()if p.name.endswith(('.tar.gz','.zip'))}
assert actual==expected,'incomplete or unexpected native archives'
sums=''.join(hashlib.sha256((Path('dist')/name).read_bytes()).hexdigest()+' '+name+'\n'for name in sorted(expected))
notice=Path('dist/THIRD_PARTY_NOTICES.txt')
assert notice.read_bytes()==Path('THIRD_PARTY_NOTICES.txt').read_bytes(),'starter notices changed'
sums=''.join(hashlib.sha256((Path('dist')/name).read_bytes()).hexdigest()+' '+name+'\n'for name in sorted(expected|{'THIRD_PARTY_NOTICES.txt'}))
receipt=Path('dist/checksums.txt')
if args.record:receipt.write_text(sums);return
assert receipt.read_text()==sums,'release archive bytes changed'
Expand Down
2 changes: 1 addition & 1 deletion sdk/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "termcade-sdk"
version = "0.1.0"
version = "0.1.1"
edition = "2024"
rust-version = "1.94"
publish = false
Expand Down
9 changes: 8 additions & 1 deletion sdk/src/canvas.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,10 @@ impl Canvas {
"canvas needs a positive width and positive even height"
);
assert!(
shape.cols > 0 && shape.rows > 0 && shape.cols * shape.rows <= 9,
shape.cols > 0
&& shape.rows > 0
&& (shape.cols.checked_mul(shape.rows).is_some_and(|n| n <= 9)
|| shape.glyphs.is_empty()),
"invalid cell subdivision"
);
let fw = w.checked_mul(shape.cols).expect("canvas width overflow");
Expand Down Expand Up @@ -179,6 +182,10 @@ impl Canvas {
}
}
pub fn cell(&self, x: usize, y: usize) -> Cell {
assert!(
!self.shape.glyphs.is_empty() && self.shape.cols * self.shape.rows <= 9,
"glyph rendering needs a terminal cell shape"
);
let mut buffer = [Color(0); 9];
let mut n = 0;
for row in 0..self.shape.rows {
Expand Down
2 changes: 1 addition & 1 deletion sdk/src/tcgame.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ mod reactor {
((u64::from(high) << 32) | u64::from(low)) as i64
}
pub fn initialize(cols: i32, rows: i32, game: Box<dyn Game>) -> i64 {
if !(1..=3).contains(&cols) || !(1..=3).contains(&rows) {
if !(1..=32).contains(&cols) || !(1..=32).contains(&rows) {
return 0;
}
let info = game.info();
Expand Down
22 changes: 22 additions & 0 deletions sdk/tests/browser_framebuffer.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
use termcade_sdk::{BLACK, Canvas, CellShape, RED};
#[test]
fn browser_framebuffers_keep_logical_coordinates_above_terminal_density() {
for scale in [4, 8, 16, 32] {
let mut canvas = Canvas::new(16, 8, BLACK, CellShape::host(scale, scale));
assert_eq!(canvas.pixel_size(), (16 * scale, 8 * scale / 2));
canvas.fill_rect_f(1., 1., 2., 3., RED);
assert_eq!(canvas.at_pixel(scale as i64, (scale / 2) as i64), RED);
assert_eq!(
canvas.at_pixel((3 * scale - 1) as i64, (4 * scale / 2 - 1) as i64),
RED
);
assert_eq!(
canvas.at_pixel((scale - 1) as i64, (scale / 2) as i64),
BLACK
);
assert_eq!(
canvas.at_pixel((3 * scale) as i64, (scale / 2) as i64),
BLACK
);
}
}
Loading