From c9902a6fa30508935648aa93f439b38da32f0e6c Mon Sep 17 00:00:00 2001
From: RodrigoDornelles <rodrigo.dornelles.poa@gmail.com>
Date: Tue, 5 Nov 2024 17:31:36 -0300
Subject: [PATCH] feat: revision 0.0.11 (#115)

* feat: add core none
* feat: add core lite
* feat: add core html5_lite
* fix: pause in core-native-html5
* ci: add linter to `cli`
* ci: add linter to `lite`
* ci: add linter to `repl`
* style: refact build
* fix: support cli in windows
* docs: add topic languages
---
 .github/workflows/CI.yml                   |   4 +-
 .luaurc                                    |   8 +-
 Doxyfile                                   |   2 +-
 docs/lang_esc_g.txt                        |  59 +++++++++++
 ee/engine/core/ginga/main.lua              |   2 +-
 src/cli/commands/build.lua                 |  67 ++++++++----
 src/cli/commands/cli.lua                   |  12 +--
 src/cli/commands/fs.lua                    |   2 +-
 src/cli/commands/info.lua                  |   7 +-
 src/cli/commands/init.lua                  |   6 +-
 src/cli/commands/tools.lua                 |  28 +++--
 src/engine/core/html5/core-native-html5.js |  17 ++-
 src/engine/core/lite/main.lua              | 116 +++++++++++++++++++++
 src/engine/core/love/main.lua              |   2 +-
 src/engine/core/native/main.lua            |   4 +-
 src/engine/core/repl/main.lua              | 101 +++++++++++-------
 src/lib/cli/argparse.lua                   |   4 +-
 src/lib/cli/bootstrap.lua                  |  37 +++----
 src/lib/cli/builder.lua                    |  95 +++++++++++------
 src/lib/cli/bundler.lua                    |  17 +--
 src/lib/cli/filler.lua                     |   1 -
 src/lib/cli/fs.lua                         |  21 +++-
 src/lib/cli/meta.lua                       |   6 +-
 src/lib/{engine/raw => common}/module.lua  |   0
 src/lib/engine/api/game.lua                |  14 ++-
 src/lib/engine/api/hash.lua                |   2 +-
 src/lib/engine/api/i18n.lua                |  20 ++--
 src/lib/engine/api/key.lua                 |   1 +
 src/lib/engine/raw/node.lua                |   9 +-
 src/lib/object/std.lua                     |   3 +-
 src/lib/util/cmd.lua                       |  56 ++++++++++
 src/lib/util/fs.lua                        | 105 +++++++++++++++++++
 tests/test_lib_engine_game.lua             |   1 +
 tests/test_lib_util_fs.lua                 |  83 +++++++++++++++
 tools/ci_luau-analyze.lua                  |  18 +++-
 tools/ci_version_local.lua                 |  12 +++
 36 files changed, 735 insertions(+), 207 deletions(-)
 create mode 100644 docs/lang_esc_g.txt
 create mode 100644 src/engine/core/lite/main.lua
 rename src/lib/{engine/raw => common}/module.lua (100%)
 create mode 100644 src/lib/util/cmd.lua
 create mode 100644 src/lib/util/fs.lua
 create mode 100644 tests/test_lib_util_fs.lua

diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml
index 8c2b548..334be2b 100644
--- a/.github/workflows/CI.yml
+++ b/.github/workflows/CI.yml
@@ -40,7 +40,7 @@ jobs:
         bash <(curl -s https://codecov.io/bash) -t ${{ secrets.CODECOV_TOKEN }}
 
   version:
-    if: github.event_name == 'push'
+    if: github.event_name == 'push' || github.event.pull_request.draft == false
     name: test version
     runs-on: ubuntu-latest
     steps:
@@ -60,7 +60,7 @@ jobs:
     strategy:
       fail-fast: false
       matrix:
-        core: ['native', 'love']
+        core: ['cli', 'repl', 'lite', 'native', 'love']
     steps:
     -
       uses: actions/checkout@master
diff --git a/.luaurc b/.luaurc
index 616b1f7..7f340d4 100644
--- a/.luaurc
+++ b/.luaurc
@@ -6,6 +6,9 @@
     "arg",
     "bit",
     "Buffer",
+    "BOOTSTRAP",
+    "BOOTSTRAP_DIRS",
+    "BOOTSTRAP_LIST",
     "canvas",
     "event",
     "io",
@@ -14,6 +17,8 @@
     "load",
     "loadfile",
     "love",
+    "mock_separator",
+    "native_dict_secret",
     "native_dict_game",
     "native_dict_http",
     "native_dict_json",
@@ -29,6 +34,7 @@
     "native_draw_start",
     "native_draw_text_tui",
     "native_draw_text",
-    "native_get_system_lang"
+    "native_get_system_lang",
+    "package"
   ]
 }
diff --git a/Doxyfile b/Doxyfile
index 6d81574..3258492 100644
--- a/Doxyfile
+++ b/Doxyfile
@@ -1,7 +1,7 @@
 # Custom Configs
 DOXYFILE_ENCODING      = UTF-8
 PROJECT_NAME           = "Gly Game Engine"
-PROJECT_NUMBER         = 0.0.10
+PROJECT_NUMBER         = 0.0.11
 PROJECT_BRIEF          = "Game engine in lua"
 PROJECT_LOGO           = assets/icon80x80.png
 GENERATE_RTF           = NO
diff --git a/docs/lang_esc_g.txt b/docs/lang_esc_g.txt
new file mode 100644
index 0000000..2fa38f2
--- /dev/null
+++ b/docs/lang_esc_g.txt
@@ -0,0 +1,59 @@
+@defgroup Languages
+@{
+
+@defgroup lang_esc_g ESC G
+@{
+
+@par Backus–Naur Form 
+@startebnf
+protocol_esc_g = esc, "G", command (* 7 bits *), size (* MSB command\ndefines x1024 *), [ buffer ], checksum  (* DJB2 *);
+esc = "\x1b";
+command = byte;
+size = byte;
+buffer = 4 * byte | 1024 * byte;
+checksum = 4 * byte;
+@endebnf
+
+@par Commands
+
+|     | command name   | parameters      | sizes in bytes|
+| :-: | :------------- | :-------------- | :------------ |
+| 0   | hello          |                 | 0             |
+| 1   | hdmi cec       | command         | 4..20         |
+| 2   | press menu key |                 | 0             |
+| 3   | gamepad update | z,x,c,v,w,a,s,d | 8             |
+| 44  | set img index  | index,type,w,h  | 4 or 8        |
+| 45  | update image   | image           | 0..254000     |
+| 46  | set text index | index           | 4             |
+| 47  | update text    | text            | 0..254000     | 
+| 48  | draw mode      | m1, m2, s1, s2  | 4             |
+| 49  | draw color     | r, g, b, a      | 4             |
+| 50  | draw rect      | x, y, w, h      | 4, 8 or 16    |
+| 51  | draw line      | x1, y1, x2, y2  | 4, 8 or 16    |
+| 52  | draw text      | x, y, id, size  | 4             |
+| 52  | draw image     | x, y, id        | 4             |
+| 64..127  | custom    |                 | 0..254000     |
+
+@par Modes
+
+@li @b 0 tint fill
+@li @b 1 tint frame
+@li @b 2 tint open
+@li @b 4 tint save
+@li @b 5 erase fill
+@li @b 6 erase frame
+@li @b 7 erase open
+
+@par Image Types
+
+@li @b 0 RGB 15bits
+@li @b 1 RGBA 32bits
+@li @b 2 BMP
+@li @b 3 PNG 
+
+@par Examples
+
+@li @c std.draw.clear(0xAABBCCDD) @n ESC G 0x48 0x00000003 0x00000000 @n ESC G 0x49 0x01 0xAABBCCDD 0x00000000 @n ESC G 0x50 0x01 0x0000FFFFF 0x00000000
+
+@}
+@}
diff --git a/ee/engine/core/ginga/main.lua b/ee/engine/core/ginga/main.lua
index 16065df..304a1e6 100644
--- a/ee/engine/core/ginga/main.lua
+++ b/ee/engine/core/ginga/main.lua
@@ -1,4 +1,4 @@
-local zeebo_module = require('src/lib/engine/raw/module')
+local zeebo_module = require('src/lib/common/module')
 --
 local core_draw = require('ee/engine/core/ginga/draw')
 local core_keys = require('ee/engine/core/ginga/keys')
