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: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ crates/openstrike-psp/target/
# Vita VPK crate and staged copyrighted map data.
crates/openstrike-vita/target/
crates/openstrike-vita/static/maps/
# Symbian app-specific static core (standalone nightly build-std crate).
crates/openstrike-symbian/target/
# script/e2e outputs
/out/
/pocketjs-dbg/
13 changes: 11 additions & 2 deletions Cargo.lock

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

17 changes: 11 additions & 6 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,12 @@ resolver = "2"
members = ["crates/openstrike", "crates/openstrike-core"]
# The vendored engine keeps its own workspaces (Cargo would otherwise try to
# adopt the path-dependency crates into this one and break their inheritance).
exclude = ["vendor", "crates/openstrike-psp", "crates/openstrike-vita"]
exclude = [
"vendor",
"crates/openstrike-psp",
"crates/openstrike-vita",
"crates/openstrike-symbian",
]

[workspace.package]
version = "0.1.0"
Expand All @@ -14,11 +19,11 @@ repository = "https://github.com/pocket-stack/open-strike"
[workspace.dependencies]
# The engine family is vendored as a git submodule (vendor/pocketjs) so the
# Rust crates and the JS framework/build pipeline stay pinned to ONE commit.
pocket3d = { path = "vendor/pocketjs/pocket3d/crates/pocket3d" }
pocket3d-bsp = { path = "vendor/pocketjs/pocket3d/crates/pocket3d-bsp" }
pocket-mod = { path = "vendor/pocketjs/pocket3d/crates/pocket-mod" }
pocket-ui-wgpu = { path = "vendor/pocketjs/pocket3d/crates/pocket-ui-wgpu" }
pocketjs-core = { path = "vendor/pocketjs/core", features = ["std"] }
pocket3d = { path = "vendor/pocketjs/engine/pocket3d/crates/pocket3d" }
pocket3d-bsp = { path = "vendor/pocketjs/engine/pocket3d/crates/pocket3d-bsp" }
pocket-mod = { path = "vendor/pocketjs/engine/crates/pocket-mod" }
pocket-ui-wgpu = { path = "vendor/pocketjs/engine/crates/pocket-ui-wgpu" }
pocketjs-core = { path = "vendor/pocketjs/engine/core", features = ["std"] }

wgpu = "25"
glam = "0.33"
Expand Down
88 changes: 82 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@
</p>

<p align="center"><em>A CS-like FPS on classic BSP maps — Pocket3D worlds, a PocketJS JSX HUD, gameplay in TypeScript.<br/>
The same game targets desktop (wgpu), PSP (sceGu), and PS Vita (vita2d/GXM); the bottom shot is the real PSP running at a locked 60 fps.</em></p>
The full 3D game targets desktop (wgpu), PSP (sceGu), PS Vita
(vita2d/GXM), and Nokia E7 (OpenGL ES 2). The bottom shot is the real PSP
running at a locked 60 fps.</em></p>

A single-player CS-like FPS built on the **Pocket runtime family**: a Rust
core (Pocket3D) simulates and renders; the *product* — round rules, weapon
Expand All @@ -37,6 +39,9 @@ crates/openstrike-psp the PSP build: an EBOOT on pocket3d-gu (sceGu) and
the PocketJS PSP host — same surfaces, same bundle
crates/openstrike-vita the PS Vita build: a native 960×544 VPK on
pocket3d-vita + PocketJS's vita2d/GXM host
crates/openstrike-symbian
the E7 app core: the shared simulation + Pocket3D
GLES2, statically linked into PocketJS's Qt host

game/ the product bundle (JS/TSX) — runs on every target
├─ sdk.ts `strike` SDK: state snapshots, events, commands
Expand Down Expand Up @@ -77,11 +82,13 @@ toolchains. The vendored PocketJS manifest pins the organization-owned
personal-fork checkout is required.

