feat(docs): add development documentation, prototypes, and war-room components

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
2026-04-11 08:49:41 +08:00
parent 1ca8855751
commit 21c869db62
1218 changed files with 11858 additions and 0 deletions

View File

@@ -0,0 +1,8 @@
Star Office stage integration notes
- Upstream project: https://github.com/ringhyacinth/Star-Office-UI
- Upstream code license: MIT
- Upstream README states art assets are non-commercial only
This local integration is intended as an internal/read-only stage inside Jarvis war-room.
Review upstream asset terms before any external or commercial redistribution.

Binary file not shown.

After

Width:  |  Height:  |  Size: 568 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 341 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 468 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 464 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 306 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 322 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 364 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 364 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 982 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 193 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 580 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 996 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 MiB

View File

@@ -0,0 +1,369 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Jarvis Star Office Stage</title>
<style>
@font-face {
font-family: 'ArkPixel';
src: url('./fonts/ark-pixel-12px-proportional-zh_cn.ttf.woff2') format('woff2');
font-weight: normal;
font-style: normal;
}
@font-face {
font-family: 'ArkPixelLatin';
src: url('./fonts/ark-pixel-12px-proportional-latin.ttf.woff2') format('woff2');
font-weight: normal;
font-style: normal;
}
:root {
color-scheme: dark;
--bg: #050810;
--panel: rgba(8, 16, 29, 0.86);
--panel-border: rgba(97, 211, 255, 0.18);
--text: #e9fbff;
--muted: #86cbe2;
--accent: #6fe0ff;
--warn: #ffd36b;
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
html, body {
width: 100%;
min-height: 100%;
background:
radial-gradient(circle at top left, rgba(64, 176, 255, 0.12), transparent 28%),
radial-gradient(circle at top right, rgba(255, 196, 94, 0.08), transparent 20%),
linear-gradient(180deg, #07111d 0%, #03070e 100%);
color: var(--text);
font-family: 'ArkPixel', 'ArkPixelLatin', 'Courier New', monospace;
}
body {
display: flex;
flex-direction: column;
gap: 14px;
padding: 14px;
overflow-y: auto;
}
.hud {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
padding: 12px 14px;
border: 1px solid var(--panel-border);
background: var(--panel);
backdrop-filter: blur(12px);
}
.hud-copy {
display: grid;
gap: 4px;
}
.hud-kicker,
.meta-chip,
.panel-kicker,
.license-note {
font-size: 11px;
letter-spacing: 0.14em;
text-transform: uppercase;
color: var(--muted);
}
.hud-title {
font-size: 24px;
color: var(--text);
letter-spacing: 0.08em;
}
.hud-subtitle {
font-size: 12px;
line-height: 1.5;
color: rgba(233, 251, 255, 0.74);
max-width: 760px;
}
.hud-meta {
display: flex;
gap: 8px;
flex-wrap: wrap;
justify-content: flex-end;
}
.meta-chip {
padding: 8px 10px;
border: 1px solid rgba(111, 224, 255, 0.18);
background: rgba(7, 18, 30, 0.92);
color: var(--text);
letter-spacing: 0.1em;
}
.stage {
display: grid;
gap: 14px;
}
.game-shell {
position: relative;
width: 100%;
aspect-ratio: 16 / 9;
border: 1px solid rgba(107, 214, 255, 0.16);
background: rgba(6, 13, 22, 0.88);
overflow: hidden;
}
#game-container {
width: 100%;
height: 100%;
}
#game-container canvas {
width: 100% !important;
height: 100% !important;
image-rendering: pixelated;
display: block;
}
.overlay-status {
position: absolute;
left: 14px;
bottom: 14px;
max-width: calc(100% - 28px);
padding: 10px 12px;
border: 1px solid rgba(111, 224, 255, 0.16);
background: rgba(2, 8, 16, 0.82);
color: var(--text);
font-size: 12px;
line-height: 1.45;
z-index: 5;
pointer-events: none;
}
#coords-overlay,
#coords-toggle {
display: none;
}
.panels {
display: grid;
grid-template-columns: minmax(0, 1fr) 330px;
gap: 14px;
}
.panel {
border: 1px solid var(--panel-border);
background: var(--panel);
backdrop-filter: blur(12px);
padding: 12px 14px;
min-height: 168px;
}
.panel-head {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
margin-bottom: 10px;
}
.panel-title {
font-size: 16px;
letter-spacing: 0.08em;
color: var(--text);
}
.memo-card {
height: calc(100% - 30px);
display: grid;
grid-template-rows: auto 1fr;
gap: 8px;
padding: 12px;
border: 1px solid rgba(111, 224, 255, 0.12);
background:
linear-gradient(rgba(255,255,255,0.03), rgba(255,255,255,0.03)),
url('./assets/memo-bg.webp') center/cover no-repeat;
color: #09131d;
}
#memo-date {
font-size: 11px;
color: #2c4456;
letter-spacing: 0.1em;
text-transform: uppercase;
}
#memo-content {
white-space: pre-wrap;
font-size: 13px;
line-height: 1.55;
color: #081019;
overflow: auto;
}
.legend-list {
display: grid;
gap: 10px;
}
.legend-item {
display: grid;
gap: 4px;
padding: 10px;
border: 1px solid rgba(111, 224, 255, 0.1);
background: rgba(7, 18, 30, 0.74);
}
.legend-name {
font-size: 12px;
color: var(--text);
letter-spacing: 0.08em;
text-transform: uppercase;
}
.legend-detail {
font-size: 12px;
line-height: 1.45;
color: rgba(233, 251, 255, 0.72);
}
.license-note {
color: rgba(255, 211, 107, 0.86);
}
#loading-overlay {
position: absolute;
inset: 0;
z-index: 9;
display: flex;
align-items: center;
justify-content: center;
background: rgba(3, 8, 14, 0.94);
}
.loading-box {
width: min(420px, calc(100% - 40px));
display: grid;
gap: 12px;
padding: 18px;
border: 1px solid rgba(111, 224, 255, 0.16);
background: rgba(7, 18, 30, 0.94);
}
#loading-text {
font-size: 14px;
color: var(--text);
line-height: 1.45;
}
#loading-progress-container {
width: 100%;
height: 12px;
border: 1px solid rgba(111, 224, 255, 0.16);
background: rgba(2, 8, 16, 0.82);
}
#loading-progress-bar {
width: 0%;
height: 100%;
background: linear-gradient(90deg, #4ad4ff, #ffd36b);
transition: width .2s ease;
}
@media (max-width: 900px) {
.hud {
grid-template-columns: 1fr;
display: grid;
}
.hud-meta {
justify-content: flex-start;
}
.panels {
grid-template-columns: 1fr;
}
}
</style>
</head>
<body>
<header class="hud">
<div class="hud-copy">
<div class="hud-kicker">Jarvis War Room / Star Office Stage</div>
<div class="hud-title">STAR OFFICE</div>
<div class="hud-subtitle">
Pixel office stage adapted from the upstream Star Office UI project and wired into Jarvis office state endpoints.
</div>
</div>
<div class="hud-meta">
<div class="meta-chip">READ ONLY STAGE</div>
<div class="meta-chip" id="live-state-chip">STATE / IDLE</div>
<div class="meta-chip">API / <span id="agents-count">1</span> AGENTS</div>
</div>
</header>
<main class="stage">
<section class="game-shell">
<div id="game-container"></div>
<div id="loading-overlay">
<div class="loading-box">
<div id="loading-text">Loading Jarvis Star Office...</div>
<div id="loading-progress-container">
<div id="loading-progress-bar"></div>
</div>
</div>
</div>
<div class="overlay-status" id="status-text">[IDLE] Awaiting commands...</div>
<div id="coords-overlay"></div>
<button id="coords-toggle" type="button">coords</button>
</section>
<section class="panels">
<article class="panel">
<div class="panel-head">
<div class="panel-title">YESTERDAY MEMO</div>
<div class="panel-kicker" id="memo-date">memo</div>
</div>
<div class="memo-card">
<div class="panel-kicker">OPERATIONS NOTEBOOK</div>
<div id="memo-content">Loading memo...</div>
</div>
</article>
<article class="panel">
<div class="panel-head">
<div class="panel-title">INTEGRATION NOTE</div>
<div class="panel-kicker">WAR ROOM</div>
</div>
<div class="legend-list">
<div class="legend-item">
<div class="legend-name">State Mapping</div>
<div class="legend-detail">Idle stays in lounge. Writing, researching and executing move to the desk. Syncing lights the server room. Error wakes the bug bay.</div>
</div>
<div class="legend-item">
<div class="legend-name">Agent Layer</div>
<div class="legend-detail">The main agent uses Jarvis office state. Visitor sprites fall back to local demo agents when no extra office agents are available.</div>
</div>
<div class="legend-item">
<div class="legend-name">License Note</div>
<div class="legend-detail license-note">Upstream code is MIT. Upstream art assets are marked non-commercial by the project README. Keep that constraint in mind before external distribution.</div>
</div>
</div>
</article>
</section>
</main>
<script src="./vendor/phaser-3.80.1.min.js"></script>
<script src="./office-runtime.js"></script>
</body>
</html>

