NKTgLaw
Published © LGPL

Programming Cosmic Dynamics: Variable Inertia (NKTg Law)

Variable Inertia (NKTg Law): real-time physics engine for AI, autonomous systems, and executable simulations.

ExpertProtip17
Programming Cosmic Dynamics: Variable Inertia (NKTg Law)

Things used in this project

Hardware components

Generic Computer (x86_64 / ARM)
×1
Optional: Microcontroller (ESP32 / Raspberry Pi)
×1

Story

Read more

Custom parts and enclosures

PROGRAMMING COSMIC DYNAMICS

PROGRAMMING COSMIC DYNAMICS

Schematics

PROGRAMMING COSMIC DYNAMICS

PROGRAMMING COSMIC DYNAMICS

Code

NKTg_AutoSim.html

HTML
PROGRAMMING COSMIC DYNAMICS. NKTg_AutoSim.html
<!DOCTYPE html>
<html lang="vi">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>NKTg Auto Simulation</title>
    <script src="https://cdn.tailwindcss.com"></script>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
    <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
    <style>
        @import url('https://fonts.googleapis.com/css2?family=Share+Tech+Mono&family=Exo+2:wght@300;400;700;900&display=swap');

        :root {
            --cyan: #00f5ff;
            --purple: #a855f7;
            --green: #00ff88;
            --red: #ff3366;
            --yellow: #ffd700;
            --orange: #ff8c00;
            --bg-dark: #020818;
            --glass: rgba(0, 245, 255, 0.04);
            --border: rgba(0, 245, 255, 0.15);
        }

        * { box-sizing: border-box; margin: 0; padding: 0; }

        body {
            font-family: 'Exo 2', sans-serif;
            background: var(--bg-dark);
            color: #e0f7ff;
            min-height: 100vh;
            overflow-x: hidden;
        }

        body::before {
            content: '';
            position: fixed;
            inset: 0;
            background-image:
                linear-gradient(rgba(0,245,255,0.03) 1px, transparent 1px),
                linear-gradient(90deg, rgba(0,245,255,0.03) 1px, transparent 1px);
            background-size: 40px 40px;
            pointer-events: none;
            z-index: 0;
        }

        body::after {
            content: '';
            position: fixed;
            top: -50%; left: -50%;
            width: 200%; height: 200%;
            background:
                radial-gradient(ellipse at 30% 20%, rgba(0,100,255,0.08) 0%, transparent 50%),
                radial-gradient(ellipse at 70% 80%, rgba(168,85,247,0.06) 0%, transparent 50%);
            pointer-events: none;
            z-index: 0;
        }

        .glass {
            background: var(--glass);
            backdrop-filter: blur(12px);
            border: 1px solid var(--border);
        }

        .mono { font-family: 'Share Tech Mono', monospace; }

        header {
            position: sticky; top: 0; z-index: 100;
            background: rgba(2,8,24,0.9);
            backdrop-filter: blur(20px);
            border-bottom: 1px solid var(--border);
            padding: 12px 24px;
            display: flex; justify-content: space-between; align-items: center;
        }

        .logo-text {
            font-size: 18px; font-weight: 900;
            letter-spacing: 2px; color: var(--cyan);
            text-shadow: 0 0 20px rgba(0,245,255,0.5);
        }

        main {
            position: relative; z-index: 1;
            padding: 20px;
            max-width: 1400px; margin: 0 auto;
            display: grid;
            grid-template-columns: 300px 1fr;
            gap: 20px;
        }

        @media (max-width: 1024px) { main { grid-template-columns: 1fr; } }

        /* SCENARIO BUTTONS */
        .scenario-btn {
            width: 100%;
            padding: 12px 16px;
            border-radius: 8px;
            border: 1px solid;
            cursor: pointer;
            font-family: 'Exo 2', sans-serif;
            font-weight: 700;
            font-size: 13px;
            letter-spacing: 1px;
            transition: all 0.3s;
            display: flex; align-items: center; gap: 10px;
            margin-bottom: 8px;
            background: transparent;
            color: inherit;
        }

        .scenario-btn.rocket {
            border-color: var(--orange);
            color: var(--orange);
        }
        .scenario-btn.rocket:hover, .scenario-btn.rocket.active {
            background: rgba(255,140,0,0.15);
            box-shadow: 0 0 20px rgba(255,140,0,0.3);
        }

        .scenario-btn.drone {
            border-color: var(--cyan);
            color: var(--cyan);
        }
        .scenario-btn.drone:hover, .scenario-btn.drone.active {
            background: rgba(0,245,255,0.1);
            box-shadow: 0 0 20px rgba(0,245,255,0.3);
        }

        .scenario-btn.car {
            border-color: var(--green);
            color: var(--green);
        }
        .scenario-btn.car:hover, .scenario-btn.car.active {
            background: rgba(0,255,136,0.1);
            box-shadow: 0 0 20px rgba(0,255,136,0.3);
        }

        /* PLAY/STOP BUTTON */
        #playBtn {
            width: 100%;
            padding: 14px;
            border-radius: 8px;
            border: 2px solid var(--purple);
            background: rgba(168,85,247,0.1);
            color: var(--purple);
            font-family: 'Share Tech Mono', monospace;
            font-size: 15px;
            font-weight: bold;
            letter-spacing: 2px;
            cursor: pointer;
            transition: all 0.3s;
            margin-top: 4px;
        }

        #playBtn:hover {
            background: rgba(168,85,247,0.25);
            box-shadow: 0 0 25px rgba(168,85,247,0.4);
        }

        #playBtn.running {
            border-color: var(--red);
            background: rgba(255,51,102,0.1);
            color: var(--red);
            animation: runPulse 1s ease-in-out infinite;
        }

        @keyframes runPulse {
            0%, 100% { box-shadow: 0 0 15px rgba(255,51,102,0.2); }
            50% { box-shadow: 0 0 35px rgba(255,51,102,0.5); }
        }

        /* STATUS BOX */
        #statusBox {
            font-family: 'Share Tech Mono', monospace;
            font-size: 24px; font-weight: bold;
            text-align: center; padding: 16px;
            border-radius: 8px; letter-spacing: 4px;
            transition: all 0.4s; border: 1px solid;
        }

        #statusBox.stable {
            color: var(--green);
            background: rgba(0,255,136,0.07);
            border-color: rgba(0,255,136,0.3);
        }

        #statusBox.danger {
            color: var(--red);
            background: rgba(255,51,102,0.07);
            border-color: rgba(255,51,102,0.3);
            animation: dangerPulse 0.8s ease-in-out infinite;
        }

        @keyframes dangerPulse {
            0%, 100% { box-shadow: 0 0 20px rgba(255,51,102,0.1) inset; }
            50% { box-shadow: 0 0 40px rgba(255,51,102,0.3) inset; }
        }

        #statusBox.balance {
            color: var(--cyan);
            background: rgba(0,245,255,0.07);
            border-color: rgba(0,245,255,0.3);
        }

        #statusBox.waiting {
            color: rgba(224,247,255,0.3);
            background: rgba(224,247,255,0.02);
            border-color: rgba(224,247,255,0.08);
        }

        /* METRIC CARDS */
        .metric-card {
            border-radius: 10px; padding: 14px;
            text-align: center;
        }

        .metric-label {
            font-size: 10px; letter-spacing: 2px;
            text-transform: uppercase;
            color: rgba(224,247,255,0.4); margin-bottom: 6px;
        }

        .metric-value {
            font-family: 'Share Tech Mono', monospace;
            font-size: 20px; font-weight: bold;
            transition: color 0.3s;
        }

        /* PROGRESS BAR */
        .progress-wrap {
            background: rgba(255,255,255,0.04);
            border-radius: 4px; height: 6px;
            overflow: hidden; margin-top: 8px;
        }

        .progress-fill {
            height: 100%; border-radius: 4px;
            transition: width 0.1s linear;
            background: linear-gradient(to right, var(--purple), var(--cyan));
        }

        /* PHYSICS DISPLAY */
        .phys-row {
            display: flex; justify-content: space-between;
            align-items: center; padding: 8px 0;
            border-bottom: 1px solid rgba(0,245,255,0.06);
            font-size: 13px;
        }

        .phys-row:last-child { border-bottom: none; }

        .phys-label { color: rgba(224,247,255,0.5); letter-spacing: 1px; }

        .phys-value {
            font-family: 'Share Tech Mono', monospace;
            font-size: 15px; font-weight: bold;
            color: var(--cyan); min-width: 100px; text-align: right;
        }

        /* SECTION TITLE */
        .sec-label {
            font-size: 10px; letter-spacing: 3px;
            color: rgba(0,245,255,0.4); margin-bottom: 3px;
        }

        .sec-title {
            font-size: 14px; font-weight: 700;
            color: var(--cyan); margin-bottom: 14px;
            display: flex; align-items: center; gap: 8px;
        }

        .sec-title::after {
            content: ''; flex: 1; height: 1px;
            background: linear-gradient(to right, rgba(0,245,255,0.3), transparent);
        }

        /* SCENARIO INFO */
        .scenario-info {
            background: rgba(0,0,0,0.3);
            border-radius: 8px; padding: 12px;
            font-size: 12px; line-height: 1.7;
            color: rgba(224,247,255,0.55);
            border-left: 3px solid var(--purple);
            min-height: 80px;
        }

        .scenario-info strong { color: var(--cyan); }

        /* TIME DISPLAY */
        #timeDisplay {
            font-family: 'Share Tech Mono', monospace;
            font-size: 28px; font-weight: bold;
            color: var(--yellow);
            text-align: center; padding: 10px;
            text-shadow: 0 0 20px rgba(255,215,0,0.4);
        }

        .chart-container { position: relative; height: 280px; }
    </style>
