diff --git a/api.php b/api.php
--- a/api.php
+++ b/api.php
@@ -1,320 +1,319 @@
 <?php
 
 /**
  * API entry point
  *
  * Zed. The immensity of stars. The HyperShip. The people.
  * 
  * (c) 2010, Dereckson, some rights reserved.
  * Released under BSD license.
  * 
  * @package     Zed
  * @subpackage  EntryPoints
  * @author      Sébastien Santoro aka Dereckson <dereckson@espace-win.org>
  * @copyright   2010 Sébastien Santoro aka Dereckson
  * @license     http://www.opensource.org/licenses/bsd-license.php BSD
  * @version     0.1
  * @link        http://scherzo.dereckson.be/doc/zed
  * @link        http://zed.dereckson.be/
  * @filesource
  * @todo        Consider to output documentation on / and /ship queries
  * @todo        /app/getdata
  */
 
 
 //API Preferences
 define('URL', 'http://' . $_SERVER['HTTP_HOST'] . '/index.php');
 
 //Pluton library
 require_once('includes/core.php');
 require_once('includes/config.php');
 
 //API libs
 require_once('includes/api/api_helpers.php');
 require_once('includes/api/cerbere.php');
 
 //Use our URL controller method if you want to mod_rewrite the API
 $url = explode('/', substr($_SERVER['PATH_INFO'], 1));
 
 switch ($module = $url[0]) {
 /*  -------------------------------------------------------------
     Site API
     
     /time
     /location
-    /perso              (disabled)
+    /coordinates
     - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -    */
 
     case '':
         //Nothing to do
         //TODO: offer documentation instead
         die();
     
     case 'time':
         //Hypership time
         api_output(get_hypership_time(), "time");
         break;
     
     case 'location':
         //Checks creditentials
         cerbere();
         //Gets location info
         require_once("includes/geo/location.php");
         $location = new GeoLocation($url[1], $url[2]);
         api_output($location, "location");
         break;
+
+    case 'coordinates':
+        //Checks creditentials
+        cerbere();
+        //Get coordiantes
+        api_output(GeoGalaxy::get_coordinates(), 'galaxy', 'object');
+        break;
     
-    //case 'perso':
-    //    //Checks creditentials
-    //    cerbere();
-    //    //Gets perso info
-    //    require_once("includes/objects/perso.php");
-    //    $perso = new Perso($url[1]);
-    //    api_output($perso, "perso");
-    //    break;
     
 /*  -------------------------------------------------------------
     Ship API
     
     /authenticate
     /appauthenticate
     /appauthenticated
     /move
     /land
     /flyout
     - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -    */
     
     case 'ship':
         //Ship API
         
         //Gets ship from Ship API key (distinct of regular API keys)
         require_once('includes/objects/ship.php');
         $ship = Ship::from_api_key($_REQUEST['key']) or cerbere_die("Invalid ship API key");
         
         switch ($command = $url[1]) {
             case '':
                 //Nothing to do
                 //TODO: offer documentation instead
                 die();
                 
             case 'authenticate':
                 //TODO: web authenticate
                 break;
             
             case 'appauthenticate':
                 //Allows desktop application to authenticate an user
                 $tmp_session_id = $url[2] or cerbere_die("/appauthenticate/ must be followed by any session identifier");
                 if ($_REQUEST['name']) {
                     //Perso will be offered auth invite at next login.
                     //Handy for devices like PDA, where it's not easy to auth.
                     $perso = new Perso($_REQUEST['name']);
                     if ($perso->lastError) {
                         cerbere_die($perso->lastError);
                     }
                     if (!$ship->is_perso_authenticated($perso->id)) {
                         $ship->request_perso_authenticate($perso->id);
                     }
                     $ship->request_perso_confirm_session($tmp_session_id, $perso->id);
                 } else {
                     //Delivers an URL. App have to redirects user to this URL
                     //launching a browser or printing the link.
                     $ship_code = $ship->get_code();
                     registry_set("api.ship.session.$ship_code.$tmp_session_id", -1);
                     $url = get_server_url() . get_url() . "?action=api.ship.appauthenticate&session_id=" . $tmp_session_id;
                     api_output($url, "URL");
                 }
                 break;
             
             case 'appauthenticated':
                 //Checks the user authentication
                 $tmp_session_id = $url[2] or cerbere_die("/appauthenticated/ must be followed by any session identifier you used in /appauthenticate");
                 $perso_id = $ship->get_perso_from_session($tmp_session_id);
                 if (!$isPersoAuth = $ship->is_perso_authenticated($perso_id)) {
                     //Global auth not ok/revoked.
                     $auth->status = -1;
                 } else {
                     $perso = Perso::get($perso_id);
                     $auth->status = 1;
                     $auth->perso->id = $perso->id;
                     $auth->perso->nickname = $perso->nickname;
                     $auth->perso->name = $perso->name;
                     //$auth->perso->location = $perso->location;
                     //Is the perso on board? Yes if its global location is S...
                     $auth->perso->onBoard = (
                         $perso->location_global[0] == 'S' &&
                         substr($perso->location_global, 1, 5) == $ship->id
                     );
                     if ($auth->perso->onBoard) {
                         //If so, give local location
                         $auth->perso->location_local = $perso->location_local;
                     }
                 }
                 api_output($auth, "auth");
                 break;
             
             case 'move':
                 //Moves the ship to a new location, given absolute coordinates
                 //TODO: handle relative moves
                 if (count($url) < 2) cerbere_die("/move/ must be followed by a location expression");
                 
                 //Gets location class
                 //It's allow: (1) to normalize locations between formats
                 //            (2) to ensure the syntax
                 //==> if the ship want to communicate free forms coordinates, must be added on GeoLocation a free format
                 try {
                     $location = new GeoLocation($url[2]);
                 } catch (Exception $ex) {
                     $reply->success = 0;
                     $reply->error = $ex->getMessage();
                     api_output($reply, "move");
                     break;
                 }
                 
                 $ship->location_global = $location->global;
                 $ship->save_to_database();
                 
                 $reply->success = 1;
                 $reply->location = $ship->location;
                 api_output($reply, "move");
                 break;
                 
             case 'land':
             case 'flyin':
                 //Flies in
                 try {
                     $location = new GeoLocation($location);
                 } catch (Exception $ex) {
                     $reply->success = 0; 
                     $reply->error = $ex->getMessage();
                     api_output($reply, "land");
                     break;
                 }
                 
                 break;
             
             case 'flyout':
                 //Flies out
                 
                 break;
                 
         }
         break;
     
 /*  -------------------------------------------------------------
     Application API
     
     /checkuserkey
     - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -    */
     
     case 'app':
         //Application API
         require_once("includes/objects/application.php");
         $app = Application::from_api_key($_REQUEST['key']) or cerbere_die("Invalid application API key");
         
         switch ($command = $url[1]) {
             case '':
                 //Nothing to do
                 //TODO: offer documentation instead
                 die();
                 
             case 'checkuserkey':
                 if (count($url) < 2) cerbere_die("/checkuserkey/ must be followed by an user key");
                 $reply = (boolean)$app->get_perso_id($url[2]);
                 api_output($reply, "check");
                 break;
             
             case 'pushuserdata':
                 if (count($url) < 3) cerbere_die("/pushuserdata/ must be followed by an user key");
                 $perso_id = $app->get_perso_id($url[2]) or cerbere_die("Invalid application user key");
                 //then, falls to 'pushdata'
             
             case 'pushdata':
                 $data_id = $_REQUEST['data'] ? $_REQUEST['data'] : new_guid();
                 //Gets data
                 switch ($mode = $_REQUEST['mode']) {
                     case '':
                         cerbere_die("Add in your data posted or in the URL mode=file to read data from the file posted (one file per api call) or mode=request to read data from \$_REQUEST['data'].");
                         
                     case 'request':
                         $data = $_REQUEST['data'];
                         $format = "raw";
                         break;
                     
                     case 'file':
                         $file = $_FILES['datafile']['tmp_name'] or cerbere_die("File is missing");
                         if (!is_uploaded_file($file)) cerbere_die("Invalid form request");
                         $data = "";
                         if (preg_match('/\.tar$/', $file)) {
                             $format = "tar";
                             $data = file_get_contents($file);
                         } elseif (preg_match('/\.tar\.bz2$/', $file)) {
                             $format = "tar";
                         } elseif (preg_match('/\.bz2$/', $file)) {
                             $format = "raw";
                         } else {
                             $format = "raw";
                             $data = file_get_contents($file);
                         }
                         if ($data === "") {
                             //.bz2
                             $bz = bzopen($file, "r") or cerbere_die("Couldn't open $file");
                             while (!feof($bz)) {
                               $data .= bzread($bz, BUFFER_SIZE);
                             }
                             bzclose($bz);
                         }
                         unlink($file);
                         break;
                     
                     default:
                         cerbere_die("Invalid mode. Expected: file, request");
                 }
                 
                 //Saves data
                 global $db;
                 $data_id = $db->sql_escape($data_id);
                 $data = $db->sql_escape($data);
                 $perso_id = $perso_id ? $perso_id : 'NULL';
                 $sql = "REPLACE INTO applications_data (application_id, data_id, data_content, data_format, perso_id) VALUES ('$app->id', '$data_id', '$data', '$format', $perso_id)";
                 if (!$db->sql_query($sql))
                     message_die(SQL_ERROR, "Can't save data", '', __LINE__, __FILE__, $sql);
                     //cerbere_die("Can't save data");
                     
                 //Returns
                 api_output($data_id);
                 break;
             
             case 'getuserdata':
                 //  /api.php/getuserdata/data_id/perso_key
                 //  /api.php/getdata/data_id
                 if (count($url) < 3) cerbere_die("/getuserdata/ must be followed by an user key");
                 $perso_id = $app->get_perso_id($url[2]) or cerbere_die("Invalid application user key");
                 //then, falls to 'getdata'
             
             case 'getdata':
                 if (count($url) < 2) cerbere_die('/' + $url[0] + '/ must be followed by the data ID');
                 if (!$perso_id) $perso_id = 'NULL';
                 $data_id = $db->sql_escape($url[1]);
                 $sql = "SELECT data_content FROM applications_data WHERE application_id = '$app->id' AND data_id = '$data_id' AND perso_id = $perso_id";
                 if (!$result = $db->sql_query($sql)) {
                     message_die(SQL_ERROR, "Unable to query the table", '', __LINE__, __FILE__, $sql);
                 }
                 while ($row = $db->sql_fetchrow($result)) {
                     
                 }
                 break;
                 
             default:
                 echo "Unknown module:";
                 dprint_r($url);
                 break;
         }
         break;
     
     default:
         echo "Unknown module:";
         dprint_r($url);
         break;
 }
 
 ?>
