Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
90 commits
Select commit Hold shift + click to select a range
aacc5ce
added pipelines
clragon Feb 14, 2026
d0499fc
added new game engine (wip)
clragon Jun 23, 2025
59cc751
added scheduler plugin (wip)
clragon Jun 23, 2025
de22e1f
fixed context unwrapping
clragon Jun 23, 2025
951fcea
fixed scheduling
clragon Jun 23, 2025
feeeab2
removed failed files
clragon Jun 23, 2025
2b6934c
added note about scheduling
clragon Jun 23, 2025
dcf53c7
updated scheduling to use action system
clragon Jun 23, 2025
44f8d2a
updated messages to use action system
clragon Jun 23, 2025
21f5709
renamed actions to events
clragon Jun 24, 2025
10eb0de
updated composer dsl
clragon Jun 27, 2025
d23fa0b
rewrote messages to use scheduling
clragon Jun 27, 2025
34dd49d
fixed updating messages preserving index
clragon Jun 27, 2025
cd5a04d
added composer shortcuts for simple pipes
clragon Jun 27, 2025
046e2d3
removed apply in favour of pipe
clragon Jun 28, 2025
7ce301f
restored message ui
clragon Jun 28, 2025
b892852
added use game context
clragon Jun 28, 2025
a0d2c05
improved composer.bind ease of use
clragon Jun 29, 2025
528726e
improved composer.chain ease of use
clragon Jun 29, 2025
a162b2e
fixed old lens Path usage
clragon Jun 29, 2025
71dfffa
improved event handle typing
clragon Jun 29, 2025
a476a1c
added intensity pipe
clragon Jun 29, 2025
40a340e
ignored any usage
clragon Aug 24, 2025
c3d3845
added image pipeline
clragon Aug 24, 2025
9489300
moved random image switching into own pipe
clragon Aug 24, 2025
b439a8e
added game phase pipe
clragon Aug 24, 2025
950a256
improved fps display
clragon Aug 24, 2025
1b5bf2e
added warump pipe
clragon Aug 24, 2025
916765f
improved composer dsl
clragon Aug 25, 2025
0d193d9
improved static composer functions
clragon Aug 25, 2025
e26c907
made intensity phase aware
clragon Aug 25, 2025
b74fb10
added pace pipe
clragon Aug 25, 2025
ad1026c
added tests
clragon Nov 2, 2025
23c8323
added plugin system
clragon Feb 11, 2026
039d3b2
added imperative composer api
clragon Feb 11, 2026
872e18f
added some guard rails to imperative composer
clragon Feb 11, 2026
b6ea0dd
added else to when
clragon Feb 12, 2026
4fa329e
added typed paths to composer
clragon Feb 12, 2026
19253d3
improved plugin system
clragon Feb 12, 2026
5ce7cac
added pause plugin
clragon Feb 13, 2026
4923df4
added global sdk object for plugins
clragon Feb 13, 2026
a4b2c92
added performance monitoring
clragon Feb 13, 2026
96ab0a0
moved game pips to plugins
clragon Feb 13, 2026
3f9def2
removed deep cloning
clragon Feb 13, 2026
8bc1e83
added expiration to performance overlay
clragon Feb 13, 2026
4b0c841
added remaining game features (wip)
clragon Feb 13, 2026
b7aae91
added event sequencer
clragon Feb 13, 2026
bcfa964
improved performance monitor
clragon Feb 13, 2026
39da696
removed pause button
clragon Feb 14, 2026
0a7d8a7
fixed unpausing the game
clragon Feb 14, 2026
629ff14
moved messaging to plugin
clragon Feb 14, 2026
1a4be75
removed old fps pipe
clragon Feb 14, 2026
460d33f
moved completing dice state into dealer
clragon Feb 14, 2026
5de09a0
added freezing game on tab or minimize
clragon Feb 14, 2026
5b976ea
applied formatter
clragon Feb 14, 2026
5d5fdb3
improved performance and typing of state access in ui
clragon Feb 14, 2026
b6408bf
optimized composer invocations
clragon Feb 14, 2026
df99b91
removed old gamepace component
clragon Feb 14, 2026
d7c7489
deleted context api injections in favour of static apis
clragon Feb 14, 2026
a128550
refactored old pipes to use typed paths
clragon Feb 14, 2026
6eaa940
updated event pipe dsl
clragon Feb 14, 2026
5a3d10f
updated primitive pipes to plugin pattern
clragon Feb 14, 2026
c565645
applied formatting
clragon Feb 14, 2026
363125f
refactored performance phase parameter to freeform string
clragon Feb 14, 2026
a9c121f
added sdk.debug prop
clragon Feb 14, 2026
2e3f54a
added types to events
clragon Feb 14, 2026
6c0d3af
replaced delta time with fixed ticks
clragon Feb 14, 2026
d756cb1
replaced math random with randomness plugin
clragon Feb 14, 2026
620ba68
formatted files
clragon Feb 14, 2026
4057ca9
fixed updating game settings during live game
clragon Feb 14, 2026
5a3f262
fixed nested dialog closing
clragon Feb 14, 2026
c299c97
fixed event button alignment
clragon Feb 14, 2026
79407e3
moved tests to their own folder
clragon Feb 14, 2026
d2c8dea
extracted test utilities
clragon Feb 14, 2026
0b9e628
formatted files
clragon Feb 14, 2026
5c5ffcf
moved game engine to root
clragon Feb 14, 2026
d2eeb95
added pause menu
clragon Feb 14, 2026
2f87cb9
fixed sdk registration
clragon Feb 15, 2026
b10a086
formatted files
clragon Feb 15, 2026
43499a0
added default to lens.over
clragon Feb 15, 2026
094920a
fixed automatic unpausing
clragon Feb 15, 2026
f8dc41f
fixed fps display
clragon Feb 15, 2026
5ffb325
added game over screen
clragon Feb 16, 2026
086b07e
added scene plugin
clragon Feb 16, 2026
4fd7f53
reordered plugins
clragon Feb 16, 2026
2c75248
formatted files
clragon Feb 16, 2026
2b32927
added errors pipe primitive
clragon Feb 16, 2026
8a24eef
fixed perf plugin path access
clragon Feb 16, 2026
270af91
formatted files
clragon Feb 16, 2026
bae6b40
killed game context
clragon Feb 19, 2026
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
3 changes: 3 additions & 0 deletions .eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,8 @@ module.exports = {
{ allowConstantExport: true },
],
'@typescript-eslint/no-unused-vars': 'warn',
// I am going to be real with you, this pipe system is not going to be very typed.
// Lets just. put this aside for now.
'@typescript-eslint/no-explicit-any': 'off',
},
};
19 changes: 19 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
name: Test