</head>
<body>

<!-- HEADER -->
<header>
    <div style="display:flex; align-items:center; gap:12px;">
        <div style="width:36px;height:36px;border:2px solid var(--cyan);border-radius:8px;display:flex;align-items:center;justify-content:center;color:var(--cyan);box-shadow:0 0 12px rgba(0,245,255,0.3);">
            <i class="fas fa-rocket"></i>
        </div>
        <div>
            <div class="logo-text">NKTg AUTO-SIMULATION</div>
            <div style="font-size:10px;color:rgba(0,245,255,0.4);letter-spacing:2px;">DYNAMIC PHYSICS ENGINE</div>
        </div>
    </div>
    <div id="timeDisplay">T = 0.00s</div>
</header>

<main>

    <!-- LEFT PANEL -->
    <div style="display:flex;flex-direction:column;gap:16px;">

        <!-- CHN KCH BN -->
        <div class="glass" style="border-radius:12px;padding:18px;">
            <div class="sec-label">SELECT SCENARIO</div>
            <div class="sec-title"><i class="fas fa-list" style="font-size:12px;"></i> Chn Kch bn</div>

            <button class="scenario-btn rocket active" onclick="selectScenario('rocket')">
                <i class="fas fa-rocket"></i> TN LA  Tiu hao nhin liu
            </button>
            <button class="scenario-btn drone" onclick="selectScenario('drone')">
                <i class="fas fa-plane"></i> DRONE  Th hng t ngt
            </button>
            <button class="scenario-btn car" onclick="selectScenario('car')">
                <i class="fas fa-car"></i> XE HI  Tng tc & Phanh
            </button>

            <div class="scenario-info" id="scenarioInfo">
                <strong>Tn la:</strong> Khi ng vi m=500kg, t nhin liu -5kg/s u n. Quan st NKTg tng khi tn la bay xa v NKTg m n nh khi nhin liu t u.
            </div>
        </div>

        <!-- PLAY/STOP -->
        <div class="glass" style="border-radius:12px;padding:18px;">
            <div class="sec-label">SIMULATION CONTROL</div>
            <div class="sec-title"><i class="fas fa-play-circle" style="font-size:12px;"></i> iu khin</div>

            <button id="playBtn" onclick="toggleSim()">
                 CHY M PHNG
            </button>

            <div style="margin-top:14px;">
                <div style="display:flex;justify-content:space-between;font-size:11px;color:rgba(224,247,255,0.4);margin-bottom:6px;">
                    <span>TIN  M PHNG</span>
                    <span id="progressLabel" class="mono">0%</span>
                </div>
                <div class="progress-wrap">
                    <div class="progress-fill" id="progressFill" style="width:0%;"></div>
                </div>
            </div>

            <div style="margin-top:12px;font-size:11px;color:rgba(224,247,255,0.3);text-align:center;">
                Thi gian m phng: <span class="mono" style="color:var(--yellow);">30 giy</span>  50ms/bc
            </div>
        </div>

        <!-- TRNG THI -->
        <div class="glass" style="border-radius:12px;padding:18px;">
            <div class="sec-label">SYSTEM STATE</div>
            <div class="sec-title"><i class="fas fa-broadcast-tower" style="font-size:12px;"></i> Trng thi</div>
            <div id="statusBox" class="waiting">ANG CH...</div>
            <p id="advice" style="font-size:11px;color:rgba(224,247,255,0.4);text-align:center;margin-top:8px;min-height:16px;font-style:italic;"></p>
        </div>

        <!-- GI TR VT L -->
        <div class="glass" style="border-radius:12px;padding:18px;">
            <div class="sec-label">PHYSICS VALUES</div>
            <div class="sec-title"><i class="fas fa-atom" style="font-size:12px;"></i> Thng s thc</div>

            <div class="phys-row">
                <span class="phys-label">V TR (x)</span>
                <span class="phys-value" id="disp_x">0.00 m</span>
            </div>
            <div class="phys-row">
                <span class="phys-label">VN TC (v)</span>
                <span class="phys-value" id="disp_v">0.00 m/s</span>
            </div>
            <div class="phys-row">
                <span class="phys-label">KHI LNG (m)</span>
                <span class="phys-value" id="disp_m">0.00 kg</span>
            </div>
            <div class="phys-row">
                <span class="phys-label">dm/dt</span>
                <span class="phys-value" id="disp_dmt">0.00 kg/s</span>
            </div>
            <div class="phys-row">
                <span class="phys-label">NG LNG (p)</span>
                <span class="phys-value" id="disp_p" style="color:var(--cyan);">0.00</span>
            </div>
        </div>
    </div>

    <!-- RIGHT PANEL -->
    <div style="display:flex;flex-direction:column;gap:16px;">

        <!-- METRIC CARDS -->
        <div style="display:grid;grid-template-columns:repeat(3,1fr);gap:12px;">
            <div class="glass metric-card" style="border-color:rgba(168,85,247,0.3);">
                <div class="metric-label" style="color:rgba(168,85,247,0.6);">NKTg N NH</div>
                <div class="metric-value" id="res_n1" style="color:var(--purple);">0.00</div>
                <div style="font-size:10px;color:rgba(224,247,255,0.3);margin-top:4px;">NKTm</div>
            </div>
            <div class="glass metric-card" style="border-color:rgba(0,255,136,0.2);">
                <div class="metric-label" style="color:rgba(0,255,136,0.5);">NKTg XUNG LC</div>
                <div class="metric-value" id="res_n2" style="color:var(--green);">0.00</div>
                <div style="font-size:10px;color:rgba(224,247,255,0.3);margin-top:4px;">NKTm/s</div>
            </div>
            <div class="glass metric-card">
                <div class="metric-label">BC M PHNG</div>
                <div class="metric-value" id="res_step" style="color:var(--yellow);">0 / 600</div>
                <div style="font-size:10px;color:rgba(224,247,255,0.3);margin-top:4px;">steps</div>
            </div>
        </div>

        <!-- CHART NKTg1 & NKTg2 -->
        <div class="glass" style="border-radius:12px;padding:18px;">
            <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:14px;">
                <div>
                    <div class="sec-label">REAL-TIME ANALYSIS</div>
                    <div style="font-size:14px;font-weight:700;color:var(--cyan);">Biu  NKTg & NKTg theo thi gian</div>
                </div>
                <button onclick="resetSim()" style="font-size:10px;color:rgba(224,247,255,0.3);background:none;border:1px solid rgba(224,247,255,0.1);padding:4px 10px;border-radius:4px;cursor:pointer;letter-spacing:1px;">RESET</button>
            </div>
            <div class="chart-container">
                <canvas id="nktgChart"></canvas>
            </div>
        </div>

        <!-- CHART PHYSICS -->
        <div class="glass" style="border-radius:12px;padding:18px;">
            <div style="margin-bottom:14px;">
                <div class="sec-label">PHYSICS DYNAMICS</div>
                <div style="font-size:14px;font-weight:700;color:var(--cyan);">Bin thin x, v, m theo thi gian</div>
            </div>
            <div class="chart-container">
                <canvas id="physChart"></canvas>
            </div>
        </div>

        <!-- S KIN LOG -->
        <div class="glass" style="border-radius:12px;padding:18px;">
            <div class="sec-label">EVENT LOG</div>
            <div class="sec-title"><i class="fas fa-terminal" style="font-size:12px;"></i> Nht k s kin NKTg</div>
            <div id="eventLog" style="font-family:'Share Tech Mono',monospace;font-size:11px;background:rgba(0,0,0,0.4);border:1px solid rgba(0,245,255,0.1);border-radius:8px;padding:10px;height:100px;overflow-y:auto;color:rgba(0,245,255,0.6);line-height:1.7;">
                <p style="color:rgba(0,245,255,0.4);">&gt; Chn kch bn v nhn CHY M PHNG...</p>
            </div>
        </div>
    </div>