[`pocket.json`](pocket.json) is the portable Pocket application contract. It
requires the draw list, baked glyphs, buttons and the left analog API at a
480x272 logical `integer-fit` viewport; it does not claim touch, dynamic text,
or a stock Pocket3D capability. Pocket3D remains an extension implemented by
OpenStrike's custom native hosts. Every target build validates that manifest,
runs the ordinary reachable TypeScript check, writes
requires baked glyphs and buttons, and uses the left analog API when the
target provides it. PSP/Vita select its 480x272 fixed `integer-fit` viewport;
the Nokia E7 selects the same manifest's `display.viewport.live` enhancement
and 640x360-through-360x640 dynamic viewport. Pocket3D remains an extension
implemented by OpenStrike's custom native hosts rather than a private
manifest channel. Every target build validates its manifest, runs the
ordinary reachable TypeScript check, writes
`.pocket/<target>/plan.json`, and delegates compilation to `pocket compile`.
The public `@pocketjs/framework/manifest` helpers verify the build-plan
checksum, project stable `HostBuildInputs`, and generate Cargo's target, host
Expand All @@ -90,6 +97,75 @@ ABI and viewport environment. Target artifacts are isolated under
another. At runtime PocketJS compares target and host ABI; the plan checksum
is build-time consistency data, not a runtime trust mechanism.

## Nokia E7: full 3D OpenStrike

The E7 build is the real FPS, not the former top-down substitute. Its
application-specific static core source-shares `openstrike-core`, installs the
native `strike` surface before the canonical `game/openstrike.tsx` bundle is
evaluated, loads the selected cooked map on demand, and renders it through
Pocket3D's OpenGL ES 2 backend. The depth-tested world, bots, weapon viewmodel,
muzzle effects, collision, round logic, and PocketJS JSX HUD all run in the
same app.

The Qt host owns QuickJS, the GL context, and presentation. The native
extension renders the 3D scene first; PocketJS composites the retained HUD
over it. At the E7 host's 30 Hz presentation rate the simulation advances two
fixed 60 Hz ticks per frame. Rotation updates the camera aspect and live UI
viewport without rebuilding the simulation, so an active match survives
landscape/portrait changes.

E7 keyboard controls:

| Key | Action |
| --- | --- |
| W / A / S / D | move |
| Arrow keys | look during play; select maps and dialog choices in menus |
| Enter | deploy the selected map or confirm the selected dialog choice |
| Enter or E | fire (Enter confirms the selected item while a menu is open) |
| R | reload |
| Space | jump |
| Shift | walk |
| Backspace or Home | open/close the return-to-menu dialog |

The local E7 build produces one SIS containing the runtime and all eight
locally supplied cooked maps: `cs_assault`, `cs_office`, `de_aztec`,
`de_dust`, `de_dust2`, `de_inferno`, `de_nuke`, and `de_train`. Maps are
installed as separate data files and loaded one at a time, so the 32 MiB
process heap never holds the complete map set or duplicates it through
QuickJS. Touch aiming is not implemented yet.

Map and WAD data remain user-supplied. They, the generated P3D files, and the
resulting map-bearing SIS must not be committed to this repository or
published as a release artifact. `OPENSTRIKE_MAPS` may point to a source tree
with `maps/*.bsp` and `support/*.wad`; otherwise the local default is
`~/Downloads/cs-maps-20260705-1836`. The build cooks and verifies the eight
maps into ignored `dist/maps/*.p3d`. A complete set of already cooked,
canonically verified P3D files is also a valid local build input when the
copyrighted BSP/WAD source tree is unavailable.

```sh
# Hermetic manifest/PAK/tooling tests: no Docker, map data, or device needed.
bun run test:symbian

# Cook/verify all eight local maps and validate the guest plus map catalogue.
OPENSTRIKE_MAPS=~/path/to/cs-maps bun run test:symbian-bundle

# Build the pinned native core and one independently installable, eight-map SIS.
OPENSTRIKE_MAPS=~/path/to/cs-maps bun run build:symbian
```