diff --git a/dev/objects_viewer.html b/dev/objects_viewer.html
new file mode 100644
--- /dev/null
+++ b/dev/objects_viewer.html
@@ -0,0 +1,219 @@
+<html xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office" >
+<head>
+	<title>Zed galaxy :: objects representation</title>
+	<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+    <link rel="Stylesheet" href="../css/zed/theme.css" type="text/css" />
+	<style type="text/css">
+		@import "/js/dojo/dojo/resources/dojo.css";
+		@import "/js/dojo/dijit/tests/css/dijitTests.css";
+		@import "/js/dojo/dijit/themes/tundra/tundra.css";
+        
+        body {
+            margin-left: auto;
+            margin-right: auto;
+        }
+        
+        .viewscreen {
+            width: 500px; height: 500px;
+            background-color: black;
+            background-image: url('http://www.formandfunction.com/wraptures/datum-TILE/Astronomy/Cluster_Virgo.jpg');
+        }
+	</style>
+    <script type="text/javascript" src="/js/misc.js" djConfig="isDebug: false"></script>
+	<script type="text/javascript" src="/js/dojo/dojo/dojo.js" djConfig="isDebug: false"></script>
+	<script type="text/javascript" src="/js/dojo/dojox/gfx3d/object.js"></script>
+	<script type="text/javascript" src="/js/dojo/dojox/gfx3d/scheduler.js"></script>
+
+	<script type="text/javascript">
+		dojo.require("dojox.gfx3d");
+        
+        viewer = {
+            angles:     {x: 30, y: 30, z: 0},
+            view:       null,
+            objects:    null,
+            
+            getObjects: function() {
+                var url = 'http://zed51.dereckson.be/api.php/coordinates?key=303392c7-97c6-11df-a1e9-000c2923380c&format=json';
+                
+                dojo.xhrGet({
+                    handleAs:       "json",
+                    url:            url,
+                    preventCache:   true,
+                    handle:         function (response, ioArgs) {
+                        viewer.objects = response;
+                        viewer.drawObjects();
+                    }
+                });
+            },
+            
+            initialize: function() {
+                viewer.getObjects();
+            },
+            
+            drawObjects: function () {
+                viewer.makeObjects();
+                
+                //Some cones to help understand the axis rotation
+                //(thanks Alphos for the tip)
+                var coneZ = [
+                    {x: 0, y: 0, z: 15}, 
+                    {x: 5, y: 0, z: 0},
+                    {x: 0, y: 5, z: 0},
+                    {x: -5, y: 0, z: 0},
+                    {x: 0, y: -5, z: 0}
+                ];
+                
+                var coneX = [
+                    {x: 15, y: 0, z: 0}, 
+                    {x: 0, y: 5, z: 0},
+                    {x: 0, y: 0, z: 5},
+                    {x: 0, y: -5, z: 0},
+                    {x: 0, y: 0, z: -5}
+                ];
+                
+                var coneY = [
+                    {x: 0, y: 15, z: 0}, 
+                    {x: 0, y: 0, z: 5},
+                    {x: 5, y: 0, z: 0},
+                    {x: 0, y: 0, z: -5},
+                    {x: -5, y: 0, z: 0}
+                ];
+
+                view.createTriangles(coneZ, "fan")
+                .setStroke({color: "blue", width: 1})
+        		.setFill("blue")
+                .applyTransform(dojox.gfx3d.matrix.translate({x: 0, y: 0, z: 200}));
+                
+                view.createTriangles(coneX, "fan")
+                .setStroke({color: "red", width: 1})
+        		.setFill("red")
+                .applyTransform(dojox.gfx3d.matrix.translate({x: 200, y: 0, z: 0}));
+                
+                view.createTriangles(coneY, "fan")
+                .setStroke({color: "green", width: 1})
+        		.setFill("green")
+                .applyTransform(dojox.gfx3d.matrix.translate({x: 0, y: 200, z: 0}));
+                
+                //Zed objects
+                for (i = 0 ; i < this.objects.length ; i++) {
+                    var object = this.objects[i];
+                    switch (object[1]) {
+                        case 'ship':
+                            //Spaceship -> blue cube
+                            var c = {bottom: object[2], top: {x: object[2].x + 10, y: object[2].y + 10, z: object[2].z + 10}};
+                            view.createCube(c).setFill({ type: "plastic", finish: "dull", color: "blue" });
+                            break;
+                        
+                        case 'hypership':
+                            //Hypership -> Yellow cylinder
+                            var c = {center: object[2], height: 15, radius: 8}
+                            view.createCylinder(c)
+                                .setStroke("black")
+                                .setFill({type: "plastic", finish: "dull", color: "yellow"});
+                            break;
+                        
+                        case 'asteroid':
+                            //Asteroid -> Red orbit
+                            var o = {center: object[2], radius: 8}
+                            view.createOrbit(o)
+                                .setStroke({color: "red", width: 1});
+                            break;
+                        
+                        default:
+                            alert('Not handled object type: ' + object[1]);
+                        
+                    }
+                }
+            },
+            
+            rotate: function() {
+                var m = dojox.gfx3d.matrix;
+    
+                if(dojo.byId('rx').checked){
+                    viewer.angles.x += 1;
+                }
+                if(dojo.byId('ry').checked){
+                    viewer.angles.y += 1;
+                }
+                if(dojo.byId('rz').checked){
+                    viewer.angles.z += 1;
+                }
+                var t = m.normalize([
+                    m.cameraTranslate(-300, -200, 0),
+                    m.cameraRotateXg(viewer.angles.x), 
+                    m.cameraRotateYg(viewer.angles.y), 
+                    m.cameraRotateZg(viewer.angles.z)
+                    ]);
+                // console.debug(t);
+                view.setCameraTransform(t);
+                view.render();
+            },
+
+            makeObjects: function(){
+                var surface = dojox.gfx.createSurface("test", 500, 500);
+                view = surface.createViewport();
+    
+                view.setLights([
+                        { direction: { x: -10, y: -5, z: 5 }, color: "white"}
+                    ], 
+                    { color:"white", intensity: 2 },
+                    "white"
+                );
+    
+                var xaxis = [{x: 0, y: 0, z: 0}, {x: 200, y: 0, z: 0}];
+                var yaxis = [{x: 0, y: 0, z: 0}, {x: 0, y: 200, z: 0}];
+                var zaxis = [{x: 0, y: 0, z: 0}, {x: 0, y: 0, z: 200}];
+    
+                var m = dojox.gfx3d.matrix;
+    
+                view.createEdges(xaxis).setStroke({color: "red", width: 1});
+                view.createEdges(yaxis).setStroke({color: "green", width: 1});
+                view.createEdges(zaxis).setStroke({color: "blue", width: 1});
+    
+                var camera = dojox.gfx3d.matrix.normalize([
+                    m.cameraTranslate(-300, -200, 0),
+                    m.cameraRotateXg(this.angles.x), 
+                    m.cameraRotateYg(this.angles.y), 
+                    m.cameraRotateZg(this.angles.z)
+                ]);
+    
+                view.applyCameraTransform(camera);
+                view.render();
+                setInterval(viewer.rotate, 50);
+            }
+        };
+        
+		dojo.addOnLoad(viewer.initialize);
+	</script>
+</head>
+<body class="tundra">
+<div style="width: 960px; margin: auto; margin-top: 1em;" class="container_16">
+	<div style="float: right; width: 400px;">
+    <h1>Zed objects viewer</h1>
+    <h2>Objects viewer</h2>
+    <p>This page shows the different objects in the Zed galaxy.</p>
+    <p>This is based on the camera rotate <a href="http://archive.dojotoolkit.org/nightly/dojotoolkit/dojox/gfx3d/tests/test_camerarotate_shaded.html">dojox.gfx3d demo</a>.
+    <br />The background is a <a href="http://www.formandfunction.com/wraptures/LINX/w_astronomy.html">Cluster Virgo</a> from Jonathan Gibson, under CC-BY-SA license.</p>
+    <h2>Controls</h2>
+    <form>
+		<input id="rx" type="checkbox" name="rotateX" checked="true" value="on"/> 
+		<label for="rx"> Rotate around X-axis (red)</label> <br/>
+		<input id="ry" type="checkbox" name="rotateY" checked="false" value="off"/> 
+		<label for="ry"> Rotate around Y-axis (green)</label> <br/>
+		<input id="rz" type="checkbox" name="rotateZ" checked="false" value="off"/> 
+		<label for="rz"> Rotate around Z-axis (blue)</label> <br/>
+	</form>
+    <h2>Legend</h2>
+    <ul>
+        <li>Blue cube: ship</li>
+        <li>Yellow cylinder: hypership</li>
+        <li>Red orbit: asteroid</li>
+    </ul>
+    </div>
+
+
+	<div id="test" class="viewscreen"></div>
+
+</div>
+</body>
+</html>
\ No newline at end of file
diff --git a/dev/quux.php b/dev/quux.php
--- a/dev/quux.php
+++ b/dev/quux.php
@@ -1,87 +1,105 @@
 <?php
 
     require_once('includes/objects/ship.php');
     require_once('includes/objects/port.php');
     require_once('includes/objects/application.php');
     require_once('includes/objects/content.php');
     require_once('includes/objects/message.php');
     require_once('includes/objects/invite.php');
     require_once('includes/cache/cache.php');
         
     include('controllers/header.php');
     