</main>

<script>
// =========================================================
// CU HNH CC KCH BN
// =========================================================
const SCENARIOS = {
    rocket: {
        name: 'Tn la',
        icon: '',
        // Trng thi ban u
        x0: 0, v0: 0, m0: 500, dm_dt: -5,
        // Lc y (N)  gim dn khi ht nhin liu
        thrust: 15000,
        // Thi gian m phng (giy)
        duration: 30,
        // M t s kin
        events: [
            { t: 0,  msg: ' Tn la khai ha! Lc y 15,000N', type: 'info' },
            { t: 10, msg: '  tiu hao 50kg nhin liu  xe nh hn, v tng nhanh hn', type: 'warn' },
            { t: 20, msg: ' Nhin liu cn 40%  NKTg tng vt do x v v u ln', type: 'danger' },
            { t: 28, msg: ' Ht nhin liu  dm/dt = 0, h thng n nh qun tnh', type: 'success' },
        ],
        info: '<strong>Tn la:</strong> Khi ng m=500kg, t -5kg/s u n, lc y 15,000N. Quan st NKTg tng khi tn la bay xa v NKTg m n nh khi nhin liu t u.'
    },
    drone: {
        name: 'Drone',
        icon: '',
        x0: 0, v0: 5, m0: 50, dm_dt: -0.1,
        thrust: 500,
        duration: 30,
        events: [
            { t: 0,  msg: ' Drone ct cnh  v=5m/s, m=50kg', type: 'info' },
            { t: 10, msg: ' Drone n im th hng  chun b th 20kg', type: 'warn' },
            { t: 12, msg: ' TH HNG! dm/dt = -20 kg/s t ngt  NKTg m mnh!', type: 'danger' },
            { t: 13, msg: ' Drone bt ln do mt khi lng t ngt  nguy him!', type: 'danger' },
            { t: 16, msg: ' H thng b lc rotor  Drone ly li cn bng', type: 'success' },
        ],
        info: '<strong>Drone:</strong> Bay bnh thng, n t=12s th hng 20kg t ngt. Quan st NKTg m nhy vt  y l cnh bo PID khng c!'
    },
    car: {
        name: 'Xe hi',
        icon: '',
        x0: 0, v0: 0, m0: 1500, dm_dt: -0.02,
        thrust: 3000,
        duration: 30,
        events: [
            { t: 0,  msg: ' Xe xut pht  tng tc t 0', type: 'info' },
            { t: 8,  msg: ' t 100 km/h  NKTg tng nhanh', type: 'warn' },
            { t: 15, msg: ' NKTg vt ngng 1,000,000  phanh khn cp!', type: 'danger' },
            { t: 16, msg: ' Kch hot phanh  lc y = 0, xe gim tc', type: 'warn' },
            { t: 25, msg: ' Xe dng an ton  NKTg v 0', type: 'success' },
        ],
        info: '<strong>Xe hi:</strong> Tng tc t 0, n t=15s NKTg vt ngng nguy him  phanh khn cp. Quan st qu trnh gim tc v NKTg tr v an ton.'
    }
};

