@jjlmoya/utils-games-development 1.64.0 → 1.66.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/category/index.ts +2 -1
- package/src/entries.ts +4 -1
- package/src/tests/bibliography_wellformed_export.test.ts +46 -0
- package/src/tests/locale_completeness.test.ts +2 -2
- package/src/tests/seo_parity.test.ts +2 -2
- package/src/tests/seo_translation_completeness.test.ts +69 -0
- package/src/tests/seo_wellformed_export.test.ts +65 -0
- package/src/tests/tool_validation.test.ts +2 -2
- package/src/tests/translation_copy.test.ts +21 -18
- package/src/tool/audioLoopPointFinder/i18n/de.ts +13 -0
- package/src/tool/audioLoopPointFinder/i18n/fr.ts +26 -0
- package/src/tool/audioLoopPointFinder/i18n/id.ts +4 -0
- package/src/tool/audioLoopPointFinder/i18n/it.ts +3 -0
- package/src/tool/audioLoopPointFinder/i18n/nl.ts +4 -0
- package/src/tool/audioLoopPointFinder/i18n/pl.ts +4 -0
- package/src/tool/audioLoopPointFinder/i18n/pt.ts +3 -0
- package/src/tool/audioLoopPointFinder/i18n/ru.ts +4 -0
- package/src/tool/audioLoopPointFinder/i18n/sv.ts +4 -0
- package/src/tool/audioLoopPointFinder/i18n/tr.ts +4 -0
- package/src/tool/audioLoopPointFinder/i18n/zh.ts +4 -0
- package/src/tool/damageFormulaLab/bibliography.astro +3 -21
- package/src/tool/damageFormulaLab/i18n/de.ts +13 -0
- package/src/tool/damageFormulaLab/i18n/fr.ts +13 -0
- package/src/tool/damageFormulaLab/i18n/id.ts +13 -0
- package/src/tool/damageFormulaLab/i18n/it.ts +1 -0
- package/src/tool/damageFormulaLab/i18n/ja.ts +1 -0
- package/src/tool/damageFormulaLab/i18n/ko.ts +1 -0
- package/src/tool/damageFormulaLab/i18n/nl.ts +1 -0
- package/src/tool/damageFormulaLab/i18n/pl.ts +1 -0
- package/src/tool/damageFormulaLab/i18n/pt.ts +1 -0
- package/src/tool/damageFormulaLab/i18n/ru.ts +2 -0
- package/src/tool/damageFormulaLab/i18n/sv.ts +2 -0
- package/src/tool/damageFormulaLab/i18n/tr.ts +2 -0
- package/src/tool/damageFormulaLab/i18n/zh.ts +2 -0
- package/src/tool/damageFormulaLab/seo.astro +7 -5
- package/src/tool/gameDeltaTimeFixedTimestepLab/bibliography.astro +6 -0
- package/src/tool/gameDeltaTimeFixedTimestepLab/bibliography.ts +10 -0
- package/src/tool/gameDeltaTimeFixedTimestepLab/component.astro +51 -0
- package/src/tool/gameDeltaTimeFixedTimestepLab/controller.ts +79 -0
- package/src/tool/gameDeltaTimeFixedTimestepLab/dom-views.ts +88 -0
- package/src/tool/gameDeltaTimeFixedTimestepLab/entry.ts +27 -0
- package/src/tool/gameDeltaTimeFixedTimestepLab/evaluator.ts +21 -0
- package/src/tool/gameDeltaTimeFixedTimestepLab/game-delta-time-fixed-timestep-lab.css +491 -0
- package/src/tool/gameDeltaTimeFixedTimestepLab/i18n/de.ts +56 -0
- package/src/tool/gameDeltaTimeFixedTimestepLab/i18n/en.ts +180 -0
- package/src/tool/gameDeltaTimeFixedTimestepLab/i18n/es.ts +56 -0
- package/src/tool/gameDeltaTimeFixedTimestepLab/i18n/fr.ts +46 -0
- package/src/tool/gameDeltaTimeFixedTimestepLab/i18n/id.ts +46 -0
- package/src/tool/gameDeltaTimeFixedTimestepLab/i18n/it.ts +46 -0
- package/src/tool/gameDeltaTimeFixedTimestepLab/i18n/ja.ts +46 -0
- package/src/tool/gameDeltaTimeFixedTimestepLab/i18n/ko.ts +46 -0
- package/src/tool/gameDeltaTimeFixedTimestepLab/i18n/nl.ts +46 -0
- package/src/tool/gameDeltaTimeFixedTimestepLab/i18n/pl.ts +46 -0
- package/src/tool/gameDeltaTimeFixedTimestepLab/i18n/pt.ts +46 -0
- package/src/tool/gameDeltaTimeFixedTimestepLab/i18n/ru.ts +46 -0
- package/src/tool/gameDeltaTimeFixedTimestepLab/i18n/sv.ts +46 -0
- package/src/tool/gameDeltaTimeFixedTimestepLab/i18n/tr.ts +46 -0
- package/src/tool/gameDeltaTimeFixedTimestepLab/i18n/zh.ts +46 -0
- package/src/tool/gameDeltaTimeFixedTimestepLab/index.ts +11 -0
- package/src/tool/gameDeltaTimeFixedTimestepLab/logic.test.ts +35 -0
- package/src/tool/gameDeltaTimeFixedTimestepLab/logic.ts +140 -0
- package/src/tool/gameDeltaTimeFixedTimestepLab/seo.astro +15 -0
- package/src/tool/gameDeltaTimeFixedTimestepLab/seo_contract.test.ts +25 -0
- package/src/tool/gameDeltaTimeFixedTimestepLab/storage.ts +23 -0
- package/src/tool/gameDeltaTimeFixedTimestepLab/ui.ts +54 -0
- package/src/tool/gameUIAccessibilityTester/i18n/de.ts +13 -0
- package/src/tool/gameUIAccessibilityTester/i18n/fr.ts +13 -0
- package/src/tool/gameUIAccessibilityTester/i18n/id.ts +13 -0
- package/src/tool/gameUIAccessibilityTester/i18n/it.ts +1 -0
- package/src/tool/gameUIAccessibilityTester/i18n/nl.ts +5 -0
- package/src/tool/gameUIAccessibilityTester/i18n/pl.ts +5 -0
- package/src/tool/gameUIAccessibilityTester/i18n/pt.ts +2 -0
- package/src/tool/gameUIAccessibilityTester/i18n/ru.ts +1 -0
- package/src/tool/gameUIAccessibilityTester/i18n/sv.ts +1 -0
- package/src/tool/gameUIAccessibilityTester/i18n/tr.ts +2 -0
- package/src/tool/gameUIAccessibilityTester/i18n/zh.ts +1 -0
- package/src/tool/hitboxHurtboxAnimator/i18n/de.ts +14 -0
- package/src/tool/hitboxHurtboxAnimator/i18n/fr.ts +13 -0
- package/src/tool/hitboxHurtboxAnimator/i18n/id.ts +13 -0
- package/src/tool/hitboxHurtboxAnimator/i18n/it.ts +1 -0
- package/src/tool/hitboxHurtboxAnimator/i18n/nl.ts +1 -0
- package/src/tool/hitboxHurtboxAnimator/i18n/pl.ts +2 -0
- package/src/tool/hitboxHurtboxAnimator/i18n/pt.ts +2 -0
- package/src/tool/hitboxHurtboxAnimator/i18n/ru.ts +2 -0
- package/src/tool/hitboxHurtboxAnimator/i18n/sv.ts +1 -0
- package/src/tool/hitboxHurtboxAnimator/i18n/tr.ts +2 -0
- package/src/tool/hitboxHurtboxAnimator/seo.astro +7 -5
- package/src/tool/itchioGameTester/i18n/de.ts +13 -0
- package/src/tool/itchioGameTester/i18n/es.ts +2 -2
- package/src/tool/itchioGameTester/i18n/fr.ts +26 -0
- package/src/tool/itchioGameTester/i18n/id.ts +3 -0
- package/src/tool/itchioGameTester/i18n/it.ts +3 -0
- package/src/tool/itchioGameTester/i18n/ja.ts +3 -0
- package/src/tool/itchioGameTester/i18n/ko.ts +3 -0
- package/src/tool/itchioGameTester/i18n/nl.ts +3 -0
- package/src/tool/itchioGameTester/i18n/pl.ts +3 -0
- package/src/tool/itchioGameTester/i18n/pt.ts +3 -0
- package/src/tool/itchioGameTester/i18n/ru.ts +3 -0
- package/src/tool/itchioGameTester/i18n/sv.ts +3 -0
- package/src/tool/itchioGameTester/i18n/tr.ts +3 -0
- package/src/tool/itchioGameTester/i18n/zh.ts +3 -0
- package/src/tool/localizationSanitizer/i18n/de.ts +13 -0
- package/src/tool/localizationSanitizer/i18n/es.ts +26 -0
- package/src/tool/localizationSanitizer/i18n/fr.ts +4 -0
- package/src/tool/localizationSanitizer/i18n/id.ts +4 -0
- package/src/tool/localizationSanitizer/i18n/it.ts +4 -0
- package/src/tool/localizationSanitizer/i18n/ja.ts +4 -0
- package/src/tool/localizationSanitizer/i18n/ko.ts +4 -0
- package/src/tool/localizationSanitizer/i18n/nl.ts +5 -0
- package/src/tool/localizationSanitizer/i18n/pl.ts +5 -0
- package/src/tool/localizationSanitizer/i18n/pt.ts +5 -0
- package/src/tool/localizationSanitizer/i18n/ru.ts +5 -0
- package/src/tool/localizationSanitizer/i18n/sv.ts +5 -0
- package/src/tool/localizationSanitizer/i18n/tr.ts +5 -0
- package/src/tool/localizationSanitizer/i18n/zh.ts +6 -0
- package/src/tool/pixelArtPaletteSwapper/i18n/ja.ts +13 -0
- package/src/tool/pixelArtPaletteSwapper/i18n/ko.ts +13 -0
- package/src/tool/pixelArtPaletteSwapper/i18n/zh.ts +18 -0
- package/src/tool/retroSfxGenerator/i18n/de.ts +13 -0
- package/src/tool/retroSfxGenerator/i18n/fr.ts +13 -0
- package/src/tool/retroSfxGenerator/i18n/id.ts +13 -0
- package/src/tool/retroSfxGenerator/i18n/it.ts +1 -0
- package/src/tool/retroSfxGenerator/i18n/nl.ts +1 -0
- package/src/tool/retroSfxGenerator/i18n/pl.ts +1 -0
- package/src/tool/retroSfxGenerator/i18n/pt.ts +1 -0
- package/src/tool/retroSfxGenerator/i18n/ru.ts +1 -0
- package/src/tool/retroSfxGenerator/i18n/sv.ts +2 -0
- package/src/tool/retroSfxGenerator/i18n/tr.ts +1 -0
- package/src/tool/retroSfxGenerator/i18n/zh.ts +1 -0
- package/src/tool/spriteSheetPacker/i18n/de.ts +13 -0
- package/src/tool/spriteSheetPacker/i18n/es.ts +2 -2
- package/src/tool/spriteSheetPacker/i18n/fr.ts +26 -0
- package/src/tool/spriteSheetPacker/i18n/id.ts +4 -0
- package/src/tool/spriteSheetPacker/i18n/it.ts +4 -0
- package/src/tool/spriteSheetPacker/i18n/ja.ts +3 -0
- package/src/tool/spriteSheetPacker/i18n/ko.ts +3 -0
- package/src/tool/spriteSheetPacker/i18n/nl.ts +4 -0
- package/src/tool/spriteSheetPacker/i18n/pl.ts +4 -0
- package/src/tool/spriteSheetPacker/i18n/pt.ts +4 -0
- package/src/tool/spriteSheetPacker/i18n/ru.ts +4 -0
- package/src/tool/spriteSheetPacker/i18n/sv.ts +4 -0
- package/src/tool/spriteSheetPacker/i18n/tr.ts +4 -0
- package/src/tool/spriteSheetPacker/i18n/zh.ts +4 -0
- package/src/tool/steamBbcodeTranslator/i18n/de.ts +22 -0
- package/src/tool/steamBbcodeTranslator/i18n/es.ts +23 -1
- package/src/tool/steamBbcodeTranslator/i18n/fr.ts +6 -1
- package/src/tool/steamBbcodeTranslator/i18n/id.ts +7 -1
- package/src/tool/steamBbcodeTranslator/i18n/it.ts +6 -1
- package/src/tool/steamBbcodeTranslator/i18n/ja.ts +5 -1
- package/src/tool/steamBbcodeTranslator/i18n/ko.ts +5 -1
- package/src/tool/steamBbcodeTranslator/i18n/nl.ts +7 -1
- package/src/tool/steamBbcodeTranslator/i18n/pl.ts +7 -1
- package/src/tool/steamBbcodeTranslator/i18n/pt.ts +7 -1
- package/src/tool/steamBbcodeTranslator/i18n/ru.ts +7 -1
- package/src/tool/steamBbcodeTranslator/i18n/sv.ts +7 -1
- package/src/tool/steamBbcodeTranslator/i18n/tr.ts +7 -1
- package/src/tool/steamBbcodeTranslator/i18n/zh.ts +5 -1
- package/src/tool/steamCapsuleGenerator/i18n/de.ts +2 -2
- package/src/tool/steamCapsuleGenerator/i18n/es.ts +2 -2
- package/src/tool/steamCapsuleGenerator/i18n/fr.ts +2 -2
- package/src/tool/steamCapsuleGenerator/i18n/pl.ts +13 -0
- package/src/tool/steamCapsuleGenerator/i18n/ru.ts +26 -0
- package/src/tool/steamCapsuleGenerator/i18n/sv.ts +1 -0
- package/src/tool/steamCapsuleGenerator/i18n/tr.ts +1 -0
- package/src/tools.ts +2 -0
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { normalizeLoopConfig, simulateLoop } from './logic';
|
|
3
|
+
|
|
4
|
+
describe('game delta time fixed timestep lab', () => {
|
|
5
|
+
it('keeps both models aligned without frame spikes', () => {
|
|
6
|
+
const result = simulateLoop({ fps: 60, spikeMs: 0, durationSeconds: 1, velocity: 10 });
|
|
7
|
+
|
|
8
|
+
expect(result.variablePosition).toBeCloseTo(10, 4);
|
|
9
|
+
expect(result.fixedPosition).toBeCloseTo(10, 3);
|
|
10
|
+
expect(result.divergence).toBeCloseTo(0, 3);
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it('makes an unclamped variable model run ahead after spikes', () => {
|
|
14
|
+
const result = simulateLoop({ fps: 60, spikeMs: 80, spikeEveryFrames: 30, durationSeconds: 1, velocity: 10 });
|
|
15
|
+
|
|
16
|
+
expect(result.variablePosition).toBeGreaterThan(result.fixedPosition);
|
|
17
|
+
expect(result.maxCatchUp).toBeGreaterThan(1);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('shows the time discarded by a delta clamp', () => {
|
|
21
|
+
const result = simulateLoop({ fps: 60, spikeMs: 80, spikeEveryFrames: 30, durationSeconds: 1, velocity: 10, clampEnabled: true, clampMs: 20 });
|
|
22
|
+
|
|
23
|
+
expect(result.variableTime).toBeLessThan(result.totalWallTime);
|
|
24
|
+
expect(result.variablePosition).toBeLessThan(result.fixedPosition);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('normalizes invalid values to safe experiment bounds', () => {
|
|
28
|
+
const config = normalizeLoopConfig({ fps: 0, fixedDtMs: -1, durationSeconds: Number.NaN, spikeMs: 900 });
|
|
29
|
+
|
|
30
|
+
expect(config.fps).toBe(60);
|
|
31
|
+
expect(config.fixedDtMs).toBeCloseTo(16.666, 3);
|
|
32
|
+
expect(config.durationSeconds).toBe(6);
|
|
33
|
+
expect(config.spikeMs).toBe(500);
|
|
34
|
+
});
|
|
35
|
+
});
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
export interface LoopConfig {
|
|
2
|
+
fps: number;
|
|
3
|
+
spikeMs: number;
|
|
4
|
+
spikeEveryFrames: number;
|
|
5
|
+
fixedDtMs: number;
|
|
6
|
+
velocity: number;
|
|
7
|
+
durationSeconds: number;
|
|
8
|
+
clampEnabled: boolean;
|
|
9
|
+
clampMs: number;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface LoopSample {
|
|
13
|
+
frame: number;
|
|
14
|
+
wallTime: number;
|
|
15
|
+
variableTime: number;
|
|
16
|
+
fixedTime: number;
|
|
17
|
+
variablePosition: number;
|
|
18
|
+
fixedPosition: number;
|
|
19
|
+
fixedSteps: number;
|
|
20
|
+
frameMs: number;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface LoopResult {
|
|
24
|
+
config: LoopConfig;
|
|
25
|
+
samples: LoopSample[];
|
|
26
|
+
frameCount: number;
|
|
27
|
+
totalWallTime: number;
|
|
28
|
+
variableTime: number;
|
|
29
|
+
fixedTime: number;
|
|
30
|
+
variablePosition: number;
|
|
31
|
+
fixedPosition: number;
|
|
32
|
+
divergence: number;
|
|
33
|
+
fixedSteps: number;
|
|
34
|
+
maxCatchUp: number;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export const DEFAULT_LOOP_CONFIG: LoopConfig = {
|
|
38
|
+
fps: 60,
|
|
39
|
+
spikeMs: 80,
|
|
40
|
+
spikeEveryFrames: 30,
|
|
41
|
+
fixedDtMs: 16.666,
|
|
42
|
+
velocity: 20,
|
|
43
|
+
durationSeconds: 6,
|
|
44
|
+
clampEnabled: false,
|
|
45
|
+
clampMs: 100,
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
function clampNumber(value: number, minimum: number, maximum: number): number {
|
|
49
|
+
return Math.min(maximum, Math.max(minimum, value));
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function finiteOr(value: number | undefined, fallback: number): number {
|
|
53
|
+
return Number.isFinite(value) ? value as number : fallback;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function positiveOr(value: number | undefined, fallback: number): number {
|
|
57
|
+
const candidate = finiteOr(value, fallback);
|
|
58
|
+
return candidate > 0 ? candidate : fallback;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function normalizeLoopConfig(input: Partial<LoopConfig> = {}): LoopConfig {
|
|
62
|
+
return {
|
|
63
|
+
fps: clampNumber(positiveOr(input.fps, DEFAULT_LOOP_CONFIG.fps), 1, 240),
|
|
64
|
+
spikeMs: clampNumber(finiteOr(input.spikeMs, DEFAULT_LOOP_CONFIG.spikeMs), 0, 500),
|
|
65
|
+
spikeEveryFrames: clampNumber(Math.round(positiveOr(input.spikeEveryFrames, DEFAULT_LOOP_CONFIG.spikeEveryFrames)), 1, 600),
|
|
66
|
+
fixedDtMs: clampNumber(positiveOr(input.fixedDtMs, DEFAULT_LOOP_CONFIG.fixedDtMs), 1, 100),
|
|
67
|
+
velocity: clampNumber(finiteOr(input.velocity, DEFAULT_LOOP_CONFIG.velocity), 0, 100),
|
|
68
|
+
durationSeconds: clampNumber(positiveOr(input.durationSeconds, DEFAULT_LOOP_CONFIG.durationSeconds), 1, 30),
|
|
69
|
+
clampEnabled: input.clampEnabled === true,
|
|
70
|
+
clampMs: clampNumber(positiveOr(input.clampMs, DEFAULT_LOOP_CONFIG.clampMs), 1, 250),
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function frameDeltaMs(config: LoopConfig, frame: number): number {
|
|
75
|
+
const isSpike = config.spikeMs > 0 && frame % config.spikeEveryFrames === 0;
|
|
76
|
+
return 1000 / config.fps + (isSpike ? config.spikeMs : 0);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function fixedStepCount(accumulator: number, fixedDt: number): number {
|
|
80
|
+
return Math.floor((accumulator + Number.EPSILON) / fixedDt);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
interface SimulationState {
|
|
84
|
+
wallTime: number;
|
|
85
|
+
variableTime: number;
|
|
86
|
+
fixedTime: number;
|
|
87
|
+
variablePosition: number;
|
|
88
|
+
fixedPosition: number;
|
|
89
|
+
accumulator: number;
|
|
90
|
+
fixedSteps: number;
|
|
91
|
+
maxCatchUp: number;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function sampleFrame(config: LoopConfig, frame: number, state: SimulationState): LoopSample {
|
|
95
|
+
const actualMs = frameDeltaMs(config, frame);
|
|
96
|
+
const variableMs = config.clampEnabled ? Math.min(actualMs, config.clampMs) : actualMs;
|
|
97
|
+
const actualSeconds = actualMs / 1000;
|
|
98
|
+
const variableSeconds = variableMs / 1000;
|
|
99
|
+
const fixedDt = config.fixedDtMs / 1000;
|
|
100
|
+
state.wallTime += actualSeconds;
|
|
101
|
+
state.variableTime += variableSeconds;
|
|
102
|
+
state.variablePosition += config.velocity * variableSeconds;
|
|
103
|
+
state.accumulator += actualSeconds;
|
|
104
|
+
const steps = fixedStepCount(state.accumulator, fixedDt);
|
|
105
|
+
state.accumulator -= steps * fixedDt;
|
|
106
|
+
state.fixedTime += steps * fixedDt;
|
|
107
|
+
state.fixedPosition += steps * config.velocity * fixedDt;
|
|
108
|
+
state.fixedSteps += steps;
|
|
109
|
+
state.maxCatchUp = Math.max(state.maxCatchUp, steps);
|
|
110
|
+
return {
|
|
111
|
+
frame,
|
|
112
|
+
wallTime: state.wallTime,
|
|
113
|
+
variableTime: state.variableTime,
|
|
114
|
+
fixedTime: state.fixedTime,
|
|
115
|
+
variablePosition: state.variablePosition,
|
|
116
|
+
fixedPosition: state.fixedPosition,
|
|
117
|
+
fixedSteps: state.fixedSteps,
|
|
118
|
+
frameMs: actualMs,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export function simulateLoop(input: Partial<LoopConfig> = {}): LoopResult {
|
|
123
|
+
const config = normalizeLoopConfig(input);
|
|
124
|
+
const frameCount = Math.ceil(config.durationSeconds * config.fps);
|
|
125
|
+
const state: SimulationState = { wallTime: 0, variableTime: 0, fixedTime: 0, variablePosition: 0, fixedPosition: 0, accumulator: 0, fixedSteps: 0, maxCatchUp: 0 };
|
|
126
|
+
const samples = Array.from({ length: frameCount }, (_, index) => sampleFrame(config, index + 1, state));
|
|
127
|
+
return {
|
|
128
|
+
config,
|
|
129
|
+
samples,
|
|
130
|
+
frameCount,
|
|
131
|
+
totalWallTime: state.wallTime,
|
|
132
|
+
variableTime: state.variableTime,
|
|
133
|
+
fixedTime: state.fixedTime,
|
|
134
|
+
variablePosition: state.variablePosition,
|
|
135
|
+
fixedPosition: state.fixedPosition,
|
|
136
|
+
divergence: state.variablePosition - state.fixedPosition,
|
|
137
|
+
fixedSteps: state.fixedSteps,
|
|
138
|
+
maxCatchUp: state.maxCatchUp,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { SEORenderer } from '@jjlmoya/utils-shared';
|
|
3
|
+
import { gameDeltaTimeFixedTimestepLab } from './entry';
|
|
4
|
+
import type { KnownLocale } from '../../types';
|
|
5
|
+
|
|
6
|
+
interface Props {
|
|
7
|
+
locale?: KnownLocale;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const { locale = 'en' } = Astro.props;
|
|
11
|
+
const loader = gameDeltaTimeFixedTimestepLab.i18n[locale] ?? gameDeltaTimeFixedTimestepLab.i18n.en;
|
|
12
|
+
const content = loader ? await loader() : null;
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
{content && content.seo.length > 0 && <SEORenderer content={{ locale, sections: content.seo }} />}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { describe, expect, it } from 'vitest';
|
|
3
|
+
import { gameDeltaTimeFixedTimestepLab } from './entry';
|
|
4
|
+
|
|
5
|
+
describe('Game delta time SEO loader contract', () => {
|
|
6
|
+
it('provides translated SEO sections for every registered locale', async () => {
|
|
7
|
+
const locales = Object.keys(gameDeltaTimeFixedTimestepLab.i18n);
|
|
8
|
+
const contents = await Promise.all(locales.map(async (locale) => gameDeltaTimeFixedTimestepLab.i18n[locale as keyof typeof gameDeltaTimeFixedTimestepLab.i18n]!()));
|
|
9
|
+
|
|
10
|
+
expect(locales).toHaveLength(15);
|
|
11
|
+
contents.forEach((content) => {
|
|
12
|
+
expect(content.seo.length).toBeGreaterThan(0);
|
|
13
|
+
expect(content.faq.length).toBe(5);
|
|
14
|
+
expect(content.howTo.length).toBe(5);
|
|
15
|
+
expect(content.schemas).toHaveLength(3);
|
|
16
|
+
});
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('loads the requested locale before passing its SEO sections to the renderer', () => {
|
|
20
|
+
const source = readFileSync(new URL('./seo.astro', import.meta.url), 'utf8');
|
|
21
|
+
|
|
22
|
+
expect(source).toContain('gameDeltaTimeFixedTimestepLab.i18n[locale]');
|
|
23
|
+
expect(source).toContain('sections: content.seo');
|
|
24
|
+
});
|
|
25
|
+
});
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { LoopConfig } from './logic';
|
|
2
|
+
|
|
3
|
+
const STORAGE_KEY = 'jjlmoya-game-delta-time-lab';
|
|
4
|
+
|
|
5
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
6
|
+
return typeof value === 'object' && value !== null;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function loadLoopConfig(): Partial<LoopConfig> {
|
|
10
|
+
try {
|
|
11
|
+
const raw = window.localStorage.getItem(STORAGE_KEY);
|
|
12
|
+
const parsed: unknown = raw ? JSON.parse(raw) : {};
|
|
13
|
+
return isRecord(parsed) ? parsed as Partial<LoopConfig> : {};
|
|
14
|
+
} catch {
|
|
15
|
+
return {};
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function saveLoopConfig(config: LoopConfig): void {
|
|
20
|
+
try {
|
|
21
|
+
window.localStorage.setItem(STORAGE_KEY, JSON.stringify(config));
|
|
22
|
+
} catch {}
|
|
23
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
export interface GameDeltaTimeFixedTimestepLabUI extends Record<string, string> {
|
|
2
|
+
controlsTitle: string;
|
|
3
|
+
fpsLabel: string;
|
|
4
|
+
fpsHint: string;
|
|
5
|
+
spikeLabel: string;
|
|
6
|
+
spikeHint: string;
|
|
7
|
+
spikeEveryLabel: string;
|
|
8
|
+
fixedDtLabel: string;
|
|
9
|
+
fixedDtHint: string;
|
|
10
|
+
velocityLabel: string;
|
|
11
|
+
durationLabel: string;
|
|
12
|
+
clampLabel: string;
|
|
13
|
+
clampToggle: string;
|
|
14
|
+
runLabel: string;
|
|
15
|
+
resetLabel: string;
|
|
16
|
+
stageKicker: string;
|
|
17
|
+
stageTitle: string;
|
|
18
|
+
stageCaption: string;
|
|
19
|
+
frameTraceLabel: string;
|
|
20
|
+
positionPlotLabel: string;
|
|
21
|
+
differencePlotLabel: string;
|
|
22
|
+
variableLane: string;
|
|
23
|
+
fixedLane: string;
|
|
24
|
+
frameCountLabel: string;
|
|
25
|
+
wallTimeLabel: string;
|
|
26
|
+
variableTimeLabel: string;
|
|
27
|
+
fixedTimeLabel: string;
|
|
28
|
+
divergenceLabel: string;
|
|
29
|
+
stepsLabel: string;
|
|
30
|
+
diagnosticsTitle: string;
|
|
31
|
+
stableStatus: string;
|
|
32
|
+
variableStatus: string;
|
|
33
|
+
fixedStatus: string;
|
|
34
|
+
clampStatus: string;
|
|
35
|
+
timelineTitle: string;
|
|
36
|
+
timelineCaption: string;
|
|
37
|
+
frameAxis: string;
|
|
38
|
+
positionAxis: string;
|
|
39
|
+
legendVariable: string;
|
|
40
|
+
legendFixed: string;
|
|
41
|
+
legendSpike: string;
|
|
42
|
+
tableTitle: string;
|
|
43
|
+
tableFrame: string;
|
|
44
|
+
tableWall: string;
|
|
45
|
+
tableVariable: string;
|
|
46
|
+
tableFixed: string;
|
|
47
|
+
tableDelta: string;
|
|
48
|
+
modelNote: string;
|
|
49
|
+
privacyDisclosure: string;
|
|
50
|
+
unitMs: string;
|
|
51
|
+
unitSeconds: string;
|
|
52
|
+
unitPixels: string;
|
|
53
|
+
statusReady: string;
|
|
54
|
+
}
|
|
@@ -195,6 +195,19 @@ export const content: ToolLocaleContent<GameUiAccessibilityTesterUI> = {
|
|
|
195
195
|
type: 'paragraph',
|
|
196
196
|
html: 'Der herunterladbare JSON-Bericht sowie das Vergleichsbild lassen sich direkt in Issue-Tracker einbinden, um konkrete Verbesserungen an der Benutzeroberfläche zu dokumentieren.',
|
|
197
197
|
},
|
|
198
|
+
{
|
|
199
|
+
type: 'title',
|
|
200
|
+
level: 2,
|
|
201
|
+
text: 'Befunde reproduzierbar dokumentieren',
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
type: 'paragraph',
|
|
205
|
+
html: 'Halten Sie fest, welche Szene, Auflösung und Farbpaarung geprüft wurde. Ein klarer Befund beschreibt das sichtbare Signal, den betroffenen Zustand und eine mögliche Ergänzung durch Form, Symbol, Muster oder Ton.',
|
|
206
|
+
},
|
|
207
|
+
{
|
|
208
|
+
type: 'paragraph',
|
|
209
|
+
html: 'Die Simulation unterstützt eine frühe Designprüfung, misst aber nicht die gesamte Barrierefreiheit eines Spiels. Wiederholen Sie die Prüfung mit echten Spielszenen, verschiedenen Displays und Menschen mit unterschiedlichen Sehbedingungen.',
|
|
210
|
+
},
|
|
198
211
|
],
|
|
199
212
|
faq,
|
|
200
213
|
howTo,
|
|
@@ -195,6 +195,19 @@ export const content: ToolLocaleContent<GameUiAccessibilityTesterUI> = {
|
|
|
195
195
|
type: 'paragraph',
|
|
196
196
|
html: 'Le rapport JSON et le visuel comparatif PNG peuvent être directement joints à vos tickets de développement pour faciliter les corrections de design UI.',
|
|
197
197
|
},
|
|
198
|
+
{
|
|
199
|
+
type: 'title',
|
|
200
|
+
level: 2,
|
|
201
|
+
text: 'Documenter les constats de façon reproductible',
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
type: 'paragraph',
|
|
205
|
+
html: 'Notez la scène, la résolution et la paire de couleurs examinée. Un constat utile décrit le signal visible, l état concerné et une amélioration possible avec une forme, une icône, un motif ou un son.',
|
|
206
|
+
},
|
|
207
|
+
{
|
|
208
|
+
type: 'paragraph',
|
|
209
|
+
html: 'La simulation aide à repérer tôt les problèmes de conception, mais elle ne mesure pas toute l accessibilité d un jeu. Répétez l essai avec de vraies scènes, plusieurs écrans et des personnes aux conditions visuelles différentes.',
|
|
210
|
+
},
|
|
198
211
|
],
|
|
199
212
|
faq,
|
|
200
213
|
howTo,
|
|
@@ -195,6 +195,19 @@ export const content: ToolLocaleContent<GameUiAccessibilityTesterUI> = {
|
|
|
195
195
|
type: 'paragraph',
|
|
196
196
|
html: 'Ekspor laporan JSON dan gambar perbandingan PNG dapat dilampirkan langsung pada tiket pengembangan untuk mempermudah perbaikan antarmuka.',
|
|
197
197
|
},
|
|
198
|
+
{
|
|
199
|
+
type: 'title',
|
|
200
|
+
level: 2,
|
|
201
|
+
text: 'Mendokumentasikan temuan secara konsisten',
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
type: 'paragraph',
|
|
205
|
+
html: 'Catat adegan, resolusi, dan pasangan warna yang diperiksa. Temuan yang berguna menjelaskan sinyal yang terlihat, kondisi yang terdampak, serta perbaikan dengan bentuk, ikon, pola, atau suara.',
|
|
206
|
+
},
|
|
207
|
+
{
|
|
208
|
+
type: 'paragraph',
|
|
209
|
+
html: 'Simulasi membantu menemukan masalah desain lebih awal, tetapi tidak mengukur seluruh aksesibilitas game. Ulangi pemeriksaan dengan adegan nyata, beberapa layar, dan pemain dengan kondisi penglihatan yang beragam.',
|
|
210
|
+
},
|
|
198
211
|
],
|
|
199
212
|
faq,
|
|
200
213
|
howTo,
|
|
@@ -195,6 +195,7 @@ export const content: ToolLocaleContent<GameUiAccessibilityTesterUI> = {
|
|
|
195
195
|
type: 'paragraph',
|
|
196
196
|
html: 'Il report JSON e la scheda PNG scaricabili possono essere allegati ai task di sviluppo per facilitare la correzione dei problemi di interfaccia visiva.',
|
|
197
197
|
},
|
|
198
|
+
{ type: 'paragraph', html: 'Annota scena, risoluzione e coppia di colori esaminata. La simulazione aiuta a individuare problemi di design, ma non sostituisce prove con scene reali, display diversi e persone con condizioni visive differenti.' },
|
|
198
199
|
],
|
|
199
200
|
faq,
|
|
200
201
|
howTo,
|
|
@@ -177,6 +177,10 @@ export const content: ToolLocaleContent<GameUiAccessibilityTesterUI> = {
|
|
|
177
177
|
level: 2,
|
|
178
178
|
text: 'Meet kleuren die beslissingen van spelers beïnvloeden',
|
|
179
179
|
},
|
|
180
|
+
{
|
|
181
|
+
type: 'paragraph',
|
|
182
|
+
html: 'Bekijk daarna dezelfde interface op ware spelgrootte en in beweging. Noteer welke signalen verdwijnen wanneer de achtergrond verandert en combineer kleur met vorm, patroon, tekst of geluid. Zo wordt het rapport een concreet ontwerpplan in plaats van alleen een contrastcijfer.',
|
|
183
|
+
},
|
|
180
184
|
{
|
|
181
185
|
type: 'paragraph',
|
|
182
186
|
html: 'Richt uw metingen op kleurparen die belangrijk zijn voor de speler, zoals vijand en bondgenoot. Voeg vormen of pictogrammen toe als het contrast afneemt onder simulatie.',
|
|
@@ -195,6 +199,7 @@ export const content: ToolLocaleContent<GameUiAccessibilityTesterUI> = {
|
|
|
195
199
|
type: 'paragraph',
|
|
196
200
|
html: 'Het JSON-rapport en de PNG-afbeelding kunnen direct aan uw taken worden toegevoegd om designverbeteringen te bespreken.',
|
|
197
201
|
},
|
|
202
|
+
{ type: 'paragraph', html: 'Noteer scène, resolutie en het onderzochte kleurpaar. De simulatie helpt ontwerpproblemen vroeg te vinden, maar vervangt geen tests met echte scènes, verschillende schermen en mensen met uiteenlopende visuele omstandigheden.' },
|
|
198
203
|
],
|
|
199
204
|
faq,
|
|
200
205
|
howTo,
|
|
@@ -181,6 +181,10 @@ export const content: ToolLocaleContent<GameUiAccessibilityTesterUI> = {
|
|
|
181
181
|
type: 'paragraph',
|
|
182
182
|
html: 'Skup się na parach kolorów kluczowych dla rozgrywki, takich jak wrok i sojusznik. Jeśli kontrast spada, rozważ dodanie ikony lub kształtu.',
|
|
183
183
|
},
|
|
184
|
+
{
|
|
185
|
+
type: 'paragraph',
|
|
186
|
+
html: 'Następnie obejrzyj ten sam interfejs w docelowej skali i podczas ruchu. Zapisz, które sygnały znikają po zmianie tła, a potem połącz kolor z kształtem, wzorem, tekstem lub dźwiękiem. Dzięki temu raport prowadzi do konkretnych poprawek projektu, a nie kończy się na pojedynczej wartości kontrastu.',
|
|
187
|
+
},
|
|
184
188
|
{
|
|
185
189
|
type: 'tip',
|
|
186
190
|
title: 'Testuj dynamiczne kadry z gry',
|
|
@@ -195,6 +199,7 @@ export const content: ToolLocaleContent<GameUiAccessibilityTesterUI> = {
|
|
|
195
199
|
type: 'paragraph',
|
|
196
200
|
html: 'Raport JSON oraz obraz PNG można dołączyć do zadań w systemie zarządzania projektem, aby ułatwić komunikację z zespołem.',
|
|
197
201
|
},
|
|
202
|
+
{ type: 'paragraph', html: 'Zapisz scenę, rozdzielczość i badaną parę kolorów. Symulacja pomaga wcześnie znaleźć problemy projektu, ale nie zastępuje testów z prawdziwymi scenami, różnymi ekranami i osobami o różnych warunkach widzenia.' },
|
|
198
203
|
],
|
|
199
204
|
faq,
|
|
200
205
|
howTo,
|
|
@@ -195,6 +195,8 @@ export const content: ToolLocaleContent<GameUiAccessibilityTesterUI> = {
|
|
|
195
195
|
type: 'paragraph',
|
|
196
196
|
html: 'O relatório JSON e a folha comparativa PNG podem ser anexados às tarefas de desenvolvimento para facilitar a correção de problemas de UI.',
|
|
197
197
|
},
|
|
198
|
+
{ type: 'paragraph', html: 'Registre a cena, a resolução e o par de cores analisado. A simulação ajuda a encontrar problemas de design cedo, mas não substitui testes com cenas reais, telas diferentes e pessoas com condições visuais variadas.' },
|
|
199
|
+
{ type: 'paragraph', html: 'Veja a mesma interface na escala final e durante o movimento. Anote quais sinais desaparecem quando o fundo muda e combine cor com forma, padrão, texto ou som. Assim, o relatório gera ações concretas de design em vez de ficar limitado a um único número de contraste.' },
|
|
198
200
|
],
|
|
199
201
|
faq,
|
|
200
202
|
howTo,
|
|
@@ -195,6 +195,7 @@ export const content: ToolLocaleContent<GameUiAccessibilityTesterUI> = {
|
|
|
195
195
|
type: 'paragraph',
|
|
196
196
|
html: 'Файл отчета JSON и сравнительное изображение PNG можно прикреплять к задачам разработки для обсуждения исправлений UI.',
|
|
197
197
|
},
|
|
198
|
+
{ type: 'paragraph', html: 'Запишите сцену, разрешение и проверенную пару цветов. Симуляция помогает рано находить проблемы дизайна, но не заменяет тесты с реальными сценами, разными экранами и людьми с различными особенностями зрения.' },
|
|
198
199
|
],
|
|
199
200
|
faq,
|
|
200
201
|
howTo,
|
|
@@ -195,6 +195,7 @@ export const content: ToolLocaleContent<GameUiAccessibilityTesterUI> = {
|
|
|
195
195
|
type: 'paragraph',
|
|
196
196
|
html: 'JSON-rapporten och PNG-jämförelsebilden kan bifogas i ert projekthanteringsverktyg för att underlätta kommunikationen kring gränssnittsändringar.',
|
|
197
197
|
},
|
|
198
|
+
{ type: 'paragraph', html: 'Anteckna scen, upplösning och färgparet som granskades. Simuleringen hjälper till att hitta designproblem tidigt, men ersätter inte tester med riktiga scener, olika skärmar och personer med varierande synförhållanden.' },
|
|
198
199
|
],
|
|
199
200
|
faq,
|
|
200
201
|
howTo,
|
|
@@ -195,6 +195,8 @@ export const content: ToolLocaleContent<GameUiAccessibilityTesterUI> = {
|
|
|
195
195
|
type: 'paragraph',
|
|
196
196
|
html: 'İndirilebilir JSON raporu ve PNG görseli, arayüz düzeltmelerini kolaylaştırmak için görev yönetimi sisteminize eklenebilir.',
|
|
197
197
|
},
|
|
198
|
+
{ type: 'paragraph', html: 'İncelenen sahneyi, çözünürlüğü ve renk çiftini kaydedin. Simülasyon tasarım sorunlarını erken bulmaya yardımcı olur, ancak gerçek sahneler, farklı ekranlar ve çeşitli görme koşullarına sahip kişilerle yapılan testlerin yerini tutmaz.' },
|
|
199
|
+
{ type: 'paragraph', html: 'Aynı arayüzü son oyun ölçeğinde ve hareket hâlindeyken inceleyin. Arka plan değiştiğinde hangi sinyallerin kaybolduğunu not edin ve rengi şekil, desen, metin veya sesle destekleyin. Böylece rapor tek bir kontrast sayısında kalmaz, doğrudan uygulanabilir tasarım görevleri üretir.' },
|
|
198
200
|
],
|
|
199
201
|
faq,
|
|
200
202
|
howTo,
|
|
@@ -195,6 +195,7 @@ export const content: ToolLocaleContent<GameUiAccessibilityTesterUI> = {
|
|
|
195
195
|
type: 'paragraph',
|
|
196
196
|
html: '导出的 JSON 报告与 PNG 对比图可直接附在 Jira 或 GitHub Issue 等任务单中,帮助设计师与工程师针对具体场景展开讨论与修改。',
|
|
197
197
|
},
|
|
198
|
+
{ type: 'paragraph', html: '记录检查过的场景、分辨率和颜色组合。模拟可以帮助及早发现设计问题,但不能代替真实场景、不同屏幕以及不同视觉条件用户参与的测试。' },
|
|
198
199
|
],
|
|
199
200
|
faq,
|
|
200
201
|
howTo,
|
|
@@ -121,6 +121,7 @@ export const content: ToolLocaleContent<HitboxHurtboxAnimatorUI> = {
|
|
|
121
121
|
importJson: 'JSON importieren',
|
|
122
122
|
exportContactSheet: 'Übersichtsbild herunterladen',
|
|
123
123
|
resetTool: 'Ebenen zurücksetzen',
|
|
124
|
+
resetProject: 'Kollisionsebenen löschen',
|
|
124
125
|
undo: 'Rückgängig',
|
|
125
126
|
redo: 'Wiederholen',
|
|
126
127
|
statusReady: 'Arbeitsfläche bereit.',
|
|
@@ -186,6 +187,19 @@ export const content: ToolLocaleContent<HitboxHurtboxAnimatorUI> = {
|
|
|
186
187
|
type: 'paragraph',
|
|
187
188
|
html: 'Das Übersichts-PNG zeigt alle Frames mit ihren eingezeichneten Ebenen auf einer Fläche. Nutzen Sie es zusammen mit dem JSON-Export für die Abstimmung zwischen Grafik, Design und Programmierung.',
|
|
188
189
|
},
|
|
190
|
+
{
|
|
191
|
+
type: 'title',
|
|
192
|
+
level: 2,
|
|
193
|
+
text: 'Aktive Frames und Koordinaten gemeinsam testen',
|
|
194
|
+
},
|
|
195
|
+
{
|
|
196
|
+
type: 'paragraph',
|
|
197
|
+
html: 'Eine gute Kollisionsform folgt nicht nur der Silhouette, sondern auch dem Zeitpunkt der Aktion. Prüfen Sie Start, aktive Phase und Erholung getrennt und vergleichen Sie die Boxen mit dem Export, damit ein Treffer nicht vor der sichtbaren Bewegung oder nach ihrem Ende auslöst.',
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
type: 'paragraph',
|
|
201
|
+
html: 'Die Vorschau dokumentiert Geometrie und Layer, nicht die vollständige Kollisionslogik Ihrer Engine. Testen Sie Reichweite, Priorität, Pushback und Netzwerkverhalten anschließend mit dem tatsächlich eingebundenen Projekt.',
|
|
202
|
+
},
|
|
189
203
|
],
|
|
190
204
|
faq,
|
|
191
205
|
bibliographyTitle: 'Referenzen zur Kollisionsentwicklung',
|
|
@@ -186,6 +186,19 @@ export const content: ToolLocaleContent<HitboxHurtboxAnimatorUI> = {
|
|
|
186
186
|
type: 'paragraph',
|
|
187
187
|
html: 'La planche de contact PNG affiche toutes les images et leurs calques sur un seul visuel, idéal pour échanger entre graphistes et développeurs.',
|
|
188
188
|
},
|
|
189
|
+
{
|
|
190
|
+
type: 'title',
|
|
191
|
+
level: 2,
|
|
192
|
+
text: 'Tester les frames actives et les coordonnées ensemble',
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
type: 'paragraph',
|
|
196
|
+
html: 'Une bonne forme de collision suit la silhouette mais aussi le moment de l action. Vérifiez séparément le début, la phase active et la récupération, puis comparez les boîtes exportées afin qu un coup ne se déclenche pas avant le mouvement visible ni après sa fin.',
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
type: 'paragraph',
|
|
200
|
+
html: 'La prévisualisation documente la géométrie et les calques, pas toute la logique de collision du moteur. Testez ensuite la portée, la priorité, le recul et le comportement réseau dans le projet réellement intégré.',
|
|
201
|
+
},
|
|
189
202
|
],
|
|
190
203
|
faq,
|
|
191
204
|
bibliographyTitle: 'Références sur la gestion des collisions',
|
|
@@ -186,6 +186,19 @@ export const content: ToolLocaleContent<HitboxHurtboxAnimatorUI> = {
|
|
|
186
186
|
type: 'paragraph',
|
|
187
187
|
html: 'Lembaran kontak PNG menampilkan semua bingkai dan lapisan tabrakan dalam satu gambar untuk mempermudah komunikasi tim.',
|
|
188
188
|
},
|
|
189
|
+
{
|
|
190
|
+
type: 'title',
|
|
191
|
+
level: 2,
|
|
192
|
+
text: 'Menguji frame aktif dan koordinat secara bersamaan',
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
type: 'paragraph',
|
|
196
|
+
html: 'Bentuk collision yang baik mengikuti siluet sekaligus waktu aksi. Periksa fase awal, aktif, dan pemulihan secara terpisah, lalu bandingkan box hasil ekspor agar hit tidak aktif sebelum gerakan terlihat atau setelah gerakan berakhir.',
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
type: 'paragraph',
|
|
200
|
+
html: 'Pratinjau mendokumentasikan geometri dan layer, bukan seluruh logika collision pada engine. Uji jangkauan, prioritas, pushback, dan perilaku jaringan di proyek yang benar-benar terintegrasi.',
|
|
201
|
+
},
|
|
189
202
|
],
|
|
190
203
|
faq,
|
|
191
204
|
bibliographyTitle: 'Referensi desain area tabrakan',
|
|
@@ -186,6 +186,7 @@ export const content: ToolLocaleContent<HitboxHurtboxAnimatorUI> = {
|
|
|
186
186
|
type: 'paragraph',
|
|
187
187
|
html: 'La scheda di contatto PNG mostra tutti i fotogrammi e i loro livelli di colore su un unica immagine, ideale per la collaborazione tra artisti e programmatori.',
|
|
188
188
|
},
|
|
189
|
+
{ type: 'paragraph', html: 'Una forma di collisione deve seguire sia il profilo sia il momento dell azione. Controlla le fasi iniziale, attiva e di recupero e prova portata, priorità, rinculo e rete nel progetto realmente integrato.' },
|
|
189
190
|
],
|
|
190
191
|
faq,
|
|
191
192
|
bibliographyTitle: 'Riferimenti per lo sviluppo delle collisioni',
|
|
@@ -186,6 +186,7 @@ export const content: ToolLocaleContent<HitboxHurtboxAnimatorUI> = {
|
|
|
186
186
|
type: 'paragraph',
|
|
187
187
|
html: 'Het PNG-contactblad toont alle frames en hun kleurlagen op één afbeelding, ideaal voor overleg tussen ontwikkelaars en vormgevers.',
|
|
188
188
|
},
|
|
189
|
+
{ type: 'paragraph', html: 'Een collisionvorm moet zowel de omtrek als het actiemoment volgen. Controleer start, actieve fase en herstel afzonderlijk en test bereik, prioriteit, terugslag en netwerkgedrag in het werkelijk geïntegreerde project.' },
|
|
189
190
|
],
|
|
190
191
|
faq,
|
|
191
192
|
bibliographyTitle: 'Referenties voor botsingsontwerp',
|
|
@@ -186,6 +186,8 @@ export const content: ToolLocaleContent<HitboxHurtboxAnimatorUI> = {
|
|
|
186
186
|
type: 'paragraph',
|
|
187
187
|
html: 'Arkusz styków PNG pokazuje wszystkie klatki i ich warstwy na jednym obrazie, co ułatwia komunikację z zespołem.',
|
|
188
188
|
},
|
|
189
|
+
{ type: 'paragraph', html: 'Kształt kolizji powinien podążać za sylwetką i chwilą akcji. Sprawdź początek, fazę aktywną i powrót osobno, a następnie przetestuj zasięg, priorytet, odrzut i sieć w rzeczywiście zintegrowanym projekcie.' },
|
|
190
|
+
{ type: 'paragraph', html: 'Po eksporcie przejrzyj klatki graniczne, w których animacja zmienia kierunek albo kończy atak. Zbyt duży hurtbox powoduje niesprawiedliwe trafienia, a zbyt mały hitbox odbiera atakowi czytelność. Porównanie warstw z ruchem postaci ułatwia znalezienie takich rozbieżności przed testami z graczami.' },
|
|
189
191
|
],
|
|
190
192
|
faq,
|
|
191
193
|
bibliographyTitle: 'Referencje dotyczące projektowania kolizji',
|
|
@@ -186,6 +186,8 @@ export const content: ToolLocaleContent<HitboxHurtboxAnimatorUI> = {
|
|
|
186
186
|
type: 'paragraph',
|
|
187
187
|
html: 'A folha de contatos PNG exibe todos os quadros e suas camadas de cor em uma só imagem, facilitando a comunicação no time.',
|
|
188
188
|
},
|
|
189
|
+
{ type: 'paragraph', html: 'Uma forma de colisão deve acompanhar tanto a silhueta quanto o momento da ação. Verifique início, fase ativa e recuperação separadamente e teste alcance, prioridade, recuo e rede no projeto realmente integrado.' },
|
|
190
|
+
{ type: 'paragraph', html: 'Depois de exportar, revise os frames em que a animação muda de direção ou termina o ataque. Um hurtbox grande demais causa acertos injustos, enquanto um hitbox pequeno demais reduz a clareza do golpe. Compare as camadas com o movimento do sprite antes de validar o comportamento em testes com jogadores.' },
|
|
189
191
|
],
|
|
190
192
|
faq,
|
|
191
193
|
bibliographyTitle: 'Referências para desenvolvimento de colisões',
|
|
@@ -186,6 +186,8 @@ export const content: ToolLocaleContent<HitboxHurtboxAnimatorUI> = {
|
|
|
186
186
|
type: 'paragraph',
|
|
187
187
|
html: 'Сравнительный PNG-лист отображает все кадры и слои на одном изображении, что упрощает обсуждение разработчиками и художниками.',
|
|
188
188
|
},
|
|
189
|
+
{ type: 'paragraph', html: 'Форма столкновения должна следовать силуэту и моменту действия. Проверьте начало, активную фазу и восстановление отдельно, а затем протестируйте дальность, приоритет, отбрасывание и сеть в реально интегрированном проекте.' },
|
|
190
|
+
{ type: 'paragraph', html: 'После экспорта отдельно проверьте кадры, где персонаж меняет направление или завершает атаку. Слишком большой hurtbox создает несправедливые попадания, а слишком маленький hitbox делает удар нечитабельным. Сопоставьте слои с движением спрайта до проверки поведения на реальных игровых сценариях.' },
|
|
189
191
|
],
|
|
190
192
|
faq,
|
|
191
193
|
bibliographyTitle: 'Ссылки по проектированию коллизий в играх',
|
|
@@ -186,6 +186,7 @@ export const content: ToolLocaleContent<HitboxHurtboxAnimatorUI> = {
|
|
|
186
186
|
type: 'paragraph',
|
|
187
187
|
html: 'PNG-kontaktkartan visar alla bildrutor och deras färglager på en och samma bild, vilket underlättar kommunikationen mellan grafiker och programmerare.',
|
|
188
188
|
},
|
|
189
|
+
{ type: 'paragraph', html: 'En kollisionsform måste följa både silhuetten och handlingens tidpunkt. Kontrollera start, aktiv fas och återhämtning separat och testa räckvidd, prioritet, knuff och nätverk i det verkligt integrerade projektet.' },
|
|
189
190
|
],
|
|
190
191
|
faq,
|
|
191
192
|
bibliographyTitle: 'Referenser för kollisionsdesign',
|
|
@@ -186,6 +186,8 @@ export const content: ToolLocaleContent<HitboxHurtboxAnimatorUI> = {
|
|
|
186
186
|
type: 'paragraph',
|
|
187
187
|
html: 'PNG temas levhası tüm kareleri ve katmanlarını tek bir görselde göstererek yazılımcılar ve çizerler arasındaki iletişimi kolaylaştırır.',
|
|
188
188
|
},
|
|
189
|
+
{ type: 'paragraph', html: 'Bir çarpışma şekli hem silüeti hem de eylemin zamanını izlemelidir. Başlangıç, etkin aşama ve toparlanmayı ayrı kontrol edin; menzil, öncelik, geri itme ve ağ davranışını gerçek projede test edin.' },
|
|
190
|
+
{ type: 'paragraph', html: 'Dışa aktardıktan sonra karakterin yön değiştirdiği veya saldırıyı bitirdiği kareleri ayrı inceleyin. Fazla büyük bir hurtbox haksız isabetlere, fazla küçük bir hitbox ise okunaksız saldırılara yol açar. Gerçek oyun senaryolarında doğrulamadan önce katmanları sprite hareketiyle karşılaştırın.' },
|
|
189
191
|
],
|
|
190
192
|
faq,
|
|
191
193
|
bibliographyTitle: 'Çarpışma tasarımı referansları',
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
---
|
|
2
2
|
import { SEORenderer } from '@jjlmoya/utils-shared';
|
|
3
|
-
import
|
|
3
|
+
import { hitboxHurtboxAnimator } from './entry';
|
|
4
|
+
import type { KnownLocale } from '../../types';
|
|
4
5
|
|
|
5
6
|
interface Props {
|
|
6
|
-
locale?:
|
|
7
|
-
sections?: SEOSection[];
|
|
7
|
+
locale?: KnownLocale;
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
-
const { locale = '
|
|
10
|
+
const { locale = 'es' } = Astro.props;
|
|
11
|
+
const loader = hitboxHurtboxAnimator.i18n[locale as KnownLocale] ?? hitboxHurtboxAnimator.i18n.es ?? hitboxHurtboxAnimator.i18n.en;
|
|
12
|
+
const content = loader ? await loader() : null;
|
|
11
13
|
---
|
|
12
14
|
|
|
13
|
-
<SEORenderer content={{ locale, sections }} />
|
|
15
|
+
{content?.seo && content.seo.length > 0 && <SEORenderer content={{ locale, sections: content.seo }} />}
|
|
@@ -76,6 +76,19 @@ export const content: ToolLocaleContent<ItchioGameTesterUI> = {
|
|
|
76
76
|
type: 'paragraph',
|
|
77
77
|
html: 'Bei Godot Webexporten müssen die benötigten Sicherheitsheader und Browserfunktionen verfügbar sein. Testen Sie das Spiel zusätzlich in einem privaten Browserfenster.'
|
|
78
78
|
},
|
|
79
|
+
{
|
|
80
|
+
type: 'title',
|
|
81
|
+
level: 2,
|
|
82
|
+
text: 'Viewport, Canvas und Einbettung gemeinsam prüfen',
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
type: 'paragraph',
|
|
86
|
+
html: 'Vergleichen Sie die tatsächliche Canvasgröße mit dem geplanten Itch.io-Viewport. Ein Seitenverhältnis von 16:9 verhindert allein noch keine Scrollbars: CSS-Ränder, feste Mindestbreiten und ein nicht skaliertes Canvas können weiterhin Überlauf erzeugen.',
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
type: 'paragraph',
|
|
90
|
+
html: 'Lesen Sie den Bericht nach jedem Build erneut, weil sich Dateinamen, Einstiegspunkt und Engine-Ausgabe ändern können. Der Tester zeigt strukturelle und geometrische Hinweise; er ersetzt keinen Test auf verschiedenen Browsern und Geräten.',
|
|
91
|
+
},
|
|
79
92
|
],
|
|
80
93
|
faq: [
|
|
81
94
|
{
|