diff --git a/game.js b/game.js
index 714c370..fd4151a 100644
--- a/game.js
+++ b/game.js
@@ -1479,7 +1479,7 @@ function customConfirm(msg, onYes) {
sampleRate: 16000, // 16kHz — sufficient for voice, saves 33% bandwidth
frameMs: 20, // 20ms frames = 320 samples @ 16kHz
samplesPerFrame: 320, // 16000 * 0.02
- vadThreshold: 0.008, // RMS threshold for voice detection
+ vadThreshold: 0.0005, // RMS threshold for voice detection
vadHangover: 5, // 100ms hangover after speech ends
jbufTargetMs: 80, // Target jitter: 80ms (was 200ms)
jbufMinMs: 40,
@@ -1514,7 +1514,7 @@ class VoiceCaptureProcessor extends AudioWorkletProcessor {
this._speaking = false;
this._silenceFrames = 0;
this._hangover = 5; // 100ms
- this._vadThreshold = 0.008;
+ this._vadThreshold = 0.0005;
this._lastRms = 0;
}
process(inputs) {
@@ -1659,7 +1659,7 @@ registerProcessor('voice-playback', VoicePlaybackProcessor);
// ==================== Opus ENCODE/DECODE ====================
async function initVoiceEncoder() {
- if ('AudioEncoder' in window) {
+ if (false && 'AudioEncoder' in window) {
try {
// Check if Opus is supported
const support = await AudioEncoder.isConfigSupported({
@@ -1676,6 +1676,7 @@ registerProcessor('voice-playback', VoicePlaybackProcessor);
const data = new Uint8Array(chunk.byteLength);
chunk.copyTo(data);
// Send as binary frame via Socket.IO
+ console.log('[voice] TX Opus chunk, seq:', voiceSeq, 'size:', data.buffer.byteLength);
voiceSocket.emit('voice_data', {
codec: 'opus',
data: data.buffer,
@@ -1777,12 +1778,13 @@ registerProcessor('voice-playback', VoicePlaybackProcessor);
// Connect: lowpass → panner → gain → (connected to mixer later)
sp.lowpassNode.connect(sp.pannerNode);
sp.pannerNode.connect(sp.gainNode);
- sp.gainNode.gain.value = 0; // Start silent
+ sp.gainNode.gain.value = 1.0; // Start with volume (worklet needs non-zero gain)
remoteSpeakers.set(socketId, sp);
// Register speaker in playback worklet
if (playbackNode) {
- playbackNode.port.postMessage({ type: 'addSpeaker', id: socketId, gain: sp.gainNode.gain.value, pan: sp.pannerNode.pan.value });
+ playbackNode.port.postMessage({ type: 'addSpeaker', id: socketId, gain: 1.0, pan: 0 });
+ console.log('[voice] addSpeaker posted to worklet, id:', socketId?.substring(0,8), 'gain:', sp.gainNode.gain.value);
}
initDecoderForSpeaker(socketId, codec);
return sp;
@@ -1841,6 +1843,7 @@ registerProcessor('voice-playback', VoicePlaybackProcessor);
if (playbackNode) {
const currentGain = sp.gainNode.gain.value;
const currentPan = sp.pannerNode.pan.value;
+ if (currentGain > 0.01) console.log('[voice] updateSpatial, id:', sp.id?.substring(0,8), 'gain:', currentGain.toFixed(3), 'pan:', currentPan.toFixed(3));
playbackNode.port.postMessage({
type: 'updateSpatial',
id: sp.id,
@@ -1971,14 +1974,26 @@ registerProcessor('voice-playback', VoicePlaybackProcessor);
// Per-speaker Web Audio nodes (lowpass, pan, gain) used for spatial UPDATES only (values sent to worklet via messages)
// The actual mixing happens inside the worklet
+ // Connect to voice server FIRST so capture frames have a socket
+ voiceSocket = io(VOICE_SERVER, { transports: ['websocket'] });
+
// Handle capture frames
voiceSeq = 0;
voiceTimestamp = 0;
wasSpeaking = false;
+ voiceActive = true; // Enable capture BEFORE onmessage handler
+ let _frameCount = 0;
captureNode.port.onmessage = (e) => {
const { type, samples, speaking, rms } = e.data;
if (type !== 'frame') return;
- if (!voiceActive || !voiceSocket || !voiceSocket.connected) return;
+ _frameCount++;
+ if (_frameCount <= 5 || _frameCount % 100 === 0) {
+ console.log('[voice] capture frame #', _frameCount, 'rms:', rms?.toFixed(4), 'speaking:', speaking, 'voiceActive:', voiceActive, 'socket:', !!voiceSocket, 'connected:', voiceSocket?.connected, 'codec:', voiceCodec);
+ }
+ if (!voiceActive || !voiceSocket || !voiceSocket.connected) {
+
+ return;
+ }
voiceTimestamp += VC.samplesPerFrame; // 320 samples * (1/16000) = 20ms per frame
@@ -2003,17 +2018,17 @@ registerProcessor('voice-playback', VoicePlaybackProcessor);
audioData.close();
} catch (err) {
// Fallback: send as PCM
- sendPCMFrame(samples);
+ sendPCMFrame(samples, speaking, wasSpeaking);
}
} else {
- sendPCMFrame(samples);
+ sendPCMFrame(samples, speaking, wasSpeaking);
}
wasSpeaking = speaking;
silenceFrames = 0;
};
- function sendPCMFrame(samples) {
+ function sendPCMFrame(samples, isSpeaking, wasSp) {
const int16 = new Int16Array(samples.length);
for (let i = 0; i < samples.length; i++) {
const s = Math.max(-1, Math.min(1, samples[i]));
@@ -2024,7 +2039,7 @@ registerProcessor('voice-playback', VoicePlaybackProcessor);
data: int16.buffer,
seq: voiceSeq++,
ts: performance.now(),
- speaking: wasSpeaking || speaking
+ speaking: isSpeaking || wasSp
});
}
@@ -2032,8 +2047,7 @@ registerProcessor('voice-playback', VoicePlaybackProcessor);
await initVoiceEncoder();
codecIndicator.textContent = voiceCodec === 'opus' ? '🔊 Opus' : '🔊 PCM';
- // Connect to voice server
- voiceSocket = io(VOICE_SERVER, { transports: ['websocket'] });
+ // Voice socket already created above
voiceSocket.on('connect', () => {
console.log('[voice] Connected, id:', voiceSocket.id, 'codec:', voiceCodec);
@@ -2051,8 +2065,9 @@ registerProcessor('voice-playback', VoicePlaybackProcessor);
});
voiceSocket.on('voice_in', (payload) => {
+ console.log('[voice] RX voice_in, codec:', payload.codec, 'dataSize:', payload.data?.byteLength || payload.data?.length || 'N/A', 'from:', payload.meta?.from?.substring(0,8));
const { data, meta } = payload;
- if (!audioCtx || audioCtx.state === 'closed') return;
+ if (!audioCtx || audioCtx.state === 'closed') { console.warn('[voice] audioCtx missing/closed'); return; }
let sp = remoteSpeakers.get(meta.from);
if (!sp) {
@@ -2111,7 +2126,7 @@ registerProcessor('voice-playback', VoicePlaybackProcessor);
for (const [id] of remoteSpeakers) removeRemoteSpeaker(id);
});
- voiceActive = true;
+ // voiceActive already set to true above
voiceBtn.textContent = '🎤';
voiceBtn.style.background = '#2ecc71';
console.log('[voice] Voice chat ACTIVE, codec:', voiceCodec);
@@ -2125,6 +2140,7 @@ registerProcessor('voice-playback', VoicePlaybackProcessor);
// ==================== PCM decode helper ====================
function decodeAndPushPCM(speakerId, buffer) {
+ console.log('[voice] decodeAndPushPCM, id:', speakerId?.substring(0,8), 'bufSize:', buffer?.byteLength || buffer?.length || 'N/A');
const int16 = new Int16Array(buffer);
const float32 = new Float32Array(int16.length);
for (let i = 0; i < int16.length; i++) {
@@ -4287,7 +4303,7 @@ registerProcessor('voice-playback', VoicePlaybackProcessor);
const biome = getCachedBiome(pGX);
const pGY = Math.floor(player.y / TILE);
const sGY = surfaceGyAt(pGX);
- const isUndergroundTemp = (sGY - pGY) > 2;
+ const isUndergroundTemp = (pGY - sGY) > 2; // player deeper than 2 blocks below surface
let targetTemp = BIOME_TEMP[biome] || 15;
if (isUndergroundTemp) targetTemp = UNDERGROUND_TEMP;
if (isNight() && !isUndergroundTemp) targetTemp -= 10;
@@ -4325,14 +4341,14 @@ registerProcessor('voice-playback', VoicePlaybackProcessor);
player.hp = Math.min(100, player.hp + 1 * dt);
}
}
- if (player.temperature < COLD_THRESHOLD) {
+ if (player.temperature < COLD_THRESHOLD && !isUndergroundTemp) {
const severity = Math.abs(player.temperature - COLD_THRESHOLD) / 15;
- player.hp -= 3 * severity * dt;
+ player.hp -= 1 * severity * dt;
if (severity > 0.5) player.vx *= (1 - 0.3 * Math.min(1, severity) * dt);
}
- if (player.temperature > HEAT_THRESHOLD) {
+ if (player.temperature > HEAT_THRESHOLD && !isUndergroundTemp) {
const severity = (player.temperature - HEAT_THRESHOLD) / 15;
- player.hp -= 2.5 * severity * dt;
+ player.hp -= 1 * severity * dt;
if (severity > 0.5) player.hunger -= 0.5 * severity * dt;
}
// Игрок не может двигаться во время сна
diff --git a/index.html b/index.html
index cd78f8d..a6e3b6b 100644
--- a/index.html
+++ b/index.html
@@ -94,6 +94,6 @@
-
+