// =========================================================
// BIN TRNG THI
// =========================================================
let currentScenario = 'rocket';
let simTimer = null;
let isRunning = false;
let simTime = 0;
const DT = 0.05; // 50ms mi bc
let state = {};
let brakingActive = false;

// Lu lch s  v  th
const MAXPOINTS = 600;
let histTime = [];
let histN1 = [], histN2 = [];
let histX = [], histV = [], histM = [];

// =========================================================
// KHI TO  TH
// =========================================================
const nktgChart = new Chart(document.getElementById('nktgChart').getContext('2d'), {
    type: 'line',
    data: {
        labels: [],
        datasets: [
            {
                label: 'NKTg (n nh)',
                data: [], borderColor: '#a855f7', borderWidth: 2,
                tension: 0.3, fill: true,
                backgroundColor: 'rgba(168,85,247,0.06)',
                pointRadius: 0
            },
            {
                label: 'NKTg (Xung lc)',
                data: [], borderColor: '#00ff88', borderWidth: 2,
                tension: 0.3, fill: false,
                pointRadius: 0
            }
        ]
    },
    options: {
        responsive: true, maintainAspectRatio: false,
        animation: { duration: 0 },
        scales: {
            x: { ticks: { color: 'rgba(224,247,255,0.3)', maxTicksLimit: 10, font: { family: "'Share Tech Mono'" } }, grid: { color: 'rgba(0,245,255,0.04)' } },
            y: { ticks: { color: 'rgba(224,247,255,0.3)', font: { family: "'Share Tech Mono'" } }, grid: { color: 'rgba(0,245,255,0.04)' } }
        },
        plugins: {
            legend: { labels: { color: 'rgba(224,247,255,0.5)', font: { family: "'Exo 2'" }, boxWidth: 14, padding: 12 } }
        }
    }
});

