gitworld / src /main.ts
SNAPKITTYWEST's picture
push from SNAPKITTYWEST/gitworld
6281708 verified
Raw
History Blame Contribute Delete
5.5 kB
import { EVENT_TYPES, TOOLBELT, commandTranscript } from './shared.js';
import {
advanceWorldState,
createWorldState,
getNearbyDistrict,
renderMinimap,
renderWorld,
worldReducer
} from './world-architect.js';
import { bindUi, renderHud } from './ui-reality.js';
const app = document;
const worldViewport = app.getElementById('world-viewport');
const minimap = app.getElementById('minimap-viewport');
let state = createWorldState();
function assetPath(relativePath) {
const root = './phase2-unreal/ReferenceSource/';
const UrlConstructor = window.URL;
return new UrlConstructor(root + relativePath, window.location.href).href;
}
function setupBrowserScene() {
const opening = app.getElementById('opening-scene');
const video = app.getElementById('opening-video');
const enter = app.querySelector('[data-action="enter-game"]');
const sound = app.querySelector('[data-action="toggle-opening-sound"]');
const status = app.getElementById('opening-status');
if (!opening || !video || !enter) return;
const enterWorld = () => {
opening.classList.add('is-dismissed');
opening.setAttribute('aria-hidden', 'true');
video.pause();
worldViewport?.focus();
};
try {
video.src = assetPath('Opening/3099.mp4');
video.poster = assetPath('NeonReflections/3090.webp');
video.load();
const openingFallback = window.setTimeout(() => {
if (video.readyState < 2) enterWorld();
}, 2500);
video.play().catch(() => {
if (status) status.textContent = 'Opening scene ready 路 press ENTER THE WORLD';
});
video.addEventListener('playing', () => {
window.clearTimeout(openingFallback);
if (status) status.textContent = 'Playing 3099.mp4 路 opening scene';
}, { once: true });
video.addEventListener('error', () => {
if (status) status.textContent = 'Opening media unavailable 路 entering browser scene';
window.setTimeout(enterWorld, 700);
}, { once: true });
} catch {
if (status) status.textContent = 'Opening media unavailable 路 entering browser scene';
enterWorld();
}
enter.addEventListener('click', enterWorld);
sound?.addEventListener('click', () => {
video.muted = !video.muted;
sound.textContent = video.muted ? 'SOUND OFF' : 'SOUND ON';
});
try {
app.querySelectorAll('[data-reference-image]').forEach((plate) => {
plate.setAttribute('poster', assetPath(plate.dataset.referenceImage || ''));
});
} catch {
// Reference plates are optional presentation assets; never block gameplay.
}
}
function dispatch(event) {
state = worldReducer(state, event);
window.gitWorldState = state;
render();
}
function render() {
const nearby = getNearbyDistrict(state);
if (worldViewport) renderWorld(worldViewport, state, (districtId) => dispatch({ type: EVENT_TYPES.SELECT_DISTRICT, districtId }));
if (minimap) renderMinimap(minimap, state);
renderHud(app, state, nearby);
}
function move(dx, dy) {
dispatch({ type: EVENT_TYPES.PLAYER_MOVE, dx, dy });
}
function useToolByKey(key) {
const tool = TOOLBELT.find((candidate) => candidate.key === key);
if (!tool) return;
dispatch({ type: EVENT_TYPES.USE_TOOL, toolId: tool.id, message: `${tool.name}: ${tool.verb}.` });
}
function inspectNearest() {
const nearby = getNearbyDistrict(state);
if (nearby.inRange) {
dispatch({ type: EVENT_TYPES.ENTER_DISTRICT, districtId: nearby.district.id });
} else {
dispatch({ type: EVENT_TYPES.SELECT_DISTRICT, districtId: nearby.district.id });
}
}
function onKeyDown(event) {
const target = event.target;
const typing = target instanceof HTMLInputElement || target instanceof HTMLTextAreaElement;
if (typing && event.key !== 'Escape') return;
const key = event.key.toLowerCase();
const movement = {
w: [0, -1], arrowup: [0, -1],
s: [0, 1], arrowdown: [0, 1],
a: [-1, 0], arrowleft: [-1, 0],
d: [1, 0], arrowright: [1, 0]
}[key];
if (movement) {
event.preventDefault();
move(movement[0], movement[1]);
return;
}
if (key === 'e' || event.key === 'Enter') {
event.preventDefault();
inspectNearest();
return;
}
if (key === 't') {
event.preventDefault();
dispatch({ type: EVENT_TYPES.OPEN_TERMINAL });
return;
}
if (key === 'i') {
event.preventDefault();
dispatch({ type: EVENT_TYPES.OPEN_INSPECTOR });
return;
}
if (/^[1-8]$/.test(event.key)) {
event.preventDefault();
useToolByKey(event.key);
return;
}
if (event.key === 'Escape') {
dispatch(state.terminal.open ? { type: EVENT_TYPES.CLOSE_TERMINAL } : { type: EVENT_TYPES.CLOSE_INSPECTOR });
}
}
bindUi(app, dispatch);
app.addEventListener('keydown', onKeyDown);
window.addEventListener('resize', render);
window.gitWorldState = state;
render();
setupBrowserScene();
// Fixed-step browser-local simulation: the world advances on a deterministic clock,
// while the render layer simply reflects the latest serializable state.
window.setInterval(() => {
const advanced = advanceWorldState(state);
state = advanced.state;
window.gitWorldState = state;
render();
}, 1600);
// Keep the transcript helper reachable for the UI's delegated terminal buttons in
// environments where the app is embedded without a module bundler.
window.gitWorldCommandTranscript = commandTranscript;