blacktrigram 0.7.23 โ 0.7.25
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/ARCHITECTURE.md +51 -47
- package/CRA-ASSESSMENT.md +13 -13
- package/DATA_MODEL.md +6 -2
- package/SECURITY_ARCHITECTURE.md +6 -6
- package/THREAT_MODEL.md +6 -6
- package/lib/audio/AudioManager.js +3 -2
- package/lib/audio/AudioManager.js.map +1 -1
- package/lib/audio/types.js.map +1 -1
- package/lib/components/screens/intro/IntroScreen3D.js +1 -1
- package/lib/components/shared/ui/SplashScreen.js +2 -2
- package/lib/systems/ai/AdaptiveDifficulty.js +2 -1
- package/lib/systems/ai/AdaptiveDifficulty.js.map +1 -1
- package/lib/systems/ai/types.js.map +1 -1
- package/lib/systems/animation/builders/MartialArtsConstants.js.map +1 -1
- package/lib/systems/animation/core/types.js.map +1 -1
- package/lib/systems/bodypart/types.js.map +1 -1
- package/lib/systems/breathing/BreathingDisruptionSystem.js.map +1 -1
- package/lib/systems/combat/BalanceSystem.js.map +1 -1
- package/lib/systems/combat/CombatStateSystem.js.map +1 -1
- package/lib/systems/combat/ConsciousnessSystem.js.map +1 -1
- package/lib/systems/combat/PainResponseSystem.js.map +1 -1
- package/lib/systems/effects.js.map +1 -1
- package/lib/systems/game.js.map +1 -1
- package/lib/systems/physics/SpeedModifierSystem.js.map +1 -1
- package/lib/types/AccessibilityTypes.js.map +1 -1
- package/lib/types/common.js.map +1 -1
- package/lib/types/facial.js.map +1 -1
- package/lib/types/hand-animation.js.map +1 -1
- package/lib/types/injury.js.map +1 -1
- package/lib/types/skeletal.js.map +1 -1
- package/lib/types/techniqueId.js.map +1 -1
- package/lib/utils/deviceDetection.js.map +1 -1
- package/package.json +8 -8
package/ARCHITECTURE.md
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
# ๐ฎ Black Trigram (ํ๊ด) โ Technical Architecture (
|
|
1
|
+
# ๐ฎ Black Trigram (ํ๊ด) โ Technical Architecture (Q2 2026)
|
|
2
2
|
|
|
3
|
-
**Last Updated**:
|
|
4
|
-
**Architecture Version**: 2.
|
|
5
|
-
**
|
|
3
|
+
**Last Updated**: 2026-04-21
|
|
4
|
+
**Architecture Version**: 2.1 (Production-Ready)
|
|
5
|
+
**Product Version**: 0.7.24
|
|
6
|
+
**Status**: Production-Ready (9.4/10) - Combat Realism 100% Complete (13/13 Systems)
|
|
6
7
|
|
|
7
8
|
---
|
|
8
9
|
|
|
@@ -13,12 +14,12 @@
|
|
|
13
14
|
| **[๐ System Context](#-system-context)** | C4 Model | High-level view showing actors (Player, CDNs) and the entirely front-end application |
|
|
14
15
|
| **[๐ข Container View](#-container-view)** | C4 Model | Frontend-only architecture: UI Layer, Game Logic, Three.js Renderer, Animation System, State Management |
|
|
15
16
|
| **[๐งฉ Component View](#-component-view)** | C4 Model | Detailed breakdown: Combat System, Trigram System (8 stances), Vital Point System (70 points), Skeletal Animation (28 bones) |
|
|
16
|
-
| **[๐ง File Structure](#-file-structure-
|
|
17
|
+
| **[๐ง File Structure](#-file-structure-q2-2026)** | Organization | Q2 2026 project structure with systems/, components/, data/, types/ layout |
|
|
17
18
|
| **[๐ Combat Flow Sequence](#-combat-flow-sequence)** | Sequence Diagram | Input โ Trigram โ Vital Point โ Damage โ Three.js rendering with skeletal animation |
|
|
18
19
|
| **[๐ฌ Skeletal Animation](#-skeletal-animation-architecture)** | Animation System | 28-bone hierarchy, 7 hand poses, muscle tension visualization |
|
|
19
20
|
| **[โก Performance Architecture](#-performance-architecture-q1-2026)** | Performance | Three.js optimization, 60fps targets, instancing, LOD, benchmarks |
|
|
20
|
-
| **[๐ SWOT Analysis](#-swot-analysis)** | Strategy |
|
|
21
|
-
| **[๐ Game Status Report](game-status.md)** | Current Progress | Comprehensive status (
|
|
21
|
+
| **[๐ SWOT Analysis](#-swot-analysis)** | Strategy | Q2 2026 status: Strengths (70/70 vital points, 100% combat realism), Weaknesses (niche market), Opportunities, Threats |
|
|
22
|
+
| **[๐ Game Status Report](game-status.md)** | Current Progress | Comprehensive status (72.34% line coverage per docs/coverage/coverage-summary.json, 13/13 combat realism systems, 8/8 trigram stances) |
|
|
22
23
|
| **[๐ฎ Future Architecture](FUTURE_ARCHITECTURE.md)** | Roadmap | Q2 2026+ evolution: Combat realism completion, VR/AR integration, advanced features |
|
|
23
24
|
| **[๐ฏ Core Game Concepts](#-core-game-concepts)** | Game Design | Player archetypes (5), trigram system (8), resources & mechanics |
|
|
24
25
|
| **[๐๏ธ Architecture Concepts](#-architecture-concepts)** | Technical Design | Mindmap of system architecture layers and components |
|
|
@@ -32,7 +33,7 @@
|
|
|
32
33
|
```mermaid
|
|
33
34
|
%%{init: {'theme':'base', 'themeVariables': {'primaryColor':'#2979FF','primaryTextColor':'#fff','primaryBorderColor':'#0D47A1','lineColor':'#455A64','secondaryColor':'#4CAF50','tertiaryColor':'#FF9800','personBkg':'#1565C0','containerBkg':'#2979FF','componentBkg':'#4CAF50','person_bg':'#1565C0','container_bg':'#2979FF','component_bg':'#4CAF50'}}}%%
|
|
34
35
|
C4Context
|
|
35
|
-
title System Context - Black Trigram (ํ๊ด) Web Application (
|
|
36
|
+
title System Context - Black Trigram (ํ๊ด) Web Application (v0.7.24)
|
|
36
37
|
|
|
37
38
|
Person(player, "๐งโ๐คโ๐ง Martial Arts Student", "Learns Korean vital point targeting with 70 anatomical points and 28-bone skeletal animation")
|
|
38
39
|
Person(instructor, "๐ฅ Martial Arts Instructor", "Uses for teaching traditional Korean techniques and I Ching philosophy")
|
|
@@ -224,22 +225,24 @@ C4Component
|
|
|
224
225
|
UpdateLayoutConfig($c4ShapeInRow="4", $c4BoundaryInRow="1")
|
|
225
226
|
```
|
|
226
227
|
|
|
227
|
-
### ๐งฉ Component Implementation Status (
|
|
228
|
+
### ๐งฉ Component Implementation Status (Q2 2026)
|
|
228
229
|
|
|
229
|
-
#### Combat System (
|
|
230
|
-
- **Combat Realism**:
|
|
230
|
+
#### Combat System (9.6/10 - Production-Ready)
|
|
231
|
+
- **Combat Realism**: 13/13 systems complete (100%)
|
|
231
232
|
- โ
**Body Part Health**: 100% (8 parts tracked with Korean/English labels)
|
|
232
233
|
- โ
**Vital Point Targeting**: 100% (70/70 points with TCM meridian mapping)
|
|
233
234
|
- โ
**Enhanced Anatomy**: 95% (polygon-based detection, 13 zones, <0.01ms)
|
|
234
|
-
- โ
**Visual Feedback**:
|
|
235
|
-
- โ
**Pain Response**:
|
|
236
|
-
- โ
**Consciousness Levels**:
|
|
237
|
-
- โ
**Breathing Disruption**:
|
|
238
|
-
-
|
|
239
|
-
-
|
|
240
|
-
-
|
|
241
|
-
-
|
|
242
|
-
-
|
|
235
|
+
- โ
**Visual Feedback**: 97% (damage numbers, hit effects, combo counter, limb exposure)
|
|
236
|
+
- โ
**Pain Response**: 100% (production-ready with comprehensive tests)
|
|
237
|
+
- โ
**Consciousness Levels**: 100% (production-ready, 4-level gradation)
|
|
238
|
+
- โ
**Breathing Disruption**: 95% (full CombatSystem integration with UI)
|
|
239
|
+
- โ
**Trauma Visualization**: 90% (per-player injury tracking)
|
|
240
|
+
- โ
**Balance/Vulnerability**: 70% (transitions refined)
|
|
241
|
+
- โ
**Combat Readiness HUD**: 85% (EndScreen integration complete)
|
|
242
|
+
- โ
**Injury-Based Movement**: 60% (dynamic speed modifiers)
|
|
243
|
+
- โ
**Bone Impact Audio**: 80% (anatomical region detection)
|
|
244
|
+
- โ
**Grappling System**: 95% (4 animation states, visual feedback, audio hooks)
|
|
245
|
+
- โ
**Limb Exposure/Counter-Attacks**: 100% (AI integration, visual indicators, 132 tests)
|
|
243
246
|
|
|
244
247
|
#### Vital Point System (9.5/10 - Excellent)
|
|
245
248
|
- 70/70 vital points with Korean names (100% complete)
|
|
@@ -347,10 +350,10 @@ Black Trigram uses Three.js for 3D rendering, achieving enhanced visual capabili
|
|
|
347
350
|
|
|
348
351
|
The following Three.js packages are now installed and configured:
|
|
349
352
|
|
|
350
|
-
- **three@0.
|
|
351
|
-
- **@react-three/fiber@9.
|
|
353
|
+
- **three@0.184.0** - Core Three.js 3D engine with WebGL rendering
|
|
354
|
+
- **@react-three/fiber@9.6.0** - React renderer for Three.js (declarative 3D in React)
|
|
352
355
|
- **@react-three/drei@10.7.7** - Useful helpers and abstractions (@react-three/fiber)
|
|
353
|
-
- **@types/three@0.
|
|
356
|
+
- **@types/three@0.184.0** - TypeScript type definitions for Three.js
|
|
354
357
|
|
|
355
358
|
### ๐๏ธ Architecture Integration
|
|
356
359
|
|
|
@@ -906,15 +909,15 @@ PELVIS (root)
|
|
|
906
909
|
- 2 characters (player + opponent): ~1220 bytes animation overhead
|
|
907
910
|
- Negligible impact on 60fps target (<1ms per frame)
|
|
908
911
|
|
|
909
|
-
### ๐ฏ Performance Targets (
|
|
912
|
+
### ๐ฏ Performance Targets (Q2 2026)
|
|
910
913
|
|
|
911
914
|
| Platform | Resolution | Target FPS | Achieved FPS | Particles | Memory | Status |
|
|
912
915
|
|----------|-----------|------------|--------------|-----------|--------|--------|
|
|
913
916
|
| **Desktop** | 1200x800 | 60fps | 60fps | 1000+ | 180MB | โ
Met |
|
|
914
|
-
| **Tablet** | 1024x768 | 30fps |
|
|
915
|
-
| **Mobile** | 720p | 30fps |
|
|
917
|
+
| **Tablet** | 1024x768 | 30fps | 55fps+ | 750+ | 150MB | โ
Optimized |
|
|
918
|
+
| **Mobile** | 720p | 30fps | 55fps+ | 500+ | 150MB | โ
Optimized |
|
|
916
919
|
|
|
917
|
-
**Overall Performance Rating**: 8.
|
|
920
|
+
**Overall Performance Rating**: 8.5/10 (60fps desktop maintained, mobile optimization complete 55fps+)
|
|
918
921
|
|
|
919
922
|
### ๐ Three.js Optimization Techniques
|
|
920
923
|
|
|
@@ -955,16 +958,16 @@ Static environment objects merged into single mesh for reduced draw calls.
|
|
|
955
958
|
- **Custom performance metrics** tracked in React/Three.js state and surfaced via `PerformanceOverlay3D`
|
|
956
959
|
- **Performance tests** in the Vitest suite validating `PerformanceOverlay3D` frame timing, overlay stability, and animation timing
|
|
957
960
|
|
|
958
|
-
### ๐ฑ Mobile Performance Optimization (Q2 2026
|
|
961
|
+
### ๐ฑ Mobile Performance Optimization (Q2 2026 Complete)
|
|
959
962
|
|
|
960
|
-
**Current Status:**
|
|
963
|
+
**Current Status:** 55fps+ on mobile (exceeded 30fps baseline target)
|
|
961
964
|
|
|
962
|
-
**Optimization
|
|
965
|
+
**Optimization Completed:**
|
|
963
966
|
1. Adaptive quality settings based on device capabilities
|
|
964
967
|
2. Simplified shaders for mobile-optimized materials
|
|
965
968
|
3. Reduced particle count (500 cap vs 1000 desktop)
|
|
966
969
|
4. LOD aggressive tuning for earlier low-detail transitions
|
|
967
|
-
5.
|
|
970
|
+
5. Mobile R3F fixes and coordinate system corrections
|
|
968
971
|
|
|
969
972
|
### ๐ Documentation & Resources
|
|
970
973
|
|
|
@@ -1016,7 +1019,7 @@ Static environment objects merged into single mesh for reduced draw calls.
|
|
|
1016
1019
|
|
|
1017
1020
|
---
|
|
1018
1021
|
|
|
1019
|
-
## ๐ง File Structure (
|
|
1022
|
+
## ๐ง File Structure (Q2 2026)
|
|
1020
1023
|
|
|
1021
1024
|
### Current Implementation Structure
|
|
1022
1025
|
|
|
@@ -1096,17 +1099,17 @@ src/
|
|
|
1096
1099
|
โโโ useCombat.ts # Combat-related hooks
|
|
1097
1100
|
```
|
|
1098
1101
|
|
|
1099
|
-
### Key Implementation Files (
|
|
1102
|
+
### Key Implementation Files (Q2 2026)
|
|
1100
1103
|
|
|
1101
1104
|
**Combat Systems:**
|
|
1102
|
-
- `src/systems/CombatSystem.ts` (36,888 bytes ~36KB) - Core combat logic with
|
|
1105
|
+
- `src/systems/CombatSystem.ts` (36,888 bytes ~36KB) - Core combat logic with 13/13 realism systems
|
|
1103
1106
|
- `src/systems/VitalPointSystem.ts` (19,583 bytes ~20KB) - 70-point vital targeting (100% complete)
|
|
1104
1107
|
- `src/systems/TrigramSystem.ts` (12,843 bytes ~13KB) - 8-stance management with I Ching philosophy
|
|
1105
|
-
- `src/systems/combat/PainResponseSystem.ts` - Pain response system (
|
|
1106
|
-
- `src/systems/combat/ConsciousnessSystem.ts` - Consciousness levels (
|
|
1107
|
-
- `src/systems/breathing/BreathingDisruptionSystem.ts` - Breathing disruption (
|
|
1108
|
+
- `src/systems/combat/PainResponseSystem.ts` - Pain response system (100% complete, production-ready)
|
|
1109
|
+
- `src/systems/combat/ConsciousnessSystem.ts` - Consciousness levels (100% complete, 4-level gradation)
|
|
1110
|
+
- `src/systems/breathing/BreathingDisruptionSystem.ts` - Breathing disruption (95% complete)
|
|
1108
1111
|
|
|
1109
|
-
**Skeletal Animation (
|
|
1112
|
+
**Skeletal Animation (Q2 2026):**
|
|
1110
1113
|
- `src/types/skeletal.ts` (857 lines) - 28-bone hierarchy definitions, BoneName enum, SkeletalRig interface
|
|
1111
1114
|
- `src/types/muscle.ts` - Muscle tension visualization system (0.0-1.0 intensity mapping)
|
|
1112
1115
|
- `src/types/hand-animation.ts` - Hand pose system (7 primary poses: fist_vertical, fist_horizontal, open_hand_knife, spear_hand, grasping, open_palm, relaxed)
|
|
@@ -1119,22 +1122,23 @@ src/
|
|
|
1119
1122
|
|
|
1120
1123
|
**Three.js Components:**
|
|
1121
1124
|
- `src/components/shared/three/` - 3D rendering components (Player3DUnified, StanceAura, SkeletalRig)
|
|
1122
|
-
- `src/components/screens/combat/` - CombatScreen3D implementation (production-ready,
|
|
1125
|
+
- `src/components/screens/combat/` - CombatScreen3D implementation (production-ready, 41.71% line coverage per docs/coverage/coverage-summary.json)
|
|
1123
1126
|
- `src/utils/player3DHelpers.ts` - PlayerState to Three.js conversion utilities
|
|
1124
1127
|
|
|
1125
|
-
**Audio System (
|
|
1128
|
+
**Audio System (88.08% Line Coverage):**
|
|
1126
1129
|
- `src/audio/AudioProvider.tsx` - React context provider and useAudio hook
|
|
1127
1130
|
- `src/audio/AudioAssetRegistry.ts` - Sound library with damage-scaled audio
|
|
1128
1131
|
- `src/audio/AudioManager.ts` - Audio playback with Web Audio API
|
|
1129
1132
|
- `src/audio/BoneImpactAudioMap.ts` - Bone impact audio system (60% complete)
|
|
1130
|
-
-
|
|
1133
|
+
- 88.08% line coverage across audio systems (843/957 lines, per docs/coverage/audio/index.html)
|
|
1131
1134
|
|
|
1132
|
-
**Test Coverage (
|
|
1133
|
-
- **Overall**:
|
|
1135
|
+
**Test Coverage (Q2 2026):**
|
|
1136
|
+
- **Overall**: 72.34% line coverage (17,451 / 24,121 lines, 518 tests - Vitest unit + Cypress E2E, per docs/coverage/coverage-summary.json)
|
|
1134
1137
|
- **New Components**: 95% test coverage
|
|
1135
1138
|
- **Core Systems**: >85% test coverage
|
|
1136
|
-
- **Audio System**:
|
|
1137
|
-
- **
|
|
1139
|
+
- **Audio System**: 88.08% line coverage (843/957 lines)
|
|
1140
|
+
- **Combat Realism**: 518 tests passing (372 + 146 new)
|
|
1141
|
+
- **Grappling/Limb Exposure**: 132 comprehensive tests
|
|
1138
1142
|
|
|
1139
1143
|
### Component Naming Conventions
|
|
1140
1144
|
|
|
@@ -2421,6 +2425,6 @@ The Q1 2026 architecture successfully demonstrates the feasibility of authentic
|
|
|
2421
2425
|
**โ
Approved by:** James Pether Sรถrling, CEO
|
|
2422
2426
|
**๐ค Distribution:** Public
|
|
2423
2427
|
**๐ท๏ธ Classification:** [](https://github.com/Hack23/ISMS-PUBLIC/blob/main/CLASSIFICATION.md#confidentiality-levels) [](https://github.com/Hack23/ISMS-PUBLIC/blob/main/CLASSIFICATION.md#integrity-levels) [](https://github.com/Hack23/ISMS-PUBLIC/blob/main/CLASSIFICATION.md#availability-levels)
|
|
2424
|
-
**๐
Effective Date:** 2026-
|
|
2425
|
-
**โฐ Next Review:** 2026-
|
|
2428
|
+
**๐
Effective Date:** 2026-04-21
|
|
2429
|
+
**โฐ Next Review:** 2026-10-21
|
|
2426
2430
|
**๐ฏ Framework Compliance:** [](https://github.com/Hack23/ISMS-PUBLIC/blob/main/CLASSIFICATION.md) [](https://github.com/Hack23/ISMS-PUBLIC/blob/main/CLASSIFICATION.md) [](https://github.com/Hack23/ISMS-PUBLIC/blob/main/CLASSIFICATION.md)
|
package/CRA-ASSESSMENT.md
CHANGED
|
@@ -13,13 +13,13 @@
|
|
|
13
13
|
|
|
14
14
|
<p align="center">
|
|
15
15
|
<a href="#"><img src="https://img.shields.io/badge/Owner-CEO-0A66C2?style=for-the-badge" alt="Owner"/></a>
|
|
16
|
-
<a href="#"><img src="https://img.shields.io/badge/Version-1.
|
|
17
|
-
<a href="#"><img src="https://img.shields.io/badge/Effective-2026--
|
|
16
|
+
<a href="#"><img src="https://img.shields.io/badge/Version-1.3-555?style=for-the-badge" alt="Version"/></a>
|
|
17
|
+
<a href="#"><img src="https://img.shields.io/badge/Effective-2026--04--21-success?style=for-the-badge" alt="Effective Date"/></a>
|
|
18
18
|
<a href="#"><img src="https://img.shields.io/badge/Review-Quarterly-orange?style=for-the-badge" alt="Review Cycle"/></a>
|
|
19
19
|
</p>
|
|
20
20
|
|
|
21
|
-
**Document Owner:** CEO | **Version:** 1.
|
|
22
|
-
**Review Cycle:** Quarterly | **Next Review:** 2026-
|
|
21
|
+
**Document Owner:** CEO | **Version:** 1.3 | **Last Updated:** 2026-04-21
|
|
22
|
+
**Review Cycle:** Quarterly | **Next Review:** 2026-07-21
|
|
23
23
|
|
|
24
24
|
---
|
|
25
25
|
|
|
@@ -91,7 +91,7 @@ _Supports CRA Annex V ยง 1 - Product Description Requirements_
|
|
|
91
91
|
| Field | Value |
|
|
92
92
|
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
93
93
|
| ๐ฆ Product | Black Trigram (ํ๊ด) - Korean Martial Arts Combat Simulator |
|
|
94
|
-
| ๐ท๏ธ Version Tag | 0.
|
|
94
|
+
| ๐ท๏ธ Version Tag | 0.7.24 (reflects current project state) |
|
|
95
95
|
| ๐ Repository | https://github.com/Hack23/blacktrigram |
|
|
96
96
|
| ๐ง Security Contact | security@hack23.org |
|
|
97
97
|
| ๐ฏ Purpose (1โ2 lines) | Educational 3D combat game teaching authentic Korean martial arts through realistic anatomical targeting and traditional Eight Trigram philosophy |
|
|
@@ -270,10 +270,10 @@ GitHub Attestations: `https://github.com/Hack23/blacktrigram/attestations`
|
|
|
270
270
|
**๐ฏ Release Assets Structure:**
|
|
271
271
|
|
|
272
272
|
```
|
|
273
|
-
blacktrigram-0.
|
|
274
|
-
blacktrigram-0.
|
|
275
|
-
blacktrigram-0.
|
|
276
|
-
blacktrigram-0.
|
|
273
|
+
blacktrigram-0.7.24.zip # Main application bundle
|
|
274
|
+
blacktrigram-0.7.24.zip.intoto.jsonl # SLSA provenance attestation
|
|
275
|
+
blacktrigram-0.7.24.spdx.json # SPDX SBOM
|
|
276
|
+
blacktrigram-0.7.24.spdx.json.intoto.jsonl # SBOM attestation
|
|
277
277
|
```
|
|
278
278
|
|
|
279
279
|
**๐ Release Notes Format:**
|
|
@@ -304,7 +304,7 @@ blacktrigram-0.6.58.spdx.json.intoto.jsonl # SBOM attestation
|
|
|
304
304
|
|
|
305
305
|
Thanks to @dependabot[bot] for automated security updates!
|
|
306
306
|
|
|
307
|
-
**Full Changelog**: https://github.com/Hack23/blacktrigram/compare/v0.
|
|
307
|
+
**Full Changelog**: https://github.com/Hack23/blacktrigram/compare/v0.7.23...v0.7.24
|
|
308
308
|
```
|
|
309
309
|
|
|
310
310
|
**๐ Evidence Validation Commands:**
|
|
@@ -357,7 +357,7 @@ _Supports CRA Article 28 - EU Declaration of Conformity_
|
|
|
357
357
|
> **๐ Complete when placing product on EU market**
|
|
358
358
|
|
|
359
359
|
**๐ข Manufacturer:** Hack23 AB, Stockholm, Sweden
|
|
360
|
-
**๐ฆ Product:** Black Trigram (ํ๊ด) 0.
|
|
360
|
+
**๐ฆ Product:** Black Trigram (ํ๊ด) 0.7.24
|
|
361
361
|
**๐ CRA Compliance:** Self-assessment documentation supporting CRA essential cybersecurity requirements evaluation
|
|
362
362
|
**๐ Assessment:** Self-assessment documentation per Article 24 - Standard product classification
|
|
363
363
|
**๐ Standards:** ETSI EN 303 645 (IoT Security), ISO/IEC 27001 (ISMS), OWASP ASVS (Application Security), NIST SSDF (Secure Development)
|
|
@@ -422,7 +422,7 @@ CRA assessment updated only when changes constitute "substantial modification" u
|
|
|
422
422
|
```markdown
|
|
423
423
|
## Current CRA Self-Assessment Evidence
|
|
424
424
|
|
|
425
|
-
**๐ท๏ธ Product Version:** 0.
|
|
425
|
+
**๐ท๏ธ Product Version:** 0.7.24
|
|
426
426
|
**๐ฆ CRA Technical Documentation:** This assessment + [Latest Release](https://github.com/Hack23/blacktrigram/releases/latest)
|
|
427
427
|
**๐ก๏ธ Security Attestations:** [GitHub Attestations](https://github.com/Hack23/blacktrigram/attestations)
|
|
428
428
|
**๐ Assessment Status:** 
|
|
@@ -503,6 +503,6 @@ CRA assessment updated only when changes constitute "substantial modification" u
|
|
|
503
503
|
**Approved by:** James Pether Sรถrling, CEO
|
|
504
504
|
**Distribution:** Public
|
|
505
505
|
**Classification:** [](https://github.com/Hack23/ISMS-PUBLIC/blob/main/CLASSIFICATION.md#confidentiality-levels)
|
|
506
|
-
**Effective Date:** 2026-
|
|
506
|
+
**Effective Date:** 2026-04-21
|
|
507
507
|
**CRA Alignment:** Template supports CRA Annex V technical documentation and self-assessment requirements
|
|
508
508
|
**ISMS Integration:** Comprehensive alignment with public ISMS framework for operational excellence
|
package/DATA_MODEL.md
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# ๐ Black Trigram (ํ๊ด) Data Model
|
|
2
2
|
|
|
3
|
+
**Last Updated:** 2026-04-21
|
|
4
|
+
**Product Version:** 0.7.24
|
|
5
|
+
**Status:** Production-Ready
|
|
6
|
+
|
|
3
7
|
## ๐ Related Documentation
|
|
4
8
|
|
|
5
9
|
| Document | Focus | Description |
|
|
@@ -1020,6 +1024,6 @@ This data model documentation ensures type-safe, performant, and culturally auth
|
|
|
1020
1024
|
**โ
Approved by:** James Pether Sรถrling, CEO
|
|
1021
1025
|
**๐ค Distribution:** Public
|
|
1022
1026
|
**๐ท๏ธ Classification:** [](https://github.com/Hack23/ISMS-PUBLIC/blob/main/CLASSIFICATION.md#confidentiality-levels) [](https://github.com/Hack23/ISMS-PUBLIC/blob/main/CLASSIFICATION.md#integrity-levels) [](https://github.com/Hack23/ISMS-PUBLIC/blob/main/CLASSIFICATION.md#availability-levels)
|
|
1023
|
-
**๐
Effective Date:** 2026-
|
|
1024
|
-
**โฐ Next Review:** 2026-
|
|
1027
|
+
**๐
Effective Date:** 2026-04-21
|
|
1028
|
+
**โฐ Next Review:** 2026-10-21
|
|
1025
1029
|
**๐ฏ Framework Compliance:** [](https://github.com/Hack23/ISMS-PUBLIC/blob/main/CLASSIFICATION.md) [](https://github.com/Hack23/ISMS-PUBLIC/blob/main/CLASSIFICATION.md)
|
package/SECURITY_ARCHITECTURE.md
CHANGED
|
@@ -11,13 +11,13 @@
|
|
|
11
11
|
|
|
12
12
|
<p align="center">
|
|
13
13
|
<a><img src="https://img.shields.io/badge/Owner-CEO-0A66C2?style=for-the-badge" alt="Owner"/></a>
|
|
14
|
-
<a><img src="https://img.shields.io/badge/Version-2.
|
|
15
|
-
<a><img src="https://img.shields.io/badge/Effective-2026--
|
|
14
|
+
<a><img src="https://img.shields.io/badge/Version-2.1-555?style=for-the-badge" alt="Version"/></a>
|
|
15
|
+
<a><img src="https://img.shields.io/badge/Effective-2026--04--21-success?style=for-the-badge" alt="Effective Date"/></a>
|
|
16
16
|
<a><img src="https://img.shields.io/badge/Review-Annual-orange?style=for-the-badge" alt="Review Cycle"/></a>
|
|
17
17
|
</p>
|
|
18
18
|
|
|
19
|
-
**๐ Document Owner:** CEO | **๐ Version:** 2.
|
|
20
|
-
**๐ Review Cycle:** Annual | **โฐ Next Review:** 2027-
|
|
19
|
+
**๐ Document Owner:** CEO | **๐ Version:** 2.1 | **๐
Last Updated:** 2026-04-21 (UTC)
|
|
20
|
+
**๐ Review Cycle:** Annual | **โฐ Next Review:** 2027-04-21
|
|
21
21
|
**๐ท๏ธ Classification:** Public (Open Source Educational Gaming Platform)
|
|
22
22
|
|
|
23
23
|
---
|
|
@@ -1174,8 +1174,8 @@ As documented in the [End-of-Life Strategy](End-of-Life-Strategy.md), any future
|
|
|
1174
1174
|
**โ
Approved by:** James Pether Sรถrling, CEO
|
|
1175
1175
|
**๐ค Distribution:** Public
|
|
1176
1176
|
**๐ท๏ธ Classification:** [](https://github.com/Hack23/ISMS-PUBLIC/blob/main/CLASSIFICATION.md#confidentiality-levels) [](https://github.com/Hack23/ISMS-PUBLIC/blob/main/CLASSIFICATION.md#integrity-levels) [](https://github.com/Hack23/ISMS-PUBLIC/blob/main/CLASSIFICATION.md#availability-levels)
|
|
1177
|
-
**๐
Effective Date:** 2026-
|
|
1178
|
-
**โฐ Next Review:** 2027-
|
|
1177
|
+
**๐
Effective Date:** 2026-04-21
|
|
1178
|
+
**โฐ Next Review:** 2027-04-21
|
|
1179
1179
|
**๐ฏ Framework Compliance:** [](https://github.com/Hack23/ISMS-PUBLIC/blob/main/CLASSIFICATION.md) [](https://github.com/Hack23/ISMS-PUBLIC/blob/main/CLASSIFICATION.md) [](https://github.com/Hack23/ISMS-PUBLIC/blob/main/CLASSIFICATION.md) [](https://github.com/Hack23/ISMS-PUBLIC/blob/main/CLASSIFICATION.md)
|
|
1180
1180
|
|
|
1181
1181
|
**ํ๊ด์ ๊ธธ์ ๊ฑธ์ด๋ผ** - _Walk the Path of the Black Trigram with Security_
|
package/THREAT_MODEL.md
CHANGED
|
@@ -11,13 +11,13 @@
|
|
|
11
11
|
|
|
12
12
|
<p align="center">
|
|
13
13
|
<a><img src="https://img.shields.io/badge/Owner-CEO-0A66C2?style=for-the-badge" alt="Owner"/></a>
|
|
14
|
-
<a><img src="https://img.shields.io/badge/Version-2.
|
|
15
|
-
<a><img src="https://img.shields.io/badge/Effective-2026--
|
|
14
|
+
<a><img src="https://img.shields.io/badge/Version-2.1-555?style=for-the-badge" alt="Version"/></a>
|
|
15
|
+
<a><img src="https://img.shields.io/badge/Effective-2026--04--21-success?style=for-the-badge" alt="Effective Date"/></a>
|
|
16
16
|
<a><img src="https://img.shields.io/badge/Review-Annual-orange?style=for-the-badge" alt="Review Cycle"/></a>
|
|
17
17
|
</p>
|
|
18
18
|
|
|
19
|
-
**๐ Document Owner:** CEO | **๐ Version:** 2.
|
|
20
|
-
**๐ Review Cycle:** Annual | **โฐ Next Review:** 2027-
|
|
19
|
+
**๐ Document Owner:** CEO | **๐ Version:** 2.1 | **๐
Last Updated:** 2026-04-21 (UTC)
|
|
20
|
+
**๐ Review Cycle:** Annual | **โฐ Next Review:** 2027-04-21
|
|
21
21
|
**๐ท๏ธ Classification:** Public (Open Source Educational Gaming Platform)
|
|
22
22
|
|
|
23
23
|
---
|
|
@@ -1165,6 +1165,6 @@ Following [Hack23 AB Threat Modeling Policy ยง2.1](https://github.com/Hack23/ISM
|
|
|
1165
1165
|
**โ
Approved by:** James Pether Sรถrling, CEO
|
|
1166
1166
|
**๐ค Distribution:** Public
|
|
1167
1167
|
**๐ท๏ธ Classification:** [](https://github.com/Hack23/ISMS-PUBLIC/blob/main/CLASSIFICATION.md)
|
|
1168
|
-
**๐
Effective Date:** 2026-
|
|
1169
|
-
**โฐ Next Review:** 2027-
|
|
1168
|
+
**๐
Effective Date:** 2026-04-21
|
|
1169
|
+
**โฐ Next Review:** 2027-04-21
|
|
1170
1170
|
**๐ฏ Framework Compliance:** [](https://github.com/Hack23/ISMS-PUBLIC/blob/main/CLASSIFICATION.md) [](https://github.com/Hack23/ISMS-PUBLIC/blob/main/CLASSIFICATION.md) [](https://github.com/Hack23/ISMS-PUBLIC/blob/main/CLASSIFICATION.md) [](https://github.com/Hack23/ISMS-PUBLIC/blob/main/CLASSIFICATION.md) [](https://github.com/Hack23/ISMS-PUBLIC/blob/main/Threat_Modeling.md)
|
|
@@ -52,11 +52,12 @@ var AudioManager = class {
|
|
|
52
52
|
this.assetLoader = new AudioAssetLoader();
|
|
53
53
|
this.audioPool = new AudioElementPool();
|
|
54
54
|
this.monitor = new AudioMonitor();
|
|
55
|
-
|
|
55
|
+
const cacheConfig = {
|
|
56
56
|
maxSizeBytes: 30 * 1024 * 1024,
|
|
57
57
|
criticalAssets: [...CRITICAL_AUDIO_ASSETS],
|
|
58
58
|
debug: typeof process !== "undefined" && process.env.NODE_ENV === "development"
|
|
59
|
-
}
|
|
59
|
+
};
|
|
60
|
+
this.audioCache = new AudioCache(cacheConfig);
|
|
60
61
|
}
|
|
61
62
|
/**
|
|
62
63
|
* Helper method to release pooled audio back to the pool after playback
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AudioManager.js","names":[],"sources":["../../src/audio/AudioManager.ts"],"sourcesContent":["import { AudioAssetLoader, LoadOptions } from \"./AudioAssetLoader\";\nimport { AudioCache, AudioCacheConfig } from \"./AudioCache\";\nimport {\n AudioFPSImpact,\n AudioMemoryStats,\n AudioMonitor,\n AudioPerformanceStats,\n MemoryWarning,\n} from \"./AudioMonitor\";\nimport { AudioElementPool, PoolStatistics } from \"./AudioPool\";\nimport {\n AudioAsset,\n AudioConfig,\n IAudioManager,\n MusicTrackId,\n SoundEffectId,\n} from \"./types\";\n\n// Estimated average size per audio asset in MB (based on typical compressed audio file sizes)\nconst ESTIMATED_ASSET_SIZE_MB = 0.5;\n\n/**\n * Critical assets that should never be evicted from cache\n * ์บ์์์ ์ ๋ ์ ๊ฑฐ๋์ง ์์์ผ ํ๋ ์ค์ํ ์์ฐ\n */\nconst CRITICAL_AUDIO_ASSETS = [\n // Menu sounds - instant playback required\n \"menu_hover\",\n \"menu_select\",\n \"menu_click\",\n \"menu_navigate\",\n \"menu_back\",\n // Common combat sounds - instant playback required\n \"hit_impact\",\n \"hit_light\",\n \"hit_medium\",\n \"hit_heavy\",\n \"guard_block\",\n \"attack_whoosh\",\n \"attack_light\",\n \"stance_change\",\n] as const;\n\nexport class AudioManager implements IAudioManager {\n private _masterVolume: number = 1.0;\n private _musicVolume: number = 0.7;\n private _sfxVolume: number = 0.8;\n private _muted: boolean = false;\n private _currentMusicTrack: string | null = null;\n private _fallbackMode: boolean = false;\n private currentMusic: HTMLAudioElement | null = null;\n private soundCache: Map<string, HTMLAudioElement> = new Map();\n private _isInitialized: boolean = false;\n\n // Track music assets currently being loaded to prevent race conditions\n // ๋ ์ด์ค ์ปจ๋์
๋ฐฉ์ง๋ฅผ ์ํด ํ์ฌ ๋ก๋ ์ค์ธ ์์
์์ฐ ์ถ์ \n private loadingMusic: Set<string> = new Set();\n\n // New optimized components\n private assetLoader: AudioAssetLoader;\n private audioPool: AudioElementPool;\n private monitor: AudioMonitor;\n private audioCache: AudioCache;\n private frequentSounds: Set<string> = new Set([\n \"hit_light\",\n \"attack_light\",\n \"stance_change\",\n ]);\n\n constructor(config?: Partial<AudioConfig>) {\n if (config) {\n this._masterVolume = config.masterVolume ?? 1.0;\n this._musicVolume = config.musicVolume ?? 0.7;\n this._sfxVolume = config.sfxVolume ?? 0.8;\n }\n\n // Initialize new components\n this.assetLoader = new AudioAssetLoader();\n this.audioPool = new AudioElementPool();\n this.monitor = new AudioMonitor();\n\n // Initialize AudioCache with 30MB limit and critical assets\n // 30MB ์ ํ ๋ฐ ์ค์ ์์ฐ์ผ๋ก AudioCache ์ด๊ธฐํ\n const cacheConfig: AudioCacheConfig = {\n maxSizeBytes: 30 * 1024 * 1024, // 30MB default\n criticalAssets: [...CRITICAL_AUDIO_ASSETS],\n debug: typeof process !== \"undefined\" && process?.env?.NODE_ENV === \"development\",\n };\n this.audioCache = new AudioCache(cacheConfig);\n }\n\n /**\n * Helper method to release pooled audio back to the pool after playback\n * @param id - Asset ID\n * @param audio - Audio element to release\n */\n private releasePooledAudio(id: string, audio: HTMLAudioElement): void {\n if (!audio.paused) {\n audio.addEventListener(\n \"ended\",\n () => {\n this.audioPool.release(id, audio);\n },\n { once: true }\n );\n } else {\n this.audioPool.release(id, audio);\n }\n }\n\n // Interface getters\n get isInitialized(): boolean {\n return this._isInitialized;\n }\n\n get fallbackMode(): boolean {\n return this._fallbackMode;\n }\n\n get currentMusicTrack(): string | null {\n return this._currentMusicTrack;\n }\n\n get masterVolume(): number {\n return this._masterVolume;\n }\n\n get sfxVolume(): number {\n return this._sfxVolume;\n }\n\n get musicVolume(): number {\n return this._musicVolume;\n }\n\n get muted(): boolean {\n return this._muted;\n }\n\n async initialize(config?: AudioConfig): Promise<void> {\n try {\n // Create AudioContext using globalThis for cross-platform compatibility\n const AudioContextClass =\n globalThis.AudioContext ||\n (globalThis as unknown as { webkitAudioContext: typeof AudioContext })\n .webkitAudioContext;\n new AudioContextClass();\n this._isInitialized = true;\n this._fallbackMode = false;\n\n if (config) {\n this._masterVolume = config.masterVolume ?? this._masterVolume;\n this._musicVolume = config.musicVolume ?? this._musicVolume;\n this._sfxVolume = config.sfxVolume ?? this._sfxVolume;\n }\n\n // Set memory threshold if configured\n if (config?.maxSimultaneousSounds) {\n const estimatedMemoryMB =\n config.maxSimultaneousSounds * ESTIMATED_ASSET_SIZE_MB;\n this.monitor.setMemoryThreshold(estimatedMemoryMB);\n }\n } catch (error) {\n console.warn(\n \"AudioContext initialization failed, using fallback mode:\",\n error\n );\n this._isInitialized = true;\n this._fallbackMode = true;\n }\n }\n\n async loadAsset(asset: AudioAsset, options?: LoadOptions): Promise<void> {\n const startTime = performance.now();\n\n try {\n // Check cache first - instant playback for cached assets\n // ์บ์ ๋จผ์ ํ์ธ - ์บ์๋ ์์ฐ์ ์ฆ์ ์ฌ์\n const cachedAsset = this.audioCache.get(asset.id);\n if (cachedAsset) {\n // Asset already loaded in cache, use it\n const cached = this.soundCache.get(asset.id);\n if (cached) {\n return; // Already loaded and cached\n }\n }\n\n // Use faster timeouts for testing environment\n // Safely check for Node.js environment to avoid runtime errors in browsers\n const isTest =\n typeof process !== \"undefined\" && process?.env?.NODE_ENV === \"test\";\n const loadOptions: LoadOptions = {\n timeout: isTest ? 100 : 10000,\n maxRetries: isTest ? 1 : 3,\n retryDelay: isTest ? 10 : 1000,\n ...options,\n };\n\n // Use AudioAssetLoader with retry and fallback\n const result = await this.assetLoader.loadAsset(asset, loadOptions);\n\n if (result.success && result.audio) {\n result.audio.volume = asset.volume ?? 1.0;\n this.soundCache.set(asset.id, result.audio);\n\n // Estimate size for cache management\n // ์บ์ ๊ด๋ฆฌ๋ฅผ ์ํ ํฌ๊ธฐ ์ถ์ \n const estimatedSize = this.estimateAudioSize(asset);\n\n // Add to LRU cache with size tracking\n // ํฌ๊ธฐ ์ถ์ ๊ณผ ํจ๊ป LRU ์บ์์ ์ถ๊ฐ\n this.audioCache.set(asset.id, asset, estimatedSize);\n\n // Track performance and memory\n const loadTime = performance.now() - startTime;\n this.monitor.recordLoad(asset.id, loadTime, estimatedSize / (1024 * 1024));\n\n // Create pool for frequently used sounds\n if (this.frequentSounds.has(asset.id)) {\n this.audioPool.createPool(asset.id, asset.url, {\n initialSize: 3,\n maxSize: 10,\n autoExpand: true,\n });\n }\n } else {\n // Record failure\n const error = result.error ?? new Error(\"Unknown load error\");\n this.monitor.recordLoadFailure(asset.id, error);\n\n // Still cache the placeholder audio if available\n if (result.audio) {\n this.soundCache.set(asset.id, result.audio);\n\n // Add to cache even if failed (placeholder)\n const estimatedSize = this.estimateAudioSize(asset);\n this.audioCache.set(asset.id, asset, estimatedSize);\n }\n }\n } catch (error) {\n const err = error instanceof Error ? error : new Error(String(error));\n console.warn(`Failed to load audio asset ${asset.id}:`, err);\n this.monitor.recordLoadFailure(asset.id, err);\n }\n }\n\n /**\n * Estimate audio asset size in bytes\n * ๋ฐ์ดํธ ๋จ์์ ์ค๋์ค ์์ฐ ํฌ๊ธฐ ์ถ์ \n *\n * @returns Estimated size in bytes\n */\n private estimateAudioSize(_asset: AudioAsset): number {\n // Use ESTIMATED_ASSET_SIZE_MB constant for consistent estimation\n // Conservative estimate: ~0.5MB per audio file (compressed MP3/WebM)\n return ESTIMATED_ASSET_SIZE_MB * 1024 * 1024;\n }\n\n async playSoundEffect(id: SoundEffectId): Promise<void> {\n if (this._muted) return;\n\n const playbackStart = performance.now();\n\n try {\n // Update cache access time for LRU tracking\n // LRU ์ถ์ ์ ์ํ ์บ์ ์ก์ธ์ค ์๊ฐ ์
๋ฐ์ดํธ\n this.audioCache.get(id);\n\n // Use pool for frequently played sounds\n if (this.frequentSounds.has(id) && this.audioPool.hasPool(id)) {\n const audio = this.audioPool.acquire(id);\n if (audio) {\n audio.volume = this._sfxVolume * this._masterVolume;\n await audio.play();\n\n // Release back to pool after playback (non-blocking)\n this.releasePooledAudio(id, audio);\n\n const latency = performance.now() - playbackStart;\n this.monitor.recordPlaybackLatency(latency);\n return;\n }\n }\n\n // Fallback to cached audio\n const audio = this.soundCache.get(id);\n if (audio) {\n audio.currentTime = 0;\n audio.volume = this._sfxVolume * this._masterVolume;\n await audio.play();\n\n const latency = performance.now() - playbackStart;\n this.monitor.recordPlaybackLatency(latency);\n }\n } catch (error) {\n console.warn(`Failed to play sound effect ${id}:`, error);\n }\n }\n\n // Alias for playSoundEffect to match interface\n async playSFX(id: SoundEffectId, volume?: number): Promise<void> {\n if (this._muted) return;\n\n const playbackStart = performance.now();\n\n try {\n // Update cache access time for LRU tracking\n // LRU ์ถ์ ์ ์ํ ์บ์ ์ก์ธ์ค ์๊ฐ ์
๋ฐ์ดํธ\n this.audioCache.get(id);\n\n // Use pool for frequently played sounds\n if (this.frequentSounds.has(id) && this.audioPool.hasPool(id)) {\n const audio = this.audioPool.acquire(id);\n if (audio) {\n audio.volume = (volume ?? this._sfxVolume) * this._masterVolume;\n await audio.play();\n\n // Release back to pool after playback (non-blocking)\n this.releasePooledAudio(id, audio);\n\n const latency = performance.now() - playbackStart;\n this.monitor.recordPlaybackLatency(latency);\n return;\n }\n }\n\n // Fallback to cached audio\n const audio = this.soundCache.get(id);\n if (audio) {\n audio.currentTime = 0;\n audio.volume = (volume ?? this._sfxVolume) * this._masterVolume;\n await audio.play();\n\n const latency = performance.now() - playbackStart;\n this.monitor.recordPlaybackLatency(latency);\n }\n } catch (error) {\n console.warn(`Failed to play sound effect ${id}:`, error);\n }\n }\n\n async playMusic(id: MusicTrackId, volume?: number): Promise<void> {\n if (this._muted) return;\n\n this.stopMusic();\n\n // Update cache access time for LRU tracking\n // LRU ์ถ์ ์ ์ํ ์บ์ ์ก์ธ์ค ์๊ฐ ์
๋ฐ์ดํธ\n this.audioCache.get(id);\n\n let audio = this.soundCache.get(id);\n \n // On-demand loading: If music not in cache, load it first\n // ์จ๋๋งจ๋ ๋ก๋ฉ: ์์
์ด ์บ์์ ์์ผ๋ฉด ๋จผ์ ๋ก๋\n if (!audio) {\n // Check if this music is already being loaded to prevent race conditions\n // JavaScript's single-threaded nature ensures atomic check-and-add operations\n // ๋ ์ด์ค ์ปจ๋์
๋ฐฉ์ง๋ฅผ ์ํด ์ด๋ฏธ ๋ก๋ ์ค์ธ์ง ํ์ธ\n // JavaScript์ ์ฑ๊ธ ์ค๋ ๋ ํน์ฑ์ผ๋ก ์์์ check-and-add ์์
๋ณด์ฅ\n if (this.loadingMusic.has(id)) {\n if (import.meta.env.DEV) {\n console.log(`[AudioManager] Music \"${id}\" is already being loaded, waiting...`);\n }\n // Wait for the ongoing load to complete\n // ์งํ ์ค์ธ ๋ก๋๊ฐ ์๋ฃ๋ ๋๊น์ง ๋๊ธฐ\n await this.waitForMusicLoad(id);\n audio = this.soundCache.get(id);\n } else {\n // Mark as loading before starting the load (atomic operation in single-threaded JS)\n // ๋ก๋ ์์ ์ ์ ๋ก๋ฉ ์ค์ผ๋ก ํ์ (์ฑ๊ธ ์ค๋ ๋ JS์์ ์์์ ์์
)\n this.loadingMusic.add(id);\n \n if (import.meta.env.DEV) {\n console.log(`[AudioManager] Music \"${id}\" not in cache, loading on-demand...`);\n }\n \n try {\n const { audioAssetRegistry } = await import(\"./AudioAssetRegistry\");\n const musicAsset = audioAssetRegistry.getMusic(id);\n \n if (musicAsset) {\n try {\n await this.loadAsset(musicAsset);\n audio = this.soundCache.get(id);\n \n if (import.meta.env.DEV && audio) {\n console.log(`[AudioManager] Successfully loaded music \"${id}\" on-demand`);\n }\n } catch (error) {\n console.warn(`Failed to load music asset ${id} on-demand:`, error);\n return;\n }\n } else {\n console.warn(`Music asset not found in registry: ${id}`);\n return;\n }\n } finally {\n // Always remove from loading set when done\n // ์๋ฃ ์ ํญ์ ๋ก๋ฉ ์ธํธ์์ ์ ๊ฑฐ\n this.loadingMusic.delete(id);\n }\n }\n }\n\n if (audio) {\n try {\n audio.currentTime = 0;\n audio.volume = (volume ?? this._musicVolume) * this._masterVolume;\n audio.loop = true;\n this.currentMusic = audio;\n this._currentMusicTrack = id;\n await audio.play();\n \n if (import.meta.env.DEV) {\n console.log(`[AudioManager] Playing music: ${id} (volume: ${audio.volume.toFixed(2)})`);\n }\n } catch (error) {\n console.warn(`Failed to play music ${id}:`, error);\n }\n }\n }\n\n /**\n * Wait for a music asset to finish loading\n * ์์
์์ฐ ๋ก๋๊ฐ ์๋ฃ๋ ๋๊น์ง ๋๊ธฐ\n * @param id - Music track ID\n * @param maxWaitMs - Maximum time to wait in milliseconds\n */\n private async waitForMusicLoad(id: string, maxWaitMs: number = 5000): Promise<void> {\n const startTime = Date.now();\n const checkInterval = 100; // Check every 100ms\n \n while (this.loadingMusic.has(id)) {\n if (Date.now() - startTime > maxWaitMs) {\n console.warn(`[AudioManager] Timeout waiting for music \"${id}\" to load`);\n return;\n }\n await new Promise(resolve => setTimeout(resolve, checkInterval));\n }\n }\n\n stopMusic(): void {\n if (this.currentMusic) {\n this.currentMusic.pause();\n this.currentMusic.currentTime = 0;\n this.currentMusic = null;\n this._currentMusicTrack = null;\n }\n }\n\n stopAll(): void {\n this.stopMusic();\n this.soundCache.forEach((audio) => {\n if (!audio.paused) {\n audio.pause();\n audio.currentTime = 0;\n }\n });\n }\n\n setVolume(type: \"master\" | \"sfx\" | \"music\" | \"voice\", volume: number): void {\n const clampedVolume = Math.max(0, Math.min(1, volume));\n\n switch (type) {\n case \"master\":\n this._masterVolume = clampedVolume;\n break;\n case \"sfx\":\n this._sfxVolume = clampedVolume;\n break;\n case \"music\":\n this._musicVolume = clampedVolume;\n if (this.currentMusic) {\n this.currentMusic.volume = this._musicVolume * this._masterVolume;\n }\n break;\n case \"voice\":\n // Handle voice volume if needed\n break;\n }\n }\n\n mute(): void {\n this._muted = true;\n if (this.currentMusic) {\n this.currentMusic.volume = 0;\n }\n }\n\n unmute(): void {\n this._muted = false;\n if (this.currentMusic) {\n this.currentMusic.volume = this._musicVolume * this._masterVolume;\n }\n }\n\n async fadeOut(duration: number = 1000): Promise<void> {\n if (!this.currentMusic) return;\n\n const musicElement = this.currentMusic;\n return new Promise((resolve) => {\n const startVolume = musicElement.volume;\n const fadeStep = startVolume / (duration / 50);\n\n const fadeInterval = setInterval(() => {\n if (this.currentMusic && this.currentMusic.volume > 0) {\n this.currentMusic.volume = Math.max(\n 0,\n this.currentMusic.volume - fadeStep\n );\n } else {\n clearInterval(fadeInterval);\n this.stopMusic();\n resolve();\n }\n }, 50);\n });\n }\n\n async fadeIn(trackId: MusicTrackId, duration: number = 1000): Promise<void> {\n await this.playMusic(trackId, 0);\n\n if (!this.currentMusic) return;\n\n return new Promise((resolve) => {\n const targetVolume = this._musicVolume * this._masterVolume;\n const fadeStep = targetVolume / (duration / 50);\n\n const fadeInterval = setInterval(() => {\n if (this.currentMusic && this.currentMusic.volume < targetVolume) {\n this.currentMusic.volume = Math.min(\n targetVolume,\n this.currentMusic.volume + fadeStep\n );\n } else {\n clearInterval(fadeInterval);\n resolve();\n }\n }, 50);\n });\n }\n\n async crossfade(\n fromTrackId: MusicTrackId,\n toTrackId: MusicTrackId,\n duration: number = 1000\n ): Promise<void> {\n // Fix: Remove unused fromTrackId parameter or use it properly\n const fadeOutPromise = this.fadeOut(duration);\n await fadeOutPromise;\n await this.fadeIn(toTrackId, duration);\n console.log(`Crossfaded from ${fromTrackId} to ${toTrackId}`);\n }\n\n getLoadedAssets(): ReadonlyMap<string, HTMLAudioElement> {\n return new Map(this.soundCache);\n }\n\n // Additional methods to match interface\n async playVoice(id: string): Promise<void> {\n return this.playSoundEffect(id);\n }\n\n async playKoreanTechniqueSound(\n techniqueId: string,\n archetype: string\n ): Promise<void> {\n const soundId = `${archetype}_${techniqueId}`;\n return this.playSoundEffect(soundId);\n }\n\n async playTrigramStanceSound(stance: string): Promise<void> {\n const soundId = `stance_${stance}`;\n return this.playSoundEffect(soundId);\n }\n\n async playVitalPointHitSound(severity: string): Promise<void> {\n const soundId = `vital_point_${severity}`;\n return this.playSoundEffect(soundId);\n }\n\n async playDojiangAmbience(): Promise<void> {\n return this.playMusic(\"dojang_ambience\");\n }\n\n // Legacy getters for backward compatibility\n getMasterVolume(): number {\n return this._masterVolume;\n }\n\n getMusicVolume(): number {\n return this._musicVolume;\n }\n\n getSfxVolume(): number {\n return this._sfxVolume;\n }\n\n get initialized(): boolean {\n return this._isInitialized;\n }\n\n // New optimized methods\n\n /**\n * Batch load multiple assets with progress tracking\n * @param assets - Array of audio assets to load\n * @param options - Optional load configuration (timeout, retries, etc.)\n * @param onProgress - Optional callback for progress updates with loaded and total counts\n * @returns Promise that resolves when all assets are processed\n */\n async batchLoadAssets(\n assets: readonly AudioAsset[],\n options?: LoadOptions,\n onProgress?: (loaded: number, total: number) => void\n ): Promise<void> {\n const results = await this.assetLoader.batchLoad(\n assets,\n options,\n (progress) => {\n onProgress?.(progress.loaded, progress.total);\n }\n );\n\n // Process results\n for (let i = 0; i < results.length; i++) {\n const result = results[i];\n const asset = assets[i];\n\n if (result.success && result.audio) {\n result.audio.volume = asset.volume ?? 1.0;\n this.soundCache.set(asset.id, result.audio);\n\n // Create pool for frequently used sounds\n if (this.frequentSounds.has(asset.id)) {\n this.audioPool.createPool(asset.id, asset.url, {\n initialSize: 3,\n maxSize: 10,\n autoExpand: true,\n });\n }\n }\n }\n }\n\n /**\n * Unload an asset to free memory\n * @param assetId - ID of the asset to unload\n * @returns true if asset was unloaded, false if asset was not found\n */\n unloadAsset(assetId: string): boolean {\n // Remove from cache\n const audio = this.soundCache.get(assetId);\n if (audio) {\n audio.pause();\n audio.src = \"\";\n this.soundCache.delete(assetId);\n }\n\n // Remove from AudioCache (LRU cache)\n // LRU ์บ์์์ ์ ๊ฑฐ\n const removedFromCache = this.audioCache.remove(assetId);\n\n // Remove from loader cache\n const unloaded = this.assetLoader.unloadAsset(assetId);\n\n // Remove pool if exists\n if (this.audioPool.hasPool(assetId)) {\n this.audioPool.removePool(assetId);\n }\n\n // Unregister from monitor\n this.monitor.unregisterAsset(assetId);\n\n return unloaded || audio !== undefined || removedFromCache;\n }\n\n /**\n * Get cache statistics including LRU cache metrics\n * LRU ์บ์ ๋ฉํธ๋ฆญ์ ํฌํจํ ์บ์ ํต๊ณ ๊ฐ์ ธ์ค๊ธฐ\n *\n * @returns Cache statistics\n */\n getCacheStats(): {\n readonly lruCache: {\n readonly totalSize: number;\n readonly assetCount: number;\n readonly criticalCount: number;\n readonly utilizationPercent: number;\n readonly evictionCount: number;\n readonly hitCount: number;\n readonly missCount: number;\n readonly hitRate: number;\n };\n readonly soundCache: number;\n readonly poolStats: Map<string, PoolStatistics>;\n } {\n return {\n lruCache: this.audioCache.getStats(),\n soundCache: this.soundCache.size,\n poolStats: this.audioPool.getAllStatistics(),\n };\n }\n\n /**\n * Get memory statistics including total loaded MB, asset count, and warnings\n * @returns Memory statistics object\n */\n getMemoryStats(): AudioMemoryStats {\n return this.monitor.getMemoryStats();\n }\n\n /**\n * Get performance statistics including load times and playback latency\n * @returns Performance statistics object\n */\n getPerformanceStats(): AudioPerformanceStats {\n return this.monitor.getPerformanceStats();\n }\n\n /**\n * Get FPS impact analysis showing baseline vs current FPS and detected drops\n * @returns FPS impact analysis object\n */\n getFPSImpact(): AudioFPSImpact {\n return this.monitor.getFPSImpact();\n }\n\n /**\n * Update FPS measurement for monitoring\n * @param fps - Current frames per second measurement\n */\n updateFPS(fps: number): void {\n this.monitor.updateFPS(fps);\n }\n\n /**\n * Get comprehensive monitoring report including memory, performance, FPS, and warnings\n * @returns Comprehensive monitoring report object\n */\n getMonitoringReport(): {\n readonly memory: AudioMemoryStats;\n readonly performance: AudioPerformanceStats;\n readonly fps: AudioFPSImpact;\n readonly warnings: readonly MemoryWarning[];\n } {\n return this.monitor.getReport();\n }\n\n /**\n * Get all memory warnings\n * @returns Array of memory warnings\n */\n getMemoryWarnings(): readonly MemoryWarning[] {\n return this.monitor.getWarnings();\n }\n\n /**\n * Clear memory warnings\n */\n clearMemoryWarnings(): void {\n this.monitor.clearWarnings();\n }\n\n /**\n * Get pool statistics for a specific asset or all pools\n * @param assetId - Optional asset ID to get specific pool stats\n * @returns Pool statistics for the specified asset or all pools\n */\n getPoolStatistics(\n assetId?: string\n ): PoolStatistics | Map<string, PoolStatistics> | undefined {\n if (assetId) {\n return this.audioPool.getPoolStatistics(assetId);\n }\n return this.audioPool.getAllStatistics();\n }\n\n /**\n * Get loader statistics including cached assets and loading state\n * @returns Loader statistics object\n */\n getLoaderStatistics(): {\n readonly cached: number;\n readonly loading: number;\n readonly totalAttempts: number;\n } {\n return this.assetLoader.getStatistics();\n }\n}\n\nexport default AudioManager;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"AudioManager.js","names":[],"sources":["../../src/audio/AudioManager.ts"],"sourcesContent":["import { AudioAssetLoader, LoadOptions } from \"./AudioAssetLoader\";\nimport { AudioCache, AudioCacheConfig } from \"./AudioCache\";\nimport {\n AudioFPSImpact,\n AudioMemoryStats,\n AudioMonitor,\n AudioPerformanceStats,\n MemoryWarning,\n} from \"./AudioMonitor\";\nimport { AudioElementPool, PoolStatistics } from \"./AudioPool\";\nimport {\n AudioAsset,\n AudioConfig,\n IAudioManager,\n MusicTrackId,\n SoundEffectId,\n} from \"./types\";\n\n// Estimated average size per audio asset in MB (based on typical compressed audio file sizes)\nconst ESTIMATED_ASSET_SIZE_MB = 0.5;\n\n/**\n * Critical assets that should never be evicted from cache\n * ์บ์์์ ์ ๋ ์ ๊ฑฐ๋์ง ์์์ผ ํ๋ ์ค์ํ ์์ฐ\n */\nconst CRITICAL_AUDIO_ASSETS = [\n // Menu sounds - instant playback required\n \"menu_hover\",\n \"menu_select\",\n \"menu_click\",\n \"menu_navigate\",\n \"menu_back\",\n // Common combat sounds - instant playback required\n \"hit_impact\",\n \"hit_light\",\n \"hit_medium\",\n \"hit_heavy\",\n \"guard_block\",\n \"attack_whoosh\",\n \"attack_light\",\n \"stance_change\",\n] as const;\n\nexport class AudioManager implements IAudioManager {\n private _masterVolume: number = 1.0;\n private _musicVolume: number = 0.7;\n private _sfxVolume: number = 0.8;\n private _muted: boolean = false;\n private _currentMusicTrack: string | null = null;\n private _fallbackMode: boolean = false;\n private currentMusic: HTMLAudioElement | null = null;\n private soundCache: Map<string, HTMLAudioElement> = new Map();\n private _isInitialized: boolean = false;\n\n // Track music assets currently being loaded to prevent race conditions\n // ๋ ์ด์ค ์ปจ๋์
๋ฐฉ์ง๋ฅผ ์ํด ํ์ฌ ๋ก๋ ์ค์ธ ์์
์์ฐ ์ถ์ \n private loadingMusic: Set<string> = new Set();\n\n // New optimized components\n private assetLoader: AudioAssetLoader;\n private audioPool: AudioElementPool;\n private monitor: AudioMonitor;\n private audioCache: AudioCache;\n private frequentSounds: Set<string> = new Set([\n \"hit_light\",\n \"attack_light\",\n \"stance_change\",\n ]);\n\n constructor(config?: Partial<AudioConfig>) {\n if (config) {\n this._masterVolume = config.masterVolume ?? 1.0;\n this._musicVolume = config.musicVolume ?? 0.7;\n this._sfxVolume = config.sfxVolume ?? 0.8;\n }\n\n // Initialize new components\n this.assetLoader = new AudioAssetLoader();\n this.audioPool = new AudioElementPool();\n this.monitor = new AudioMonitor();\n\n // Initialize AudioCache with 30MB limit and critical assets\n // 30MB ์ ํ ๋ฐ ์ค์ ์์ฐ์ผ๋ก AudioCache ์ด๊ธฐํ\n const cacheConfig: AudioCacheConfig = {\n maxSizeBytes: 30 * 1024 * 1024, // 30MB default\n criticalAssets: [...CRITICAL_AUDIO_ASSETS],\n debug: typeof process !== \"undefined\" && process?.env?.NODE_ENV === \"development\",\n };\n this.audioCache = new AudioCache(cacheConfig);\n }\n\n /**\n * Helper method to release pooled audio back to the pool after playback\n * @param id - Asset ID\n * @param audio - Audio element to release\n */\n private releasePooledAudio(id: string, audio: HTMLAudioElement): void {\n if (!audio.paused) {\n audio.addEventListener(\n \"ended\",\n () => {\n this.audioPool.release(id, audio);\n },\n { once: true }\n );\n } else {\n this.audioPool.release(id, audio);\n }\n }\n\n // Interface getters\n get isInitialized(): boolean {\n return this._isInitialized;\n }\n\n get fallbackMode(): boolean {\n return this._fallbackMode;\n }\n\n get currentMusicTrack(): string | null {\n return this._currentMusicTrack;\n }\n\n get masterVolume(): number {\n return this._masterVolume;\n }\n\n get sfxVolume(): number {\n return this._sfxVolume;\n }\n\n get musicVolume(): number {\n return this._musicVolume;\n }\n\n get muted(): boolean {\n return this._muted;\n }\n\n async initialize(config?: AudioConfig): Promise<void> {\n try {\n // Create AudioContext using globalThis for cross-platform compatibility\n const AudioContextClass =\n globalThis.AudioContext ||\n (globalThis as unknown as { webkitAudioContext: typeof AudioContext })\n .webkitAudioContext;\n new AudioContextClass();\n this._isInitialized = true;\n this._fallbackMode = false;\n\n if (config) {\n this._masterVolume = config.masterVolume ?? this._masterVolume;\n this._musicVolume = config.musicVolume ?? this._musicVolume;\n this._sfxVolume = config.sfxVolume ?? this._sfxVolume;\n }\n\n // Set memory threshold if configured\n if (config?.maxSimultaneousSounds) {\n const estimatedMemoryMB =\n config.maxSimultaneousSounds * ESTIMATED_ASSET_SIZE_MB;\n this.monitor.setMemoryThreshold(estimatedMemoryMB);\n }\n } catch (error) {\n console.warn(\n \"AudioContext initialization failed, using fallback mode:\",\n error\n );\n this._isInitialized = true;\n this._fallbackMode = true;\n }\n }\n\n async loadAsset(asset: AudioAsset, options?: LoadOptions): Promise<void> {\n const startTime = performance.now();\n\n try {\n // Check cache first - instant playback for cached assets\n // ์บ์ ๋จผ์ ํ์ธ - ์บ์๋ ์์ฐ์ ์ฆ์ ์ฌ์\n const cachedAsset = this.audioCache.get(asset.id);\n if (cachedAsset) {\n // Asset already loaded in cache, use it\n const cached = this.soundCache.get(asset.id);\n if (cached) {\n return; // Already loaded and cached\n }\n }\n\n // Use faster timeouts for testing environment\n // Safely check for Node.js environment to avoid runtime errors in browsers\n const isTest =\n typeof process !== \"undefined\" && process?.env?.NODE_ENV === \"test\";\n const loadOptions: LoadOptions = {\n timeout: isTest ? 100 : 10000,\n maxRetries: isTest ? 1 : 3,\n retryDelay: isTest ? 10 : 1000,\n ...options,\n };\n\n // Use AudioAssetLoader with retry and fallback\n const result = await this.assetLoader.loadAsset(asset, loadOptions);\n\n if (result.success && result.audio) {\n result.audio.volume = asset.volume ?? 1.0;\n this.soundCache.set(asset.id, result.audio);\n\n // Estimate size for cache management\n // ์บ์ ๊ด๋ฆฌ๋ฅผ ์ํ ํฌ๊ธฐ ์ถ์ \n const estimatedSize = this.estimateAudioSize(asset);\n\n // Add to LRU cache with size tracking\n // ํฌ๊ธฐ ์ถ์ ๊ณผ ํจ๊ป LRU ์บ์์ ์ถ๊ฐ\n this.audioCache.set(asset.id, asset, estimatedSize);\n\n // Track performance and memory\n const loadTime = performance.now() - startTime;\n this.monitor.recordLoad(asset.id, loadTime, estimatedSize / (1024 * 1024));\n\n // Create pool for frequently used sounds\n if (this.frequentSounds.has(asset.id)) {\n this.audioPool.createPool(asset.id, asset.url, {\n initialSize: 3,\n maxSize: 10,\n autoExpand: true,\n });\n }\n } else {\n // Record failure\n const error = result.error ?? new Error(\"Unknown load error\");\n this.monitor.recordLoadFailure(asset.id, error);\n\n // Still cache the placeholder audio if available\n if (result.audio) {\n this.soundCache.set(asset.id, result.audio);\n\n // Add to cache even if failed (placeholder)\n const estimatedSize = this.estimateAudioSize(asset);\n this.audioCache.set(asset.id, asset, estimatedSize);\n }\n }\n } catch (error) {\n const err = error instanceof Error ? error : new Error(String(error));\n console.warn(`Failed to load audio asset ${asset.id}:`, err);\n this.monitor.recordLoadFailure(asset.id, err);\n }\n }\n\n /**\n * Estimate audio asset size in bytes\n * ๋ฐ์ดํธ ๋จ์์ ์ค๋์ค ์์ฐ ํฌ๊ธฐ ์ถ์ \n *\n * @returns Estimated size in bytes\n */\n private estimateAudioSize(_asset: AudioAsset): number {\n // Use ESTIMATED_ASSET_SIZE_MB constant for consistent estimation\n // Conservative estimate: ~0.5MB per audio file (compressed MP3/WebM)\n return ESTIMATED_ASSET_SIZE_MB * 1024 * 1024;\n }\n\n async playSoundEffect(id: SoundEffectId): Promise<void> {\n if (this._muted) return;\n\n const playbackStart = performance.now();\n\n try {\n // Update cache access time for LRU tracking\n // LRU ์ถ์ ์ ์ํ ์บ์ ์ก์ธ์ค ์๊ฐ ์
๋ฐ์ดํธ\n this.audioCache.get(id);\n\n // Use pool for frequently played sounds\n if (this.frequentSounds.has(id) && this.audioPool.hasPool(id)) {\n const audio = this.audioPool.acquire(id);\n if (audio) {\n audio.volume = this._sfxVolume * this._masterVolume;\n await audio.play();\n\n // Release back to pool after playback (non-blocking)\n this.releasePooledAudio(id, audio);\n\n const latency = performance.now() - playbackStart;\n this.monitor.recordPlaybackLatency(latency);\n return;\n }\n }\n\n // Fallback to cached audio\n const audio = this.soundCache.get(id);\n if (audio) {\n audio.currentTime = 0;\n audio.volume = this._sfxVolume * this._masterVolume;\n await audio.play();\n\n const latency = performance.now() - playbackStart;\n this.monitor.recordPlaybackLatency(latency);\n }\n } catch (error) {\n console.warn(`Failed to play sound effect ${id}:`, error);\n }\n }\n\n // Alias for playSoundEffect to match interface\n async playSFX(id: SoundEffectId, volume?: number): Promise<void> {\n if (this._muted) return;\n\n const playbackStart = performance.now();\n\n try {\n // Update cache access time for LRU tracking\n // LRU ์ถ์ ์ ์ํ ์บ์ ์ก์ธ์ค ์๊ฐ ์
๋ฐ์ดํธ\n this.audioCache.get(id);\n\n // Use pool for frequently played sounds\n if (this.frequentSounds.has(id) && this.audioPool.hasPool(id)) {\n const audio = this.audioPool.acquire(id);\n if (audio) {\n audio.volume = (volume ?? this._sfxVolume) * this._masterVolume;\n await audio.play();\n\n // Release back to pool after playback (non-blocking)\n this.releasePooledAudio(id, audio);\n\n const latency = performance.now() - playbackStart;\n this.monitor.recordPlaybackLatency(latency);\n return;\n }\n }\n\n // Fallback to cached audio\n const audio = this.soundCache.get(id);\n if (audio) {\n audio.currentTime = 0;\n audio.volume = (volume ?? this._sfxVolume) * this._masterVolume;\n await audio.play();\n\n const latency = performance.now() - playbackStart;\n this.monitor.recordPlaybackLatency(latency);\n }\n } catch (error) {\n console.warn(`Failed to play sound effect ${id}:`, error);\n }\n }\n\n async playMusic(id: MusicTrackId, volume?: number): Promise<void> {\n if (this._muted) return;\n\n this.stopMusic();\n\n // Update cache access time for LRU tracking\n // LRU ์ถ์ ์ ์ํ ์บ์ ์ก์ธ์ค ์๊ฐ ์
๋ฐ์ดํธ\n this.audioCache.get(id);\n\n let audio = this.soundCache.get(id);\n \n // On-demand loading: If music not in cache, load it first\n // ์จ๋๋งจ๋ ๋ก๋ฉ: ์์
์ด ์บ์์ ์์ผ๋ฉด ๋จผ์ ๋ก๋\n if (!audio) {\n // Check if this music is already being loaded to prevent race conditions\n // JavaScript's single-threaded nature ensures atomic check-and-add operations\n // ๋ ์ด์ค ์ปจ๋์
๋ฐฉ์ง๋ฅผ ์ํด ์ด๋ฏธ ๋ก๋ ์ค์ธ์ง ํ์ธ\n // JavaScript์ ์ฑ๊ธ ์ค๋ ๋ ํน์ฑ์ผ๋ก ์์์ check-and-add ์์
๋ณด์ฅ\n if (this.loadingMusic.has(id)) {\n if (import.meta.env.DEV) {\n console.log(`[AudioManager] Music \"${id}\" is already being loaded, waiting...`);\n }\n // Wait for the ongoing load to complete\n // ์งํ ์ค์ธ ๋ก๋๊ฐ ์๋ฃ๋ ๋๊น์ง ๋๊ธฐ\n await this.waitForMusicLoad(id);\n audio = this.soundCache.get(id);\n } else {\n // Mark as loading before starting the load (atomic operation in single-threaded JS)\n // ๋ก๋ ์์ ์ ์ ๋ก๋ฉ ์ค์ผ๋ก ํ์ (์ฑ๊ธ ์ค๋ ๋ JS์์ ์์์ ์์
)\n this.loadingMusic.add(id);\n \n if (import.meta.env.DEV) {\n console.log(`[AudioManager] Music \"${id}\" not in cache, loading on-demand...`);\n }\n \n try {\n const { audioAssetRegistry } = await import(\"./AudioAssetRegistry\");\n const musicAsset = audioAssetRegistry.getMusic(id);\n \n if (musicAsset) {\n try {\n await this.loadAsset(musicAsset);\n audio = this.soundCache.get(id);\n \n if (import.meta.env.DEV && audio) {\n console.log(`[AudioManager] Successfully loaded music \"${id}\" on-demand`);\n }\n } catch (error) {\n console.warn(`Failed to load music asset ${id} on-demand:`, error);\n return;\n }\n } else {\n console.warn(`Music asset not found in registry: ${id}`);\n return;\n }\n } finally {\n // Always remove from loading set when done\n // ์๋ฃ ์ ํญ์ ๋ก๋ฉ ์ธํธ์์ ์ ๊ฑฐ\n this.loadingMusic.delete(id);\n }\n }\n }\n\n if (audio) {\n try {\n audio.currentTime = 0;\n audio.volume = (volume ?? this._musicVolume) * this._masterVolume;\n audio.loop = true;\n this.currentMusic = audio;\n this._currentMusicTrack = id;\n await audio.play();\n \n if (import.meta.env.DEV) {\n console.log(`[AudioManager] Playing music: ${id} (volume: ${audio.volume.toFixed(2)})`);\n }\n } catch (error) {\n console.warn(`Failed to play music ${id}:`, error);\n }\n }\n }\n\n /**\n * Wait for a music asset to finish loading\n * ์์
์์ฐ ๋ก๋๊ฐ ์๋ฃ๋ ๋๊น์ง ๋๊ธฐ\n * @param id - Music track ID\n * @param maxWaitMs - Maximum time to wait in milliseconds\n */\n private async waitForMusicLoad(id: string, maxWaitMs: number = 5000): Promise<void> {\n const startTime = Date.now();\n const checkInterval = 100; // Check every 100ms\n \n while (this.loadingMusic.has(id)) {\n if (Date.now() - startTime > maxWaitMs) {\n console.warn(`[AudioManager] Timeout waiting for music \"${id}\" to load`);\n return;\n }\n await new Promise(resolve => setTimeout(resolve, checkInterval));\n }\n }\n\n stopMusic(): void {\n if (this.currentMusic) {\n this.currentMusic.pause();\n this.currentMusic.currentTime = 0;\n this.currentMusic = null;\n this._currentMusicTrack = null;\n }\n }\n\n stopAll(): void {\n this.stopMusic();\n this.soundCache.forEach((audio) => {\n if (!audio.paused) {\n audio.pause();\n audio.currentTime = 0;\n }\n });\n }\n\n setVolume(type: \"master\" | \"sfx\" | \"music\" | \"voice\", volume: number): void {\n const clampedVolume = Math.max(0, Math.min(1, volume));\n\n switch (type) {\n case \"master\":\n this._masterVolume = clampedVolume;\n break;\n case \"sfx\":\n this._sfxVolume = clampedVolume;\n break;\n case \"music\":\n this._musicVolume = clampedVolume;\n if (this.currentMusic) {\n this.currentMusic.volume = this._musicVolume * this._masterVolume;\n }\n break;\n case \"voice\":\n // Handle voice volume if needed\n break;\n }\n }\n\n mute(): void {\n this._muted = true;\n if (this.currentMusic) {\n this.currentMusic.volume = 0;\n }\n }\n\n unmute(): void {\n this._muted = false;\n if (this.currentMusic) {\n this.currentMusic.volume = this._musicVolume * this._masterVolume;\n }\n }\n\n async fadeOut(duration: number = 1000): Promise<void> {\n if (!this.currentMusic) return;\n\n const musicElement = this.currentMusic;\n return new Promise((resolve) => {\n const startVolume = musicElement.volume;\n const fadeStep = startVolume / (duration / 50);\n\n const fadeInterval = setInterval(() => {\n if (this.currentMusic && this.currentMusic.volume > 0) {\n this.currentMusic.volume = Math.max(\n 0,\n this.currentMusic.volume - fadeStep\n );\n } else {\n clearInterval(fadeInterval);\n this.stopMusic();\n resolve();\n }\n }, 50);\n });\n }\n\n async fadeIn(trackId: MusicTrackId, duration: number = 1000): Promise<void> {\n await this.playMusic(trackId, 0);\n\n if (!this.currentMusic) return;\n\n return new Promise((resolve) => {\n const targetVolume = this._musicVolume * this._masterVolume;\n const fadeStep = targetVolume / (duration / 50);\n\n const fadeInterval = setInterval(() => {\n if (this.currentMusic && this.currentMusic.volume < targetVolume) {\n this.currentMusic.volume = Math.min(\n targetVolume,\n this.currentMusic.volume + fadeStep\n );\n } else {\n clearInterval(fadeInterval);\n resolve();\n }\n }, 50);\n });\n }\n\n async crossfade(\n fromTrackId: MusicTrackId,\n toTrackId: MusicTrackId,\n duration: number = 1000\n ): Promise<void> {\n // Fix: Remove unused fromTrackId parameter or use it properly\n const fadeOutPromise = this.fadeOut(duration);\n await fadeOutPromise;\n await this.fadeIn(toTrackId, duration);\n console.log(`Crossfaded from ${fromTrackId} to ${toTrackId}`);\n }\n\n getLoadedAssets(): ReadonlyMap<string, HTMLAudioElement> {\n return new Map(this.soundCache);\n }\n\n // Additional methods to match interface\n async playVoice(id: string): Promise<void> {\n return this.playSoundEffect(id);\n }\n\n async playKoreanTechniqueSound(\n techniqueId: string,\n archetype: string\n ): Promise<void> {\n const soundId = `${archetype}_${techniqueId}`;\n return this.playSoundEffect(soundId);\n }\n\n async playTrigramStanceSound(stance: string): Promise<void> {\n const soundId = `stance_${stance}`;\n return this.playSoundEffect(soundId);\n }\n\n async playVitalPointHitSound(severity: string): Promise<void> {\n const soundId = `vital_point_${severity}`;\n return this.playSoundEffect(soundId);\n }\n\n async playDojiangAmbience(): Promise<void> {\n return this.playMusic(\"dojang_ambience\");\n }\n\n // Legacy getters for backward compatibility\n getMasterVolume(): number {\n return this._masterVolume;\n }\n\n getMusicVolume(): number {\n return this._musicVolume;\n }\n\n getSfxVolume(): number {\n return this._sfxVolume;\n }\n\n get initialized(): boolean {\n return this._isInitialized;\n }\n\n // New optimized methods\n\n /**\n * Batch load multiple assets with progress tracking\n * @param assets - Array of audio assets to load\n * @param options - Optional load configuration (timeout, retries, etc.)\n * @param onProgress - Optional callback for progress updates with loaded and total counts\n * @returns Promise that resolves when all assets are processed\n */\n async batchLoadAssets(\n assets: readonly AudioAsset[],\n options?: LoadOptions,\n onProgress?: (loaded: number, total: number) => void\n ): Promise<void> {\n const results = await this.assetLoader.batchLoad(\n assets,\n options,\n (progress) => {\n onProgress?.(progress.loaded, progress.total);\n }\n );\n\n // Process results\n for (let i = 0; i < results.length; i++) {\n const result = results[i];\n const asset = assets[i];\n\n if (result.success && result.audio) {\n result.audio.volume = asset.volume ?? 1.0;\n this.soundCache.set(asset.id, result.audio);\n\n // Create pool for frequently used sounds\n if (this.frequentSounds.has(asset.id)) {\n this.audioPool.createPool(asset.id, asset.url, {\n initialSize: 3,\n maxSize: 10,\n autoExpand: true,\n });\n }\n }\n }\n }\n\n /**\n * Unload an asset to free memory\n * @param assetId - ID of the asset to unload\n * @returns true if asset was unloaded, false if asset was not found\n */\n unloadAsset(assetId: string): boolean {\n // Remove from cache\n const audio = this.soundCache.get(assetId);\n if (audio) {\n audio.pause();\n audio.src = \"\";\n this.soundCache.delete(assetId);\n }\n\n // Remove from AudioCache (LRU cache)\n // LRU ์บ์์์ ์ ๊ฑฐ\n const removedFromCache = this.audioCache.remove(assetId);\n\n // Remove from loader cache\n const unloaded = this.assetLoader.unloadAsset(assetId);\n\n // Remove pool if exists\n if (this.audioPool.hasPool(assetId)) {\n this.audioPool.removePool(assetId);\n }\n\n // Unregister from monitor\n this.monitor.unregisterAsset(assetId);\n\n return unloaded || audio !== undefined || removedFromCache;\n }\n\n /**\n * Get cache statistics including LRU cache metrics\n * LRU ์บ์ ๋ฉํธ๋ฆญ์ ํฌํจํ ์บ์ ํต๊ณ ๊ฐ์ ธ์ค๊ธฐ\n *\n * @returns Cache statistics\n */\n getCacheStats(): {\n readonly lruCache: {\n readonly totalSize: number;\n readonly assetCount: number;\n readonly criticalCount: number;\n readonly utilizationPercent: number;\n readonly evictionCount: number;\n readonly hitCount: number;\n readonly missCount: number;\n readonly hitRate: number;\n };\n readonly soundCache: number;\n readonly poolStats: Map<string, PoolStatistics>;\n } {\n return {\n lruCache: this.audioCache.getStats(),\n soundCache: this.soundCache.size,\n poolStats: this.audioPool.getAllStatistics(),\n };\n }\n\n /**\n * Get memory statistics including total loaded MB, asset count, and warnings\n * @returns Memory statistics object\n */\n getMemoryStats(): AudioMemoryStats {\n return this.monitor.getMemoryStats();\n }\n\n /**\n * Get performance statistics including load times and playback latency\n * @returns Performance statistics object\n */\n getPerformanceStats(): AudioPerformanceStats {\n return this.monitor.getPerformanceStats();\n }\n\n /**\n * Get FPS impact analysis showing baseline vs current FPS and detected drops\n * @returns FPS impact analysis object\n */\n getFPSImpact(): AudioFPSImpact {\n return this.monitor.getFPSImpact();\n }\n\n /**\n * Update FPS measurement for monitoring\n * @param fps - Current frames per second measurement\n */\n updateFPS(fps: number): void {\n this.monitor.updateFPS(fps);\n }\n\n /**\n * Get comprehensive monitoring report including memory, performance, FPS, and warnings\n * @returns Comprehensive monitoring report object\n */\n getMonitoringReport(): {\n readonly memory: AudioMemoryStats;\n readonly performance: AudioPerformanceStats;\n readonly fps: AudioFPSImpact;\n readonly warnings: readonly MemoryWarning[];\n } {\n return this.monitor.getReport();\n }\n\n /**\n * Get all memory warnings\n * @returns Array of memory warnings\n */\n getMemoryWarnings(): readonly MemoryWarning[] {\n return this.monitor.getWarnings();\n }\n\n /**\n * Clear memory warnings\n */\n clearMemoryWarnings(): void {\n this.monitor.clearWarnings();\n }\n\n /**\n * Get pool statistics for a specific asset or all pools\n * @param assetId - Optional asset ID to get specific pool stats\n * @returns Pool statistics for the specified asset or all pools\n */\n getPoolStatistics(\n assetId?: string\n ): PoolStatistics | Map<string, PoolStatistics> | undefined {\n if (assetId) {\n return this.audioPool.getPoolStatistics(assetId);\n }\n return this.audioPool.getAllStatistics();\n }\n\n /**\n * Get loader statistics including cached assets and loading state\n * @returns Loader statistics object\n */\n getLoaderStatistics(): {\n readonly cached: number;\n readonly loading: number;\n readonly totalAttempts: number;\n } {\n return this.assetLoader.getStatistics();\n }\n}\n\nexport default AudioManager;\n"],"mappings":"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"}
|
package/lib/audio/types.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","names":[],"sources":["../../src/audio/types.ts"],"sourcesContent":["// filepath: /workspaces/blacktrigram/src/audio/types.ts\n/**\n * Type definitions for audio types\n * Auto-generated by type migration script\n */\n\nimport { KoreanText, PlayerArchetype, TrigramStance } from \"@/types\";\n\n// Export AudioCategory enum (only one declaration)\nexport enum AudioCategory {\n SFX = \"sfx\",\n MUSIC = \"music\",\n VOICE = \"voice\",\n UI = \"ui\",\n // Remove AMBIENT since it's causing errors\n}\n\n// Audio context for spatial audio\nexport interface AudioContext3D {\n readonly position: { x: number; y: number; z?: number };\n readonly velocity?: { x: number; y: number; z?: number };\n readonly orientation?: { x: number; y: number; z?: number };\n readonly maxDistance?: number;\n readonly rolloffFactor?: number;\n}\n\n// Combat audio mapping\n// Add missing type exports\nexport type SoundEffectId = string;\nexport type MusicTrackId = string;\nexport type VoiceLineId = string;\n\n// Audio format type (string literals only, not enum)\nexport type AudioFormat =\n | \"audio/mp3\"\n | \"audio/wav\"\n | \"audio/ogg\"\n | \"audio/webm\";\n\n// Audio effect definitions\nexport interface AudioEffect {\n readonly type: \"reverb\" | \"delay\" | \"distortion\" | \"filter\" | \"compressor\";\n readonly parameters: Record<string, number>;\n readonly enabled: boolean;\n}\n\n// Audio mixer channel\nexport interface AudioChannel {\n readonly id: string;\n readonly category: AudioCategory;\n readonly volume: number;\n readonly muted: boolean;\n readonly effects: readonly AudioEffect[];\n readonly connectedSources: readonly string[];\n}\n\n// Audio platform capabilities\nexport interface AudioCapabilities {\n readonly supportsWebAudio: boolean;\n readonly supportsHowler: boolean;\n readonly maxSources: number;\n readonly formats: readonly string[];\n readonly spatialAudio: boolean;\n readonly realTimeEffects: boolean;\n}\n\nexport interface IAudioManager {\n readonly isInitialized: boolean;\n readonly masterVolume: number;\n readonly sfxVolume: number;\n readonly musicVolume: number;\n readonly muted: boolean;\n\n initialize(config?: AudioConfig): Promise<void>;\n loadAsset(asset: AudioAsset): Promise<void>; // โ
Added missing method\n setVolume(type: \"master\" | \"sfx\" | \"music\" | \"voice\", volume: number): void;\n playMusic(trackId: string): Promise<void>;\n playSoundEffect(soundId: string): Promise<void>;\n playSFX(soundId: string, volume?: number): Promise<void>; // โ
Added missing playSFX method\n stopMusic(): void;\n mute(): void;\n unmute(): void;\n fadeIn(trackId: string, duration?: number): Promise<void>; // โ
Added for combat audio\n fadeOut(duration?: number): Promise<void>; // โ
Added for combat audio\n playKoreanTechniqueSound(\n techniqueId: string,\n archetype: string\n ): Promise<void>;\n playTrigramStanceSound(stance: string): Promise<void>;\n playVitalPointHitSound(severity: string): Promise<void>;\n playDojiangAmbience(): Promise<void>;\n}\n\nexport interface AudioAsset {\n readonly id: string;\n readonly name?: string;\n readonly type: \"sound\" | \"music\" | \"voice\";\n readonly url: string;\n readonly formats: readonly string[];\n readonly loaded: boolean;\n readonly volume?: number;\n readonly category?: string;\n}\n\nexport interface MusicTrack extends AudioAsset {\n readonly type: \"music\";\n readonly title?: KoreanText;\n readonly artist?: string;\n readonly album?: string;\n readonly bpm?: number;\n readonly loop?: boolean;\n readonly fadeInTime?: number;\n readonly fadeOutTime?: number;\n readonly variations?: readonly string[];\n readonly category: \"music\" | \"voice\";\n}\n\nexport interface SoundEffect extends AudioAsset {\n readonly type: \"sound\";\n readonly pitch?: number;\n readonly variations?: readonly string[];\n readonly category: \"sfx\" | \"ui\";\n}\n\nexport interface VoiceLine extends AudioAsset {\n readonly type: \"voice\";\n readonly text: KoreanText;\n readonly archetype?: PlayerArchetype;\n readonly emotion?:\n | \"neutral\"\n | \"aggressive\"\n | \"defensive\"\n | \"victorious\"\n | \"defeated\";\n category?: AudioCategory;\n volume?: number;\n}\n\nexport interface AudioConfig {\n readonly enableSpatialAudio: boolean;\n readonly maxSimultaneousSounds: number;\n readonly audioFormats: readonly string[];\n readonly fadeTransitionTime: number;\n readonly defaultVolume?: number;\n masterVolume?: number;\n musicVolume?: number;\n sfxVolume?: number;\n}\n\nexport interface AudioEvent {\n readonly type: \"play\" | \"stop\" | \"pause\" | \"resume\" | \"volume\" | \"fade\";\n readonly assetId: string;\n readonly volume?: number;\n readonly delay?: number;\n readonly fadeTime?: number;\n readonly loop?: boolean;\n readonly priority?: number;\n}\n\nexport interface CombatAudioMap {\n readonly attacks: Record<string, SoundEffectId>;\n readonly impacts: Record<string, SoundEffectId>;\n readonly stances: Record<TrigramStance, string>;\n readonly environments: Record<string, SoundEffectId>;\n readonly ui: Record<string, SoundEffectId>;\n}\n\nexport interface AudioState {\n readonly isPlaying: boolean;\n readonly isPaused: boolean;\n readonly currentTime: number;\n readonly duration: number;\n readonly volume: number;\n readonly loop: boolean;\n readonly masterVolume: number;\n readonly sfxVolume: number;\n readonly musicVolume: number;\n readonly muted: boolean;\n readonly currentMusicTrack: string | null;\n readonly isInitialized: boolean;\n readonly fallbackMode: boolean;\n}\n\nexport interface AudioPlaybackOptions {\n readonly volume?: number;\n readonly loop?: boolean;\n readonly fadeIn?: number;\n readonly fadeOut?: number;\n readonly delay?: number;\n readonly startTime?: number;\n readonly endTime?: number;\n readonly rate?: number;\n}\n\nexport interface ProceduralSoundConfig {\n readonly frequency: number;\n readonly duration: number;\n readonly type: \"sine\" | \"square\" | \"sawtooth\" | \"triangle\" | \"noise\";\n readonly attack?: number;\n readonly decay?: number;\n readonly sustain?: number;\n readonly release?: number;\n readonly volume?: number;\n}\n\nexport interface CombatAudioEvent {\n readonly type: \"attack\" | \"hit\" | \"block\" | \"dodge\" | \"stance_change\";\n readonly technique?: string;\n readonly stance?: string;\n readonly damage?: number;\n readonly critical?: boolean;\n}\n\n/**\n * Audio-specific body regions for impact sound mapping\n * Maps to Korean martial arts vital point locations\n */\nexport type AudioBodyRegion =\n | \"head\" // ๋๋ถ (Head/Skull): temple, jaw, neck\n | \"torso\" // ๋ชธํต (Torso): ribs, sternum, solar plexus, organs\n | \"arms\" // ํ (Arms): shoulder, elbow, forearm, wrist\n | \"legs\" // ๋ค๋ฆฌ (Legs): hip, knee, shin, ankle\n | \"soft_tissue\"; // ์ฐ์กฐ์ง (Soft tissue): muscle, flesh, non-bone areas\n\n/**\n * Impact intensity levels for bone/flesh contact\n * Determines audio selection and volume variation\n */\nexport type ImpactIntensity =\n | \"light\" // ๊ฒฝํ (Light): Glancing blows, minimal damage\n | \"medium\" // ์คํ (Medium): Solid contact, moderate damage\n | \"heavy\" // ๊ฐํ (Heavy): Devastating strikes, severe damage\n | \"critical\" // ๊ธ์ํ (Critical): Vital point precision strikes\n | \"fracture\"; // ๊ณจ์ (Fracture): Bone-breaking force, <30% health\n\n/**\n * Bone impact audio event with body region and intensity\n * Used for anatomically accurate combat sound feedback\n */\nexport interface BoneImpactEvent {\n readonly region: AudioBodyRegion;\n readonly intensity: ImpactIntensity;\n readonly vitalPoint?: boolean; // True if hitting a vital point\n readonly remainingHealth?: number; // For fracture detection (<30%)\n}\n\nexport interface AudioLoadingState {\n readonly total: number;\n readonly loaded: number;\n readonly failed: number;\n readonly currentAsset?: string;\n readonly progress: number;\n readonly errors: readonly string[];\n}\nexport interface AudioSystemInterface {\n playSFX: (id: SoundEffectId, options?: AudioPlaybackOptions) => void;\n playMusic: (id: MusicTrackId, options?: AudioPlaybackOptions) => void;\n stopMusic: (id?: MusicTrackId, fadeOutDuration?: number) => void;\n setVolume: (type: \"master\" | \"sfx\" | \"music\", volume: number) => void;\n loadAudioAsset: (asset: AudioAsset) => Promise<void>;\n isMusicPlaying: (id?: MusicTrackId) => boolean;\n}\n\nexport interface AudioManagerInterface extends IAudioManager {}\n"],"mappings":";AASA,IAAY,gBAAL,yBAAA,eAAA;AACL,eAAA,
|
|
1
|
+
{"version":3,"file":"types.js","names":[],"sources":["../../src/audio/types.ts"],"sourcesContent":["// filepath: /workspaces/blacktrigram/src/audio/types.ts\n/**\n * Type definitions for audio types\n * Auto-generated by type migration script\n */\n\nimport { KoreanText, PlayerArchetype, TrigramStance } from \"@/types\";\n\n// Export AudioCategory enum (only one declaration)\nexport enum AudioCategory {\n SFX = \"sfx\",\n MUSIC = \"music\",\n VOICE = \"voice\",\n UI = \"ui\",\n // Remove AMBIENT since it's causing errors\n}\n\n// Audio context for spatial audio\nexport interface AudioContext3D {\n readonly position: { x: number; y: number; z?: number };\n readonly velocity?: { x: number; y: number; z?: number };\n readonly orientation?: { x: number; y: number; z?: number };\n readonly maxDistance?: number;\n readonly rolloffFactor?: number;\n}\n\n// Combat audio mapping\n// Add missing type exports\nexport type SoundEffectId = string;\nexport type MusicTrackId = string;\nexport type VoiceLineId = string;\n\n// Audio format type (string literals only, not enum)\nexport type AudioFormat =\n | \"audio/mp3\"\n | \"audio/wav\"\n | \"audio/ogg\"\n | \"audio/webm\";\n\n// Audio effect definitions\nexport interface AudioEffect {\n readonly type: \"reverb\" | \"delay\" | \"distortion\" | \"filter\" | \"compressor\";\n readonly parameters: Record<string, number>;\n readonly enabled: boolean;\n}\n\n// Audio mixer channel\nexport interface AudioChannel {\n readonly id: string;\n readonly category: AudioCategory;\n readonly volume: number;\n readonly muted: boolean;\n readonly effects: readonly AudioEffect[];\n readonly connectedSources: readonly string[];\n}\n\n// Audio platform capabilities\nexport interface AudioCapabilities {\n readonly supportsWebAudio: boolean;\n readonly supportsHowler: boolean;\n readonly maxSources: number;\n readonly formats: readonly string[];\n readonly spatialAudio: boolean;\n readonly realTimeEffects: boolean;\n}\n\nexport interface IAudioManager {\n readonly isInitialized: boolean;\n readonly masterVolume: number;\n readonly sfxVolume: number;\n readonly musicVolume: number;\n readonly muted: boolean;\n\n initialize(config?: AudioConfig): Promise<void>;\n loadAsset(asset: AudioAsset): Promise<void>; // โ
Added missing method\n setVolume(type: \"master\" | \"sfx\" | \"music\" | \"voice\", volume: number): void;\n playMusic(trackId: string): Promise<void>;\n playSoundEffect(soundId: string): Promise<void>;\n playSFX(soundId: string, volume?: number): Promise<void>; // โ
Added missing playSFX method\n stopMusic(): void;\n mute(): void;\n unmute(): void;\n fadeIn(trackId: string, duration?: number): Promise<void>; // โ
Added for combat audio\n fadeOut(duration?: number): Promise<void>; // โ
Added for combat audio\n playKoreanTechniqueSound(\n techniqueId: string,\n archetype: string\n ): Promise<void>;\n playTrigramStanceSound(stance: string): Promise<void>;\n playVitalPointHitSound(severity: string): Promise<void>;\n playDojiangAmbience(): Promise<void>;\n}\n\nexport interface AudioAsset {\n readonly id: string;\n readonly name?: string;\n readonly type: \"sound\" | \"music\" | \"voice\";\n readonly url: string;\n readonly formats: readonly string[];\n readonly loaded: boolean;\n readonly volume?: number;\n readonly category?: string;\n}\n\nexport interface MusicTrack extends AudioAsset {\n readonly type: \"music\";\n readonly title?: KoreanText;\n readonly artist?: string;\n readonly album?: string;\n readonly bpm?: number;\n readonly loop?: boolean;\n readonly fadeInTime?: number;\n readonly fadeOutTime?: number;\n readonly variations?: readonly string[];\n readonly category: \"music\" | \"voice\";\n}\n\nexport interface SoundEffect extends AudioAsset {\n readonly type: \"sound\";\n readonly pitch?: number;\n readonly variations?: readonly string[];\n readonly category: \"sfx\" | \"ui\";\n}\n\nexport interface VoiceLine extends AudioAsset {\n readonly type: \"voice\";\n readonly text: KoreanText;\n readonly archetype?: PlayerArchetype;\n readonly emotion?:\n | \"neutral\"\n | \"aggressive\"\n | \"defensive\"\n | \"victorious\"\n | \"defeated\";\n category?: AudioCategory;\n volume?: number;\n}\n\nexport interface AudioConfig {\n readonly enableSpatialAudio: boolean;\n readonly maxSimultaneousSounds: number;\n readonly audioFormats: readonly string[];\n readonly fadeTransitionTime: number;\n readonly defaultVolume?: number;\n masterVolume?: number;\n musicVolume?: number;\n sfxVolume?: number;\n}\n\nexport interface AudioEvent {\n readonly type: \"play\" | \"stop\" | \"pause\" | \"resume\" | \"volume\" | \"fade\";\n readonly assetId: string;\n readonly volume?: number;\n readonly delay?: number;\n readonly fadeTime?: number;\n readonly loop?: boolean;\n readonly priority?: number;\n}\n\nexport interface CombatAudioMap {\n readonly attacks: Record<string, SoundEffectId>;\n readonly impacts: Record<string, SoundEffectId>;\n readonly stances: Record<TrigramStance, string>;\n readonly environments: Record<string, SoundEffectId>;\n readonly ui: Record<string, SoundEffectId>;\n}\n\nexport interface AudioState {\n readonly isPlaying: boolean;\n readonly isPaused: boolean;\n readonly currentTime: number;\n readonly duration: number;\n readonly volume: number;\n readonly loop: boolean;\n readonly masterVolume: number;\n readonly sfxVolume: number;\n readonly musicVolume: number;\n readonly muted: boolean;\n readonly currentMusicTrack: string | null;\n readonly isInitialized: boolean;\n readonly fallbackMode: boolean;\n}\n\nexport interface AudioPlaybackOptions {\n readonly volume?: number;\n readonly loop?: boolean;\n readonly fadeIn?: number;\n readonly fadeOut?: number;\n readonly delay?: number;\n readonly startTime?: number;\n readonly endTime?: number;\n readonly rate?: number;\n}\n\nexport interface ProceduralSoundConfig {\n readonly frequency: number;\n readonly duration: number;\n readonly type: \"sine\" | \"square\" | \"sawtooth\" | \"triangle\" | \"noise\";\n readonly attack?: number;\n readonly decay?: number;\n readonly sustain?: number;\n readonly release?: number;\n readonly volume?: number;\n}\n\nexport interface CombatAudioEvent {\n readonly type: \"attack\" | \"hit\" | \"block\" | \"dodge\" | \"stance_change\";\n readonly technique?: string;\n readonly stance?: string;\n readonly damage?: number;\n readonly critical?: boolean;\n}\n\n/**\n * Audio-specific body regions for impact sound mapping\n * Maps to Korean martial arts vital point locations\n */\nexport type AudioBodyRegion =\n | \"head\" // ๋๋ถ (Head/Skull): temple, jaw, neck\n | \"torso\" // ๋ชธํต (Torso): ribs, sternum, solar plexus, organs\n | \"arms\" // ํ (Arms): shoulder, elbow, forearm, wrist\n | \"legs\" // ๋ค๋ฆฌ (Legs): hip, knee, shin, ankle\n | \"soft_tissue\"; // ์ฐ์กฐ์ง (Soft tissue): muscle, flesh, non-bone areas\n\n/**\n * Impact intensity levels for bone/flesh contact\n * Determines audio selection and volume variation\n */\nexport type ImpactIntensity =\n | \"light\" // ๊ฒฝํ (Light): Glancing blows, minimal damage\n | \"medium\" // ์คํ (Medium): Solid contact, moderate damage\n | \"heavy\" // ๊ฐํ (Heavy): Devastating strikes, severe damage\n | \"critical\" // ๊ธ์ํ (Critical): Vital point precision strikes\n | \"fracture\"; // ๊ณจ์ (Fracture): Bone-breaking force, <30% health\n\n/**\n * Bone impact audio event with body region and intensity\n * Used for anatomically accurate combat sound feedback\n */\nexport interface BoneImpactEvent {\n readonly region: AudioBodyRegion;\n readonly intensity: ImpactIntensity;\n readonly vitalPoint?: boolean; // True if hitting a vital point\n readonly remainingHealth?: number; // For fracture detection (<30%)\n}\n\nexport interface AudioLoadingState {\n readonly total: number;\n readonly loaded: number;\n readonly failed: number;\n readonly currentAsset?: string;\n readonly progress: number;\n readonly errors: readonly string[];\n}\nexport interface AudioSystemInterface {\n playSFX: (id: SoundEffectId, options?: AudioPlaybackOptions) => void;\n playMusic: (id: MusicTrackId, options?: AudioPlaybackOptions) => void;\n stopMusic: (id?: MusicTrackId, fadeOutDuration?: number) => void;\n setVolume: (type: \"master\" | \"sfx\" | \"music\", volume: number) => void;\n loadAudioAsset: (asset: AudioAsset) => Promise<void>;\n isMusicPlaying: (id?: MusicTrackId) => boolean;\n}\n\nexport interface AudioManagerInterface extends IAudioManager {}\n"],"mappings":";AASA,IAAY,gBAAL,yBAAA,eAAA;AACL,eAAA,SAAM;AACN,eAAA,WAAQ;AACR,eAAA,WAAQ;AACR,eAAA,QAAK;;KAEN"}
|
|
@@ -21,7 +21,7 @@ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
|
21
21
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
22
22
|
import { Canvas } from "@react-three/fiber";
|
|
23
23
|
//#region src/components/screens/intro/IntroScreen3D.tsx
|
|
24
|
-
var APP_VERSION = "0.7.
|
|
24
|
+
var APP_VERSION = "0.7.25";
|
|
25
25
|
var MENU_ITEMS = [
|
|
26
26
|
{
|
|
27
27
|
mode: GameMode.VERSUS,
|