Skip to content
Closed
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
8 changes: 8 additions & 0 deletions .github/actions/artifact-check/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
name: Artifact Check
description: Verify the shipping Linux archive's Godot export, replay and terminal player.
runs:
using: composite
steps:
- name: Real Godot terminal artifact check
shell: bash
run: make artifact-check
3 changes: 3 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,6 @@ jobs:

- name: Test
uses: ./.github/actions/test

- name: Artifact Check
uses: ./.github/actions/artifact-check
3 changes: 3 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,9 @@ jobs:
- name: Test
uses: ./.github/actions/test

- name: Artifact Check
uses: ./.github/actions/artifact-check

- name: Verify released binary version
env:
ARCHIVE: dist/termcade_Linux_x86_64.tar.gz
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,6 @@ game.wasm

.artifacts/
__pycache__/
.godot/
*.tgd
examples/godot/*/build/
10 changes: 7 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,16 @@ SHELL := /bin/bash
.DEFAULT_GOAL := help
.NOTPARALLEL:
VERSION ?= dev
.PHONY: help install lint test build check dev stop clean
export PATH := $(CURDIR)/.artifacts/godot/bin:$(PATH)
.PHONY: help install lint test build artifact-check check dev stop clean
help:
@echo 'make check: complete five-platform archive build, lint and race/consumer checks'
@echo 'make check: five-platform archives, lint, race/consumer and real Godot terminal checks'
install:
mise trust .mise.toml
mise install go python actionlint shellcheck
mise exec -- go mod download
cd sdk && mise exec -- go mod download
mise exec -- python3 scripts/install-godot.py
lint:
mise exec -- bash scripts/lint.sh
mise exec -- actionlint
Expand All @@ -19,7 +21,9 @@ test:
mise exec -- bash scripts/test.sh
build:
mise exec -- env VERSION="$(VERSION)" bash scripts/build.sh
check: build lint test
artifact-check:
mise exec -- python3 scripts/test-godot-cli.py
check: build lint test artifact-check
dev stop:
@echo '$@: unsupported: interactive arcade sessions require caller-owned terminal and account configuration'
clean:
Expand Down
47 changes: 47 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
<!-- Generated from private documentation source. Do not edit directly. Source SHA256: f8d65bed5a6cc825f968db47c0d98b582ba176feb364fcbad709fb67cb503c12 -->

# termcade

A terminal arcade. Classic games rendered at sub-cell resolution with block
Expand Down Expand Up @@ -293,3 +295,48 @@ go test ./... # unit + shell tests
go test ./internal/plugin/ # includes wasm end-to-end tests (builds guests)
go vet ./...
```

## Godot terminal target (experimental)

Godot 4.7.2 projects can export to a custom **Termcade** target and play inside
terminal character cells. The first target supports solid `Polygon2D`, `Line2D`,
`ColorRect`, imported `Sprite2D` textures, transforms, visibility and z ordering.
Godot runs the scene's GDScript and physics; a software renderer produces the
framebuffer for Termcade's existing quadrant, sextant, half-block or ASCII renderer.
There is no browser, graphical window or GPU readback in this target.

Install Godot **4.7.2.stable.official.ed1daf0bf** and make `godot` available on
PATH, or set `GODOT_BIN` to that executable. From a source checkout, `make install`
installs the checksum-pinned Linux engine needed by the full gate. The release
archives do not bundle Godot.

```sh
termcade godot export examples/godot/paddle build/paddle.tgd
termcade godot play --trusted build/paddle.tgd
termcade godot capture --trusted --json --frames 120 build/paddle.tgd frame.png
```

For your own project, `termcade godot init /path/to/project` installs the addon
and adds a Termcade preset to Godot's Export dialog. The CLI exporter uses that
same platform in a disposable project copy, leaving your source settings and
import cache untouched. Output files must not already exist. Export and capture
support `--json` for success and failure results. Flags precede positional paths.

In the player, **Ctrl+P** pauses and **Esc** or **Ctrl+C** exits. Arrow keys,
letters, space, tab, backspace and enter are translated to Godot key events.
Terminals with release reporting supply exact held keys; other terminals use
an auto-repeat timeout. The preview needs at least 72 columns by 22 rows.

Capture accepts `--input replay.json`, an array of `{ "frame": 1, "code": 4194321,
"down": true }` events using Godot key codes; `4194321` is the right arrow.
Each capture starts a fresh scene. Output is a PNG of the framebuffer before
terminal character conversion. Determinism depends on the game's code and inputs.

This is a **local developer target**. `.tgd` packages execute native Godot code
with host access, which is why play and capture require `--trusted`; they are
not Wasm-sandboxed `.tcade` packages and cannot be published to the existing
marketplace. User data is temporary for each preview. General Godot compatibility,
native terminal text nodes, audio, mouse/controller input and terminal image
protocols are not implemented. Unsupported visual nodes, cameras, materials,
autoloads and native extensions are rejected. A hung frame terminates its engine
process rather than blocking the terminal indefinitely.
16 changes: 16 additions & 0 deletions cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@ package main

import (
"context"
"encoding/json"
"errors"
"fmt"
"os"
"os/exec"
"path/filepath"
"regexp"
"slices"
"strings"

"github.com/aviorstudio/termcade/internal/plugin"
Expand Down Expand Up @@ -46,6 +48,14 @@ usage:
termcade dev build [dir] build a game directory into a .tcade package
termcade dev install <file> install a local .tcade while developing

termcade godot init <project> install the Godot Termcade export target
termcade godot export <project> <game.tgd>
validate and export a Godot 2D game
termcade godot play --trusted <game.tgd>
play a local Godot game inside the terminal
termcade godot capture --trusted <game.tgd> <frame.png>
capture a terminal-resolution frame for agents

This arcade is also the whole dev kit: dev new, hack, dev build, dev install,
play. See docs/sdk.md to get started.
`
Expand Down Expand Up @@ -93,6 +103,8 @@ func runCommand(args []string) bool {
default:
err = fmt.Errorf("unknown dev subcommand; try: termcade dev new <author/slug> · termcade dev build [dir] · termcade dev install <file>")
}
case "godot":
err = cmdGodot(args[1:])
case "version", "-v", "--version":
fmt.Println("termcade", resolveVersion())
case "help", "-h", "--help":
Expand All @@ -102,6 +114,10 @@ func runCommand(args []string) bool {
os.Exit(2)
}
if err != nil {
if args[0] == "godot" && slices.Contains(args, "--json") {
_ = json.NewEncoder(os.Stdout).Encode(map[string]any{"ok": false, "error": err.Error(), "runtime": "godot-terminal-2d-v1"})
os.Exit(1)
}
fmt.Fprintln(os.Stderr, "termcade:", err)
os.Exit(1)
}
Expand Down
4 changes: 4 additions & 0 deletions engineering-profile.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@
"make",
"check"
],
"artifact-check": [
"make",
"artifact-check"
],
"clean": [
"make",
"clean"
Expand Down
Binary file added examples/godot/paddle/badge.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
16 changes: 16 additions & 0 deletions examples/godot/paddle/game.gd
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
extends Node2D

var velocity := Vector2(24, 28)

func _physics_process(delta: float) -> void:
$Paddle.position.x = clampf($Paddle.position.x + Input.get_axis("ui_left", "ui_right") * 80.0 * delta, 14, 130)
$Ball.position += velocity * delta
if $Ball.position.x < 3 or $Ball.position.x > 141:
velocity.x = -velocity.x
if $Ball.position.y < 3:
velocity.y = absf(velocity.y)
if velocity.y > 0 and $Ball.position.y >= 66 and $Ball.position.y <= 72 and absf($Ball.position.x - $Paddle.position.x) < 14:
velocity.y = -absf(velocity.y)
if $Ball.position.y > 82:
$Ball.position = Vector2(72, 40)
velocity = Vector2(24, 28)
26 changes: 26 additions & 0 deletions examples/godot/paddle/main.tscn
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
[gd_scene load_steps=3 format=3]

[ext_resource type="Script" path="res://game.gd" id="1"]
[ext_resource type="Texture2D" path="res://badge.png" id="2"]

[node name="PaddleGame" type="Node2D"]
script = ExtResource("1")

[node name="Badge" type="Sprite2D" parent="."]
position = Vector2(132, 10)
texture = ExtResource("2")

[node name="Paddle" type="Polygon2D" parent="."]
position = Vector2(72, 70)
color = Color(0.25, 0.77, 0.79, 1)
polygon = PackedVector2Array(-12, -2, 12, -2, 12, 2, -12, 2)

[node name="Ball" type="Polygon2D" parent="."]
position = Vector2(72, 40)
color = Color(0.9, 0.79, 0.27, 1)
polygon = PackedVector2Array(-2, -2, 2, -2, 2, 2, -2, 2)

[node name="Boundary" type="Line2D" parent="."]
points = PackedVector2Array(1, 79, 1, 1, 143, 1, 143, 79)
width = 1.0
default_color = Color(0.25, 0.25, 0.25, 1)
13 changes: 13 additions & 0 deletions examples/godot/paddle/project.godot
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
config_version=5

[application]
config/name="Godot Terminal Paddle"
run/main_scene="res://main.tscn"

[display]
window/size/viewport_width=144
window/size/viewport_height=80

[rendering]
renderer/rendering_method="gl_compatibility"
environment/defaults/default_clear_color=Color(0, 0, 0, 1)
Loading
Loading