diff --git a/src/cli/commands/build.lua b/src/cli/commands/build.lua
index b1081cf..a164214 100644
--- a/src/cli/commands/build.lua
+++ b/src/cli/commands/build.lua
@@ -3,33 +3,40 @@ local zeebo_bundler = require('src/lib/cli/bundler')
 local zeebo_builder = require('src/lib/cli/builder')
 local zeebo_meta = require('src/lib/cli/meta')
 local zeebo_fs = require('src/lib/cli/fs')
+local util_fs = require('src/lib/util/fs')
 
 local function build(args)
     local bundler = ''
     local screen = args.screen
-    local dist = args.dist
+    local dist = util_fs.path(args.dist).get_unix_path()
 
     local core_list = {
+        none = {
+
+        },
         repl={
-            src='src/engine/core/repl/main.lua',
+            src='src/engine/core/repl',
             post_exe='lua dist/main.lua'
         },
         love={
-            src='src/engine/core/love/main.lua',
+            src='src/engine/core/love',
             post_exe='love dist -'..'-screen '..screen
         },
         ginga={
-            src='ee/engine/core/ginga/main.lua',
+            src='ee/engine/core/ginga',
             post_exe='ginga dist/main.ncl -s '..screen,
             extras={
                 'ee/engine/meta/ginga/main.ncl'
             }
         },
+        lite={
+            src='src/engine/core/lite',
+        },
         native={
-            src='src/engine/core/native/main.lua',
+            src='src/engine/core/native',
         },
         html5_webos={
-            src='src/engine/core/native/main.lua',
+            src='src/engine/core/native',
             post_exe='webos24 $(pwd)/dist',
             pipeline={
                 zeebo_meta.late(dist..'game.lua'):file(dist..'index.html'):file(dist..'appinfo.json'):pipe()
@@ -44,7 +51,7 @@ local function build(args)
             }
         },
         html5_tizen={
-            src='src/engine/core/native/main.lua',
+            src='src/engine/core/native',
             pipeline={
                 zeebo_meta.late(dist..'game.lua'):file(dist..'index.html'):file(dist..'config.xml'):pipe(),
                 function() os.execute('cd '..dist..';~/tizen-studio/tools/ide/bin/tizen.sh package -t wgt;true') end
@@ -59,8 +66,19 @@ local function build(args)
                 'assets/icon80x80.png'
             }
         },
+        html5_lite={
+            src='src/engine/core/lite',
+            pipeline={
+                zeebo_meta.late(dist..'game.lua'):file(dist..'index.html'):pipe()
+            },
+            extras={
+                'src/engine/core/html5/index.html',
+                'src/engine/core/html5/driver-wasmoon.js',
+                'src/engine/core/html5/core-native-html5.js'
+            }
+        },
         html5={
-            src='src/engine/core/native/main.lua',
+            src='src/engine/core/native',
             pipeline={
                 zeebo_meta.late(dist..'game.lua'):file(dist..'index.html'):pipe()
             },
@@ -73,7 +91,7 @@ local function build(args)
     }
 
     -- clean dist
-    zeebo_fs.clear(args.dist)
+    zeebo_fs.clear(dist)
 
     -- check core
     if not core_list[args.core] then
@@ -93,27 +111,36 @@ local function build(args)
 
     -- move game
     if args.game then
-        local dir, file = args.game:match("(.*/)([^/]+)$")
-        zeebo_bundler.build(dir, file, dist..'game.lua')
+        local game = util_fs.file(args.game)
+        zeebo_builder.build(game.get_unix_path(), game.get_file(), dist..bundler, 'game.lua', 'game_')
     end
 
     -- core move
-    local index = 1
     local core = core_list[args.core]
-    zeebo_builder.build(core.src, dist..bundler)
-    if core.extras then
-        while index <= #core.extras do
-            local file = core.extras[index]
-            zeebo_fs.move(file, dist..file:gsub('.*/', ''))
-            index = index + 1
+    do
+        local index = 1
+        if core.src then
+            zeebo_builder.build(core.src, 'main.lua', dist..bundler, 'main.lua', 'core_')
+        end
+        if core.extras then
+            while index <= #core.extras do
+                local file = core.extras[index]
+                zeebo_fs.move(file, dist..file:gsub('.*/', ''))
+                index = index + 1
+            end
         end
     end
 
     -- combine files
     if #bundler > 0 then
-        zeebo_bundler.build(dist..bundler, 'main.lua', dist..'main.lua')
+        if core.src then
+            zeebo_bundler.build(dist..bundler, 'main.lua', dist..'main.lua')
+        end
+        if args.game then
+            zeebo_bundler.build(dist..bundler, 'game.lua', dist..'game.lua')
+        end
         zeebo_fs.clear(dist..bundler)
-        os.remove(dist..bundler)
+        zeebo_fs.rmdir(dist..bundler)
     end
 
     -- post process
diff --git a/src/cli/commands/cli.lua b/src/cli/commands/cli.lua
index 9ec535f..bf8c110 100644
--- a/src/cli/commands/cli.lua
+++ b/src/cli/commands/cli.lua
@@ -29,20 +29,14 @@ local function cli_build(args)
     return ok, message
 end
 
-local function cli_dump(args)
-    return zeebo_bootstrap.dump(dist)
-end
-
-local function cli_init(args)
-    local project_name = args[1] or 'my_project'
-    init.init_project(project_name)
+local function cli_dump()
+    return zeebo_bootstrap.dump()
 end
 
 local P = {
     ['cli-build'] = cli_build,
     ['cli-test'] = cli_test,
-    ['cli-dump'] = cli_dump,
-    ['cli-init'] = cli_init
+    ['cli-dump'] = cli_dump
 }
 
 return P
diff --git a/src/cli/commands/fs.lua b/src/cli/commands/fs.lua
index 3ec2d79..9f36cdb 100644
--- a/src/cli/commands/fs.lua
+++ b/src/cli/commands/fs.lua
@@ -42,7 +42,7 @@ local function vim_xxd_i(args)
         end
     end
 
-    local content, length, column = '', 0, 0
+    local length, column = 0, 0
     local const =  args.const and 'const ' or '' 
     local var_name = args.name or args.file:gsub('[%._/]', '_'):gsub("__+", "_"):gsub('^_', '')
 
diff --git a/src/cli/commands/info.lua b/src/cli/commands/info.lua
index 2092566..aa11f82 100644
--- a/src/cli/commands/info.lua
+++ b/src/cli/commands/info.lua
@@ -43,7 +43,7 @@ local help_message = "Available commands:\n"
   .."- To run a game: ./cli.sh run ./examples/asteroids/game.lua "  .. "-" .. "-core repl\n"
   .."- To display metadata: ./cli.sh meta ./examples/asteroids/game.lua\n"
 
-local version_message = '0.0.10'
+local version_message = '0.0.11'
 
 local function help()
   return true, help_message
@@ -53,11 +53,6 @@ local function version()
   return true, version_message
 end
 
---! @todo show all commands with complete flags
-local function show(args)
-  return false, 'not implemented!'
-end
-
 local function meta()
   local description = 'not implemented!'
   return {
diff --git a/src/cli/commands/init.lua b/src/cli/commands/init.lua
index ca60869..e0d3931 100644
--- a/src/cli/commands/init.lua
+++ b/src/cli/commands/init.lua
@@ -1,4 +1,6 @@
 local os = require('os')
+local util_cmd = require('src/lib/util/cmd')
+
 local ok = true
 
 local function create_file(filepath, content)
@@ -13,7 +15,7 @@ local function create_file(filepath, content)
 end
 
 local function create_directory(path)
-    local success = os.execute("mkdir " .. path)
+    local success = os.execute(util_cmd.mkdir()..path)
     if not success then
         print("Error while creating directory: " .. path)
         ok = false
@@ -23,7 +25,7 @@ end
 local function init_project(args)
     local project_name = args.project
     local project_template = args.template
-    local project_gamefile, error_gamefile = io.open(project_template, 'r')
+    local project_gamefile = io.open(project_template, 'r')
 
     ok = true
 
diff --git a/src/cli/commands/tools.lua b/src/cli/commands/tools.lua
index 423fcb6..51d96ac 100644
--- a/src/cli/commands/tools.lua
+++ b/src/cli/commands/tools.lua
@@ -3,25 +3,33 @@ local zeebo_bundler = require('src/lib/cli/bundler')
 local zeebo_package = require('src/lib/cli/package')
 local zeebo_filler = require('src/lib/cli/filler')
 local zeebo_fs = require('src/lib/cli/fs')
+local util_fs = require('src/lib/util/fs')
+local util_cmd = require('src/lib/util/cmd')
 
 local function bundler(args)
-    local path, file = args.file:match("(.-)([^/\\]+)$")
-    return zeebo_bundler.build(path, file, args.dist..file)
+    local d = util_fs.path(args.dist)
+    local f = util_fs.file(args.file)
+    zeebo_fs.clear(d.get_fullfilepath())
+    return zeebo_bundler.build(f.get_sys_path(), f.get_file(), d.get_sys_path()..f.get_file())
 end
 
 local function compiler(args)
-    return zeebo_compiler.build(args.file, args.dist)
+    local file = util_fs.file(args.file).get_fullfilepath()
+    local dist = util_fs.file(args.dist).get_fullfilepath()
+    return zeebo_compiler.build(file, dist)
 end
 
 local function love_zip(args)
-    os.execute('mkdir -p '..args.dist..'_love')
-    os.execute('mv '..args.path..'/* '..args.dist..'_love 2> /dev/null')
-    local zip_pid = io.popen('cd '..args.dist..'_love && zip -9 -r Game.love .')
+    local dist = util_fs.path(args.dist).get_fullfilepath()
+    local path = util_fs.path(args.path).get_fullfilepath()
+    os.execute(util_cmd.mkdir()..dist..'_love')
+    os.execute(util_cmd.move()..path..'* '..dist..'_love'..util_cmd.silent())
+    local zip_pid = io.popen('cd '..dist..'_love && zip -9 -r Game.love .')
     local stdout = zip_pid:read('*a')
-    local ok = zip_pid:close()    
-    zeebo_fs.move(args.dist..'_love/Game.love', args.dist..'Game.love')
-    zeebo_fs.clear(args.dist..'_love')
-    os.remove(args.dist..'_love')
+    local ok = zip_pid:close()
+    zeebo_fs.move(dist..'_love/Game.love', dist..'Game.love')
+    zeebo_fs.clear(dist..'_love')
+    os.remove(dist..'_love')
     return ok, stdout
 end
 
diff --git a/src/engine/core/html5/core-native-html5.js b/src/engine/core/html5/core-native-html5.js
index 5ac81bd..6433d7f 100644
--- a/src/engine/core/html5/core-native-html5.js
+++ b/src/engine/core/html5/core-native-html5.js
@@ -216,20 +216,19 @@ const gly = {
     resume: () => {
         engine.pause = false
     },
-    update: () => {
-        gly.update_dt(16);
-    },
-    update_dt: (milis) => {
-        errorController(() => {
-            engine.listen.native_callback_loop(milis)
-            engine.listen.native_callback_draw()
-        })
+    update: (milis) => {
+        if (!engine.pause) {
+            errorController(() => {
+                engine.listen.native_callback_loop(milis ?? 16)
+                engine.listen.native_callback_draw()
+            })
+        }
     },
     update_uptime: (milis) => {
         engine.milis = engine.milis ?? milis ?? 0
         const dt = milis - engine.milis
         engine.milis = milis
-        gly.update_dt(dt)
+        gly.update(dt)
     },
     engine: {
         set: (file_name) => engine.file = file_name,
diff --git a/src/engine/core/lite/main.lua b/src/engine/core/lite/main.lua
new file mode 100644
index 0000000..f359911
--- /dev/null
+++ b/src/engine/core/lite/main.lua
@@ -0,0 +1,116 @@
+local zeebo_module = require('src/lib/common/module')
+--
+local engine_encoder = require('src/lib/engine/api/encoder')
+local engine_game = require('src/lib/engine/api/game')
+local engine_hash = require('src/lib/engine/api/hash')
+local engine_http = require('src/lib/engine/api/http')
+local engine_i18n = require('src/lib/engine/api/i18n')
+local engine_key = require('src/lib/engine/api/key')
+local engine_math = require('src/lib/engine/api/math')
+local engine_draw_fps = require('src/lib/engine/draw/fps')
+local engine_draw_poly = require('src/lib/engine/draw/poly')
+local engine_raw_memory = require('src/lib/engine/raw/memory')
+--
+local application_default = require('src/lib/object/root')
+local color = require('src/lib/object/color')
+local std = require('src/lib/object/std')
+--
+local application = application_default
+local engine = {
+    keyboard = function(a, b, c, d) end,
+    current = application_default,
+    root = application_default
+}
+
+function native_callback_loop(dt)
+    std.milis = std.milis + dt
+    std.delta = dt
+    application.callbacks.loop(std, application.data)
+end
+
+function native_callback_draw()
+    native_draw_start()
+    application.callbacks.draw(std, application.data)
+    native_draw_flush()
+end
+
+function native_callback_resize(width, height)
+    application.data.width = width
+    application.data.height = height
+    std.game.width = width
+    std.game.height = height
+end
+
+function native_callback_keyboard(key, value)
+    engine.keyboard(std, engine, key, value)
+end
+
+function native_callback_init(width, height, game_lua)
+    application = zeebo_module.loadgame(game_lua)
+
+    if application then
+        application.data.width = width
+        application.data.height = height
+        std.game.width = width
+        std.game.height = height
+    end
+    
+    std.bus = {
+        emit=function() end,
+        emit_next=function() end,
+        listen=function() end,
+        listen_std_engine=function() end
+    }
+
+    std.draw.color=native_draw_color
+    std.draw.font=native_draw_font
+    std.draw.text=native_draw_text
+    std.draw.rect=native_draw_rect
+    std.draw.line=native_draw_line
+    std.draw.image=native_draw_image
+    std.draw.clear=function(tint)
+        native_draw_clear(tint, 0, 0, application.data.width, application.data.height)
+    end
+    std.draw.tui_text=function(x, y, s, text)
+        native_draw_text_tui(x, y, 0, 0, application.data.width, application.data.height, s, text)
+    end
+
+    zeebo_module.require(std, application, engine)
+        :package('@memory', engine_raw_memory)
+        :package('@game', engine_game, native_dict_game)
+        :package('@math', engine_math)
+        :package('@key', engine_key, {})
+        :package('@draw.fps', engine_draw_fps)
+        :package('@draw.poly', engine_draw_poly, native_dict_poly)
+        :package('@color', color)
+        :package('math', engine_math.clib)
+        :package('math.random', engine_math.clib_random)
+        :package('http', engine_http, native_dict_http)
+        :package('json', engine_encoder, native_dict_json)
+        :package('xml', engine_encoder, native_dict_xml)
+        :package('i18n', engine_i18n, native_get_system_lang)
+        :package('hash', engine_hash, native_dict_secret)
+        :run()
+
+
+    application.data.width, std.game.width = width, width
+    application.data.height, std.game.height = height, height
+
+    std.game.title(application.meta.title..' - '..application.meta.version)
+
+    engine.root = application
+    engine.current = application
+
+    application.callbacks.init(std, application.data)
+end
+
+local P = {
+    meta={
+        title='gly-engine',
+        author='RodrigoDornelles',
+        description='native lite',
+        version='0.0.11'
+    }
+}
+
+return P
diff --git a/src/engine/core/love/main.lua b/src/engine/core/love/main.lua
index 435a49f..96230e7 100644
--- a/src/engine/core/love/main.lua
+++ b/src/engine/core/love/main.lua
@@ -1,6 +1,6 @@
 local os = require('os')
 --
-local zeebo_module = require('src/lib/engine/raw/module')
+local zeebo_module = require('src/lib/common/module')
 --
 local core_draw = require('src/engine/core/love/draw')
 local core_loop = require('src/engine/core/love/loop')
diff --git a/src/engine/core/native/main.lua b/src/engine/core/native/main.lua
index 1cf6770..5491eed 100644
--- a/src/engine/core/native/main.lua
+++ b/src/engine/core/native/main.lua
@@ -1,4 +1,4 @@
-local zeebo_module = require('src/lib/engine/raw/module')
+local zeebo_module = require('src/lib/common/module')
 --
 local engine_encoder = require('src/lib/engine/api/encoder')
 local engine_game = require('src/lib/engine/api/game')
@@ -156,7 +156,7 @@ local P = {
         title='gly-engine',
         author='RodrigoDornelles',
         description='native core',
-        version='0.0.8'
+        version='0.0.11'
     }
 }
 
diff --git a/src/engine/core/repl/main.lua b/src/engine/core/repl/main.lua
index f8c96fa..2a76ca7 100644
--- a/src/engine/core/repl/main.lua
+++ b/src/engine/core/repl/main.lua
@@ -1,5 +1,9 @@
---! @short Read Eval Print Loop
---! @brief an interpreter to debuging the game via stdio.
+--! @defgroup Languages
+--! @{
+--! 
+--! @defgroup REPL
+--! @{
+--!
 --! @par Extended Backus-Naur Form
 --! @startebnf
 --! line = exit | frame_skip | [frame_skip], variable, ["=", value (* assignment *)];
@@ -7,20 +11,42 @@
 --! digit = { ? 0 - 9 ? }- ;
 --! exit = "?" ;
 --! @endebnf
-local zeebo_module = require('src/lib/engine/module')
-local engine_game = require('src/lib/engine/game')
-local engine_math = require('src/lib/engine/math')
-local engine_color = require('src/lib/object/color')
-local engine_http = require('src/lib/engine/http')
-local engine_encoder = require('src/lib/engine/encoder')
-local engine_draw_fps = require('src/lib/draw/fps')
-local engine_draw_poly = require('src/lib/draw/poly')
-local protocol_curl = require('src/lib/protocol/http_curl')
-local library_csv = require('src/third_party/csv/rodrigodornelles')
-local library_json = require('src/third_party/json/rxi')
-local application_default = require('src/lib/object/application')
-local game = require('src/lib/object/game')
+--!
+--! @par Usage
+--! @code{.sql}
+--! lua cli.lua run game.lua --core repl
+--! @endcode
+--!
+--! @}
+--! @}
+
+local zeebo_module = require('src/lib/common/module')
+--
+local engine_encoder = require('src/lib/engine/api/encoder')
+local engine_game = require('src/lib/engine/api/game')
+local engine_hash = require('src/lib/engine/api/hash')
+local engine_http = require('src/lib/engine/api/http')
+local engine_i18n = require('src/lib/engine/api/i18n')
+local engine_key = require('src/lib/engine/api/key')
+local engine_math = require('src/lib/engine/api/math')
+local engine_draw_ui = require('src/lib/engine/draw/ui')
+local engine_raw_bus = require('src/lib/engine/raw/bus')
+local engine_raw_node = require('src/lib/engine/raw/node')
+local engine_raw_memory = require('src/lib/engine/raw/memory')
+--
+local application_default = require('src/lib/object/root')
+local color = require('src/lib/object/color')
 local std = require('src/lib/object/std')
+--
+local cfg_json_rxi = require('src/third_party/json/rxi')
+local cfg_http_curl = require('src/lib/protocol/http_curl')
+--
+local engine = {
+    current = application_default,
+    root = application_default,
+    offset_x = 0,
+    offset_y = 0
+}
 
 local function line_skip_frames(line_src)
     local frames, line = line_src:match('(%d+)!(.*)')
@@ -72,24 +98,36 @@ local function main()
     local frames = 0
     local variable = ''
     local assignment = ''
-    local started = false
     local application = zeebo_module.loadgame(arg[1]) or application_default
 
-    -- init the game
-    zeebo_module.require(std, game, application)
-        :package('@game', engine_game)
+    zeebo_module.require(std, application, engine)
+        :package('@bus', engine_raw_bus)
+        :package('@node', engine_raw_node)
+        :package('@memory', engine_raw_memory)
+        :package('@game', engine_game, native_dict_game)
         :package('@math', engine_math)
-        :package('@color', engine_color)
-        :package('load', zeebo_module.load)
+        :package('@key', engine_key, {})
+        :package('@draw.ui', engine_draw_ui)
+        :package('@color', color)
         :package('math', engine_math.clib)
         :package('math.random', engine_math.clib_random)
-        :package('http', engine_http, protocol_curl)
-        :package('csv', engine_encoder, library_csv)
-        :package('json', engine_encoder, library_json)
+        :package('http', engine_http, cfg_http_curl)
+        :package('json', engine_encoder, cfg_json_rxi)
+        :package('i18n', engine_i18n, native_get_system_lang)
+        :package('hash', engine_hash, {'repl'})
         :run()
+
+    std.node.spawn(application)
+
+    engine.root = application
+    engine.current = application
+
+    std.bus.emit_next('load')
+    std.bus.emit_next('init')
     
     while true do
         local index = 1
+        local output = ''
         local ok, line = pcall(io.read)
 
         if not ok or line == nil or line == '?' then
@@ -100,7 +138,7 @@ local function main()
         variable, assignment = line_assignment(line)
         frames = tonumber(frames)
 
-        local ok, output = evaluate(variable, assignment, std, game, application)
+        ok, output = evaluate(variable, assignment, std, application.data, application)
         if ok then
             print(output)
         else
@@ -108,18 +146,9 @@ local function main()
             print('\n')
         end
 
-        if not started and frames > 0 and application.callbacks.init then
-            application.callbacks.init(std, game)
-            started = true
-        end
-
         while index <= frames do
-            if application.callbacks.loop then
-                application.callbacks.loop(std, game)
-            end
-            if application.callbacks.draw then
-                application.callbacks.draw(std, game)
-            end
+            std.bus.emit('loop')
+            std.bus.emit('draw')
             index = index + 1
         end
     end
diff --git a/src/lib/cli/argparse.lua b/src/lib/cli/argparse.lua
index 79fc724..618dda5 100644
--- a/src/lib/cli/argparse.lua
+++ b/src/lib/cli/argparse.lua
@@ -152,8 +152,8 @@ local function from(host_args)
         return add_option_get(cmd, param, opt)
     end
 
-    cmd.add_option_has = function(param, opt)
-        return add_option_has(cmd, param, opt)
+    cmd.add_option_has = function(param)
+        return add_option_has(cmd, param)
     end
 
     cmd.add_subcommand = function(cmd_name, cmd_collection)
diff --git a/src/lib/cli/bootstrap.lua b/src/lib/cli/bootstrap.lua
index d5bb466..29b1c3b 100644
--- a/src/lib/cli/bootstrap.lua
+++ b/src/lib/cli/bootstrap.lua
@@ -1,3 +1,5 @@
+local util_cmd = require('src/lib/util/cmd')
+
 local function bootstrap()
     local file = io.open('mock/bootstrap.lua', 'r')
     local content = file:read('*a')
@@ -109,7 +111,8 @@ local function build(...)
         end
 
         local cmd_pid = io.popen('find '..path_src)
-        local cmd_raw, cmd_pid = cmd_pid:read('*a'), cmd_pid:close()
+        local cmd_raw = cmd_pid:read('*a')
+        cmd_pid:close()
         local list_raw = explode_string(cmd_raw)
         local list_paths, list_files, dict_files = map_files(list_raw, prefix_len)
         merge_dict_and_lists(all_list_files, all_dict_files, list_files, dict_files)
@@ -120,14 +123,13 @@ local function build(...)
     output_file:write('local BOOTSTRAP = {}\nlocal BOOTSTRAP_DISABLE = false\n')
 
     do
-        local index = 1
-        local content = 'local BOOTSTRAP_DIRS = {'
-        while index <= #all_list_paths do
-            local file_name = all_list_paths[index]
-            local file_content = all_dict_files[file_name]
+        index = 1
+        local content = 'local BOOTSTRAP_LIST = {'
+        while index <= #all_list_files do
+            local file_name = all_list_files[index]
             content = content..'\''..file_name..'\''
             index = index + 1
-            if index <= #all_list_paths then
+            if index <= #all_list_files then
                 content = content..', '
             else
                 content = content..'}\n'
@@ -137,28 +139,13 @@ local function build(...)
     end
 
     do
-        local index = 1
-        local content = 'local BOOTSTRAP_LIST = {'
+        index = 1
         while index <= #all_list_files do
             local file_name = all_list_files[index]
             local file_content = all_dict_files[file_name]
-            content = content..'\''..file_name..'\''
+            output_file:write('BOOTSTRAP[\''..file_name..'\'] = \''..file_content..'\'\n')
             index = index + 1
-            if index <= #all_list_files then
-                content = content..', '
-            else
-                content = content..'}\n'
-            end
         end
-        output_file:write(content)
-    end
-
-    index = 1
-    while index <= #all_list_files do
-        local file_name = all_list_files[index]
-        local file_content = all_dict_files[file_name]
-        output_file:write('BOOTSTRAP[\''..file_name..'\'] = \''..file_content..'\'\n')
-        index = index + 1
     end
 
     output_file:write(bootstrap())
@@ -177,7 +164,7 @@ local function dump()
     do
         local index = 1
         while index <= #BOOTSTRAP_DIRS do
-            os.execute('mkdir -p '..BOOTSTRAP_DIRS[index])
+            os.execute(util_cmd.mkdir()..BOOTSTRAP_DIRS[index])
             index = index + 1
         end
     end
diff --git a/src/lib/cli/builder.lua b/src/lib/cli/builder.lua
index db51885..6f92a66 100644
--- a/src/lib/cli/builder.lua
+++ b/src/lib/cli/builder.lua
@@ -1,31 +1,46 @@
-local function move(src_in, dist_path, dist_file)
+local util_fs = require('src/lib/util/fs')
+
+local function move(src_filename, out_filename, prefix)
     local deps = {}
-    local pattern = "local ([%w_%-]+) = require%('src/(.-)'%)"
-    local pattern_ee = "local ([%w_%-]+) = require%('ee/(.-)'%)"
-    local src_file = io.open(src_in, "r")
-    local dist_file_normalized = src_in:gsub('/', '_'):gsub('^src_', '')
-    local dist_out = dist_path:gsub('/$', '')..'/'..(dist_file or dist_file_normalized)
-    local dist_file = io.open(dist_out, "w")
+    local content = ''
+    local src_file = io.open(src_filename, 'r')
+    local out_file = src_file and io.open(out_filename, 'w')
+    local pattern_require = 'local ([%w_%-]+) = require%([\'"]([%w_/-]+)[\'"]%)'
+    local pattern_gameload = 'std%.node%.load%([\'"](.-)[\'"]%)'
+    local pattern_comment = '^-'..'-!'
 
-    if src_file and dist_file then
+    if src_file and out_file then
         repeat
             local line = src_file:read()
+
+            if line and line:find(pattern_comment) then
+                line = ''
+            end
+
             if line then
-                local line_require = { line:match(pattern) }
-                local line_require_ee = { line:match(pattern_ee) }
+                local line_require = { line:match(pattern_require) }
+                local node_require = { line:match(pattern_gameload) }
 
-                if line_require_ee and #line_require_ee > 0 then
-                    local var_name = line_require_ee[1]
-                    local module_path = line_require_ee[2]
-                    deps[#deps + 1] = 'ee/'..module_path..'.lua'
-                    dist_file:write('local '..var_name..' = require(\'ee_'..module_path:gsub('/', '_')..'\')\n')
+                if node_require and #node_require > 0 then     
+                    local mod = util_fs.file(node_require[1])
+                    local module_path = (mod.get_unix_path()..mod.get_filename()):gsub('%./', '')
+                    local var_name = 'node_'..module_path:gsub('/', '_')
+                    deps[#deps + 1] = module_path..'.lua'
+                    content = 'local '..var_name..' = require(\''..prefix..module_path:gsub('/', '_')..'\')\n'..content
+                    content = content..line:gsub(pattern_gameload, 'std.node.load('..var_name..')')..'\n'
                 elseif line_require and #line_require > 0 then
+                    local exist_as_file = io.open(line_require[2]..'.lua', 'r')
                     local var_name = line_require[1]
                     local module_path = line_require[2]
-                    deps[#deps + 1] = 'src/'..module_path..'.lua'
-                    dist_file:write('local '..var_name..' = require(\''..module_path:gsub('/', '_')..'\')\n')
+                    local module_prefix = exist_as_file and prefix or ''
+                    deps[#deps + 1] = module_path..'.lua'
+                    content = content..'local '..var_name..' = require(\''
+                    content = content..module_prefix..module_path:gsub('/', '_')..'\')\n'
+                    if exist_as_file then
+                        exist_as_file:close()
+                    end
                 else
-                    dist_file:write(line, '\n')
+                    content = content..line..'\n'
                 end
             end
         until not line
@@ -34,24 +49,33 @@ local function move(src_in, dist_path, dist_file)
     if src_file then
         src_file:close()
     end
-    if dist_file then
-        dist_file:close()
+    if out_file then
+        out_file:write(content)
+        out_file:close()
     end
 
     return deps
 end
 
-local function build(src_in, dist_path)
+local function build(path_in, src_in, path_out, src_out, prefix)
     local main = true
     local deps = {}
     local deps_builded = {}
 
+    local src = util_fs.path(path_in, src_in)
+
     repeat
-        if src_in:sub(-4) == '.lua' then
+        if src then
             local index = 1
             local index_deps = #deps
-            local file_name = main and 'main.lua'
-            local new_deps = move(src_in, dist_path, file_name)
+            local out = src_out
+            if not main then
+                out = src.get_file()
+                out = prefix..src.get_unix_path():gsub('%./', ''):gsub('/', '_')..out
+            end
+            local srcfile = src.get_fullfilepath()
+            local outfile = util_fs.path(path_out, out).get_fullfilepath()
+            local new_deps = move(srcfile, outfile, prefix)
             while index <= #new_deps do
                 deps[index_deps + index] = new_deps[index]
                 index = index + 1
@@ -59,17 +83,20 @@ local function build(src_in, dist_path)
         end
 
         main = false
-        src_in = nil
-        local index = 1
-        while index <= #deps and not src_in do
-            local dep = deps[index]
-            if not deps_builded[dep] then
-                deps_builded[dep] = true
-                src_in = dep
+        src = nil
+
+        do
+            local index = 1
+            while index <= #deps and not src do
+                local dep = deps[index]
+                if not deps_builded[dep] then
+                    deps_builded[dep] = true
+                    src = util_fs.file(dep)
+                end
+                index = index + 1
             end
-            index = index + 1
         end
-    until not src_in
+    until not src
 end
 
 local P = {
@@ -77,4 +104,4 @@ local P = {
     build=build
 }
 
-return P
\ No newline at end of file
+return P
diff --git a/src/lib/cli/bundler.lua b/src/lib/cli/bundler.lua
index d096e88..80866ac 100644
--- a/src/lib/cli/bundler.lua
+++ b/src/lib/cli/bundler.lua
@@ -75,7 +75,6 @@
 
 local function build(src_path, src_filename, dest)
     local pattern_require = "local ([%w_%-]+) = require%('(.-)'%)"
-    local pattern_gameload = "([%w_%-%.]+) = std%.node%.load%('(.-)'%)"
     local from = 'main'
     local src_in = src_path..src_filename
     local src_file = io.open(src_in, 'r')
@@ -89,12 +88,9 @@ local function build(src_path, src_filename, dest)
     local lib_module = nil
     local lib_name = nil
     local lib_var = nil
-    local index = nil
 
     repeat
         if from == 'system' then
-            local os = function() local x, y = pcall(require, 'os'); return x and y end or _G.os
-
             main_before = 'local '..lib_var..' = ((function() local x, y = pcall(require, \''..lib_module
                 ..'\'); return x and y end)()) or _G.'..lib_var..'\n'..main_before
         end
@@ -115,15 +111,8 @@ local function build(src_path, src_filename, dest)
                 end
 
                 local line_require = line and { line:match(pattern_require) }
-                local line_gameload = line and { line:match(pattern_gameload) }
 
-                if line_gameload and #line_gameload > 0 then
-                    local index = #deps_var_name + 1
-                    local gamefile = line_gameload[2]:gsub('/', '_'):gsub('%.lua$', '')
-                    deps_var_name[index] = line_gameload[1]
-                    deps_module_path[index] = line_gameload[2]:gsub('%.lua$', '')
-                    main_content = main_content..'local '..line_gameload[1]..' = std.node.load('..gamefile..')\n'
-                elseif line_require and #line_require > 0 then
+                if line_require and #line_require > 0 then
                     local index = #deps_var_name + 1
                     deps_var_name[index] = line_require[1]
                     deps_module_path[index] = line_require[2]
@@ -148,7 +137,7 @@ local function build(src_path, src_filename, dest)
             src_file = nil
         end
 
-        index = 1
+        local index = 1
         src_in = nil
         while not src_in and index <= #deps_var_name do
             lib_module = deps_module_path[index]
@@ -166,7 +155,7 @@ local function build(src_path, src_filename, dest)
         end
     until not src_in
 
-    index = 1
+    local index = 1
     while index <= #deps_var_name do
         lib_module = deps_module_path[index]
         lib_name = lib_module:gsub('/', '_')
diff --git a/src/lib/cli/filler.lua b/src/lib/cli/filler.lua
index eb25397..8d75959 100644
--- a/src/lib/cli/filler.lua
+++ b/src/lib/cli/filler.lua
@@ -3,7 +3,6 @@ local template_suffix = '\n-'..'-GLYEND'
 local template = 'return {meta={title=\'G\',author=\'G\',version=\'0.0.0\'},callbacks={draw=function(s) s.draw.rect(0,8,8,8,8) end}}'
 
 local function put(dest, size)
-    local index = 0
     local template_size = #(template..template_prefix..template_suffix)
     local padding_size = size - template_size
 
diff --git a/src/lib/cli/fs.lua b/src/lib/cli/fs.lua
index a51f66b..45faabe 100644
--- a/src/lib/cli/fs.lua
+++ b/src/lib/cli/fs.lua
@@ -1,5 +1,9 @@
+local util_fs = require('src/lib/util/fs')
+local util_cmd = require('src/lib/util/cmd')
+
 local function ls(src_path)
-    local ls_cmd = io.popen('ls -1 '..src_path)
+    local p = util_fs.path(src_path).get_fullfilepath()
+    local ls_cmd = io.popen(util_cmd.lsdir()..p)
     local ls_files = {}
 
     if ls_cmd then
@@ -13,10 +17,16 @@ local function ls(src_path)
     return ls_files
 end
 
---! @todo better support windows
+local function rmdir(src_path)
+    local p = util_fs.path(src_path).get_fullfilepath()
+    os.execute(util_cmd.rmdir()..p..util_cmd.silent())
+end
+
 local function clear(src_path)
-    os.execute('mkdir -p '..src_path)
-    os.execute('rm -Rf '..src_path..'/*')
+    local p = util_fs.path(src_path).get_fullfilepath()
+    os.execute(util_cmd.mkdir()..p..util_cmd.silent())
+    os.execute(util_cmd.rmdir()..p..'*'..util_cmd.silent())
+    os.execute(util_cmd.del()..p..'*'..util_cmd.silent())
 end
 
 local function move(src_in, dist_out)
@@ -44,7 +54,8 @@ end
 local P = {
     ls = ls,
     move = move,
-    clear = clear
+    clear = clear,
+    rmdir = rmdir
 }
 
 return P
diff --git a/src/lib/cli/meta.lua b/src/lib/cli/meta.lua
index eb0d39b..bda17c4 100644
--- a/src/lib/cli/meta.lua
+++ b/src/lib/cli/meta.lua
@@ -1,5 +1,5 @@
 local application_default = require('src/lib/object/root')
-local zeebo_module = require('src/lib/engine/raw/module')
+local zeebo_module = require('src/lib/common/module')
 
 local function replace(src, meta, default)
     if src and #src > 0 then
@@ -18,7 +18,7 @@ end
 local function file(self, file)
     local file_copy = string.format("%s", file)
     self.pipeline[#self.pipeline + 1] = function()
-        if not self.loaded then return self end
+        if not self.loaded then return end
 
         local content = ''
         local file_meta = io.open(file_copy, 'r')
@@ -45,7 +45,7 @@ local function stdout(self, format)
         format_copy = format_copy..'"version":"{{version}}","description":"{{description}}"}'
     end
     self.pipeline[#self.pipeline + 1] = function()
-        if not self.loaded then return self end
+        if not self.loaded then return end
         print(replace(format_copy, self.meta, application_default.meta))
     end
     return self
diff --git a/src/lib/engine/raw/module.lua b/src/lib/common/module.lua
similarity index 100%
rename from src/lib/engine/raw/module.lua
rename to src/lib/common/module.lua
diff --git a/src/lib/engine/api/game.lua b/src/lib/engine/api/game.lua
index ea4ba97..7c93f58 100644
--- a/src/lib/engine/api/game.lua
+++ b/src/lib/engine/api/game.lua
@@ -6,9 +6,15 @@ local util_decorator = require('src/lib/util/decorator')
 --! @{
 
 --! @hideparam std
-local function reset(std)
-    std.bus.emit('exit')
-    std.bus.emit('init')
+--! @hideparam engine
+local function reset(std, engine)
+    if std.node then
+        std.bus.emit('exit')
+        std.bus.emit('init')
+    else
+        engine.root.callbacks.exit(std, engine.root.data)
+        engine.root.callbacks.init(std, engine.root.data)
+    end
 end
 
 --! @hideparam std
@@ -41,7 +47,7 @@ local function install(std, engine, config)
 
     std.game.title = util_decorator.prefix1(config.set_title, title)
     std.game.exit = util_decorator.prefix1(std, exit)
-    std.game.reset = util_decorator.prefix1(std, reset)
+    std.game.reset = util_decorator.prefix2(std, engine, reset)
     std.game.get_fps = config.fps
 
     return std.game
diff --git a/src/lib/engine/api/hash.lua b/src/lib/engine/api/hash.lua
index 26845c4..3d12a48 100644
--- a/src/lib/engine/api/hash.lua
+++ b/src/lib/engine/api/hash.lua
@@ -49,7 +49,7 @@ end
 --! @}
 
 local function install(std, engine, all_your_secrets)
-    local id = fingerprint(all_your_secrets)
+    local id = fingerprint(all_your_secrets or {'not secret!'})
     std = std or {}
     std.hash = std.hash or {}
     std.hash.djb2 = djb2
diff --git a/src/lib/engine/api/i18n.lua b/src/lib/engine/api/i18n.lua
index 6d17d7b..538fdaa 100644
--- a/src/lib/engine/api/i18n.lua
+++ b/src/lib/engine/api/i18n.lua
@@ -108,11 +108,13 @@ end
 --! @endcode
 local function back_language()
     local index = language_inverse_list[language]
-    if index <= 1 then
-        index = #language_list + 1
+    if index then
+        if index <= 1 then
+            index = #language_list + 1
+        end
+        index = index - 1
+        set_language(language_list[index])
     end
-    index = index - 1
-    set_language(language_list[index])
 end
 
 --! @par Example
@@ -123,11 +125,13 @@ end
 --! @endcode
 local function next_language()
     local index = language_inverse_list[language]
-    if index >= #language_list then
-        index = 0
+    if index then
+        if index >= #language_list then
+            index = 0
+        end
+        index = index + 1
+        set_language(language_list[index])
     end
-    index = index + 1
-    set_language(language_list[index])
 end
 
 --! @}
diff --git a/src/lib/engine/api/key.lua b/src/lib/engine/api/key.lua
index f840e7e..47ca719 100644
--- a/src/lib/engine/api/key.lua
+++ b/src/lib/engine/api/key.lua
@@ -56,6 +56,7 @@ end
 
 local function install(std, engine, key_bindings)
     engine.key_bindings = key_bindings or {}
+    engine.keyboard = real_key
 end
 
 local P = {
diff --git a/src/lib/engine/raw/node.lua b/src/lib/engine/raw/node.lua
index 9b2cd33..281ff41 100644
--- a/src/lib/engine/raw/node.lua
+++ b/src/lib/engine/raw/node.lua
@@ -1,4 +1,4 @@
-local zeebo_module = require('src/lib/engine/raw/module')
+local zeebo_module = require('src/lib/common/module')
 local node_default = require('src/lib/object/node')
 
 local buses = {
@@ -90,13 +90,12 @@ local function emit(std, application, key, a, b, c, d, e, f)
 end
 
 --! @short create new node
---! @note When build the main game file, it will be directly affected by the @ref bundler,
---! if it finds a path to the game it will be unified.
+--! @note When build the main game file, it will be directly affected by the @b build.
 --! @param [in] application
 --! @return node
 --! @par Example
 --! @code{.java}
---! local game = std.node.load('examples/pong/game.lua')
+--! local game = std.node.load('examples/awesome/game.lua')
 --! print(game.meta.title)
 --! @endcode
 local function load(application)
@@ -109,7 +108,7 @@ end
 --! @param [in/out] application
 --! @par Example
 --! @code{.java}
---! local game = std.node.load('examples/pong/game.lua')
+--! local game = std.node.load('examples/awesome/game.lua')
 --! std.node.spawn(game)
 --! @endcode
 local function spawn(engine, application)
diff --git a/src/lib/object/std.lua b/src/lib/object/std.lua
index 305c095..eb60939 100644
--- a/src/lib/object/std.lua
+++ b/src/lib/object/std.lua
@@ -15,7 +15,8 @@ local P = {
         text = function () end,
         font = function () end,
         line = function () end,
-        poly = function () end
+        poly = function () end,
+        tui_text = function() end
     },
     game = {
         width = 1280,
diff --git a/src/lib/util/cmd.lua b/src/lib/util/cmd.lua
new file mode 100644
index 0000000..e51f663
--- /dev/null
+++ b/src/lib/util/cmd.lua
@@ -0,0 +1,56 @@
+local function dos()
+    return (mock_separator or (_G.package and _G.package.config) or '/'):sub(1,1) ~= '/'
+end
+
+local function lsdir()
+    if dos() then
+        return 'dir /s /b '
+    end
+    return 'ls -1 '
+end
+
+local function rmdir()
+    if dos() then
+        return 'rmdir /s /q '
+    end
+    return 'rm -Rf '
+end
+
+local function mkdir()
+    if dos() then
+        return 'mkdir '
+    end
+    return 'mkdir -p '
+end
+
+local function move()
+    if dos() then
+        return 'move '
+    end
+    return 'mv '
+end
+
+local function del()
+    if dos() then
+        return 'del /q /s '
+    end
+    return 'rm -Rf '
+end
+
+local function silent()
+    if dos() then
+        return ' 2> nul > nul'
+    end
+    return ' 2> /dev/null > /dev/null'
+end
+
+local P = {
+    silent=silent,
+    lsdir=lsdir,
+    rmdir=rmdir,
+    mkdir=mkdir,
+    move=move,
+    del=del
+}
+
+return P
diff --git a/src/lib/util/fs.lua b/src/lib/util/fs.lua
new file mode 100644
index 0000000..e850436
--- /dev/null
+++ b/src/lib/util/fs.lua
@@ -0,0 +1,105 @@
+local function get_ext(self)
+    return function()
+        return self.extension
+    end
+end
+
+local function get_file(self)
+    return function()
+        if self.extension and #self.extension > 0 then
+            return self.filename..'.'..self.extension
+        end
+        return self.filename
+    end
+end
+
+local function get_filename(self)
+    return function()
+        return self.filename
+    end
+end
+
+local function get_path(self, separator)
+    return function()
+        local index = 1
+        local content = self.absolute and separator or ''
+        if self.absolute and separator == '\\' then
+            content = self.windriver..':'..content
+        end
+        while index <= #self.path do
+            content = content..self.path[index]..separator
+            index = index + 1
+        end
+        return content
+    end
+end
+
+local function get_fullfilepath(self, separator)
+    return function()
+        return get_path(self, separator)()..get_file(self)()
+    end
+end
+
+local function scan(type_file)
+    return function(src, src2)
+        src = (src or ''):gsub('%s*$', '')
+        if #src == 0 then return nil end
+
+        local hasfile = type_file
+        local firstchar = src:sub(1,1)
+        local windriver = string.match(src, '^([A-Z]):[/\\]')
+        local separator = (mock_separator or (_G.package and _G.package.config) or '/'):sub(1,1)
+
+        local self = {
+            windriver= 'WINDOWS',
+            absolute = false,
+            extension='',
+            filename='',
+            path={}
+        }
+
+        if firstchar == '/' or firstchar == '\\' or windriver then
+            self.windriver = windriver or 'C'
+            self.absolute = true
+        else
+            src = '.'..separator..src
+        end
+
+        if windriver then
+            src = src:sub(3)
+        end
+
+        src:gsub("([^/\\]+)", function(part)
+            self.path[#self.path + 1] = part
+        end)
+
+        if not type_file and src2 then
+            self.path[#self.path + 1] = src2
+            hasfile = true
+        end
+
+        if hasfile then
+            self.filename, self.extension = self.path[#self.path]:match("^(.-)%.([^%.]+)$")
+            self.filename = self.filename or self.path[#self.path]
+            self.extension = self.extension or ''
+            self.path[#self.path] = nil
+        end
+
+        return {
+            get_ext=get_ext(self),
+            get_file=get_file(self),
+            get_filename=get_filename(self),
+            get_sys_path=get_path(self, separator),
+            get_win_path=get_path(self, '\\'),
+            get_unix_path=get_path(self, '/'),
+            get_fullfilepath=get_fullfilepath(self, separator)
+        }
+    end
+end
+
+local P = {
+    file = scan(true),
+    path = scan(false)
+}
+
+return P
diff --git a/tests/test_lib_engine_game.lua b/tests/test_lib_engine_game.lua
index 33333bc..d5af130 100644
--- a/tests/test_lib_engine_game.lua
+++ b/tests/test_lib_engine_game.lua
@@ -5,6 +5,7 @@ function test_game_reset()
     local index = 1
     local buses = {}
     local std = {
+        node = {},
         bus = {
             listen = function() end,
             emit = function(key)
diff --git a/tests/test_lib_util_fs.lua b/tests/test_lib_util_fs.lua
new file mode 100644
index 0000000..40dfbd4
--- /dev/null
+++ b/tests/test_lib_util_fs.lua
@@ -0,0 +1,83 @@
+local luaunit = require('luaunit')
+local util_fs = require('src/lib/util/fs')
+
+function test_unix_foo_bar_z_txt()
+    local file = util_fs.file('foo/bar/z.txt')
+    luaunit.assertEquals(file.get_file(), 'z.txt')
+    luaunit.assertEquals(file.get_filename(), 'z')
+    luaunit.assertEquals(file.get_ext(), 'txt')
+    luaunit.assertEquals(file.get_win_path(), '.\\foo\\bar\\')
+    luaunit.assertEquals(file.get_unix_path(), './foo/bar/')
+end
+
+function test_win_baz_bar_y_txt()
+    local file = util_fs.file('baz\\bar\\y.exe')
+    luaunit.assertEquals(file.get_file(), 'y.exe')
+    luaunit.assertEquals(file.get_filename(), 'y')
+    luaunit.assertEquals(file.get_ext(), 'exe')
+    luaunit.assertEquals(file.get_win_path(), '.\\baz\\bar\\')
+    luaunit.assertEquals(file.get_unix_path(), './baz/bar/')
+end
+
+function test_unix_absolute()
+    local file = util_fs.file('/etc/hosts')
+    luaunit.assertEquals(file.get_file(), 'hosts')
+    luaunit.assertEquals(file.get_filename(), 'hosts')
+    luaunit.assertEquals(file.get_ext(), '')
+    luaunit.assertEquals(file.get_unix_path(), '/etc/')
+    luaunit.assertEquals(file.get_win_path(), 'C:\\etc\\')
+end
+
+function test_win_absolute()
+    local file = util_fs.file('\\Windows\\System32\\drivers\\etc\\hosts')
+    luaunit.assertEquals(file.get_file(), 'hosts')
+    luaunit.assertEquals(file.get_filename(), 'hosts')
+    luaunit.assertEquals(file.get_ext(), '')
+    luaunit.assertEquals(file.get_win_path(), 'C:\\Windows\\System32\\drivers\\etc\\')
+    luaunit.assertEquals(file.get_unix_path(), '/Windows/System32/drivers/etc/')
+end
+
+function test_win_absolute_with_driver()
+    local file = util_fs.file('D:\\Windows\\System32\\drivers\\etc\\hosts')
+    luaunit.assertEquals(file.get_file(), 'hosts')
+    luaunit.assertEquals(file.get_filename(), 'hosts')
+    luaunit.assertEquals(file.get_ext(), '')
+    luaunit.assertEquals(file.get_win_path(), 'D:\\Windows\\System32\\drivers\\etc\\')
+    luaunit.assertEquals(file.get_unix_path(), '/Windows/System32/drivers/etc/')
+end
+
+function test_unix_path()
+    local file = util_fs.path('/etc/bin')
+    luaunit.assertEquals(file.get_file(), '')
+    luaunit.assertEquals(file.get_filename(), '')
+    luaunit.assertEquals(file.get_ext(), '')
+    luaunit.assertEquals(file.get_win_path(), 'C:\\etc\\bin\\')
+    luaunit.assertEquals(file.get_unix_path(), '/etc/bin/')
+end
+
+function test_win_path()
+    local file = util_fs.path('C:/win32/program files')
+    luaunit.assertEquals(file.get_file(), '')
+    luaunit.assertEquals(file.get_filename(), '')
+    luaunit.assertEquals(file.get_ext(), '')
+    luaunit.assertEquals(file.get_win_path(), 'C:\\win32\\program files\\')
+    luaunit.assertEquals(file.get_unix_path(), '/win32/program files/')
+end
+
+function test_detect_separator()
+    mock_separator = '\\'
+    local file_win = util_fs.path('/user')
+    mock_separator = '/'
+    local file_unix = util_fs.path('/home')
+    luaunit.assertEquals(file_win.get_fullfilepath(), 'C:\\user\\')
+    luaunit.assertEquals(file_unix.get_fullfilepath(), '/home/')
+end
+
+function test_empty_protect()
+    luaunit.assertEquals(util_fs.file(nil), nil)
+    luaunit.assertEquals(util_fs.file(''), nil)
+    luaunit.assertEquals(util_fs.file(' '), nil)
+    luaunit.assertEquals(util_fs.file('\n'), nil)
+end
+
+os.exit(luaunit.LuaUnit.run())
diff --git a/tools/ci_luau-analyze.lua b/tools/ci_luau-analyze.lua
index 4bac32b..1a42573 100644
--- a/tools/ci_luau-analyze.lua
+++ b/tools/ci_luau-analyze.lua
@@ -1,7 +1,19 @@
 local cmd = function(c) assert(require('os').execute(c), c) end
 local core = arg[1] or 'native'
 
+local replace = './cli.sh fs-replace dist/main.lua dist/main.lua'
+
+if core == 'cli' then
+    cmd('./cli.sh bundler src/cli/main.lua')
+    cmd(replace..' --format "require%(\'src/engine/core/repl/main\'%)" --replace ""')
+    cmd(replace..' --format "BOOTSTRAP_DISABLE = true" --replace ""')
+    cmd(replace..' --format "string.dump" --replace "string.format"')
+    cmd(replace..' --format "arg = {args.game}" --replace ""')    
+    cmd(replace..' --format "arg = nil" --replace ""')    
+    return
+end
+
 cmd('./cli.sh build --bundler --core '..core)
-cmd('./cli.sh fs-replace dist/main.lua dist/main.lua --format "function native_callback" --replace "local function _native_callback"')
-cmd('./cli.sh tool-package-del dist/main.lua third_party_json_rxi')
-cmd('./cli.sh tool-package-del dist/main.lua lib_engine_api_encoder')
+cmd(replace..' --format "function native_callback" --replace "local function _native_callback"')
+cmd('./cli.sh tool-package-del dist/main.lua core_src_third_party_json_rxi')
+cmd('./cli.sh tool-package-del dist/main.lua core_src_lib_engine_api_encoder')
diff --git a/tools/ci_version_local.lua b/tools/ci_version_local.lua
index 83f6cf0..219eb01 100644
--- a/tools/ci_version_local.lua
+++ b/tools/ci_version_local.lua
@@ -6,7 +6,19 @@ local doxygen_file = io.open('Doxyfile')
 local doxygen_text = doxygen_file and doxygen_file:read('*a')
 local doxygen_version = doxygen_text and doxygen_text:match('(%d+%.%d+%.%d+)')
 
+local native_file = io.open('src/engine/core/native/main.lua')
+local native_text = native_file and native_file:read('*a')
+local native_version = native_text and native_text:match('(%d+%.%d+%.%d+)')
+
+local lite_file = io.open('src/engine/core/native/main.lua')
+local lite_text = lite_file and lite_file:read('*a')
+local lite_version = lite_text and lite_text:match('(%d+%.%d+%.%d+)')
+
+print('lite:   ', lite_version)
+print('native: ', native_version)
 print('cli.lua:', cli_version)
 print('doxygen:', doxygen_version)
 
 assert(cli_version == doxygen_version)
+assert(cli_version == lite_version)
+assert(cli_version == native_version)