const physChart = new Chart(document.getElementById('physChart').getContext('2d'), {
    type: 'line',
    data: {
        labels: [],
        datasets: [
            {
                label: 'V tr x (m)',
                data: [], borderColor: '#00f5ff', borderWidth: 2,
                tension: 0.3, fill: false, pointRadius: 0
            },
            {
                label: 'Vn tc v (m/s)',
                data: [], borderColor: '#ffd700', borderWidth: 2,
                tension: 0.3, fill: false, pointRadius: 0
            },
            {
                label: 'Khi lng m (kg/10)',
                data: [], borderColor: '#ff8c00', borderWidth: 2,
                tension: 0.3, fill: false, pointRadius: 0,
                borderDash: [5, 3]
            }
        ]
    },
    options: {
        responsive: true, maintainAspectRatio: false,
        animation: { duration: 0 },
        scales: {
            x: { ticks: { color: 'rgba(224,247,255,0.3)', maxTicksLimit: 10, font: { family: "'Share Tech Mono'" } }, grid: { color: 'rgba(0,245,255,0.04)' } },
            y: { ticks: { color: 'rgba(224,247,255,0.3)', font: { family: "'Share Tech Mono'" } }, grid: { color: 'rgba(0,245,255,0.04)' } }
        },
        plugins: {
            legend: { labels: { color: 'rgba(224,247,255,0.5)', font: { family: "'Exo 2'" }, boxWidth: 14, padding: 12 } }
        }
    }
});