View File

@@ -0,0 +1,495 @@
(function () {
const ASSET_BASE = './assets'
const API_BASE = '/api/office'
const GAME_WIDTH = 1280
const GAME_HEIGHT = 720
const POLL_MS = 2500
const STATUS_META = {
idle: { label: 'IDLE', detail: 'Awaiting commands', area: 'breakroom', bubble: ['Standing by.', 'Quiet office. Ready when you are.'] },
writing: { label: 'WRITING', detail: 'Working at the command desk', area: 'writing', bubble: ['Drafting the next move.', 'Keeping the mainline clean.'] },
researching: { label: 'RESEARCH', detail: 'Collecting evidence and context', area: 'writing', bubble: ['Tracing the signal.', 'Checking the facts before acting.'] },
executing: { label: 'EXECUTING', detail: 'Pushing tasks through the pipeline', area: 'writing', bubble: ['Executing the plan.', 'Reducing the task to concrete steps.'] },
syncing: { label: 'SYNCING', detail: 'Aligning state in the server room', area: 'syncing', bubble: ['Sync in progress.', 'Writing the latest changes to the shared state.'] },
error: { label: 'ERROR', detail: 'Investigating a failure path', area: 'error', bubble: ['Something broke. Looking at it now.', 'The bug bay is active.'] },
}
const AREA_POSITIONS = {
breakroom: { x: 798, y: 272 },
writing: { x: 217, y: 343 },
syncing: { x: 1157, y: 592 },
error: { x: 1007, y: 221 },
}
const VISITOR_SLOTS = {
breakroom: [
{ x: 700, y: 350 },
{ x: 855, y: 322 },
{ x: 926, y: 362 },
],
writing: [
{ x: 386, y: 356 },
{ x: 462, y: 319 },
{ x: 538, y: 356 },
],
syncing: [
{ x: 1035, y: 520 },
{ x: 1096, y: 560 },
{ x: 974, y: 560 },
],
error: [
{ x: 1080, y: 262 },
{ x: 1138, y: 250 },
{ x: 1078, y: 324 },
],
}
const DEMO_VISITORS = [
{ agentId: 'demo-nika', name: 'Nika', state: 'researching' },
{ agentId: 'demo-mercury', name: 'Mercury', state: 'writing' },
{ agentId: 'demo-echo', name: 'Echo', state: 'syncing' },
]
let game
let mainState = 'idle'
let mainDetail = STATUS_META.idle.detail
let lastPollAt = 0
let lastBubbleAt = 0
let lastVisitorShuffleAt = 0
let lastCatBubbleAt = 0
let lastAgentsCount = 1
let lastRemoteAgents = []
let officeBgSprite
let sofa
let sofaShadow
let starIdle
let starWorking
let serverRoom
let syncAnim
let errorBug
let mainBubble = null
let catBubble = null
let guestSprites = {}
const statusText = document.getElementById('status-text')
const stateChip = document.getElementById('live-state-chip')
const agentsCount = document.getElementById('agents-count')
const memoDate = document.getElementById('memo-date')
const memoContent = document.getElementById('memo-content')
const loadingText = document.getElementById('loading-text')
const loadingBar = document.getElementById('loading-progress-bar')
const loadingOverlay = document.getElementById('loading-overlay')
function setLoading(progress, text) {
if (loadingBar) loadingBar.style.width = `${progress}%`
if (loadingText) loadingText.textContent = text
}
function hideLoading() {
if (!loadingOverlay) return
loadingOverlay.style.transition = 'opacity .35s ease'
loadingOverlay.style.opacity = '0'
window.setTimeout(() => {
loadingOverlay.style.display = 'none'
}, 350)
}
async function fetchJson(path) {
const response = await fetch(path, { cache: 'no-store' })
if (!response.ok) {
throw new Error(`${response.status} ${response.statusText}`)
}
return response.json()
}
async function loadMemo() {
try {
const payload = await fetchJson(`${API_BASE}/yesterday-memo?t=${Date.now()}`)
memoDate.textContent = payload.date || 'memo'
memoContent.textContent = payload.memo || 'No memo available.'
} catch (error) {
memoDate.textContent = 'memo unavailable'
memoContent.textContent = 'Unable to load the office memo.'
console.error('Failed to load office memo', error)
}
}
function normalizeState(state) {
const raw = String(state || '').trim().toLowerCase()
if (!raw) return 'idle'
if (raw === 'working' || raw === 'run' || raw === 'running') return 'executing'
if (raw === 'sync') return 'syncing'
if (raw === 'research') return 'researching'
return STATUS_META[raw] ? raw : 'idle'
}
function updateHud() {
const meta = STATUS_META[mainState] || STATUS_META.idle
statusText.textContent = `[${meta.label}] ${mainDetail || meta.detail}`
stateChip.textContent = `STATE / ${meta.label}`
agentsCount.textContent = String(lastAgentsCount)
}
function clearBubble(target) {
if (target) {
target.destroy()
}
}
function spawnBubble(anchorX, anchorY, text, tint = 0xffffff) {
const bg = game.add.rectangle(anchorX, anchorY, Math.max(110, text.length * 8 + 24), 28, 0xffffff, 0.96)
bg.setStrokeStyle(2, tint)
const label = game.add.text(anchorX, anchorY, text, {
fontFamily: 'ArkPixel, ArkPixelLatin, monospace',
fontSize: '11px',
color: '#031019',
align: 'center',
}).setOrigin(0.5)
const container = game.add.container(0, 0, [bg, label])
container.setDepth(2400)
return container
}
function maybeShowMainBubble(time) {
if (!game || mainState === 'idle') return
if (time - lastBubbleAt < 7000) return
lastBubbleAt = time
clearBubble(mainBubble)
const meta = STATUS_META[mainState] || STATUS_META.idle
const text = meta.bubble[Math.floor(Math.random() * meta.bubble.length)]
let anchor = AREA_POSITIONS.breakroom
if (mainState === 'syncing') anchor = AREA_POSITIONS.syncing
else if (mainState === 'error') anchor = AREA_POSITIONS.error
else if (mainState !== 'idle') anchor = AREA_POSITIONS.writing
mainBubble = spawnBubble(anchor.x, anchor.y - 88, text, 0x61d3ff)
window.setTimeout(() => {
clearBubble(mainBubble)
mainBubble = null
}, 3200)
}
function maybeShowCatBubble(time) {
if (!game || !window.catSprite) return
if (time - lastCatBubbleAt < 17000) return
lastCatBubbleAt = time
clearBubble(catBubble)
catBubble = spawnBubble(window.catSprite.x, window.catSprite.y - 64, 'mrrp', 0xd4a574)
window.setTimeout(() => {
clearBubble(catBubble)
catBubble = null
}, 2200)
}
function applyMainState(nextState, detail) {
mainState = normalizeState(nextState)
mainDetail = detail || STATUS_META[mainState].detail
updateHud()
sofa.setTexture('sofa_idle')
if (mainState === 'idle') {
starIdle.setVisible(true)
if (!starIdle.anims.isPlaying) starIdle.anims.play('star_idle', true)
starWorking.setVisible(false)
starWorking.anims.stop()
syncAnim.setVisible(false)
syncAnim.anims.stop()
syncAnim.setFrame(0)
errorBug.setVisible(false)
errorBug.anims.stop()
serverRoom.anims.stop()
serverRoom.setFrame(0)
return
}
starIdle.setVisible(false)
starIdle.anims.stop()
if (mainState === 'syncing') {
starWorking.setVisible(false)
starWorking.anims.stop()
errorBug.setVisible(false)
errorBug.anims.stop()
syncAnim.setVisible(true)
syncAnim.anims.play('sync_anim', true)
serverRoom.anims.play('serverroom_on', true)
return
}
syncAnim.setVisible(false)
syncAnim.anims.stop()
syncAnim.setFrame(0)
if (mainState === 'error') {
starWorking.setVisible(false)
starWorking.anims.stop()
errorBug.setVisible(true)
errorBug.anims.play('error_bug', true)
serverRoom.anims.play('serverroom_on', true)
return
}
errorBug.setVisible(false)
errorBug.anims.stop()
starWorking.setVisible(true)
starWorking.anims.play('star_working', true)
serverRoom.anims.play('serverroom_on', true)
}
function randomizeDemoVisitors() {
const states = Object.keys(STATUS_META)
DEMO_VISITORS.forEach((agent, index) => {
agent.state = states[(index + Math.floor(Math.random() * states.length)) % states.length]
})
}
function destroyGuestSprites() {
Object.values(guestSprites).forEach((entry) => entry.destroy())
guestSprites = {}
}
function renderVisitors(apiAgents = []) {
const visitors = apiAgents.filter((agent) => !agent.isMain)
const effectiveVisitors = visitors.length > 0 ? visitors : DEMO_VISITORS
destroyGuestSprites()
lastAgentsCount = Math.max(1, apiAgents.length)
updateHud()
effectiveVisitors.forEach((agent, index) => {
const state = normalizeState(agent.state)
const area = STATUS_META[state]?.area || 'breakroom'
const slot = VISITOR_SLOTS[area][index % VISITOR_SLOTS[area].length]
const sheetKey = `guest_anim_${(index % 6) + 1}`
const sprite = game.add.sprite(slot.x, slot.y, sheetKey, 0).setOrigin(0.5)
const animKey = `${sheetKey}_idle`
if (game.anims.exists(animKey)) {
sprite.anims.play(animKey, true)
}
sprite.setDepth(1200)
const label = game.add.text(slot.x, slot.y - 34, agent.name, {
fontFamily: 'ArkPixel, ArkPixelLatin, monospace',
fontSize: '11px',
color: '#ecfdf5',
stroke: '#0f172a',
strokeThickness: 4,
}).setOrigin(0.5)
label.setDepth(1201)
guestSprites[agent.agentId || `demo-${index}`] = game.add.container(0, 0, [sprite, label])
})
}
async function pollOfficeState(force = false) {
const now = Date.now()
if (!force && now - lastPollAt < POLL_MS) return
lastPollAt = now
try {
const [statusPayload, agentsPayload] = await Promise.all([
fetchJson(`${API_BASE}/status?t=${now}`),
fetchJson(`${API_BASE}/agents?t=${now}`),
])
applyMainState(statusPayload.state, statusPayload.detail)
lastRemoteAgents = Array.isArray(agentsPayload) ? agentsPayload : []
renderVisitors(lastRemoteAgents)
} catch (error) {
applyMainState('error', 'Office API unavailable')
console.error('Failed to poll office state', error)
}
}
function preload() {
setLoading(2, 'Loading Star Office room...')
const fileCount = 17
let loadedCount = 0
this.load.on('filecomplete', () => {
loadedCount += 1
setLoading(Math.min(95, Math.round((loadedCount / fileCount) * 100)), 'Streaming pixel office assets...')
})
this.load.on('complete', () => {
setLoading(100, 'Star Office ready.')
hideLoading()
})
this.load.image('office_bg', `${ASSET_BASE}/office_bg_small.webp`)
this.load.spritesheet('star_idle', `${ASSET_BASE}/star-idle-v5.png`, { frameWidth: 256, frameHeight: 256 })
this.load.image('sofa_idle', `${ASSET_BASE}/sofa-idle-v3.png`)
this.load.image('sofa_shadow', `${ASSET_BASE}/sofa-shadow-v1.png`)
this.load.spritesheet('plants', `${ASSET_BASE}/plants-spritesheet.webp`, { frameWidth: 160, frameHeight: 160 })
this.load.spritesheet('posters', `${ASSET_BASE}/posters-spritesheet.webp`, { frameWidth: 160, frameHeight: 160 })
this.load.spritesheet('coffee_machine', `${ASSET_BASE}/coffee-machine-v3-grid.webp`, { frameWidth: 230, frameHeight: 230 })
this.load.image('coffee_machine_shadow', `${ASSET_BASE}/coffee-machine-shadow-v1.png`)
this.load.spritesheet('serverroom', `${ASSET_BASE}/serverroom-spritesheet.webp`, { frameWidth: 180, frameHeight: 251 })
this.load.spritesheet('error_bug', `${ASSET_BASE}/error-bug-spritesheet-grid.webp`, { frameWidth: 220, frameHeight: 220 })
this.load.spritesheet('cats', `${ASSET_BASE}/cats-spritesheet.webp`, { frameWidth: 160, frameHeight: 160 })
this.load.spritesheet('star_working', `${ASSET_BASE}/star-working-spritesheet-grid.webp`, { frameWidth: 300, frameHeight: 300 })
this.load.spritesheet('sync_anim', `${ASSET_BASE}/sync-animation-v3-grid.webp`, { frameWidth: 256, frameHeight: 256 })
this.load.image('desk_v2', `${ASSET_BASE}/desk-v3.webp`)
this.load.spritesheet('flowers', `${ASSET_BASE}/flowers-bloom-v2.webp`, { frameWidth: 128, frameHeight: 128 })
for (let index = 1; index <= 6; index += 1) {
this.load.spritesheet(`guest_anim_${index}`, `${ASSET_BASE}/guest_anim_${index}.webp`, { frameWidth: 32, frameHeight: 32 })
}
}
function create() {
game = this
officeBgSprite = this.add.image(640, 360, 'office_bg')
officeBgSprite.setDisplaySize(GAME_WIDTH, GAME_HEIGHT)
sofaShadow = this.add.image(798, 272, 'sofa_shadow').setOrigin(0.5)
sofaShadow.setDepth(9)
sofa = this.add.sprite(798, 272, 'sofa_idle').setOrigin(0.5)
sofa.setDepth(10)
this.anims.create({
key: 'star_idle',
frames: this.anims.generateFrameNumbers('star_idle', { start: 0, end: Math.max(0, this.textures.get('star_idle').frameTotal - 1) }),
frameRate: 12,
repeat: -1,
})
this.anims.create({
key: 'coffee_machine',
frames: this.anims.generateFrameNumbers('coffee_machine', { start: 0, end: Math.max(0, this.textures.get('coffee_machine').frameTotal - 2) }),
frameRate: 12,
repeat: -1,
})
this.anims.create({
key: 'serverroom_on',
frames: this.anims.generateFrameNumbers('serverroom', { start: 0, end: Math.max(0, this.textures.get('serverroom').frameTotal - 2) }),
frameRate: 6,
repeat: -1,
})
this.anims.create({
key: 'star_working',
frames: this.anims.generateFrameNumbers('star_working', { start: 0, end: 37 }),
frameRate: 12,
repeat: -1,
})
this.anims.create({
key: 'error_bug',
frames: this.anims.generateFrameNumbers('error_bug', { start: 0, end: 71 }),
frameRate: 12,
repeat: -1,
})
this.anims.create({
key: 'sync_anim',
frames: this.anims.generateFrameNumbers('sync_anim', { start: 1, end: 52 }),
frameRate: 12,
repeat: -1,
})
for (let index = 1; index <= 6; index += 1) {
this.anims.create({
key: `guest_anim_${index}_idle`,
frames: this.anims.generateFrameNumbers(`guest_anim_${index}`, { start: 0, end: 7 }),
frameRate: 8,
repeat: -1,
})
}
starIdle = game.physics.add.sprite(798, 272, 'star_idle')
starIdle.setOrigin(0.5)
starIdle.setScale(0.58)
starIdle.setDepth(20)
starIdle.anims.play('star_idle', true)
const plantFrames = 16
;[
[565, 178],
[230, 185],
[977, 496],
].forEach(([x, y]) => {
const plant = game.add.sprite(x, y, 'plants', Math.floor(Math.random() * plantFrames)).setOrigin(0.5)
plant.setDepth(5)
})
const poster = game.add.sprite(252, 66, 'posters', Math.floor(Math.random() * Math.max(1, this.textures.get('posters').frameTotal - 1))).setOrigin(0.5)
poster.setDepth(4)
const cat = game.add.sprite(94, 557, 'cats', Math.floor(Math.random() * Math.max(1, this.textures.get('cats').frameTotal - 1))).setOrigin(0.5)
cat.setDepth(2000)
window.catSprite = cat
const coffeeShadow = this.add.image(659, 397, 'coffee_machine_shadow').setOrigin(0.5)
coffeeShadow.setDepth(98)
const coffeeMachine = this.add.sprite(659, 397, 'coffee_machine').setOrigin(0.5)
coffeeMachine.setDepth(99)
coffeeMachine.anims.play('coffee_machine', true)
serverRoom = this.add.sprite(1021, 142, 'serverroom', 0).setOrigin(0.5)
serverRoom.setDepth(2)
serverRoom.setFrame(0)
const desk = this.add.image(218, 417, 'desk_v2').setOrigin(0.5)
desk.setDepth(1001)
const flower = this.add.sprite(310, 390, 'flowers', Math.floor(Math.random() * 16)).setOrigin(0.5)
flower.setScale(0.8)
flower.setDepth(1100)
errorBug = this.add.sprite(1007, 221, 'error_bug', 0).setOrigin(0.5)
errorBug.setDepth(50)
errorBug.setScale(0.9)
errorBug.setVisible(false)
starWorking = this.add.sprite(217, 343, 'star_working', 0).setOrigin(0.5)
starWorking.setVisible(false)
starWorking.setScale(0.9)
starWorking.setDepth(900)
syncAnim = this.add.sprite(1157, 592, 'sync_anim', 0).setOrigin(0.5)
syncAnim.setDepth(40)
syncAnim.setVisible(false)
applyMainState('idle', STATUS_META.idle.detail)
randomizeDemoVisitors()
renderVisitors([])
pollOfficeState(true)
loadMemo()
}
function update(time) {
if (time - lastPollAt > POLL_MS) {
pollOfficeState()
}
if (time - lastVisitorShuffleAt > 8000) {
lastVisitorShuffleAt = time
randomizeDemoVisitors()
if (lastRemoteAgents.filter((agent) => !agent.isMain).length === 0) {
renderVisitors(lastRemoteAgents)
}
}
maybeShowMainBubble(time)
maybeShowCatBubble(time)
}
const config = {
type: Phaser.AUTO,
width: GAME_WIDTH,
height: GAME_HEIGHT,
parent: 'game-container',
pixelArt: true,
physics: {
default: 'arcade',
arcade: { gravity: { y: 0 }, debug: false },
},
scene: { preload, create, update },
}
setLoading(0, 'Booting Star Office runtime...')
new Phaser.Game(config)
})()

File diff suppressed because one or more lines are too long