The pinned `vendor/pocketjs` checkout is authoritative.
`POCKETJS_ROOT=/path/to/pocketjs` (or `--pocketjs-root`) is available only for
guest-only compatibility checks. A native build rejects any checkout other
than `vendor/pocketjs`, because the Rust dependency graph and Qt packager must
use the same pinned ABI. The full command uses PocketJS's exact Symbian
nightly, 30 Hz host rate, custom target JSON, and
`build-std=core,alloc,compiler_builtins`, then passes the resulting static
library through the toolchain's explicit `--core-library` boundary. It writes
`dist/symbian/openstrike.sis`, validates the cooked map through Pocket3D's
canonical Rust reader, and revalidates the packaged JS/PAK against that exact
map; installation and visual acceptance still require a real E7.

### Map data

Maps and textures are **not** in this repo (they are Valve-copyrighted game
Expand Down
2 changes: 1 addition & 1 deletion crates/openstrike-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,6 @@ std = ["pocket3d-bsp/std", "glam/std"]
libm = ["pocket3d-bsp/libm", "glam/libm"]

[dependencies]
pocket3d-bsp = { path = "../../vendor/pocketjs/pocket3d/crates/pocket3d-bsp", default-features = false }
pocket3d-bsp = { path = "../../vendor/pocketjs/engine/pocket3d/crates/pocket3d-bsp", default-features = false }
glam = { version = "0.33", default-features = false }
libm = "0.2"
3 changes: 2 additions & 1 deletion crates/openstrike-core/src/sim.rs
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,8 @@ pub struct SimInput {
pub jump: bool,
/// Trigger held.
pub fire: bool,
/// Reload pressed this tick.
/// Reload requested this tick; platforms may map either an edge or a held
/// level because the weapon gate makes repeated requests idempotent.
pub reload: bool,
}

Expand Down
20 changes: 20 additions & 0 deletions crates/openstrike-core/src/weapon.rs
Original file line number Diff line number Diff line change
Expand Up @@ -277,3 +277,23 @@ impl Rng {
lo + self.f32() * (hi - lo)
}
}

#[cfg(test)]
mod tests {
use super::Weapon;

#[test]
fn held_reload_request_refills_once_without_spending_extra_reserve() {
let mut weapon = Weapon::default();
weapon.ammo = 23;

for _ in 0..360 {
weapon.tick(1.0 / 60.0);
weapon.trigger_reload();
}

assert_eq!(weapon.ammo, 30);
assert_eq!(weapon.reserve, 83);
assert!(!weapon.reloading());
}
}
8 changes: 4 additions & 4 deletions crates/openstrike-psp/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@ edition = "2021"
[dependencies]
psp = { git = "https://github.com/pocket-stack/rust-psp.git", rev = "2cbaf8c9bc72569c76240a1d9743de10731e5f6b", features = ["external-c-heap", "abort-only", "external-global-alloc"] }
libquickjs-sys = { git = "https://github.com/pocket-stack/quickjs-rs.git", rev = "0fc946fb670c0c29bc0135f510bcb0f595415a61" }
pocketjs-psp = { path = "../../vendor/pocketjs/native" }
pocketjs-core = { path = "../../vendor/pocketjs/core" }
pocket3d-bsp = { path = "../../vendor/pocketjs/pocket3d/crates/pocket3d-bsp", default-features = false, features = ["libm"] }
pocket3d-gu = { path = "../../vendor/pocketjs/pocket3d/crates/pocket3d-gu" }
pocketjs-psp = { path = "../../vendor/pocketjs/hosts/psp" }
pocketjs-core = { path = "../../vendor/pocketjs/engine/core" }
pocket3d-bsp = { path = "../../vendor/pocketjs/engine/pocket3d/crates/pocket3d-bsp", default-features = false, features = ["libm"] }
pocket3d-gu = { path = "../../vendor/pocketjs/engine/pocket3d/crates/pocket3d-gu" }
openstrike-core = { path = "../openstrike-core", default-features = false, features = ["libm"] }
glam = { version = "0.33", default-features = false, features = ["libm"] }
libm = "0.2"
Expand Down
Loading