// =========================================================
// CHN KCH BN
// =========================================================
function selectScenario(name) {
    if (isRunning) return;
    currentScenario = name;

    // Cp nht nt
    document.querySelectorAll('.scenario-btn').forEach(b => b.classList.remove('active'));
    document.querySelector(`.scenario-btn.${name}`).classList.add('active');

    // Cp nht m t
    document.getElementById('scenarioInfo').innerHTML = SCENARIOS[name].info;

    resetSim();
}

// =========================================================
// PLAY / STOP
// =========================================================
function toggleSim() {
    if (isRunning) {
        stopSim();
    } else {
        startSim();
    }
}

function startSim() {
    const sc = SCENARIOS[currentScenario];

    // Khi to trng thi
    state = {
        x: sc.x0, v: sc.v0, m: sc.m0,
        dm_dt: sc.dm_dt,
        thrust: sc.thrust,
        t: 0
    };
    brakingActive = false;
    simTime = 0;

    // Xa lch s
    histTime = []; histN1 = []; histN2 = [];
    histX = []; histV = []; histM = [];
    nktgChart.data.labels = [];
    nktgChart.data.datasets.forEach(d => d.data = []);
    physChart.data.labels = [];
    physChart.data.datasets.forEach(d => d.data = []);

    // Xa log
    document.getElementById('eventLog').innerHTML = '';
    addLog(`${sc.icon} Bt u m phng: ${sc.name}`, 'info');

    isRunning = true;
    document.getElementById('playBtn').className = 'running';
    document.getElementById('playBtn').innerText = ' DNG M PHNG';

    simTimer = setInterval(simStep, 50);
}