-    $case = 'travel';
+    $case = 'spherical';
     
     switch ($case) {
+        case 'spherical':
+            require_once('includes/geo/galaxy.php');
+            echo '<H2>Spherical coordinates test</H2>';
+            echo '<table cellpadding=8>';
+            echo "<tr><th>Name</th><th>Type</th><th>Cartesian coords</th><th>Spherical I</th><th>Spherical II</th><th>Pencil coordinates</th></tr>";
+            $objects = GeoGalaxy::get_coordinates();
+            foreach ($objects as $row) {
+                echo "<tr><th style='text-align: left'>$row[0]</th><td>$row[1]</td><td>$row[2]</td>";
+                $pt = $row[2];
+                echo '<td>(', implode(', ', $pt->to_spherical()), ')</td>';
+                echo '<td>(', implode(', ', $pt->to_spherical2()), ')</td>';
+                $pt->translate(500, 300, 200, 2);
+                echo '<td>', $pt, '</td>';
+                echo '</tr>';
+            }
+            echo '</table>';
+            break;
+        
         case 'travel':
             require_once('includes/travel/travel.php');
             require_once('includes/travel/place.php');
             
             $cache = Cache::load();
             $travel = $cache->get('zed_travel');
             if ($travel == '') {
                 $travel_nocached = new Travel();
                 $travel_nocached->load_xml("content/travel.xml");
                 $cache->set('zed_travel', serialize($travel_nocached));
             } else {
                 $travel = unserialize($travel);
             }
             dieprint_r($travel);
             break;
         
         case 'perso.create.notify':
             $testperso = Perso::get(4733);
             $message = new Message();
             $message->from = 0;
             $message->to = invite::who_invited(4733);
             $url = get_server_url() . get_url('who', $testperso->nickname);
             $message->text =  sprintf(lang_get('InvitePersoCreated'), $testperso->name, $url);
             $message->send();
             dieprint_r($message);
             break;
         
         case 'pushdata';
             echo '
 <h2>/api.php/app/pushdata</h2>
 <form method="post" action="/api.php/app/pushdata?mode=file&key=37d839ba-f9fc-42ca-a3e8-28053e979b90" enctype="multipart/form-data">
     <input type="file" name="datafile" /><br />
     <input type="submit" value="Send file" />   
 </form>
             ';
             break;
         
         case 'thumbnail':
             $content = new Content(1);
             dprint_r($content);
             $content->generate_thumbnail();
             break;
         
         case 'port':
             echo '<h2>Port::from_location test</h2>';
             $locations = array("B00002", "B00002123", "B00001001", "xyz: [800, 42, 220]");
             foreach ($locations as $location) {
                 dprint_r(Port::from_location($location));
             }
             break;
         
         case 'ext':
             $file = 'dev/foo.tar';
             echo "<h2>$file</h2>";
             echo "<h3>.tar.bz2</h3>";
             echo ereg('\.tar\.bz2$', $file);
             echo "<h3>.tar</h3>";
             echo ereg('\.tar$', $file);
             break;
         
         case 'app':
             echo Application::from_api_key("37d839ba-f9fc-42ca-a3e8-28053e979b90")->generate_userkey();
             break;
         
         case '':
             dieprint_r("No case currently selected.");
             break;
     }
     
     include('controllers/footer.php');
 
 ?>
