{"id":7782,"date":"2026-05-08T17:57:26","date_gmt":"2026-05-08T21:57:26","guid":{"rendered":"https:\/\/www.caskeys.com\/dc\/?p=7782"},"modified":"2026-05-09T00:18:18","modified_gmt":"2026-05-09T04:18:18","slug":"shadow-trail-script","status":"publish","type":"post","link":"https:\/\/www.caskeys.com\/dc\/shadow-trail-script\/","title":{"rendered":"Shadow Trail Sample"},"content":{"rendered":"\n<h2 class=\"wp-block-heading\">Introduction<\/h2>\n\n\n\n<p>This example demonstrates a simple shadow trail effect using OpenBOR script arrays. It is intended as a reference sample for articles covering:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>OpenBOR arrays (coming soon)<\/li>\n\n\n\n<li>Flattened data structures (coming soon)<\/li>\n\n\n\n<li>Temporary visual effects (coming soon)<\/li>\n<\/ul>\n\n\n\n<figure class=\"wp-block-embed is-type-wp-embed is-provider-dc-current wp-block-embed-dc-current\"><div class=\"wp-block-embed__wrapper\">\n<blockquote class=\"wp-embedded-content\" data-secret=\"qCaoVU7dtQ\"><a href=\"https:\/\/www.caskeys.com\/dc\/ring-buffer\/\">Ring Buffer<\/a><\/blockquote><iframe loading=\"lazy\" class=\"wp-embedded-content\" sandbox=\"allow-scripts\" security=\"restricted\" style=\"position: absolute; visibility: hidden;\" title=\"&#8220;Ring Buffer&#8221; &#8212; DC Current\" src=\"https:\/\/www.caskeys.com\/dc\/ring-buffer\/embed\/#?secret=Cgda8eUzZk#?secret=qCaoVU7dtQ\" data-secret=\"qCaoVU7dtQ\" width=\"600\" height=\"338\" frameborder=\"0\" marginwidth=\"0\" marginheight=\"0\" scrolling=\"no\"><\/iframe>\n<\/div><\/figure>\n\n\n\n<p>This is not a complete shadow trail system. It is a minimal working example meant to show the underlying data pattern.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Concept<\/h2>\n\n\n\n<p>A shadow trail is created by recording snapshots of an entity over time. Each snapshot stores the entity\u2019s position, current sprite, and expiration time.<\/p>\n\n\n\n<p>The snapshots are stored in a self-contained ring buffer attached to the entity. During a global update, entities with active trail data are scanned, and their stored snapshots are drawn back to the screen as afterimages.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Data Stored Per Trail Segment<\/h2>\n\n\n\n<p>Each trail entry stores:<\/p>\n\n\n\n<figure class=\"wp-block-table is-style-stripes\"><table class=\"has-fixed-layout\"><thead><tr><th>Field<\/th><th>Purpose<\/th><\/tr><\/thead><tbody><tr><td>X<\/td><td>Stored X position<\/td><\/tr><tr><td>Y<\/td><td>Stored Y position<\/td><\/tr><tr><td>Z<\/td><td>Stored Z position<\/td><\/tr><tr><td>Sprite<\/td><td>Sprite pointer to draw<\/td><\/tr><tr><td>Time<\/td><td>Expiration time<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p>The trail data is stored in one flat array. The first element holds the ring buffer cursor. The remaining elements contain fixed-size trail entries.<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; title: Code:; notranslate\" title=\"Code:\">\n#define SHADOWTRAIL_VARKEY_DATA       &quot;shadowtrail_data&quot;\n#define SHADOW_TRAIL_DURATION         100 \/\/ Duration for each trail segment in centiseconds.\n\n#define SHADOWTRAIL_COUNT             6   \/\/ Number of trail segments to store and draw.\n\n#define SHADOWTRAIL_DATA_CURSOR       0   \/\/ Ring buffer cursor\/index.\n#define SHADOWTRAIL_DATA_START        1   \/\/ Offset where trail entries begin.\n\n#define SHADOWTRAIL_ENTRY_X           0\n#define SHADOWTRAIL_ENTRY_Y           1\n#define SHADOWTRAIL_ENTRY_Z           2\n#define SHADOWTRAIL_ENTRY_SPRITE      3\n#define SHADOWTRAIL_ENTRY_TIME        4\n#define SHADOWTRAIL_ENTRY_SIZE        5\n<\/pre><\/div>\n\n\n<h2 class=\"wp-block-heading\">Recording Trail Data<\/h2>\n\n\n\n<p>Run this function from an entity update whenever shadow trails should be recorded.<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; title: Code:; notranslate\" title=\"Code:\">\n\/*\n* 2018-08-25\n* Caskey, Damon V.\n* \n* Sample shadow trail recording function.\n* Accepts acting entity and records shadow\n* trail data to a flattened ring buffer for\n* use by draw function.\n*\/\nvoid dc_shadowtrail_record(void acting_entity) {\n    \n    \/* Guards *\/\n    if(!acting_entity \n        || typeof(acting_entity) != openborconstant(&quot;VT_PTR&quot;)) {\n        shutdown(1, &quot;\\n\\n ERROR: shadowtrail_store_good(&quot; + acting_entity + &quot;) - Missing or invalid parameter.\\n&quot;);\n    }\n\n    \/*\n    * Retrieve the trail data array from the entity.\n    * If it doesn&#039;t exist yet, initialize it with the \n    * appropriate size and structure, then store it back \n    * in the entity variable.\n    *\/\n    void trail_data = getentityvar(acting_entity, SHADOWTRAIL_VARKEY_DATA);\n\n    if(!trail_data || typeof(trail_data) != openborconstant(&quot;VT_PTR&quot;)) {\n\n        \/*\n        * No trail data array found for this entity, so we \n        * need to initialize it.\n        *\/\n\n        \/* \n        * Calculate the total size of the trail data array \n        * based on the number of segments and entry size. \n        * Full explanation below in the code comments where \n        * we store the data.\n        *\/\n        int list_size = SHADOWTRAIL_DATA_START + (SHADOWTRAIL_COUNT * SHADOWTRAIL_ENTRY_SIZE);\n\n        \/*\n        * Allocate the trail data array with the calculated \n        * size and store its pointer in the entity variable.\n        *\/\n        trail_data = array(list_size);\n        set(trail_data, SHADOWTRAIL_DATA_CURSOR, 0);\n        setentityvar(acting_entity, SHADOWTRAIL_VARKEY_DATA, trail_data);\n    }\n\n    \/* We&#039;ll need elapsed time + duration. *\/\n    int expire_time = openborvariant(&quot;elapsed_time&quot;) + SHADOW_TRAIL_DURATION;\n\n    \/* Get current sprite and position of the entity. *\/\n    void current_sprite = getentityproperty(acting_entity, &quot;sprite&quot;);\n    float x_pos = getentityproperty(acting_entity, &quot;x&quot;);\n    float y_pos = getentityproperty(acting_entity, &quot;y&quot;);\n    float z_pos = getentityproperty(acting_entity, &quot;z&quot;);\n    int entry_index = 0;\n\n    \/* Get ring buffer index, initialize to 0 if necessary. *\/\n    int cursor = get(trail_data, SHADOWTRAIL_DATA_CURSOR);\n\n    if(typeof(cursor) != openborconstant(&quot;VT_INTEGER&quot;)) {\n        cursor = 0;\n    }\n\n    cursor = cursor % SHADOWTRAIL_COUNT;\n\n    \/* \n    * Flatten the grid data into a singular array\n    * through index offset math.\n    *\/\n    entry_index = SHADOWTRAIL_DATA_START + (cursor * SHADOWTRAIL_ENTRY_SIZE);\n\n    set(trail_data, entry_index + SHADOWTRAIL_ENTRY_X, x_pos);\n    set(trail_data, entry_index + SHADOWTRAIL_ENTRY_Y, y_pos);\n    set(trail_data, entry_index + SHADOWTRAIL_ENTRY_Z, z_pos);\n    set(trail_data, entry_index + SHADOWTRAIL_ENTRY_SPRITE, current_sprite);\n    set(trail_data, entry_index + SHADOWTRAIL_ENTRY_TIME, expire_time); \/\/ Store expiration time\n\n    \/* Increment the cursor for the next trail segment. *\/\n    cursor++;\n    set(trail_data, SHADOWTRAIL_DATA_CURSOR, cursor);\n}\n<\/pre><\/div>\n\n\n<h2 class=\"wp-block-heading\">Drawing Trail Data<\/h2>\n\n\n\n<p>Run this function during a global update as part of entity enumeration. It reads stored trail entries and draws valid, unexpired sprites back to the screen.<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; title: Code:; notranslate\" title=\"Code:\">\n\/*\n* 2018-08-25\n* Caskey, Damon V.\n* \n* Sample shadow trail draw function.\n* Accepts acting entity, reads shadow\n* trail ring buffer (if any) and draws\n* after-images to screen. Clears the\n* buffer element if expired.\n*\/\nvoid dc_shadowtrail_draw(void acting_entity) {\n    \n    \/* Guards *\/\n    if(!acting_entity || typeof(acting_entity) != openborconstant(&quot;VT_PTR&quot;)) {\n        shutdown(1, &quot;\\n\\n ERROR: shadowtrail_draw_good(&quot; + acting_entity + &quot;) - Missing or invalid parameter.\\n&quot;);\n    }\n\n    \/*\n    * Retrieve the trail data array from the entity.\n    * If it doesn&#039;t exist or is invalid, we can&#039;t draw \n    * any trails, so we just exit.\n    *\/\n    void trail_data = getentityvar(acting_entity, SHADOWTRAIL_VARKEY_DATA);\n\n    if(!trail_data || typeof(trail_data) != openborconstant(&quot;VT_PTR&quot;)) {\n        return;\n    }\n\n    int i = 0;  \/\/ Main loop index for iterating through trail segments.\n    int j = 0;  \/\/ Inner loop index for clearing expired trail segments when we find them.\n    int entry_index = 0;\n    void trail_sprite = NULL();\n    float trail_x = 0.0;\n    float trail_y = 0.0;\n    float trail_z = 0.0;\n    int sort_id = 0;\n    int expire_time = 0;\n    int elapsed_time = openborvariant(&quot;elapsed_time&quot;);\n    \n    \/* Scan through all possible trail segments in the ring buffer. *\/\n    for(i = 0; i &lt; SHADOWTRAIL_COUNT; i++) {\n\n        \/* See store function for index calculation. *\/\n        entry_index = SHADOWTRAIL_DATA_START + (i * SHADOWTRAIL_ENTRY_SIZE);\n\n        \/* \n        * Get the trail sprite for this segment. \n        * Make sure it&#039;s a valid pointer before \n        * trying to draw. If it&#039;s not valid, this \n        * segment is either uninitialized or cleared, \n        * so we skip it.\n        *\/\n       \n        trail_sprite = get(trail_data, entry_index + SHADOWTRAIL_ENTRY_SPRITE);\n\n        if(!trail_sprite || typeof(trail_sprite) != openborconstant(&quot;VT_PTR&quot;)) {\n            continue;\n        }\n\n        \/* \n        * Check if the trail segment has expired. \n        * If it has, we clear the data for this \n        * segment and skip to next iteration. \n        *\/\n\n        expire_time = get(trail_data, entry_index + SHADOWTRAIL_ENTRY_TIME);\n        \n        if(typeof(expire_time) != openborconstant(&quot;VT_INTEGER&quot;) \n            || expire_time &lt;= elapsed_time) {\n\n            \/* Clear the trail segment data for this entry. *\/\n            for(j = 0; j &lt; SHADOWTRAIL_ENTRY_SIZE; j++) {\n                set(trail_data, entry_index + j, NULL());\n            }\n\n            continue;\n        }\n\n        \/*\n        * Retrieve the position and whatever other data \n        * we need for this trail segment.\n        *\/\n\n        trail_x = get(trail_data, entry_index + SHADOWTRAIL_ENTRY_X);\n        trail_y = get(trail_data, entry_index + SHADOWTRAIL_ENTRY_Y);\n        trail_z = get(trail_data, entry_index + SHADOWTRAIL_ENTRY_Z);\n\n        \/*\n        * Older trails go further back, all go one step\n        * behind the current entity z to prevent overlap.\n        *\/\n        \n        sort_id = i - 1;\n\n        \/*\n        * Draw the trail sprite at its position.\n        *\/\n\n        drawsprite(trail_sprite, trail_x, trail_y, trail_z, sort_id);        \n    }\n}\n<\/pre><\/div>\n\n\n<h2 class=\"wp-block-heading\">Why the Array Is Flattened<\/h2>\n\n\n\n<p>Instead of creating nested arrays, each trail segment is packed into a single flat array.<\/p>\n\n\n\n<p>For example, with six trail entries and five values per entry:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&#91;cursor, x, y, z, sprite, time, x, y, z, sprite, time, ... ]<\/code><\/pre>\n\n\n\n<p>This layout is compact, predictable, and easy to index with offset math.<\/p>\n\n\n\n<p class=\"has-luminous-vivid-orange-color has-text-color has-link-color wp-elements-dd9b126602dc3ff3cd0c39e7c39c1153\">More on flattening coming soon.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Notes<\/h2>\n\n\n\n<p>This sample intentionally keeps the stored data minimal. More advanced versions could also store:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Drawmethod data\n<ul class=\"wp-block-list\">\n<li>Alpha or tint values<\/li>\n\n\n\n<li>Scale<\/li>\n\n\n\n<li>Blending<\/li>\n\n\n\n<li>Palettes<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li>Facing direction<\/li>\n\n\n\n<li>Animation frame timing<\/li>\n\n\n\n<li>Per-segment delay<\/li>\n\n\n\n<li>Entity-specific trail settings<\/li>\n<\/ul>\n\n\n\n<p>Use this sample as a foundation for tutorials explaining how OpenBOR arrays, flattened structures, and ring buffers can support reusable visual effects.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Sample shadow trail script used to demonstrate array building concepts in OpenBOR.<\/p>\n","protected":false},"author":1,"featured_media":7798,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":"","jetpack_publicize_message":"","jetpack_publicize_feature_enabled":false,"jetpack_social_post_already_shared":false,"jetpack_social_options":{"image_generator_settings":{"template":"highway","default_image_id":0,"font":"","enabled":false},"version":2},"jetpack_post_was_ever_published":false},"categories":[353,71],"tags":[232,289],"class_list":["post-7782","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-gaming-and-fantasy","category-technology-temerity","tag-applications-openbor","tag-coding-openbor-script"],"jetpack_publicize_connections":[],"jetpack_featured_media_url":"https:\/\/www.caskeys.com\/dc\/wp-content\/uploads\/2026\/05\/shadow_trail_0.png","jetpack_sharing_enabled":true,"jetpack_shortlink":"https:\/\/wp.me\/p5lNM5-21w","jetpack_likes_enabled":true,"_links":{"self":[{"href":"https:\/\/www.caskeys.com\/dc\/wp-json\/wp\/v2\/posts\/7782","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.caskeys.com\/dc\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.caskeys.com\/dc\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.caskeys.com\/dc\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.caskeys.com\/dc\/wp-json\/wp\/v2\/comments?post=7782"}],"version-history":[{"count":10,"href":"https:\/\/www.caskeys.com\/dc\/wp-json\/wp\/v2\/posts\/7782\/revisions"}],"predecessor-version":[{"id":7817,"href":"https:\/\/www.caskeys.com\/dc\/wp-json\/wp\/v2\/posts\/7782\/revisions\/7817"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.caskeys.com\/dc\/wp-json\/wp\/v2\/media\/7798"}],"wp:attachment":[{"href":"https:\/\/www.caskeys.com\/dc\/wp-json\/wp\/v2\/media?parent=7782"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.caskeys.com\/dc\/wp-json\/wp\/v2\/categories?post=7782"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.caskeys.com\/dc\/wp-json\/wp\/v2\/tags?post=7782"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}