function stopSim() {
    clearInterval(simTimer);
    isRunning = false;
    document.getElementById('playBtn').className = '';
    document.getElementById('playBtn').innerText = ' CHY LI';
    addLog(' M phng dng.', 'info');
}

function resetSim() {
    stopSim();
    simTime = 0;

    histTime = []; histN1 = []; histN2 = [];
    histX = []; histV = []; histM = [];
    nktgChart.data.labels = [];
    nktgChart.data.datasets.forEach(d => d.data = []);
    physChart.data.labels = [];
    physChart.data.datasets.forEach(d => d.data = []);
    nktgChart.update(); physChart.update();

    document.getElementById('timeDisplay').innerText = 'T = 0.00s';
    document.getElementById('statusBox').className = 'waiting';
    document.getElementById('statusBox').innerText = 'ANG CH...';
    document.getElementById('advice').innerText = '';
    document.getElementById('res_n1').innerText = '0.00';
    document.getElementById('res_n2').innerText = '0.00';
    document.getElementById('res_step').innerText = '0 / 600';
    document.getElementById('progressFill').style.width = '0%';
    document.getElementById('progressLabel').innerText = '0%';
    document.getElementById('disp_x').innerText = '0.00 m';
    document.getElementById('disp_v').innerText = '0.00 m/s';
    document.getElementById('disp_m').innerText = '0.00 kg';
    document.getElementById('disp_dmt').innerText = '0.00 kg/s';
    document.getElementById('disp_p').innerText = '0.00';
    document.getElementById('playBtn').innerText = ' CHY M PHNG';
    document.getElementById('eventLog').innerHTML = '<p style="color:rgba(0,245,255,0.4);">&gt; Nhn CHY M PHNG  bt u...</p>';
}