\ No newline at end of file
diff --git a/dev/schema-mysql.sql b/dev/schema-mysql.sql
--- a/dev/schema-mysql.sql
+++ b/dev/schema-mysql.sql
@@ -1,633 +1,641 @@
 -- phpMyAdmin SQL Dump
 -- version 3.3.3
 -- http://www.phpmyadmin.net
 --
 -- Serveur: localhost
 -- G�n�r� le : Sam 17 Juillet 2010 � 19:37
 -- Version du serveur: 5.5.4
 -- Version de PHP: 5.3.2
 
 SET SQL_MODE="NO_AUTO_VALUE_ON_ZERO";
 
 --
 -- Base de donn�es: `zed`
 --
 
 -- --------------------------------------------------------
 
 --
 -- Structure de la table `api_keys`
 --
 
 CREATE TABLE IF NOT EXISTS `api_keys` (
   `key_guid` varchar(36) NOT NULL,
   `key_active` tinyint(1) unsigned NOT NULL DEFAULT '1',
   `key_description` tinytext,
   `key_hits` bigint(20) NOT NULL DEFAULT '0',
   `key_lastcall` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
   PRIMARY KEY (`key_guid`),
   KEY `key_active` (`key_active`)
 ) ENGINE=MyISAM DEFAULT CHARSET=utf8;
 
 --
 -- Contenu de la table `api_keys`
 --
 
 
 -- --------------------------------------------------------
 
 --
 -- Doublure de structure pour la vue `content`
 --
 CREATE TABLE IF NOT EXISTS `content` (
 `content_id` mediumint(8) unsigned
 ,`location_global` varchar(9)
 ,`location_local` varchar(255)
 ,`location_k` smallint(5) unsigned
 ,`content_path` varchar(255)
 ,`user_id` smallint(5)
 ,`perso_id` smallint(5)
 ,`content_title` varchar(255)
 );
 -- --------------------------------------------------------
 
 --
 -- Structure de la table `content_files`
 --
 
 CREATE TABLE IF NOT EXISTS `content_files` (
   `content_id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
   `content_path` varchar(255) NOT NULL,
   `user_id` smallint(5) NOT NULL,
   `perso_id` smallint(5) NOT NULL,
   `content_title` varchar(255) NOT NULL,
   PRIMARY KEY (`content_id`)
 ) ENGINE=MyISAM  DEFAULT CHARSET=utf8 AUTO_INCREMENT=4 ;
 
 --
 -- Contenu de la table `content_files`
 --
 
 
 -- --------------------------------------------------------
 
 --
 -- Structure de la table `content_locations`
 --
 
 CREATE TABLE IF NOT EXISTS `content_locations` (
   `location_global` varchar(9) NOT NULL,
   `location_local` varchar(255) NOT NULL,
   `location_k` smallint(5) unsigned NOT NULL,
   `content_id` mediumint(8) unsigned NOT NULL,
   PRIMARY KEY (`location_global`,`location_local`,`location_k`),
   KEY `content_id` (`content_id`),
   KEY `location_global` (`location_global`,`location_local`)
 ) ENGINE=MyISAM DEFAULT CHARSET=latin1;
 
 --
 -- Contenu de la table `content_locations`
 --
 
 
 -- --------------------------------------------------------
 
 --
 -- Structure de la table `geo_bodies`
 --
 
 CREATE TABLE IF NOT EXISTS `geo_bodies` (
   `body_code` mediumint(5) unsigned zerofill NOT NULL AUTO_INCREMENT,
   `body_name` varchar(31) NOT NULL,
   `body_status` set('hypership','asteroid','moon','planet','star','orbital','hidden') DEFAULT NULL,
   `body_location` varchar(15) DEFAULT NULL,
   `body_description` text,
   PRIMARY KEY (`body_code`),
   KEY `body_status` (`body_status`),
   FULLTEXT KEY `text` (`body_name`,`body_description`)
 ) ENGINE=MyISAM  DEFAULT CHARSET=utf8 AUTO_INCREMENT=4 ;
 
 --
 -- Contenu de la table `geo_bodies`
 --
 
 INSERT INTO `geo_bodies` (`body_code`, `body_name`, `body_status`, `body_location`, `body_description`) VALUES
 (00001, 'Hypership', 'hypership', NULL, NULL),
 (00002, 'Xen', 'asteroid', NULL, NULL),
 (00003, 'Kaos', 'asteroid', NULL, NULL);
 
 -- --------------------------------------------------------
 
 --
 -- Doublure de structure pour la vue `geo_locations`
 --
 CREATE TABLE IF NOT EXISTS `geo_locations` (
 `location_code` varchar(9)
 ,`location_name` varchar(255)
 );
 -- --------------------------------------------------------
 
 --
 -- Structure de la table `geo_places`
 --
 
 CREATE TABLE IF NOT EXISTS `geo_places` (
   `place_id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
   `body_code` mediumint(5) unsigned zerofill NOT NULL,
   `place_code` smallint(3) unsigned zerofill NOT NULL,
   `place_name` varchar(255) NOT NULL,
   `place_description` longtext NOT NULL,
   `place_status` set('start','hidden') DEFAULT NULL,
   PRIMARY KEY (`place_id`),
   UNIQUE KEY `body_id` (`body_code`,`place_code`)
 ) ENGINE=MyISAM  DEFAULT CHARSET=utf8 AUTO_INCREMENT=6 ;
 
 --
 -- Contenu de la table `geo_places`
 --
 
 INSERT INTO `geo_places` (`place_id`, `body_code`, `place_code`, `place_name`, `place_description`, `place_status`) VALUES
 (1, 00001, 001, 'Tour', 'Tour circulaire, surplombant l''hypership, offrant une vue circulaire sur l''espace (ou l''ultraespace, ou l''hyperespace) et une rotonde aux derniers �tages.\r\n\r\n== Toponymie num�rique ==\r\nChaque niveau (correspondant � un secteur, identifi� par la lettre T suivi du niveau, en partant du haut) est divis� en 6 couloirs d''approximativement 60�.', NULL),
 (2, 00001, 002, 'Core', 'Le c.ur de l''hypership, son centre de gravit� et les 8 cubes l''entourant.\r\n\r\n== Toponymie num�rique ==\r\nLe core est divis� en 9 secteurs : C0 pour le centre de gravit�, C1 � C4 pour les cubes de la couche inf�rieure, C5 � C8 pour les cubes de la couche sup�rieure.', NULL),
 (3, 00002, 001, 'Algir', '', NULL),
 (4, 00003, 001, 'Zeta', '', 'start'),
 (5, 00001, 003, 'Bays', 'Baies permettant d''accueillir divers vaisseaux au sein de l''hypership.', NULL);
 
 -- --------------------------------------------------------
 
 --
 -- Structure de la table `log_smartline`
 --
 
 CREATE TABLE IF NOT EXISTS `log_smartline` (
   `command_id` int(10) unsigned NOT NULL AUTO_INCREMENT,
   `perso_id` smallint(5) unsigned DEFAULT NULL,
   `command_time` int(10) DEFAULT NULL,
   `command_text` varchar(255) DEFAULT NULL,
   `isError` tinyint(1) DEFAULT '0',
   PRIMARY KEY (`command_id`)
 ) ENGINE=MyISAM DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ;
 
 --
 -- Contenu de la table `log_smartline`
 --
 
 
 -- --------------------------------------------------------
 
 --
 -- Structure de la table `messages`
 --
 
 CREATE TABLE IF NOT EXISTS `messages` (
   `message_id` mediumint(9) NOT NULL AUTO_INCREMENT,
   `message_date` int(11) NOT NULL DEFAULT '0',
   `message_from` varchar(4) NOT NULL DEFAULT '0',
   `message_to` varchar(4) NOT NULL DEFAULT '0',
   `message_text` longtext NOT NULL,
   `message_flag` tinyint(4) NOT NULL DEFAULT '0',
   PRIMARY KEY (`message_id`),
   KEY `message_to` (`message_to`),
   KEY `message_flag` (`message_flag`),
   KEY `message_date` (`message_date`),
   KEY `inbox` (`message_to`,`message_flag`)
 ) ENGINE=MyISAM DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ;
 
 --
 -- Contenu de la table `messages`
 --
 
 
 -- --------------------------------------------------------
 
 --
 -- Structure de la table `motd`
 --
 
 CREATE TABLE IF NOT EXISTS `motd` (
   `motd_id` int(11) NOT NULL AUTO_INCREMENT,
   `perso_id` int(11) NOT NULL,
   `motd_text` varchar(255) NOT NULL,
   `motd_date` int(10) NOT NULL,
   PRIMARY KEY (`motd_id`),
   KEY `perso_id` (`perso_id`),
   KEY `motd_date` (`motd_date`),
   FULLTEXT KEY `motd_text` (`motd_text`)
 ) ENGINE=MyISAM  DEFAULT CHARSET=utf8 AUTO_INCREMENT=25 ;
 
 --
 -- Contenu de la table `motd`
 --
 
 INSERT INTO `motd` (`motd_id`, `perso_id`, `motd_text`, `motd_date`) VALUES
 (24, 4960, 'You''re on the *DEVELOPMENT AND TESTING server (database zed, using the repo hg)*', 1279161701);
 
 -- --------------------------------------------------------
 
 --
 -- Structure de la table `pages`
 --
 
 CREATE TABLE IF NOT EXISTS `pages` (
   `page_id` int(11) NOT NULL AUTO_INCREMENT,
   `page_code` varchar(31) NOT NULL,
   `page_title` varchar(255) NOT NULL,
   `page_content` longtext NOT NULL,
   PRIMARY KEY (`page_id`),
   UNIQUE KEY `page_code` (`page_code`),
   FULLTEXT KEY `page_text` (`page_code`,`page_title`,`page_content`)
 ) ENGINE=MyISAM  DEFAULT CHARSET=utf8 AUTO_INCREMENT=4 ;
 
 --
 -- Contenu de la table `pages`
 --
 
 INSERT INTO `pages` (`page_id`, `page_code`, `page_title`, `page_content`) VALUES
 (3, 'ArtworkCredits', 'Artwork credits', '<h2>Login screen</h2>\r\n<p>Wires and blocks use following tutorials:</p>\r\n<ul>\r\n    <li><a href="http://www.tutorio.com/tutorial/futuristic-decay-interface">Futuristic Decay Interface</a></li>\r\n    <li><a href="http://www.tutorio.com/tutorial/photoshop-wire-tutorial">Photoshop Wire Tutorial</a></li>\r\n</ul>\r\n<h2>Hypership</h2>\r\n<h3>Gallery tower</h3>\r\n<p>Technical schemas Dereckson. In the future, some could contain technical shapes Photoshop brushes, by <a href="http://scully7491.deviantart.com/">scully7491</a>.</p>\r\n<p>Portholes structure (c) Richard Carpenter, Six Revisions.<br />\r\nA <a href="http://sixrevisions.com/tutorials/photoshop-tutorials/how-to-design-a-space-futuristic-gallery-layout-in-photoshop/">tutorial is available here</a>.</p>\r\n<p>When the hypership is in hyperspace mode, portholes prints a colored background by <a href="http://www.sxc.hu/profile/ilco">ilco</a>.<br />\r\nWhen reaching a system, it prints a scene excerpt.</p>\r\n<h3>Core cancelled sector</h3>\r\n<p>Photographies: J&eacute;r&ocirc;me<br />\r\nEditing: Dereckson</p>\r\n<h2>Scenes</h2>\r\n<h3>Xen and Kaos</h3>\r\n<p>Scene composed from 2 wallpapers from Interfacelift, n&deg; 587 and 781.</p>\r\n<h2>Future sources</h2>\r\n<h3>Fasticon</h3>\r\n<p>It''s possible in the future some http://www.fasticon.com/ icons are added.</p>\r\n<h4>Comic Tiger</h4>\r\n<p>(c) <a href="mailto:dirceu@fasticon.com">Dirceu          Veiga</a> - FastIcon Studio.<br />\r\n<strong>License:</strong> All Icons          on the Fast Icon &quot;Download&quot; page are are FREEWARE, but to use          our Icons in your software, web site, in a theme or other project, <a href="mailto:contact@fasticon.com">you          need          our permission first</a>.  You          don''t need permission for personal use our Icons on your computer.</p>');
 
 -- --------------------------------------------------------
 
 --
 -- Structure de la table `pages_edits`
 --
 
 CREATE TABLE IF NOT EXISTS `pages_edits` (
   `page_edit_id` int(11) NOT NULL AUTO_INCREMENT,
   `page_code` varchar(255) DEFAULT NULL,
   `page_version` smallint(6) NOT NULL DEFAULT '0',
   `page_title` varchar(255) NOT NULL DEFAULT '',
   `page_content` longtext,
   `page_edit_reason` varchar(255) DEFAULT NULL,
   `page_edit_user_id` smallint(4) unsigned DEFAULT NULL,
   `page_edit_time` int(10) DEFAULT NULL,
   PRIMARY KEY (`page_edit_id`)
 ) ENGINE=MyISAM  DEFAULT CHARSET=utf8 AUTO_INCREMENT=2 ;
 
 --
 -- Contenu de la table `pages_edits`
 --
 
 
 -- --------------------------------------------------------
 
 --
 -- Structure de la table `persos`
 --
 
 CREATE TABLE IF NOT EXISTS `persos` (
   `user_id` smallint(4) DEFAULT NULL,
   `perso_id` smallint(4) NOT NULL DEFAULT '0',
   `perso_name` varchar(255) NOT NULL DEFAULT '',
   `perso_nickname` varchar(31) NOT NULL DEFAULT '',
   `perso_race` varchar(31) NOT NULL DEFAULT '',
   `perso_sex` enum('M','F','N','2') NOT NULL DEFAULT 'M',
   `perso_avatar` varchar(255) DEFAULT NULL,
   `location_global` varchar(9) DEFAULT 'B00001001',
   `location_local` varchar(255) DEFAULT NULL,
   PRIMARY KEY (`perso_id`),
   UNIQUE KEY `nickname` (`perso_nickname`),
   KEY `race` (`perso_race`),
   KEY `user_id` (`user_id`),
   KEY `location_global` (`location_global`)
 ) ENGINE=MyISAM DEFAULT CHARSET=utf8;
 
 --
 -- Contenu de la table `persos`
 --
 
 INSERT INTO `persos` (`user_id`, `perso_id`, `perso_name`, `perso_nickname`, `perso_race`, `perso_sex`, `perso_avatar`, `location_global`, `location_local`) VALUES
 (2600, 4960, 'Lorem Ipsum', 'demo', 'humanoid', 'M', '', 'B00003001', '1');
 
 -- --------------------------------------------------------
 
 --
 -- Structure de la table `persos_flags`
 --
 
 CREATE TABLE IF NOT EXISTS `persos_flags` (
   `flag_id` int(10) unsigned NOT NULL AUTO_INCREMENT,
   `perso_id` smallint(6) NOT NULL DEFAULT '0',
   `flag_key` varchar(255) NOT NULL,
   `flag_value` varchar(512) NOT NULL,
   PRIMARY KEY (`flag_id`),
   UNIQUE KEY `persoflag` (`perso_id`,`flag_key`),
   KEY `perso_id` (`perso_id`),
   KEY `flag_key` (`flag_key`),
   KEY `flag` (`flag_key`(127),`flag_value`(199))
 ) ENGINE=MyISAM  DEFAULT CHARSET=utf8 AUTO_INCREMENT=459 ;
 
 
 -- --------------------------------------------------------
 
 --
 -- Structure de la table `persos_notes`
 --
 
 CREATE TABLE IF NOT EXISTS `persos_notes` (
   `perso_id` smallint(4) NOT NULL,
   `note_code` varchar(63) NOT NULL,
   `note_text` longtext NOT NULL,
   PRIMARY KEY (`perso_id`,`note_code`),
   KEY `perso_id` (`perso_id`),
   KEY `note_code` (`note_code`)
 ) ENGINE=MyISAM DEFAULT CHARSET=latin1;
 
 --
 -- Contenu de la table `persos_notes`
 --
 
 
 -- --------------------------------------------------------
 
 --
 -- Structure de la table `ports`
 --
 
 CREATE TABLE IF NOT EXISTS `ports` (
   `port_id` smallint(6) NOT NULL AUTO_INCREMENT,
   `location_global` char(9) NOT NULL,
   `location_local` varchar(255) NOT NULL,
   `port_name` varchar(63) NOT NULL,
   `port_status` set('hidden','requiresPTA') NOT NULL,
   PRIMARY KEY (`port_id`)
 ) ENGINE=MyISAM  DEFAULT CHARSET=latin1 AUTO_INCREMENT=3 ;
 
 --
 -- Contenu de la table `ports`
 --
 
 INSERT INTO `ports` (`port_id`, `location_global`, `location_local`, `port_name`, `port_status`) VALUES
 (1, 'B00003001', '3', 'Le D�me de Th�tys', ''),
 (2, 'B00001003', '', 'Hypership''s general bays', '');
 
 -- --------------------------------------------------------
 
 --
 -- Structure de la table `profiles`
 --
 
 CREATE TABLE IF NOT EXISTS `profiles` (
   `perso_id` int(11) NOT NULL,
   `profile_text` longtext NOT NULL,
   `profile_updated` int(10) NOT NULL,
   `profile_fixedwidth` tinyint(1) NOT NULL DEFAULT '0',
   PRIMARY KEY (`perso_id`),
   KEY `profile_fixedwidth` (`profile_fixedwidth`),
   KEY `profile_updated` (`profile_updated`),
   FULLTEXT KEY `profile` (`profile_text`)
 ) ENGINE=MyISAM DEFAULT CHARSET=utf8;
 
 --
 -- Contenu de la table `profiles`
 --
 
 
 -- --------------------------------------------------------
 
 --
 -- Structure de la table `profiles_comments`
 --
 
 CREATE TABLE IF NOT EXISTS `profiles_comments` (
   `comment_id` mediumint(9) NOT NULL AUTO_INCREMENT,
   `perso_id` smallint(5) unsigned NOT NULL,
   `comment_author` smallint(5) unsigned NOT NULL,
   `comment_date` int(10) NOT NULL,
   `comment_text` text NOT NULL,
   PRIMARY KEY (`comment_id`)
 ) ENGINE=MyISAM  DEFAULT CHARSET=utf8 AUTO_INCREMENT=4 ;
 
 --
 -- Contenu de la table `profiles_comments`
 --
 
 
 -- --------------------------------------------------------
 
 --
 -- Structure de la table `profiles_photos`
 --
 
 CREATE TABLE IF NOT EXISTS `profiles_photos` (
   `photo_id` int(11) NOT NULL AUTO_INCREMENT,
   `perso_id` smallint(6) NOT NULL,
   `photo_name` varchar(63) NOT NULL,
   `photo_description` varchar(63) NOT NULL,
   `photo_avatar` tinyint(4) NOT NULL DEFAULT '0',
   PRIMARY KEY (`photo_id`),
   UNIQUE KEY `photo_name` (`photo_name`),
   KEY `user_id` (`perso_id`),
   KEY `photo_avatar` (`photo_avatar`),
   KEY `user_avatar` (`perso_id`,`photo_avatar`)
 ) ENGINE=MyISAM DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ;
 
 --
 -- Contenu de la table `profiles_photos`
 --
 
 
 -- --------------------------------------------------------
 
 --
 -- Structure de la table `profiles_tags`
 --
 
 CREATE TABLE IF NOT EXISTS `profiles_tags` (
   `perso_id` int(11) NOT NULL,
   `tag_code` varchar(31) NOT NULL,
   `tag_class` varchar(15) NOT NULL DEFAULT 'music',
   PRIMARY KEY (`perso_id`,`tag_code`),
   KEY `tag_code` (`tag_code`),
   KEY `tag_class` (`tag_class`)
 ) ENGINE=MyISAM DEFAULT CHARSET=utf8;
 
 --
 -- Contenu de la table `profiles_tags`
 --
 
 
 -- --------------------------------------------------------
 
 --
 -- Structure de la table `registry`
 --
 
 CREATE TABLE IF NOT EXISTS `registry` (
   `registry_key` varchar(63) NOT NULL,
   `registry_value` longtext NOT NULL,
   `registry_updated` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
   PRIMARY KEY (`registry_key`)
 ) ENGINE=MyISAM DEFAULT CHARSET=utf8;
 
 --
 -- Contenu de la table `registry`
 --
 
 INSERT INTO `registry` (`registry_key`, `registry_value`, `registry_updated`) VALUES
 ('api.ship.session.S00001.Demios0001', '1148', '2010-07-04 15:18:04');
 
 -- --------------------------------------------------------
 
 --
 -- Structure de la table `sessions`
 --
 
 CREATE TABLE IF NOT EXISTS `sessions` (
   `session_id` varchar(32) NOT NULL DEFAULT '',
   `Where` tinyint(4) NOT NULL DEFAULT '1',
   `IP` varchar(8) NOT NULL DEFAULT '',
   `user_id` smallint(5) NOT NULL DEFAULT '-1',
   `perso_id` smallint(6) DEFAULT NULL,
   `Skin` varchar(31) NOT NULL DEFAULT 'zed',
   `Skin_accent` varchar(31) NOT NULL DEFAULT '',
   `online` tinyint(4) NOT NULL DEFAULT '1',
   `HeureLimite` varchar(15) NOT NULL DEFAULT '',
   `SessionLimite` varchar(15) NOT NULL DEFAULT '',
   PRIMARY KEY (`session_id`),
   KEY `Where` (`Where`),
   KEY `HeureLimite` (`HeureLimite`)
 ) ENGINE=MEMORY DEFAULT CHARSET=latin1 COMMENT='Sessions @ Pluton';
 
 --
 -- Contenu de la table `sessions`
 --
 
 INSERT INTO `sessions` (`session_id`, `Where`, `IP`, `user_id`, `perso_id`, `Skin`, `Skin_accent`, `online`, `HeureLimite`, `SessionLimite`) VALUES
 ('11o5p5fpacnoutbc2pgvh03ih1', 21, '0a000004', -1, NULL, 'zed', '', 1, '1279392190', '1279399090'),
 ('74ue7g6k02e6k7bfirqudmhgi7', 21, '0a000004', -1, NULL, 'zed', '', 1, '1279392191', '1279399091'),
 ('tu8otohbqlhknmt0atiuk850r6', 21, '0a000004', 2600, 4960, 'zed', '', 1, '1279392446', '1279399346'),
 ('395f7o7pme0dkt32df8h8reo66', 21, '0a000004', -1, NULL, 'zed', '', 1, '1279392191', '1279399091'),
 ('klss5iti1bf6vja6a6ibd48j02', 21, '0a000004', -1, NULL, 'zed', '', 1, '1279392193', '1279399093'),
 ('ai71qqkde5hbbjc14sh4dj87o1', 21, '0a000004', -1, NULL, 'zed', '', 1, '1279392194', '1279399094');
 
 -- --------------------------------------------------------
 
 --
 -- Structure de la table `ships`
 --
 
 CREATE TABLE IF NOT EXISTS `ships` (
   `ship_id` mediumint(5) unsigned zerofill NOT NULL AUTO_INCREMENT,
   `ship_name` varchar(63) NOT NULL,
   `location_global` char(9) DEFAULT NULL,
   `location_local` varchar(255) NOT NULL,
   `api_key` varchar(36) NOT NULL,
   `ship_description` text NOT NULL,
   PRIMARY KEY (`ship_id`),
   UNIQUE KEY `ship_name` (`ship_name`),
   KEY `location` (`location_global`),
   KEY `api_key` (`api_key`),
   FULLTEXT KEY `ship_name_2` (`ship_name`,`ship_description`)
 ) ENGINE=MyISAM  DEFAULT CHARSET=utf8 AUTO_INCREMENT=2 ;
 
 --
 -- Contenu de la table `ships`
 --
 
 
 -- --------------------------------------------------------
 
 --
 -- Doublure de structure pour la vue `ships_sessions`
 --
 CREATE TABLE IF NOT EXISTS `ships_sessions` (
 `ship_id` varchar(5)
 ,`session_id` varchar(165)
 ,`perso_id` longtext
 ,`session_updated` bigint(10)
 );
 -- --------------------------------------------------------
 
 --
 -- Structure de la table `users`
 --
 
 CREATE TABLE IF NOT EXISTS `users` (
   `user_id` smallint(4) NOT NULL DEFAULT '0',
   `username` varchar(11) NOT NULL DEFAULT '',
   `user_password` varchar(32) NOT NULL DEFAULT '',
   `user_active` tinyint(1) NOT NULL DEFAULT '0',
   `user_actkey` varchar(11) DEFAULT NULL,
   `user_email` varchar(63) NOT NULL DEFAULT '',
   `user_regdate` int(10) NOT NULL DEFAULT '0',
   PRIMARY KEY (`user_id`)
 ) ENGINE=MyISAM DEFAULT CHARSET=utf8;
 
 --
 -- Contenu de la table `users`
 --
 -- Adds a default account with demo/demo as login/password
 --
 
 INSERT INTO `users` (`user_id`, `username`, `user_password`, `user_active`, `user_actkey`, `user_email`, `user_regdate`) VALUES
 (2600, 'demo', 'fe01ce2a7fbac8fafaed7c982a04e229', 1, NULL, 'lorem@ipsum.dol', 1279161321);
 
 -- --------------------------------------------------------
 
 --
 -- Structure de la table `users_invites`
 --
 
 CREATE TABLE IF NOT EXISTS `users_invites` (
   `invite_code` char(6) NOT NULL,
   `invite_date` int(10) NOT NULL,
   `invite_from_user_id` smallint(5) NOT NULL,
   `invite_from_perso_id` smallint(5) NOT NULL,
   `invite_to_user_id` smallint(5) DEFAULT NULL,
   PRIMARY KEY (`invite_code`),
   KEY `invite_to_user_id` (`invite_to_user_id`),
   KEY `invite_from_user_id` (`invite_from_user_id`)
 ) ENGINE=MyISAM DEFAULT CHARSET=utf8;
 
 --
 -- Contenu de la table `users_invites`
 --
 
 
 -- --------------------------------------------------------
 
 --
 -- Structure de la table `users_openid`
 --
 
 CREATE TABLE IF NOT EXISTS `users_openid` (
   `openid_id` mediumint(9) NOT NULL AUTO_INCREMENT,
   `openid_url` varchar(255) NOT NULL,
   `user_id` mediumint(9) NOT NULL,
   PRIMARY KEY (`openid_id`),
   UNIQUE KEY `openid_url` (`openid_url`),
   KEY `user_id` (`user_id`)
 ) ENGINE=MyISAM  DEFAULT CHARSET=utf8 AUTO_INCREMENT=5 ;
 
 --
 -- Contenu de la table `users_openid`
 --
 
 
 -- --------------------------------------------------------
 
 --
 -- Structure de la vue `content`
 --
 DROP TABLE IF EXISTS `content`;
 
 CREATE VIEW `content` AS select `cl`.`content_id` AS `content_id`,`cl`.`location_global` AS `location_global`,`cl`.`location_local` AS `location_local`,`cl`.`location_k` AS `location_k`,`cf`.`content_path` AS `content_path`,`cf`.`user_id` AS `user_id`,`cf`.`perso_id` AS `perso_id`,`cf`.`content_title` AS `content_title` from (`content_locations` `cl` join `content_files` `cf`) where (`cf`.`content_id` = `cl`.`content_id`);
 
 -- --------------------------------------------------------
 
 --
 -- Structure de la vue `geo_locations`
 --
 DROP TABLE IF EXISTS `geo_locations`;
 
 CREATE VIEW `geo_locations` AS select concat(_utf8'B',convert(`geo_bodies`.`body_code` using utf8)) AS `location_code`,`geo_bodies`.`body_name` AS `location_name` from `geo_bodies` union select concat(_utf8'B',convert(`geo_places`.`body_code` using utf8),convert(`geo_places`.`place_code` using utf8)) AS `code`,`geo_places`.`place_name` AS `NAME` from `geo_places` union select concat(_utf8'S',convert(`ships`.`ship_id` using utf8)) AS `location_code`,`ships`.`ship_name` AS `location_name` from `ships`;
 
 -- --------------------------------------------------------
 
 --
 -- Structure de la vue `ships_sessions`
 --
 DROP TABLE IF EXISTS `ships_sessions`;
 
 CREATE VIEW `ships_sessions` AS select substr(`registry`.`registry_key`,19,5) AS `ship_id`,substr(`registry`.`registry_key`,25) AS `session_id`,`registry`.`registry_value` AS `perso_id`,unix_timestamp(`registry`.`registry_updated`) AS `session_updated` from `registry` where (left(`registry`.`registry_key`,17) = _utf8'api.ship.session.');
 