on:
push:
branches: [main]
pull_request:
branches: [main]

jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20.x
cache: 'yarn'
- run: yarn install --frozen-lockfile
- run: yarn test
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ node_modules
dist
dist-ssr
*.local
coverage

# Editor directories and files
.vscode/*
Expand Down
10 changes: 7 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"format": "prettier --write .",
"format": "prettier --write src/ tests/",
"lint": "eslint . --ext ts,tsx --max-warnings 0",
"preview": "vite preview"
"preview": "vite preview",
"test": "vitest run",
"test:coverage": "vitest run --coverage"
},
"dependencies": {
"@awesome.me/webawesome": "^3.2.1",
Expand Down Expand Up @@ -54,14 +56,16 @@
"@typescript-eslint/eslint-plugin": "^7.2.0",
"@typescript-eslint/parser": "^7.2.0",
"@vitejs/plugin-react": "^4.2.1",
"@vitest/coverage-v8": "^4.0.6",
"eslint": "^8.57.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4",
"jsdom": "^27.1.0",
"prettier": "^3.2.5",
"typescript": "^5.2.2",
"vite": "^5.2.0"
"vite": "^5.2.0",
"vitest": "^4.0.6"
},
"engines": {
"node": "^20.0.0",
Expand Down
23 changes: 15 additions & 8 deletions src/app/App.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,25 @@
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import { HomePage } from '../home';
import { GamePage } from '../game';
import { EndPage } from '../end';
import { GameShell } from '../game/GameShell';
import { SceneBridge } from '../game/SceneBridge';

import '@awesome.me/webawesome/dist/styles/webawesome.css';

export const App = () => {
return (
<BrowserRouter
future={{ v7_startTransition: true, v7_relativeSplatPath: true }}
>
<Routes>
<Route path='/' element={<HomePage />} />
<Route path='/play' element={<GamePage />} />
</Routes>
</BrowserRouter>
<GameShell>
<BrowserRouter
future={{ v7_startTransition: true, v7_relativeSplatPath: true }}
>
<SceneBridge />
<Routes>
<Route path='/' element={<HomePage />} />
<Route path='/play' element={<GamePage />} />
<Route path='/end' element={<EndPage />} />
</Routes>
</BrowserRouter>
</GameShell>
);
};
1 change: 1 addition & 0 deletions src/common/ToggleTile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export class JoiToggleTileElement extends LitElement {
display: block;
width: 100%;
cursor: pointer;
text-align: left;

background: var(--wa-color-neutral-fill-quiet);
opacity: var(--tile-inactive-opacity);
Expand Down
44 changes: 44 additions & 0 deletions src/end/ClimaxResult.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import styled from 'styled-components';
import { useGameFrame } from '../game/hooks';
import { climax, type ClimaxResultType } from '../game/plugins/dice/climax';

type OutcomeDisplay = { label: string; description: string };

const outcomes: Record<NonNullable<ClimaxResultType>, OutcomeDisplay> = {
climax: { label: 'Climax', description: 'You came' },
denied: { label: 'Denied', description: 'Better luck next time' },
ruined: { label: 'Ruined', description: 'How unfortunate' },
};

const earlyEnd: OutcomeDisplay = { label: 'Ended early', description: '' };

const StyledResult = styled.div`
display: flex;
flex-direction: column;
gap: 2px;
`;

const StyledLabel = styled.span`
font-size: 1.1rem;
font-weight: bold;
color: var(--text-color);
`;

const StyledDescription = styled.span`
font-size: 0.85rem;
opacity: 0.6;
`;

export const ClimaxResult = () => {
const result = useGameFrame(climax.result) as ClimaxResultType;
const display = (result && outcomes[result]) || earlyEnd;

return (
<StyledResult>
<StyledLabel>{display.label}</StyledLabel>
{display.description && (
<StyledDescription>{display.description}</StyledDescription>
)}
</StyledResult>
);
};
159 changes: 159 additions & 0 deletions src/end/EndPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
import styled, { keyframes } from 'styled-components';
import { WaButton } from '@awesome.me/webawesome/dist/react';
import { ContentSection } from '../common';
import { useGameEngine } from '../game/hooks/UseGameEngine';
import { useGameFrame } from '../game/hooks';
import Clock from '../game/plugins/clock';
import Rand from '../game/plugins/rand';
import Scene from '../game/plugins/scene';
import { formatTime } from '../utils';
import { ClimaxResult } from './ClimaxResult';
import { GameTimeline } from './GameTimeline';

const fadeInUp = keyframes`
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
`;

const StyledEndPage = styled.div`
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: var(--wa-space-l);

min-height: 100%;
width: 100%;
padding: 16px;

& > * {
max-width: 480px;
width: 100%;
animation: ${fadeInUp} 500ms cubic-bezier(0.23, 1, 0.32, 1) both;
}

& > :nth-child(1) {
animation-delay: 100ms;
}
& > :nth-child(2) {
animation-delay: 250ms;
}
& > :nth-child(3) {
animation-delay: 400ms;
}
`;

const StyledTitle = styled.h1`
text-align: center;
font-size: clamp(2rem, 8vw, 3.5rem);
font-weight: bold;
line-height: 1;
`;

const StyledCard = styled(ContentSection)`
display: flex;
flex-direction: column;
gap: var(--wa-space-m);
padding: 20px;
`;

const StyledStatsRow = styled.div`
display: flex;
justify-content: space-around;
`;

const StyledStat = styled.div`
display: flex;
flex-direction: column;
align-items: center;
gap: 2px;
`;

const StyledStatValue = styled.span`
font-size: 1.25rem;
font-weight: bold;
color: var(--text-color);
`;

const StyledStatLabel = styled.span`
font-size: 0.65rem;
text-transform: uppercase;
letter-spacing: 0.1em;
opacity: 0.4;
`;

const StyledDivider = styled.hr`
border: none;
border-top: 1px solid currentColor;
width: 100%;
margin: 0;
`;

const StyledActions = styled.div`
display: flex;
justify-content: center;
`;

const StyledFinishButton = styled(WaButton)`
&::part(base) {
height: fit-content;
padding: 12px 40px;
}

&::part(label) {
font-size: 1.25rem;
text-transform: uppercase;
}
`;

export const EndPage = () => {
const { injectImpulse } = useGameEngine();
const clockState = useGameFrame(Clock.paths) as
| { elapsed?: number }
| undefined;
const randState = useGameFrame(Rand.paths) as { seed?: string } | undefined;

const displayTime =
typeof clockState?.elapsed === 'number' ? clockState.elapsed : 0;
const seed = randState?.seed ?? '';

return (
<StyledEndPage>
<StyledTitle>Game Over</StyledTitle>
<StyledCard>
<ClimaxResult />
<StyledDivider />
<StyledStatsRow>
<StyledStat>
<StyledStatValue>{formatTime(displayTime)}</StyledStatValue>
<StyledStatLabel>Play time</StyledStatLabel>
</StyledStat>
<StyledStat>
<StyledStatValue
style={{ fontFamily: 'monospace', fontSize: '1rem' }}
>
{seed}
</StyledStatValue>
<StyledStatLabel>Seed</StyledStatLabel>
</StyledStat>
</StyledStatsRow>
<StyledDivider />
<GameTimeline />
</StyledCard>
<StyledActions>
<StyledFinishButton
size='large'
onClick={() => injectImpulse(Scene.setScene('home'))}
>
Finish
</StyledFinishButton>
</StyledActions>
</StyledEndPage>
);
};
Loading