// =========================================================
// BC M PHNG VT L
// =========================================================
function simStep() {
    const sc = SCENARIOS[currentScenario];
    const totalSteps = sc.duration / DT;
    const currentStep = Math.round(simTime / DT);

    // Kim tra s kin theo thi gian
    sc.events.forEach(ev => {
        if (Math.abs(simTime - ev.t) < DT / 2) {
            addLog(ev.msg, ev.type);
        }
    });

    // === VT L TNG KCH BN ===

    if (currentScenario === 'rocket') {
        // Nhin liu cn th t, ht th dm_dt = 0
        if (state.m > 50) { // 50kg = khi lng khung tn la
            state.m += state.dm_dt * DT;
            state.m = Math.max(50, state.m);
        } else {
            state.dm_dt = 0;
        }
        // a = F/m
        const a = state.thrust / state.m;
        state.v += a * DT;
        state.x += state.v * DT;
    }

    else if (currentScenario === 'drone') {
        // Giai on bnh thng
        if (simTime < 12) {
            state.m += state.dm_dt * DT; // tiu hao nhin liu nh
            const a = (state.thrust - state.m * 9.81) / state.m;
            state.v += a * DT * 0.1; // gi tc  n nh
            state.x += state.v * DT;
        }
        // Th hng t ngt t=12s
        else if (simTime >= 12 && simTime < 13) {
            state.dm_dt = -20; // th 20kg/s trong 1 giy
            state.m += state.dm_dt * DT;
            state.m = Math.max(28, state.m); // ti thiu 28kg
            // Drone bt ln do mt ti t ngt
            state.v += 3 * DT;
            state.x += state.v * DT;
        }
        // Sau th hng  ly li cn bng
        else {
            state.dm_dt = -0.05; // ch tiu hao nhin liu nh
            state.m += state.dm_dt * DT;
            state.m = Math.max(20, state.m);
            const a = (state.thrust * 0.6 - state.m * 9.81) / state.m;
            state.v += a * DT * 0.05;
            state.x += state.v * DT;
        }
    }

    else if (currentScenario === 'car') {
        // Tng tc n t=15s
        if (simTime < 15 && !brakingActive) {
            state.m += state.dm_dt * DT; // tiu hao xng nh
            const a = state.thrust / state.m;
            state.v += a * DT;
            state.x += state.v * DT;

            // Kim tra ngng NKTg
            const p_check = state.m * state.v;
            const n1_check = state.x * p_check;
            if (n1_check > 1000000) {
                brakingActive = true;
                addLog(' NKTg vt ngng! Phanh khn cp kch hot!', 'danger');
            }
        }
        // Phanh
        else {
            brakingActive = true;
            state.dm_dt = 0;
            // Gim tc do phanh
            state.v -= 8 * DT; // gia tc phanh -8 m/s
            state.v = Math.max(0, state.v);
            state.x += state.v * DT;
        }
    }

    // === TNH NKTg ===
    const p  = state.m * state.v;
    const n1 = state.x * p;
    const n2 = state.dm_dt * p;

    // === CP NHT UI ===
    document.getElementById('timeDisplay').innerText = `T = ${simTime.toFixed(2)}s`;
    document.getElementById('disp_x').innerText  = state.x.toFixed(2) + ' m';
    document.getElementById('disp_v').innerText  = state.v.toFixed(2) + ' m/s';
    document.getElementById('disp_m').innerText  = state.m.toFixed(2) + ' kg';
    document.getElementById('disp_dmt').innerText = state.dm_dt.toFixed(3) + ' kg/s';
    document.getElementById('disp_p').innerText  = p.toFixed(2);
    document.getElementById('res_n1').innerText  = n1.toFixed(0);
    document.getElementById('res_n2').innerText  = n2.toFixed(0);
    document.getElementById('res_step').innerText = `${currentStep} / ${totalSteps}`;

    // Progress
    const pct = Math.min(100, (simTime / sc.duration) * 100);
    document.getElementById('progressFill').style.width = pct + '%';
    document.getElementById('progressLabel').innerText = pct.toFixed(0) + '%';

    // Status
    const sb = document.getElementById('statusBox');
    if (n1 > 0) {
        sb.innerText = ' NGUY HIM'; sb.className = 'danger';
        document.getElementById('advice').innerText = 'H thng ri xa n nh! Kch hot phanh phn x.';
    } else if (n1 < 0) {
        sb.innerText = ' N NH'; sb.className = 'stable';
        document.getElementById('advice').innerText = 'H thng hi t tt. Duy tr qun tnh bn.';
    } else {
        sb.innerText = ' CN BNG'; sb.className = 'balance';
        document.getElementById('advice').innerText = 'im cn bng l tng.';
    }

    // === CP NHT  TH ===
    const tLabel = simTime.toFixed(1) + 's';
    nktgChart.data.labels.push(tLabel);
    nktgChart.data.datasets[0].data.push(n1);
    nktgChart.data.datasets[1].data.push(n2);

    physChart.data.labels.push(tLabel);
    physChart.data.datasets[0].data.push(state.x);
    physChart.data.datasets[1].data.push(state.v);
    physChart.data.datasets[2].data.push(state.m / 10); // chia 10  fit  th

    nktgChart.update('none');
    physChart.update('none');

    // === TNG THI GIAN ===
    simTime += DT;

    // Kt thc
    if (simTime >= sc.duration) {
        stopSim();
        addLog(` M phng hon thnh sau ${sc.duration}s!`, 'success');
        document.getElementById('playBtn').innerText = ' CHY LI';
    }
}

// =========================================================
// EVENT LOG
// =========================================================
function addLog(msg, type = 'info') {
    const log = document.getElementById('eventLog');
    const colors = {
        info: 'rgba(0,245,255,0.7)',
        warn: 'rgba(255,215,0,0.8)',
        danger: 'rgba(255,51,102,0.9)',
        success: 'rgba(0,255,136,0.8)'
    };
    const p = document.createElement('p');
    p.style.color = colors[type] || colors.info;
    p.style.margin = '0';
    p.style.padding = '1px 0';
    p.textContent = `[T=${simTime.toFixed(1)}s] ${msg}`;
    log.appendChild(p);
    while (log.children.length > 50) log.removeChild(log.firstChild);
    log.scrollTop = log.scrollHeight;
}

// Khi chy
selectScenario('rocket');
</script>
</body>
</html>

Credits

NKTgLaw
9 projects β€’ 0 followers

Comments