+-- --------------------------------------------------------
+
+--
+-- Structure de la vue `geo_coordinates`
+--
+CREATE VIEW geo_coordinates AS (SELECT body_name as object_name, body_status as object_type, body_location as object_location FROM geo_bodies)
+UNION
+(SELECT ship_name as object_name, 'ship' as object_type, location_global as object_location FROM ships WHERE LEFT(location_global, 3) = 'xyz') ORDER BY object_name
\ No newline at end of file
diff --git a/dev/tests/GeoGalaxyTest.php b/dev/tests/GeoGalaxyTest.php
new file mode 100644
--- /dev/null
+++ b/dev/tests/GeoGalaxyTest.php
@@ -0,0 +1,3 @@
+<?php
+
+?>
\ No newline at end of file
diff --git a/includes/geo/galaxy.php b/includes/geo/galaxy.php
--- a/includes/geo/galaxy.php
+++ b/includes/geo/galaxy.php
@@ -1,85 +1,119 @@
 <?php
 
 /**
  * Geo galaxy  class.
  * 
  * Zed. The immensity of stars. The HyperShip. The people.
  * 
  * (c) 2010, Dereckson, some rights reserved.
  * Released under BSD license.
  *
  * A 3D grid of objects
  *
- * 0.1    2010-02-08 14:02    DcK
- *
+ * 0.1    2010-02-08 14:02    Initial version [DcK]
+ * 0.2    2010-07-25  9:20    Spherical conversion, get objects
+ * 
  * @package     Zed
  * @subpackage  Geo
  * @author      Sébastien Santoro aka Dereckson <dereckson@espace-win.org>
  * @copyright   2010 Sébastien Santoro aka Dereckson
  * @license     http://www.opensource.org/licenses/bsd-license.php BSD
  * @version     0.1
  * @link        http://scherzo.dereckson.be/doc/zed
  * @link        http://zed.dereckson.be/
  * @filesource
  */
 
 /**
  * Geo galaxy class
  *
  * This class provides methods to convert coordinate polars.
  *
- * @todo add a cartesian_to_polar method
- * @todo add a static method to get a grid of all the galaxy objects, with their x y z representation ; that will be useful to add in API, for a javascript galaxy viewer.
- *
  * @todo create a unit testing file dev/tests/GeoGalaxyTest.php
  * @todo add unit testing for the normalize_angle method in dev/tests/GeoGalaxyTest.php
- * @todo add unit testing for the polar_to_cartesian method
  */
 class GeoGalaxy {
+    /*
+     * ----------------------------------------------------------------------- *
+     *  Objects fetchers
+     * ----------------------------------------------------------------------- *
+     */
+    
+    /**
+     * Gets all the coordinates of the objects in the galaxy.
+     *
+     * @return array An array of array. Each item is  [string object_name, string object_type, GeoPoint3D coordinates]
+     */
+    static function get_coordinates () {
+        global $db;
+        $sql = "SELECT * FROM geo_coordinates";        
+        if (!$result = $db->sql_query($sql)) message_die(SQL_ERROR, "Can't query geo_coordinates view.", '', __LINE__, __FILE__, $sql);
+        
+        $objects = array();
+        while ($row = $db->sql_fetchrow($result)) {
+            //Demios  ship        xyz: [-50, 30, 40]
+            //Kaos	  asteroid    xyz: [150, -129, 10]
+            $objects[] = array($row[0], $row[1], GeoPoint3D::fromString($row[2]));
+        }
+        return $objects;
+    }
     
     /*
      * ----------------------------------------------------------------------- *
      *  Helper methods - math
      * ----------------------------------------------------------------------- *
      */
     
     /**
      * Normalizes an angle, so 0 =< angle < 2 PI
      * 
      * @param float $angle angle in radians (use deg2rad() if you've degrees)
      * @return an angle in the 0 =< angle < 2 PI interval
      */
     static function normalize_angle ($angle) {
         while ($angle < 0) {
             $angle += 2 * M_PI;
         }
         while ($angle >= 2 * M_PI) {
             $angle -= 2 * M_PI;
         }
         return $angle;
     }
-    
-    /*
-     * Converts polar coordinates in cartesian x y coordinates
-     * @param float $angle angle in radians (use deg2rad() if you've degrees)
-     * @param float $height height
-     * @return array an array of 2 float items: x, y
+        
+    /**
+     * Converts (x, y, z) cartesian to (ρ, φ, θ) spherical coordinates
+     *
+     * The algo used is from http://fr.wikipedia.org/wiki/Coordonn%C3%A9es_sph%C3%A9riques#Relation_avec_les_autres_syst.C3.A8mes_de_coordonn.C3.A9es_usuels
+     *
+     * @param int $x the x coordinate
+     * @param int $y the y coordinate
+     * @param int $z the z coordinate
+     * @return array an array of 3 floats number, representing the (ρ, φ, θ) spherical coordinates
      */
-    static function polar_to_cartesian ($angle, $height) {
-        //A story of numbers
-        if ($height < 0) {
-            //Adds 180° and gets absolute value
-            $height *= -1;
-            $angle + M_PI;
-        }
-        $x = abs(sin($angle)) . $height;
-        $y = abs(cos($angle)) . $height;
+    static function cartesian_to_spherical ($x, $y, $z) {
+        $rho = sqrt($x * $x + $y * $y + $z * $z);    //ρ = sqrt(x² + y² + z²)
+        $theta= acos($z / $rho);                    //φ = acos z/φ
+        $phi = acos($x / sqrt($x * $x + $y * $y)); //θ = acos x / sqrt(x² + y²)
+        if (y < 0) $phi = 2 * M_PI - $phi;        //∀ y < 0     θ = 2π - θ
         
-        //And now, the sign
-        
-        
-        //Returns our coordinates
-        return array($x, $y);
+        return array(round($rho, 2), round(rad2deg($theta), 2), round(rad2deg($phi), 2));
     }
     
+    /**
+     * Converts (x, y, z) cartesian to (ρ, φ, θ) spherical coordinates
+     *
+     * The algo used is from http://www.phy225.dept.shef.ac.uk/mediawiki/index.php/Cartesian_to_polar_conversion
+     *
+     * @param int $x the x coordinate
+     * @param int $y the y coordinate
+     * @param int $z the z coordinate
+     * @return array an array of 3 floats number, representing the (ρ, φ, θ) spherical coordinates
+     */
+    static function cartesian_to_spherical2 ($x, $y, $z) {
+        $rho = sqrt($x * $x + $y * $y + $z * $z); //ρ = sqrt(x² + y² + z²)
+        $theta= atan2($y, $x);                    //φ = atan2 $y $x
+        $phi = acos($z / $rho);                   //θ = acos z/φ
+        
+        return array(round($rho, 2), round(rad2deg($theta), 2), round(rad2deg($phi), 2));
+    }
 }
\ No newline at end of file
diff --git a/includes/geo/point3D.php b/includes/geo/point3D.php
--- a/includes/geo/point3D.php
+++ b/includes/geo/point3D.php
@@ -1,118 +1,208 @@
 <?php
 
 /**
  * Geo point 3D class.
  *
  * Zed. The immensity of stars. The HyperShip. The people.
  * 
  * (c) 2010, Dereckson, some rights reserved.
  * Released under BSD license.
  *
  * 0.1    2010-02-23 14:14    DcK
  *
  * @package     Zed
  * @subpackage  Geo
  * @author      Sébastien Santoro aka Dereckson <dereckson@espace-win.org>
  * @copyright   2010 Sébastien Santoro aka Dereckson
  * @license     http://www.opensource.org/licenses/bsd-license.php BSD
  * @version     0.1
  * @link        http://scherzo.dereckson.be/doc/zed
  * @link        http://zed.dereckson.be/
  * @filesource
  */
 
 /**
  * Geo point 3D class.
  *
  * This class represents a x, y, z point.
  *
  * It implements IteratorAggregate to allow the foreach instruction
  * on a GeoPoint3D object:
  *
  * <code>
  * $point = new GeoPoint3D(17, 24, -6);
  * foreach ($point as $axis => $coordinate) {
  *     echo "\n\t$axis = $coordinate";
  * }
  * //This will output:
  * //    x = 17
  * //    y = 24
  * //    z = -6
  * </code>
  *
  * The point 3D representation is xyz: [x, y, z] ; you can print it as a string
  * and get this format:
  *
  * <code>
  * $point = new GeoPoint3D(17, 24, -6);
  * echo (string)$point;   //will output xyz: [17, 24, -6]
  * </code>
  * 
  */
 class GeoPoint3D implements IteratorAggregate {
     //
     // x, y, z public properties
     //
     
     /**
      * the x coordinate
      *
      * @var integer
      */
     public $x;
 
     /**
      * the y coordinate
      *
      * @var integer
      */
     public $y;
     
     /**
      * the z coordinate
      *
      * @var integer
      */
     public $z;
     
     //
     // constructor / toString
     //
     
     /**
      * Initializes a new instance of GeoPoint3D class
      *
      * @param int $x the x coordinate
      * @param int $y the y coordinate
      * @param int $z the z coordinate
      */
     function __construct ($x, $y, $z) {
-        $this->x = $x;
-        $this->y = $y;
-        $this->z = $z;
+        $this->x = (int)$x;
+        $this->y = (int)$y;
+        $this->z = (int)$z;
     }
     
     /**
-     * Returns a xyz: [x, y, z] string representation of the point coordinates
+     * Parses a xyz: [x, y, z] string expression ang gets a GeoPoint3D object
+     * 
+     * @param string $expression the expression to parse
+     * @return GeoPoint3D If the specified expression could be parsed, a GeoPoint3D instance ; otherwise, null.
+     */
+    static function fromString ($expression) {
+        if (string_starts_with($expression, 'xyz:', false)) {
+            $pos1 = strpos($expression, '[', 4) + 1;
+            $pos2 = strpos($expression, ']', $pos1);
+            if ($pos1 > -1 && $pos2 > -1) {
+                $expression = substr($expression, $pos1, $pos2 - $pos1);
+                $xyz = explode(',', $expression, 3);
+                return new GeoPoint3D($xyz[0], $xyz[1], $xyz[2]);
+            }
+        }
+        return null;
+    }
+    
+    /**
+     * Returns a xyz: [x, y, z] string representation of the point coordinates.
      *
      * @return string a xyz: [x, y, z] string representation of the coordinates
      */
     function __toString () {
         return sprintf("xyz: [%d, %d, %d]", $this->x, $this->y, $this->z);
     }
     
+    /**
+     * Determines if this point is equal to the specified point.
+     * 
+     * @param GeoPoint3D $point The point to compare
+     * @return bool true if the two points are equal ; otherwise, false.
+     */
+    function equals ($point) {
+        return ($this->x == $point->x) && ($this->y == $point->y) && ($this->z == $point->z);
+    }
+    
+    //
+    // Math
+    //
+    
+    /**
+     * Gets the (ρ, φ, θ) spherical coordinates from the current x, y, z cartesian point
+     *
+     * The algo used is from http://fr.wikipedia.org/wiki/Coordonn%C3%A9es_sph%C3%A9riques#Relation_avec_les_autres_syst.C3.A8mes_de_coordonn.C3.A9es_usuels
+     *
+     * @return array an array of 3 floats number, representing the (ρ, φ, θ) spherical coordinates
+     */
+    function to_spherical () {
+        return GeoGalaxy::cartesian_to_spherical($this->x, $this->y, $this->z);
+    }
+    
+    /**
+     * Gets the (ρ, φ, θ) spherical coordinates from the current x, y, z cartesian point
+     *
+     * The algo used is from http://www.phy225.dept.shef.ac.uk/mediawiki/index.php/Cartesian_to_polar_conversion
+     *
+     * @return array an array of 3 floats number, representing the (ρ, φ, θ) spherical coordinates
+     */
+    function to_spherical2 () {
+        return GeoGalaxy::cartesian_to_spherical2($this->x, $this->y, $this->z);
+    }
+    
+    /**
+     * Translates the center and rescales.
+     *
+     * This method allow to help to represent coordinate in a new system
+     *
+     * This method is used to represent Zed objects in dojo with the following
+     * parameters:
+     * <code>
+     * $pointKaos = GeoPoint3D(800, 42, 220);
+     * $pointKaos->translate(500, 300, 200, 2);
+     * </code>
+     *
+     * @param int $dx the difference between the old x and new x (ie the value of x = 0 in the new system)
+     * @param int $dy the difference between the old y and new y (ie the value of y = 0 in the new system)
+     * @param int $dz the difference between the old y and new z (ie the value of z = 0 in the new system)
+     * @scale float $scale if specified, divides each coordinate by this value (optional)
+     */
+    function translate ($dx, $dy, $dz, $scale = 1) {
+        if ($scale == 1) {
+            $this->x += $dx;
+            $this->y += $dy;
+            $this->z += $dz;
+        } elseif ($scale == 0) {
+            $this->x = 0;
+            $this->y = 0;
+            $this->z = 0;
+        } else {
+            $this->x = $this->x * $scale + $dx;
+            $this->y = $this->y * $scale + $dy;
+            $this->z = $this->z * $scale + $dz;
+        }
+    }
+    
     //
     // Implementing IteratorAggregate
     //
     
     /**
      * Retrieves class iterator. It traverses x, y and z.
      * 
      * @return Traversable the iterator
      */
     function getIterator () {
         return new ArrayIterator($this);
     }
+    
+    
 }
 
 ?>
\ No newline at end of file
diff --git a/includes/story/hook_spatioport.php b/includes/story/hook_spatioport.php
--- a/includes/story/hook_spatioport.php
+++ b/includes/story/hook_spatioport.php
@@ -1,130 +1,132 @@
 <?php 
 /**
  * Story hook class :: spatioport
  *
  * Zed. The immensity of stars. The HyperShip. The people.
  * 
  * (c) 2010, Dereckson, some rights reserved.
  * Released under BSD license.
  *
  * This class allows to hook spatioport content to a story.
  *
  * It lists the ship inside the spatioport and in the surrounding space.
  *
  * @package     Zed
  * @subpackage  Story
  * @author      Sébastien Santoro aka Dereckson <dereckson@espace-win.org>
  * @copyright   2010 Sébastien Santoro aka Dereckson
  * @license     http://www.opensource.org/licenses/bsd-license.php BSD
  * @version     0.1
  * @link        http://scherzo.dereckson.be/doc/zed
  * @link        http://zed.dereckson.be/
  * @filesource
  *
  * @todo Adds spatioport services, like ship repair & co
  * @todo Adds a map of the sky, with ship around
  * @todo Consider to use the Port class instead and to move get_ships methods there.
  */
 
 require_once('includes/objects/ship.php');
 require_once('includes/geo/location.php');
 
+$class = 'SpatioportStoryHook';
+
 /**
  * Spatioport story hook class
  */
 class SpatioportStoryHook extends StoryHook {
     /**
      * The spatioport location
      * 
      * @var GeoLocation
      */
     public $location;
 
     /**
      * The spatioport global location
      * 
      * @var string
      */    
     public $location_global;
     
     /**
      * The spatioport local location
      * 
      * @var string
      */    
     public $location_local;
     
     
     /**
      * Updates and gets the current section choices
      * 
      * @param Array $links The story links
      */    
     function get_choices_links (&$links) {
         //$links[] = array('Examiner les vaisseaux', get_url('port','ships'));
     }
     
     /**
      * Initializes instance location properties
      */
     function initialize () {
         $this->location_global = $this->perso->location_global;
         $this->location_local  = $this->section->location_local;
         $this->location = new GeoLocation($this->location_global, $this->location_local);
     }
     
     /**
      * Appends ship list to the story description
      */
     function add_content () {
         $ships = $this->get_ships();
         if (count($ships)) {
             echo "\n<h2>Ships</h2>";
             echo "<p>Amongst the ships are at the spatioport:</p>";
             echo "\n<ul>";
             foreach ($ships as $ship) {
                 $url = get_url('ship', $ship);
                 echo "\n\t<li><a href=\"$url\">$ship->name</a></li>";
             }
             echo "\n</ul>";
         }
         
         $ships = $this->get_ships_in_space();
         if (count($ships)) {
             echo "\n<h2>In orbit</h2>";
             $place = (string)$this->location->body;
             echo "<p>Those ships are in space around $place:</p>";
             echo "\n<ul>";
             foreach ($ships as $ship) {
                 $url = get_url('ship', $ship);
                 echo "\n\t<li><a href=\"$url\">$ship->name</a></li>";
             }
             echo "\n</ul>";
         }
     }
   
     /**
      * Get ships in the spatioports
      * 
      * @param string $location_global global location
      * @param string $location_local local location
      * @return array The ships in the spatioport
      */
     private function get_ships () {
         return Ship::get_ships_at($this->location_global, $this->location_local);
     }
   
   
     /**
      * Get ships in the space surrounding the spatioport
      * 
      * @param string $location_global global location
      * @param string $location_local local location
      * @return array The ships in the space around the spatioport
      */  
     private function get_ships_in_space () {
         return Ship::get_ships_at($this->location_global, null); 
     }
 }
 
-?>
\ No newline at end of file
+?>
diff --git a/includes/story/story.php b/includes/story/story.php
--- a/includes/story/story.php
+++ b/includes/story/story.php
@@ -1,133 +1,135 @@
 <?php
 
 /**
  * Story class
  *
  * Zed. The immensity of stars. The HyperShip. The people.
  * 
  * (c) 2010, Dereckson, some rights reserved.
  * Released under BSD license.
  * 
  * @package     Zed
  * @subpackage  Story
  * @author      Sébastien Santoro aka Dereckson <dereckson@espace-win.org>
  * @copyright   2010 Sébastien Santoro aka Dereckson
  * @license     http://www.opensource.org/licenses/bsd-license.php BSD
  * @version     0.1
  * @link        http://scherzo.dereckson.be/doc/zed
  * @link        http://zed.dereckson.be/
  * @filesource
  */
 
+require_once('section.php');
+
 /**
  * Story class
  *
  * This class is a PHP mapping from the Story XML format.
  *
  * This class also provides a collection of helper methods to explore the story.
  */
 class Story {
     /**
      * The file path
      * 
      * @var string
      */
     public $file;
     
     /**
      * The story title
      * 
      * @var string
      */
     public $title;
     
     /**
      * An array of StorySection elements
      *
      * @var Array 
      */
     public $sections = array();
     
     /**
      * The SimpleXML parser
      *
      * @var SimpleXMLElement
      */
     private $xml;
     
     /**
      * The index of start section in sections array
      *
      * @var string
      */
     private $startSection = null;
     
     /**
      * An array of StorySection elements, indexed by location
      *
      * @var Array
      */
     private $sectionsByLocation = array();
     
     function __construct ($file) {
         //Opens .xml
         if (!file_exists($file)) {
             message_die(GENERAL_ERROR, "$file not found.", "Story loading error");
         }
         
         $this->file = $file;
         $this->parse();
     }
     
     /**
      * Gets start section
      * 
      * @return StorySection the section where the story starts, or null if not defined
      */
     function get_start_section () {
         return ($this->startSection != null) ? $this->sections[$this->startSection] : null;
     }
     
     /**
      * Gets section from local location
      * 
      * @return StorySection the default section at this location, or null if not defined
      */
     function get_section_from_location ($location) {
         return array_key_exists($location, $this->sectionsByLocation) ? $this->sectionsByLocation[$location] : null;
     }
     
     /**
      * Parses XML file
      */
     function parse () {
         //Parses it
         $this->xml = simplexml_load_file($this->file);
         $this->title = (string)$this->xml->title;
         foreach ($this->xml->section as $section) {
             //Gets section
             $section = StorySection::from_xml($section, $this);
             
             //Have we a start section?
             if ($section->start) {
                 //Ensures we've only one start section
                 if ($this->startSection != null) {
                     message_die(GENERAL_ERROR, "Two sections have start=\"true\": $section->id and $this->startSection.", "Story error");
                 }
                 $this->startSection = $section->id;
             }
             
             //By location
             if ($section->location_local) {
                 $this->sectionsByLocation[$section->location_local] = $section;
             }
             
             //Adds to sections array
             $this->sections[$section->id] = $section;
         }
     }
 }
 
 
 
-?>
\ No newline at end of file
+?>